summaryrefslogtreecommitdiff
path: root/spec/bundler
diff options
context:
space:
mode:
Diffstat (limited to 'spec/bundler')
-rw-r--r--spec/bundler/bundler/build_metadata_spec.rb50
-rw-r--r--spec/bundler/bundler/bundler_spec.rb316
-rw-r--r--spec/bundler/bundler/ci_detector_spec.rb21
-rw-r--r--spec/bundler/bundler/cli_common_spec.rb22
-rw-r--r--spec/bundler/bundler/cli_spec.rb247
-rw-r--r--spec/bundler/bundler/compact_index_client/parser_spec.rb249
-rw-r--r--spec/bundler/bundler/compact_index_client/updater_spec.rb239
-rw-r--r--spec/bundler/bundler/current_ruby_spec.rb157
-rw-r--r--spec/bundler/bundler/definition_spec.rb297
-rw-r--r--spec/bundler/bundler/dependency_spec.rb49
-rw-r--r--spec/bundler/bundler/digest_spec.rb24
-rw-r--r--spec/bundler/bundler/dsl_spec.rb428
-rw-r--r--spec/bundler/bundler/endpoint_specification_spec.rb95
-rw-r--r--spec/bundler/bundler/env_spec.rb183
-rw-r--r--spec/bundler/bundler/environment_preserver_spec.rb19
-rw-r--r--spec/bundler/bundler/errors_spec.rb91
-rw-r--r--spec/bundler/bundler/fetcher/base_spec.rb12
-rw-r--r--spec/bundler/bundler/fetcher/compact_index_spec.rb47
-rw-r--r--spec/bundler/bundler/fetcher/dependency_spec.rb72
-rw-r--r--spec/bundler/bundler/fetcher/downloader_spec.rb229
-rw-r--r--spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb60
-rw-r--r--spec/bundler/bundler/fetcher/index_spec.rb103
-rw-r--r--spec/bundler/bundler/fetcher_spec.rb174
-rw-r--r--spec/bundler/bundler/friendly_errors_spec.rb89
-rw-r--r--spec/bundler/bundler/gem_helper_spec.rb288
-rw-r--r--spec/bundler/bundler/gem_version_promoter_spec.rb248
-rw-r--r--spec/bundler/bundler/index_spec.rb1
-rw-r--r--spec/bundler/bundler/installer/gem_installer_spec.rb37
-rw-r--r--spec/bundler/bundler/installer/parallel_installer_spec.rb102
-rw-r--r--spec/bundler/bundler/installer/spec_installation_spec.rb75
-rw-r--r--spec/bundler/bundler/lockfile_parser_spec.rb221
-rw-r--r--spec/bundler/bundler/mirror_spec.rb24
-rw-r--r--spec/bundler/bundler/override_spec.rb175
-rw-r--r--spec/bundler/bundler/plugin/api/source_spec.rb13
-rw-r--r--spec/bundler/bundler/plugin/api_spec.rb9
-rw-r--r--spec/bundler/bundler/plugin/dsl_spec.rb9
-rw-r--r--spec/bundler/bundler/plugin/events_spec.rb32
-rw-r--r--spec/bundler/bundler/plugin/index_spec.rb124
-rw-r--r--spec/bundler/bundler/plugin/installer_spec.rb59
-rw-r--r--spec/bundler/bundler/plugin/source_list_spec.rb1
-rw-r--r--spec/bundler/bundler/plugin_spec.rb144
-rw-r--r--spec/bundler/bundler/psyched_yaml_spec.rb9
-rw-r--r--spec/bundler/bundler/remote_specification_spec.rb15
-rw-r--r--spec/bundler/bundler/resolver/candidate_spec.rb20
-rw-r--r--spec/bundler/bundler/resolver/cooldown_spec.rb148
-rw-r--r--spec/bundler/bundler/retry_spec.rb114
-rw-r--r--spec/bundler/bundler/ruby_dsl_spec.rb165
-rw-r--r--spec/bundler/bundler/ruby_version_spec.rb118
-rw-r--r--spec/bundler/bundler/rubygems_ext_spec.rb39
-rw-r--r--spec/bundler/bundler/rubygems_integration_spec.rb121
-rw-r--r--spec/bundler/bundler/settings/validator_spec.rb111
-rw-r--r--spec/bundler/bundler/settings_spec.rb196
-rw-r--r--spec/bundler/bundler/shared_helpers_spec.rb211
-rw-r--r--spec/bundler/bundler/source/git/git_proxy_spec.rb307
-rw-r--r--spec/bundler/bundler/source/git_spec.rb123
-rw-r--r--spec/bundler/bundler/source/rubygems/remote_spec.rb93
-rw-r--r--spec/bundler/bundler/source/rubygems_spec.rb72
-rw-r--r--spec/bundler/bundler/source_list_spec.rb132
-rw-r--r--spec/bundler/bundler/source_spec.rb111
-rw-r--r--spec/bundler/bundler/spec_set_spec.rb111
-rw-r--r--spec/bundler/bundler/specifications/foo.gemspec13
-rw-r--r--spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb141
-rw-r--r--spec/bundler/bundler/stub_specification_spec.rb77
-rw-r--r--spec/bundler/bundler/ui/shell_spec.rb112
-rw-r--r--spec/bundler/bundler/ui_spec.rb5
-rw-r--r--spec/bundler/bundler/uri_credentials_filter_spec.rb21
-rw-r--r--spec/bundler/bundler/uri_normalizer_spec.rb25
-rw-r--r--spec/bundler/bundler/version_ranges_spec.rb37
-rw-r--r--spec/bundler/bundler/worker_spec.rb69
-rw-r--r--spec/bundler/bundler/yaml_serializer_spec.rb64
-rw-r--r--spec/bundler/cache/cache_path_spec.rb24
-rw-r--r--spec/bundler/cache/gems_spec.rb341
-rw-r--r--spec/bundler/cache/git_spec.rb574
-rw-r--r--spec/bundler/cache/path_spec.rb189
-rw-r--r--spec/bundler/cache/platform_spec.rb23
-rw-r--r--spec/bundler/commands/add_spec.rb399
-rw-r--r--spec/bundler/commands/binstubs_spec.rb260
-rw-r--r--spec/bundler/commands/cache_spec.rb620
-rw-r--r--spec/bundler/commands/check_spec.rb533
-rw-r--r--spec/bundler/commands/clean_spec.rb615
-rw-r--r--spec/bundler/commands/config_spec.rb457
-rw-r--r--spec/bundler/commands/console_spec.rb251
-rw-r--r--spec/bundler/commands/doctor_spec.rb199
-rw-r--r--spec/bundler/commands/exec_spec.rb1010
-rw-r--r--spec/bundler/commands/fund_spec.rb118
-rw-r--r--spec/bundler/commands/help_spec.rb63
-rw-r--r--spec/bundler/commands/info_spec.rb229
-rw-r--r--spec/bundler/commands/init_spec.rb163
-rw-r--r--spec/bundler/commands/inject_spec.rb114
-rw-r--r--spec/bundler/commands/install_spec.rb1994
-rw-r--r--spec/bundler/commands/issue_spec.rb3
-rw-r--r--spec/bundler/commands/licenses_spec.rb15
-rw-r--r--spec/bundler/commands/list_spec.rb315
-rw-r--r--spec/bundler/commands/lock_spec.rb2742
-rw-r--r--spec/bundler/commands/newgem_spec.rb2238
-rw-r--r--spec/bundler/commands/open_spec.rb218
-rw-r--r--spec/bundler/commands/outdated_spec.rb1148
-rw-r--r--spec/bundler/commands/package_spec.rb306
-rw-r--r--spec/bundler/commands/platform_spec.rb1260
-rw-r--r--spec/bundler/commands/post_bundle_message_spec.rb183
-rw-r--r--spec/bundler/commands/pristine_spec.rb214
-rw-r--r--spec/bundler/commands/remove_spec.rb736
-rw-r--r--spec/bundler/commands/show_spec.rb146
-rw-r--r--spec/bundler/commands/ssl_spec.rb373
-rw-r--r--spec/bundler/commands/update_spec.rb1852
-rw-r--r--spec/bundler/commands/version_spec.rb65
-rw-r--r--spec/bundler/commands/viz_spec.rb150
-rw-r--r--spec/bundler/install/allow_offline_install_spec.rb63
-rw-r--r--spec/bundler/install/binstubs_spec.rb43
-rw-r--r--spec/bundler/install/bundler_spec.rb233
-rw-r--r--spec/bundler/install/cooldown_spec.rb433
-rw-r--r--spec/bundler/install/deploy_spec.rb508
-rw-r--r--spec/bundler/install/failure_spec.rb64
-rw-r--r--spec/bundler/install/force_spec.rb62
-rw-r--r--spec/bundler/install/gemfile/eval_gemfile_spec.rb77
-rw-r--r--spec/bundler/install/gemfile/force_ruby_platform_spec.rb136
-rw-r--r--spec/bundler/install/gemfile/gemspec_spec.rb648
-rw-r--r--spec/bundler/install/gemfile/git_spec.rb1072
-rw-r--r--spec/bundler/install/gemfile/groups_spec.rb244
-rw-r--r--spec/bundler/install/gemfile/install_if.rb45
-rw-r--r--spec/bundler/install/gemfile/install_if_spec.rb51
-rw-r--r--spec/bundler/install/gemfile/lockfile_spec.rb42
-rw-r--r--spec/bundler/install/gemfile/override_spec.rb401
-rw-r--r--spec/bundler/install/gemfile/path_spec.rb702
-rw-r--r--spec/bundler/install/gemfile/platform_spec.rb567
-rw-r--r--spec/bundler/install/gemfile/ruby_spec.rb138
-rw-r--r--spec/bundler/install/gemfile/sources_spec.rb1359
-rw-r--r--spec/bundler/install/gemfile/specific_platform_spec.rb2022
-rw-r--r--spec/bundler/install/gemfile_spec.rb190
-rw-r--r--spec/bundler/install/gems/compact_index_spec.rb817
-rw-r--r--spec/bundler/install/gems/dependency_api_fallback_spec.rb19
-rw-r--r--spec/bundler/install/gems/dependency_api_spec.rb443
-rw-r--r--spec/bundler/install/gems/env_spec.rb37
-rw-r--r--spec/bundler/install/gems/flex_spec.rb316
-rw-r--r--spec/bundler/install/gems/fund_spec.rb164
-rw-r--r--spec/bundler/install/gems/gemfile_source_header_spec.rb24
-rw-r--r--spec/bundler/install/gems/mirror_probe_spec.rb68
-rw-r--r--spec/bundler/install/gems/mirror_spec.rb25
-rw-r--r--spec/bundler/install/gems/native_extensions_spec.rb116
-rw-r--r--spec/bundler/install/gems/no_build_extension_spec.rb54
-rw-r--r--spec/bundler/install/gems/no_install_plugin_spec.rb53
-rw-r--r--spec/bundler/install/gems/post_install_spec.rb49
-rw-r--r--spec/bundler/install/gems/resolving_spec.rb699
-rw-r--r--spec/bundler/install/gems/standalone_spec.rb448
-rw-r--r--spec/bundler/install/gems/sudo_spec.rb179
-rw-r--r--spec/bundler/install/gems/win32_spec.rb14
-rw-r--r--spec/bundler/install/gemspecs_spec.rb131
-rw-r--r--spec/bundler/install/git_spec.rb351
-rw-r--r--spec/bundler/install/global_cache_spec.rb305
-rw-r--r--spec/bundler/install/path_spec.rb190
-rw-r--r--spec/bundler/install/post_bundle_message_spec.rb190
-rw-r--r--spec/bundler/install/prereleases_spec.rb28
-rw-r--r--spec/bundler/install/process_lock_spec.rb113
-rw-r--r--spec/bundler/install/security_policy_spec.rb39
-rw-r--r--spec/bundler/install/yanked_spec.rb242
-rw-r--r--spec/bundler/lock/git_spec.rb229
-rw-r--r--spec/bundler/lock/lockfile_spec.rb2065
-rw-r--r--spec/bundler/other/bundle_ruby_spec.rb143
-rw-r--r--spec/bundler/other/cli_dispatch_spec.rb18
-rw-r--r--spec/bundler/other/cli_man_pages_spec.rb100
-rw-r--r--spec/bundler/other/ext_spec.rb71
-rw-r--r--spec/bundler/other/major_deprecation_spec.rb832
-rw-r--r--spec/bundler/other/platform_spec.rb1292
-rw-r--r--spec/bundler/other/ssl_cert_spec.rb18
-rw-r--r--spec/bundler/plugins/command_spec.rb51
-rw-r--r--spec/bundler/plugins/hook_spec.rb331
-rw-r--r--spec/bundler/plugins/install_spec.rb270
-rw-r--r--spec/bundler/plugins/list_spec.rb60
-rw-r--r--spec/bundler/plugins/source/example_spec.rb134
-rw-r--r--spec/bundler/plugins/source_spec.rb20
-rw-r--r--spec/bundler/plugins/uninstall_spec.rb74
-rw-r--r--spec/bundler/quality_es_spec.rb61
-rw-r--r--spec/bundler/quality_spec.rb298
-rw-r--r--spec/bundler/realworld/dependency_api_spec.rb49
-rw-r--r--spec/bundler/realworld/double_check_spec.rb40
-rw-r--r--spec/bundler/realworld/edgecases_spec.rb369
-rw-r--r--spec/bundler/realworld/ffi_spec.rb57
-rw-r--r--spec/bundler/realworld/fixtures/tapioca/Gemfile5
-rw-r--r--spec/bundler/realworld/fixtures/tapioca/Gemfile.lock49
-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.lock35
-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.rb143
-rw-r--r--spec/bundler/realworld/parallel_spec.rb39
-rw-r--r--spec/bundler/realworld/slow_perf_spec.rb35
-rw-r--r--spec/bundler/resolver/basic_spec.rb280
-rw-r--r--spec/bundler/resolver/platform_spec.rb354
-rw-r--r--spec/bundler/runtime/env_helpers_spec.rb236
-rw-r--r--spec/bundler/runtime/executable_spec.rb138
-rw-r--r--spec/bundler/runtime/gem_tasks_spec.rb135
-rw-r--r--spec/bundler/runtime/inline_spec.rb617
-rw-r--r--spec/bundler/runtime/load_spec.rb46
-rw-r--r--spec/bundler/runtime/platform_spec.rb409
-rw-r--r--spec/bundler/runtime/require_spec.rb242
-rw-r--r--spec/bundler/runtime/requiring_spec.rb15
-rw-r--r--spec/bundler/runtime/self_management_spec.rb279
-rw-r--r--spec/bundler/runtime/setup_spec.rb1279
-rw-r--r--spec/bundler/runtime/with_clean_env_spec.rb135
-rw-r--r--spec/bundler/spec_helper.rb232
-rw-r--r--spec/bundler/support/activate.rb9
-rw-r--r--spec/bundler/support/artifice/compact_index.rb119
-rw-r--r--spec/bundler/support/artifice/compact_index_api_missing.rb12
-rw-r--r--spec/bundler/support/artifice/compact_index_basic_authentication.rb5
-rw-r--r--spec/bundler/support/artifice/compact_index_checksum_mismatch.rb9
-rw-r--r--spec/bundler/support/artifice/compact_index_concurrent_download.rb14
-rw-r--r--spec/bundler/support/artifice/compact_index_cooldown.rb6
-rw-r--r--spec/bundler/support/artifice/compact_index_creds_diff_host.rb11
-rw-r--r--spec/bundler/support/artifice/compact_index_etag_match.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_extra.rb34
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_api.rb49
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_api_missing.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_missing.rb7
-rw-r--r--spec/bundler/support/artifice/compact_index_forbidden.rb5
-rw-r--r--spec/bundler/support/artifice/compact_index_host_redirect.rb7
-rw-r--r--spec/bundler/support/artifice/compact_index_mirror_down.rb21
-rw-r--r--spec/bundler/support/artifice/compact_index_no_checksums.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_no_gem.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb40
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb42
-rw-r--r--spec/bundler/support/artifice/compact_index_precompiled_before.rb25
-rw-r--r--spec/bundler/support/artifice/compact_index_range_ignored.rb40
-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.rb5
-rw-r--r--spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb7
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_dependencies.rb5
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb8
-rw-r--r--spec/bundler/support/artifice/endpoint.rb71
-rw-r--r--spec/bundler/support/artifice/endpoint_500.rb11
-rw-r--r--spec/bundler/support/artifice/endpoint_api_forbidden.rb5
-rw-r--r--spec/bundler/support/artifice/endpoint_api_missing.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_basic_authentication.rb5
-rw-r--r--spec/bundler/support/artifice/endpoint_creds_diff_host.rb11
-rw-r--r--spec/bundler/support/artifice/endpoint_extra.rb13
-rw-r--r--spec/bundler/support/artifice/endpoint_extra_api.rb13
-rw-r--r--spec/bundler/support/artifice/endpoint_extra_missing.rb7
-rw-r--r--spec/bundler/support/artifice/endpoint_fallback.rb5
-rw-r--r--spec/bundler/support/artifice/endpoint_host_redirect.rb7
-rw-r--r--spec/bundler/support/artifice/endpoint_marshal_fail.rb10
-rw-r--r--spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb (renamed from spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb)5
-rw-r--r--spec/bundler/support/artifice/endpoint_mirror_source.rb9
-rw-r--r--spec/bundler/support/artifice/endpoint_redirect.rb5
-rw-r--r--spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb7
-rw-r--r--spec/bundler/support/artifice/endpoint_timeout.rb7
-rw-r--r--spec/bundler/support/artifice/fail.rb30
-rw-r--r--spec/bundler/support/artifice/helpers/artifice.rb30
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index.rb128
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index_cooldown.rb13
-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.rb113
-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/vcr.rb152
-rw-r--r--spec/bundler/support/artifice/windows.rb21
-rw-r--r--spec/bundler/support/build_metadata.rb53
-rw-r--r--spec/bundler/support/builders.rb663
-rwxr-xr-xspec/bundler/support/bundle6
-rw-r--r--spec/bundler/support/bundle.rb7
-rw-r--r--spec/bundler/support/checksums.rb135
-rw-r--r--spec/bundler/support/code_climate.rb25
-rw-r--r--spec/bundler/support/command_execution.rb78
-rw-r--r--spec/bundler/support/env.rb13
-rw-r--r--spec/bundler/support/filters.rb42
-rw-r--r--spec/bundler/support/hax.rb87
-rw-r--r--spec/bundler/support/helpers.rb679
-rw-r--r--spec/bundler/support/indexes.rb133
-rw-r--r--spec/bundler/support/less_than_proc.rb19
-rw-r--r--spec/bundler/support/matchers.rb169
-rw-r--r--spec/bundler/support/options.rb15
-rw-r--r--spec/bundler/support/path.rb321
-rw-r--r--spec/bundler/support/permissions.rb1
-rw-r--r--spec/bundler/support/platforms.rb89
-rw-r--r--spec/bundler/support/rubygems_ext.rb258
-rw-r--r--spec/bundler/support/rubygems_version_manager.rb124
-rw-r--r--spec/bundler/support/setup.rb9
-rw-r--r--spec/bundler/support/shards.rb200
-rw-r--r--spec/bundler/support/silent_logger.rb9
-rw-r--r--spec/bundler/support/sometimes.rb56
-rw-r--r--spec/bundler/support/streams.rb14
-rw-r--r--spec/bundler/support/subprocess.rb115
-rw-r--r--spec/bundler/support/sudo.rb17
-rw-r--r--spec/bundler/support/switch_rubygems.rb5
-rw-r--r--spec/bundler/support/the_bundle.rb21
-rw-r--r--spec/bundler/support/vendored_net_http.rb23
-rw-r--r--spec/bundler/update/force_spec.rb30
-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.rb29
-rw-r--r--spec/bundler/update/git_spec.rb174
-rw-r--r--spec/bundler/update/path_spec.rb6
298 files changed, 44802 insertions, 13825 deletions
diff --git a/spec/bundler/bundler/build_metadata_spec.rb b/spec/bundler/bundler/build_metadata_spec.rb
new file mode 100644
index 0000000000..2e69821f68
--- /dev/null
+++ b/spec/bundler/bundler/build_metadata_spec.rb
@@ -0,0 +1,50 @@
+# 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(:@timestamp, nil)
+ end
+
+ describe "#timestamp" do
+ it "returns %Y-%m-%d formatted current time if built_at not set" do
+ Bundler::BuildMetadata.instance_variable_set(:@built_at, nil)
+ expect(Bundler::BuildMetadata.timestamp).to eq "1970-01-01"
+ end
+
+ it "returns %Y-%m-%d formatted current time if built_at not set" do
+ Bundler::BuildMetadata.instance_variable_set(:@built_at, "2025-01-01")
+ expect(Bundler::BuildMetadata.timestamp).to eq "2025-01-01"
+ ensure
+ Bundler::BuildMetadata.instance_variable_set(:@built_at, nil)
+ 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 Timestamp, and Git SHA" do
+ expect(subject["Timestamp"]).to eq "1970-01-01"
+ expect(subject["Git SHA"]).to be_instance_of(String)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb
index 268c0d99ac..bddcbdaef3 100644
--- a/spec/bundler/bundler/bundler_spec.rb
+++ b/spec/bundler/bundler/bundler_spec.rb
@@ -1,9 +1,74 @@
-# encoding: utf-8
# frozen_string_literal: true
-require "spec_helper"
+
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" => [:development] }
+ 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/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler",
+ "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md",
+ "homepage_uri" => "https://bundler.io/",
+ "source_code_uri" => "https://github.com/ruby/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) }
@@ -11,7 +76,7 @@ RSpec.describe Bundler do
context "with incorrect YAML file" do
before do
File.open(app_gemspec_path, "wb") do |f|
- f.write strip_whitespace(<<-GEMSPEC)
+ f.write <<~GEMSPEC
---
{:!00 ao=gu\g1= 7~f
GEMSPEC
@@ -21,33 +86,9 @@ RSpec.describe Bundler do
it "catches YAML syntax errors" do
expect { subject }.to raise_error(Bundler::GemspecError, /error while loading `test.gemspec`/)
end
-
- context "on Rubies with a settable YAML engine", :if => defined?(YAML::ENGINE) do
- context "with Syck as YAML::Engine" do
- it "raises a GemspecError after YAML load throws ArgumentError" do
- orig_yamler = YAML::ENGINE.yamler
- YAML::ENGINE.yamler = "syck"
-
- expect { subject }.to raise_error(Bundler::GemspecError)
-
- YAML::ENGINE.yamler = orig_yamler
- end
- end
-
- context "with Psych as YAML::Engine" do
- it "raises a GemspecError after YAML load throws Psych::SyntaxError" do
- orig_yamler = YAML::ENGINE.yamler
- YAML::ENGINE.yamler = "psych"
-
- expect { subject }.to raise_error(Bundler::GemspecError)
-
- YAML::ENGINE.yamler = orig_yamler
- end
- end
- end
end
- context "with correct YAML file", :if => defined?(Encoding) do
+ 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
@@ -58,7 +99,7 @@ RSpec.describe Bundler do
$VERBOSE = verbose
File.open(app_gemspec_path, "wb") do |file|
- file.puts <<-GEMSPEC.gsub(/^\s+/, "")
+ file.puts <<~GEMSPEC
# -*- encoding: utf-8 -*-
Gem::Specification.new do |gem|
gem.author = "André the Giant"
@@ -98,76 +139,85 @@ RSpec.describe Bundler do
end
GEMSPEC
end
- expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated")
+ expect(Bundler.rubygems).to receive(:validate).with have_attributes(name: "validated")
subject
end
end
- end
- describe "#which" do
- let(:executable) { "executable" }
- let(:path) { %w(/a /b c ../d /e) }
- let(:expected) { "executable" }
-
- before do
- ENV["PATH"] = path.join(File::PATH_SEPARATOR)
+ context "with gemspec containing local variables" do
+ before do
+ File.open(app_gemspec_path, "wb") do |f|
+ f.write <<~GEMSPEC
+ must_not_leak = true
+ Gem::Specification.new do |gem|
+ gem.name = "leak check"
+ end
+ GEMSPEC
+ end
+ end
- 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)
+ it "should not pollute the TOPLEVEL_BINDING" do
+ subject
+ expect(TOPLEVEL_BINDING.eval("local_variables")).to_not include(:must_not_leak)
end
end
+ end
- subject { described_class.which(executable) }
+ describe "#which" do
+ it "can detect relative path" do
+ script_path = bundled_app("tmp/test_command")
+ create_file(script_path, "#!/usr/bin/env ruby\n")
+
+ result = Dir.chdir script_path.dirname.dirname do
+ Bundler.which("test_command")
+ end
+ expect(result).to eq(nil)
- shared_examples_for "it returns the correct executable" do
- it "returns the expected file" do
- expect(subject).to eq(expected)
+ result = Dir.chdir script_path.dirname do
+ Bundler.which("test_command")
end
+
+ expect(result).to eq("test_command") unless Gem.win_platform?
+ expect(result).to eq("test_command.bat") if Gem.win_platform?
end
- it_behaves_like "it returns the correct executable"
+ it "can detect absolute path" do
+ create_file("test_command", "#!/usr/bin/env ruby\n")
+
+ ENV["PATH"] = bundled_app("test_command").parent.to_s
- context "when the executable in inside a quoted path" do
- let(:expected) { "/e/executable" }
- it_behaves_like "it returns the correct executable"
+ result = Bundler.which("test_command")
+ expect(result).to eq(bundled_app("test_command").to_s) unless Gem.win_platform?
+ expect(result).to eq(bundled_app("test_command.bat").to_s) if Gem.win_platform?
end
- context "when the executable is not found" do
- let(:expected) { nil }
- it_behaves_like "it returns the correct executable"
+ it "returns nil when not found" do
+ result = Bundler.which("test_command")
+ expect(result).to eq(nil)
end
end
describe "configuration" do
context "disable_shared_gems" do
it "should unset GEM_PATH with empty string" do
- env = {}
- settings = { :disable_shared_gems => true }
- Bundler.send(:configure_gem_path, env, settings)
- expect(env.keys).to include("GEM_PATH")
- expect(env["GEM_PATH"]).to eq ""
+ 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(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 can not continue.
-You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
-Please refer to http://ruby-doc.org/stdlib-2.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
+ describe "#mkdir_p" do
+ it "creates a folder at the given path" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ allow(Bundler).to receive(:root).and_return(bundled_app)
+
+ Bundler.mkdir_p(bundled_app("foo", "bar"))
+ expect(bundled_app("foo", "bar")).to exist
end
end
@@ -180,33 +230,115 @@ EOF
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?).and_call_original
+ 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(Etc).to receive(:getlogin).and_return("USER")
- allow(Dir).to receive(:tmpdir).and_return("/TMP")
- allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(true)
- expect(FileUtils).to receive(:mkpath).with("/TMP/bundler/home/USER")
- message = <<EOF
-Your home directory is not set.
-Bundler will use `/TMP/bundler/home/USER' as your home directory temporarily.
-EOF
- expect(Bundler.ui).to receive(:warn).with(message)
- expect(Bundler.user_home).to eq(Pathname("/TMP/bundler/home/USER"))
+ 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
- describe "#tmp_home_path" do
- it "should create temporary user home" do
- allow(Dir).to receive(:tmpdir).and_return("/TMP")
- allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(false)
- expect(FileUtils).to receive(:mkpath).once.ordered.with("/TMP/bundler/home")
- expect(FileUtils).to receive(:mkpath).once.ordered.with("/TMP/bundler/home/USER")
- expect(File).to receive(:chmod).with(0o777, "/TMP/bundler/home")
- expect(Bundler.tmp_home_path("USER", "")).to eq(Pathname("/TMP/bundler/home/USER"))
+ 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/ci_detector_spec.rb b/spec/bundler/bundler/ci_detector_spec.rb
new file mode 100644
index 0000000000..299d8005e8
--- /dev/null
+++ b/spec/bundler/bundler/ci_detector_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::CIDetector do
+ # This is properly tested in rubygems, under the name Gem::CIDetector
+ # But the test that confirms that our version _stays in sync_ with that version
+ # will live here.
+
+ it "stays in sync with the rubygems implementation" do
+ rubygems_implementation_path = File.join(git_root, "lib", "rubygems", "ci_detector.rb")
+ expect(File.exist?(rubygems_implementation_path)).to be_truthy
+ rubygems_code = File.read(rubygems_implementation_path)
+ denamespaced_rubygems_code = rubygems_code.sub("Gem", "NAMESPACE")
+
+ bundler_implementation_path = File.join(source_lib_dir, "bundler", "ci_detector.rb")
+ expect(File.exist?(bundler_implementation_path)).to be_truthy
+ bundler_code = File.read(bundler_implementation_path)
+ denamespaced_bundler_code = bundler_code.sub("Bundler", "NAMESPACE")
+
+ expect(denamespaced_bundler_code).to eq(denamespaced_rubygems_code)
+ end
+end
diff --git a/spec/bundler/bundler/cli_common_spec.rb b/spec/bundler/bundler/cli_common_spec.rb
new file mode 100644
index 0000000000..015894b3a1
--- /dev/null
+++ b/spec/bundler/bundler/cli_common_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "bundler/cli"
+
+RSpec.describe Bundler::CLI::Common do
+ describe "gem_not_found_message" do
+ it "should suggest alternate gem names" do
+ message = subject.gem_not_found_message("ralis", ["BOGUS"])
+ expect(message).to match("Could not find gem 'ralis'.$")
+ message = subject.gem_not_found_message("ralis", ["rails"])
+ expect(message).to match("Did you mean 'rails'?")
+ message = subject.gem_not_found_message("Rails", ["rails"])
+ expect(message).to match("Did you mean 'rails'?")
+ message = subject.gem_not_found_message("meail", %w[email fail eval])
+ expect(message).to match("Did you mean 'email'?")
+ message = subject.gem_not_found_message("nokogri", %w[nokogiri rails sidekiq dog])
+ expect(message).to match("Did you mean 'nokogiri'?")
+ message = subject.gem_not_found_message("methosd", %w[method methods bogus])
+ expect(message).to match(/Did you mean 'method(|s)' or 'method(|s)'?/)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb
index d41e6de4a7..56caf9937e 100644
--- a/spec/bundler/bundler/cli_spec.rb
+++ b/spec/bundler/bundler/cli_spec.rb
@@ -1,21 +1,23 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/cli"
RSpec.describe "bundle executable" do
it "returns non-zero exit status when passed unrecognized options" do
- bundle "--invalid_argument"
- expect(exitstatus).to_not be_zero if exitstatus
+ 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"
- expect(exitstatus).to_not be_zero if exitstatus
+ 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['BUNDLE_RUBY'] || "/usr/bin/env ruby"
+ ruby = ENV["RUBY"] || "/usr/bin/env ruby"
f.puts "#!#{ruby}\nputs 'Hello, world'\n"
end
@@ -23,64 +25,194 @@ RSpec.describe "bundle executable" do
bundle "testtasks"
end
- expect(exitstatus).to be_zero if exitstatus
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 "tries to installs by default but print help on missing Gemfile" do
+ bundle "", raise_on_error: false
+ expect(err).to include("Could not locate Gemfile")
+ expect(out).to include("In a future version of Bundler")
+
+ 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
+
+ it "runs bundle install when default_cli_command set to install" do
+ bundle_config "default_cli_command install"
+ bundle "", raise_on_error: false
+ expect(out).to_not include("In a future version of Bundler")
+ expect(err).to include("Could not locate Gemfile")
+ expect(exitstatus).to_not be_zero
+ 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://#{gem_repo1}"
- gem 'rack'
+ gemfile bundled_app_gemfile, <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
G
- bundle :install, :env => { "BUNDLE_GEMFILE" => "" }
+ bundle :install, env: { "BUNDLE_GEMFILE" => "" }
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
- context "when ENV['RUBYGEMS_GEMDEPS'] is set" do
- it "displays a warning" do
- gemfile bundled_app("Gemfile"), <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
- G
+ context "with --verbose" do
+ before do
+ gemfile "source 'https://gem.repo1'"
+ end
+
+ it "prints the running command" do
+ bundle "info bundler", verbose: true
+ expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}")
- bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "foo" }
- expect(out).to include("RUBYGEMS_GEMDEPS")
- expect(out).to include("conflict with Bundler")
+ bundle "install", verbose: true
+ expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}")
+ end
- bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "" }
- expect(out).not_to include("RUBYGEMS_GEMDEPS")
+ it "prints the simulated version too when setting is enabled" do
+ bundle "config set simulate_version 4", verbose: true
+ bundle "info bundler", verbose: true
+ expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION} (simulating Bundler 4)")
end
end
- context "with --verbose" do
+ context "with verbose configuration" do
+ before do
+ bundle_config "verbose true"
+ end
+
it "prints the running command" do
- gemfile ""
- bundle! "info bundler", :verbose => true
- expect(out).to start_with("Running `bundle info bundler --no-color --verbose` with bundler #{Bundler::VERSION}")
+ gemfile "source 'https://gem.repo1'"
+ bundle "info bundler"
+ expect(out).to start_with("Running `bundle info bundler` with bundler #{Bundler::VERSION}")
end
+ end
+
+ describe "bundle outdated" do
+ let(:run_command) do
+ bundle "install"
- it "doesn't print defaults" do
- install_gemfile! "", :verbose => true
- expect(out).to start_with("Running `bundle install --no-color --retry 0 --verbose` with bundler #{Bundler::VERSION}")
+ bundle "outdated #{flags}", raise_on_error: false
+ end
+
+ before do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", '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("myrack 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("myrack (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("myrack (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"
- expect(err + out).to eq("Could not find command \"fail\".")
+ bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false
+ expect(stdboth).to eq("Could not find command \"fail\".")
end
end
- let(:bundler_version) { "1.1" }
+ let(:bundler_version) { "2.0" }
let(:latest_version) { nil }
before do
- simulate_bundler_version(bundler_version)
+ bundle_config_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
@@ -103,39 +235,39 @@ RSpec.describe "bundle executable" do
end
context "when the latest version is greater than the current version" do
- let(:latest_version) { "2.0" }
+ let(:latest_version) { "222.0" }
it "prints the version warning" do
- bundle "fail"
- expect(err + out).to eq(<<-EOS.strip)
+ 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, run `gem install bundler`
-Could not find command "fail".
+To update to the most recent version, run `bundle update --bundler`
EOS
end
context "and disable_version_check is set" do
- before { bundle! "config disable_version_check true" }
+ 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 --parseable foo"
- expect(out).to eq ""
+ bundle "config set foo value", env: { "BUNDLER_VERSION" => bundler_version }
+ bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version }
+ expect(out).to eq "foo=value"
+ expect(err).to eq ""
- bundle "platform --ruby"
- expect(out).to eq "Could not locate Gemfile"
+ bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false
+ expect(stdboth).to eq "Could not locate Gemfile"
end
end
context "and is a pre-release" do
- let(:latest_version) { "2.0.0.pre.4" }
+ let(:latest_version) { "222.0.0.pre.4" }
it "prints the version warning" do
- bundle "fail"
- expect(err + out).to eq(<<-EOS.strip)
+ 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, run `gem install bundler --pre`
-Could not find command "fail".
+To update to the most recent version, run `bundle update --bundler`
EOS
end
end
@@ -146,6 +278,21 @@ end
RSpec.describe "bundler executable" do
it "shows the bundler version just as the `bundle` executable does" do
bundler "--version"
- expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ expect(out).to eq(Bundler::VERSION.to_s)
+
+ bundle_config "simulate_version 5"
+ bundler "--version"
+ expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)")
+ end
+
+ it "shows cli_help when bundler install and no Gemfile is found" do
+ bundler "install", raise_on_error: false
+ expect(err).to include("Could not locate Gemfile")
+
+ 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
diff --git a/spec/bundler/bundler/compact_index_client/parser_spec.rb b/spec/bundler/bundler/compact_index_client/parser_spec.rb
new file mode 100644
index 0000000000..6aa867f058
--- /dev/null
+++ b/spec/bundler/bundler/compact_index_client/parser_spec.rb
@@ -0,0 +1,249 @@
+# frozen_string_literal: true
+
+require "bundler/compact_index_client"
+require "bundler/compact_index_client/parser"
+
+TestCompactIndexClient = Struct.new(:names, :versions, :info_data) do
+ # Requiring the checksum to match the input data helps ensure
+ # that we are parsing the correct checksum from the versions file
+ def info(name, checksum)
+ info_data.dig(name, checksum)
+ end
+
+ def set_info_data(name, value)
+ info_data[name] = value
+ end
+end
+
+RSpec.describe Bundler::CompactIndexClient::Parser do
+ subject(:parser) { described_class.new(compact_index) }
+
+ let(:compact_index) { TestCompactIndexClient.new(names, versions, info_data) }
+ let(:names) { "a\nb\nc\n" }
+ let(:versions) { <<~VERSIONS.dup }
+ created_at: 2024-05-01T00:00:04Z
+ ---
+ a 1.0.0,1.0.1,1.1.0 aaa111
+ b 2.0.0,2.0.0-java bbb222
+ c 3.0.0,3.0.3,3.3.3 ccc333
+ c -3.0.3 ccc333yanked
+ VERSIONS
+ let(:info_data) do
+ {
+ "a" => { "aaa111" => a_info },
+ "b" => { "bbb222" => b_info },
+ "c" => { "ccc333yanked" => c_info },
+ }
+ end
+ let(:a_info) { <<~INFO.dup }
+ ---
+ 1.0.0 |checksum:aaa1,ruby:>= 3.0.0,rubygems:>= 3.2.3
+ 1.0.1 |checksum:aaa2,ruby:>= 3.0.0,rubygems:>= 3.2.3
+ 1.1.0 |checksum:aaa3,ruby:>= 3.0.0,rubygems:>= 3.2.3
+ INFO
+ let(:b_info) { <<~INFO }
+ 2.0.0 a:~> 1.0&<= 3.0|checksum:bbb1
+ 2.0.0-java a:~> 1.0&<= 3.0|checksum:bbb2
+ INFO
+ let(:c_info) { <<~INFO }
+ 3.0.0 a:= 1.0.0,b:~> 2.0|checksum:ccc1,ruby:>= 2.7.0,rubygems:>= 3.0.0
+ 3.3.3 a:>= 1.1.0,b:~> 2.0|checksum:ccc3,ruby:>= 3.0.0,rubygems:>= 3.2.3,created_at:2026-05-12T10:00:00Z
+ INFO
+
+ describe "#available?" do
+ it "returns true versions are available" do
+ expect(parser).to be_available
+ end
+
+ it "returns true when versions has only one gem" do
+ compact_index.versions = +"a 1.0.0 aaa1\n"
+ expect(parser).to be_available
+ end
+
+ it "returns true when versions has a gem and a header" do
+ compact_index.versions = +"---\na 1.0.0 aaa1\n"
+ expect(parser).to be_available
+ end
+
+ it "returns true when versions has a gem and a header with header data" do
+ compact_index.versions = +"created_at: 2024-05-01T00:00:04Z\n---\na 1.0.0 aaa1\n"
+ expect(parser).to be_available
+ end
+
+ it "returns false when versions has only the header" do
+ compact_index.versions = +"---\n"
+ expect(parser).not_to be_available
+ end
+
+ it "returns false when versions has only the header with header data" do
+ compact_index.versions = +"created_at: 2024-05-01T00:00:04Z\n---\n"
+ expect(parser).not_to be_available
+ end
+
+ it "returns false when versions index is not available" do
+ compact_index.versions = nil
+ expect(parser).not_to be_available
+ end
+
+ it "returns false when versions is empty" do
+ compact_index.versions = +""
+ expect(parser).not_to be_available
+ end
+ end
+
+ describe "#names" do
+ it "returns the names" do
+ expect(parser.names).to eq(%w[a b c])
+ end
+
+ it "returns an empty array when names is empty" do
+ compact_index.names = ""
+ expect(parser.names).to eq([])
+ end
+
+ it "returns an empty array when names is not readable" do
+ compact_index.names = nil
+ expect(parser.names).to eq([])
+ end
+ end
+
+ describe "#versions" do
+ it "returns the versions" do
+ expect(parser.versions).to eq(
+ "a" => [
+ ["a", "1.0.0"],
+ ["a", "1.0.1"],
+ ["a", "1.1.0"],
+ ],
+ "b" => [
+ ["b", "2.0.0"],
+ ["b", "2.0.0", "java"],
+ ],
+ "c" => [
+ ["c", "3.0.0"],
+ ["c", "3.3.3"],
+ ],
+ )
+ end
+
+ it "returns an empty hash when versions is empty" do
+ compact_index.versions = ""
+ expect(parser.versions).to eq({})
+ end
+
+ it "returns an empty hash when versions is not readable" do
+ compact_index.versions = nil
+ expect(parser.versions).to eq({})
+ end
+ end
+
+ describe "#info" do
+ let(:a_result) do
+ [
+ [
+ "a",
+ "1.0.0",
+ nil,
+ [],
+ [["checksum", ["aaa1"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]],
+ ],
+ [
+ "a",
+ "1.0.1",
+ nil,
+ [],
+ [["checksum", ["aaa2"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]],
+ ],
+ [
+ "a",
+ "1.1.0",
+ nil,
+ [],
+ [["checksum", ["aaa3"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]],
+ ],
+ ]
+ end
+ let(:b_result) do
+ [
+ [
+ "b",
+ "2.0.0",
+ nil,
+ [["a", ["~> 1.0", "<= 3.0"]]],
+ [["checksum", ["bbb1"]]],
+ ],
+ [
+ "b",
+ "2.0.0",
+ "java",
+ [["a", ["~> 1.0", "<= 3.0"]]],
+ [["checksum", ["bbb2"]]],
+ ],
+ ]
+ end
+ let(:c_result) do
+ [
+ [
+ "c",
+ "3.0.0",
+ nil,
+ [["a", ["= 1.0.0"]], ["b", ["~> 2.0"]]],
+ [["checksum", ["ccc1"]], ["ruby", [">= 2.7.0"]], ["rubygems", [">= 3.0.0"]]],
+ ],
+ [
+ "c",
+ "3.3.3",
+ nil,
+ [["a", [">= 1.1.0"]], ["b", ["~> 2.0"]]],
+ [["checksum", ["ccc3"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]], ["created_at", ["2026-05-12T10:00:00Z"]]],
+ ],
+ ]
+ end
+
+ it "returns the info for example gem 'a' which has no deps" do
+ expect(parser.info("a")).to eq(a_result)
+ end
+
+ it "returns the info for example gem 'b' which has platform and compound deps" do
+ expect(parser.info("b")).to eq(b_result)
+ end
+
+ it "returns the info for example gem 'c' which has deps and yanked version (requires use of correct info checksum)" do
+ expect(parser.info("c")).to eq(c_result)
+ end
+
+ it "returns an empty array when the info is empty" do
+ compact_index.set_info_data("a", {})
+ expect(parser.info("a")).to eq([])
+ end
+
+ it "returns an empty array when the info is not readable" do
+ expect(parser.info("d")).to eq([])
+ end
+
+ it "handles empty lines in the versions file (Artifactory bug that they have yet to fix)" do
+ compact_index.versions = +<<~VERSIONS
+ created_at: 2024-05-01T00:00:04Z
+ ---
+ a 1.0.0,1.0.1,1.1.0 aaa111
+ b 2.0.0,2.0.0-java bbb222
+
+ c 3.0.0,3.0.3,3.3.3 ccc333
+ c -3.0.3 ccc333yanked
+ VERSIONS
+ expect(parser.info("a")).to eq(a_result)
+ end
+
+ it "handles lines without a checksum" do
+ compact_index.versions = <<~VERSIONS
+ created_at: 2024-05-01T00:00:04Z
+ ---
+ a 1.0.0,1.0.1,1.1.0 aaa111
+ b 2.0.0,2.0.0-java
+ c 3.0.0,3.0.3,3.3.3 ccc333
+ VERSIONS
+
+ expect(parser.info("a")).to eq(a_result)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/compact_index_client/updater_spec.rb b/spec/bundler/bundler/compact_index_client/updater_spec.rb
index c1cae31956..fd63a652a4 100644
--- a/spec/bundler/bundler/compact_index_client/updater_spec.rb
+++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb
@@ -1,30 +1,243 @@
# frozen_string_literal: true
-require "spec_helper"
-require "net/http"
+
+require "bundler/vendored_net_http"
require "bundler/compact_index_client"
require "bundler/compact_index_client/updater"
+require "tmpdir"
RSpec.describe Bundler::CompactIndexClient::Updater do
subject(:updater) { described_class.new(fetcher) }
let(:fetcher) { double(:fetcher) }
+ let(:local_path) { Pathname.new(Dir.mktmpdir("localpath")).join("versions") }
+ let(:etag_path) { Pathname.new(Dir.mktmpdir("localpath-etags")).join("versions.etag") }
+ let(:remote_path) { double(:remote_path) }
+
+ let(:full_body) { "abc123" }
+ let(:response) { double(:response, body: full_body, is_a?: false) }
+ let(:digest) { Digest::SHA256.base64digest(full_body) }
+
+ context "when the local path does not exist" do
+ before do
+ allow(response).to receive(:[]).with("Repr-Digest") { nil }
+ allow(response).to receive(:[]).with("Digest") { nil }
+ allow(response).to receive(:[]).with("ETag") { '"thisisanetag"' }
+ end
+
+ it "downloads the file without attempting append" do
+ expect(fetcher).to receive(:call).once.with(remote_path, {}) { response }
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("thisisanetag")
+ end
+
+ it "fails immediately on bad checksum" do
+ expect(fetcher).to receive(:call).once.with(remote_path, {}) { response }
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" }
+
+ expect do
+ updater.update(remote_path, local_path, etag_path)
+ end.to raise_error(Bundler::CompactIndexClient::Updater::MismatchedChecksumError)
+ end
+ end
+
+ context "when the local path exists" do
+ let(:local_body) { "abc" }
+
+ before do
+ local_path.open("w") {|f| f.write(local_body) }
+ end
+
+ context "with an etag" do
+ before do
+ etag_path.open("w") {|f| f.write("LocalEtag") }
+ end
+
+ let(:headers) do
+ {
+ "If-None-Match" => '"LocalEtag"',
+ "Range" => "bytes=2-",
+ }
+ end
+
+ it "does nothing if etags match" do
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { true }
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq("abc")
+ expect(etag_path.read).to eq("LocalEtag")
+ end
+
+ it "appends the file if etags do not match" do
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
+ allow(response).to receive(:[]).with("ETag") { '"NewEtag"' }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false }
+ allow(response).to receive(:body) { "c123" }
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("NewEtag")
+ end
+
+ it "replaces the file if response ignores range" do
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
+ allow(response).to receive(:[]).with("ETag") { '"NewEtag"' }
+ allow(response).to receive(:body) { full_body }
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("NewEtag")
+ end
+
+ it "tries the request again if the partial response fails digest check" do
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" }
+ allow(response).to receive(:body) { "the beginning of the file changed" }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+
+ full_response = double(:full_response, body: full_body, is_a?: false)
+ allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
+ allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' }
+ expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response)
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("NewEtag")
+ end
+
+ it "tries the request again if the partial response is blank" do
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" }
+ allow(response).to receive(:body) { "" }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+
+ full_response = double(:full_response, body: full_body, is_a?: false)
+ allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
+ allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' }
+ expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response)
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("NewEtag")
+ end
+ end
+
+ context "without an etag file" do
+ let(:headers) do
+ { "Range" => "bytes=2-" }
+ end
+
+ it "appends the file" do
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
+ allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false }
+ allow(response).to receive(:body) { "c123" }
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("OpaqueEtag")
+ end
+
+ it "replaces the file on full file response that ignores range request" do
+ expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
+ allow(response).to receive(:[]).with("Repr-Digest") { nil }
+ allow(response).to receive(:[]).with("Digest") { nil }
+ allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false }
+ allow(response).to receive(:body) { full_body }
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("OpaqueEtag")
+ end
+
+ it "tries the request again if the partial response fails digest check" do
+ allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" }
+ allow(response).to receive(:body) { "the beginning of the file changed" }
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
+ expect(fetcher).to receive(:call).once.with(remote_path, headers) do
+ # During the failed first request, we simulate another process writing the etag.
+ # This ensures the second request doesn't generate the md5 etag again but just uses whatever is written.
+ etag_path.open("w") {|f| f.write("LocalEtag") }
+ response
+ end
+
+ full_response = double(:full_response, body: full_body, is_a?: false)
+ allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
+ allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' }
+ expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response)
+
+ updater.update(remote_path, local_path, etag_path)
+
+ expect(local_path.read).to eq(full_body)
+ expect(etag_path.read).to eq("NewEtag")
+ end
+ end
+ end
context "when the ETag header is missing" do
- # Regression test for https://github.com/bundler/bundler/issues/5463
+ # Regression test for https://github.com/rubygems/bundler/issues/5463
+ let(:response) { double(:response, body: full_body) }
+
+ it "treats the response as an update" do
+ allow(response).to receive(:[]).with("Repr-Digest") { nil }
+ allow(response).to receive(:[]).with("Digest") { nil }
+ allow(response).to receive(:[]).with("ETag") { nil }
+ expect(fetcher).to receive(:call) { response }
+
+ updater.update(remote_path, local_path, etag_path)
+ end
+ end
- let(:response) { double(:response, :body => "") }
- let(:local_path) { Pathname("/tmp/localpath") }
- let(:remote_path) { double(:remote_path) }
+ context "when the download is corrupt" do
+ let(:response) { double(:response, body: "") }
- it "MisMatchedChecksumError is raised" do
- # Twice: #update retries on failure
- expect(response).to receive(:[]).with("Content-Encoding").twice { "" }
- expect(response).to receive(:[]).with("ETag").twice { nil }
- expect(fetcher).to receive(:call).twice { response }
+ 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::CompactIndexClient::Updater::MisMatchedChecksumError)
+ updater.update(remote_path, local_path, etag_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"
+ allow(response).to receive(:[]).with("Repr-Digest") { nil }
+ allow(response).to receive(:[]).with("Digest") { nil }
+ allow(response).to receive(:[]).with("ETag") { nil }
+ expect(fetcher).to receive(:call) { response }
+
+ updater.update(remote_path, local_path, etag_path)
+ ensure
+ Encoding.default_internal = previous_internal_encoding
+ $VERBOSE = old_verbose
+ end
end
end
end
diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb
new file mode 100644
index 0000000000..79eb802aa5
--- /dev/null
+++ b/spec/bundler/bundler/current_ruby_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::CurrentRuby do
+ 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,
+ ruby_34: Gem::Platform::RUBY,
+ ruby_40: Gem::Platform::RUBY,
+ ruby_41: 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,
+ mri_34: Gem::Platform::RUBY,
+ mri_40: Gem::Platform::RUBY,
+ mri_41: 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,
+ windows_34: Gem::Platform::WINDOWS,
+ windows_40: Gem::Platform::WINDOWS,
+ windows_41: Gem::Platform::WINDOWS }
+ end
+
+ let(:deprecated) do
+ { 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,
+ mswin_34: Gem::Platform::MSWIN,
+ mswin_40: Gem::Platform::MSWIN,
+ mswin_41: 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,
+ mswin64_34: Gem::Platform::MSWIN64,
+ mswin64_40: Gem::Platform::MSWIN64,
+ mswin64_41: Gem::Platform::MSWIN64,
+ mingw: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_18: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_19: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_20: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_21: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_22: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_23: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_24: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_25: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_26: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_27: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_30: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_31: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_32: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_33: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_34: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_40: Gem::Platform::UNIVERSAL_MINGW,
+ mingw_41: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_20: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_21: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_22: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_23: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_24: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_25: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_26: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_27: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_30: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_31: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_32: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_33: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_34: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW,
+ x64_mingw_41: Gem::Platform::UNIVERSAL_MINGW }
+ end
+ # rubocop:enable Naming/VariableNumber
+
+ it "includes all platforms" do
+ expect(subject).to eq(platforms.merge(deprecated))
+ end
+ end
+
+ describe "Deprecated platform" do
+ it "outputs an error and aborts when calling maglev?" do
+ expect { Bundler.current_ruby.maglev? }.to raise_error(Bundler::RemovedError, /`CurrentRuby#maglev\?` was removed with no replacement./)
+ end
+
+ it "outputs an error and aborts when calling maglev_31?" do
+ expect { Bundler.current_ruby.maglev_31? }.to raise_error(Bundler::RemovedError, /`CurrentRuby#maglev_31\?` was removed with no replacement./)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb
index 73d44a93ab..8c4a5a0331 100644
--- a/spec/bundler/bundler/definition_spec.rb
+++ b/spec/bundler/bundler/definition_spec.rb
@@ -1,168 +1,245 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/definition"
RSpec.describe Bundler::Definition do
+ describe "#overrides" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_gemfile }
+ end
+
+ subject { Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, {}) }
+
+ it "defaults to an empty array" do
+ expect(subject.overrides).to eq([])
+ end
+
+ it "is writable" do
+ override = Bundler::Override.new("rails", :version, ">= 8.0")
+ subject.overrides = [override]
+ expect(subject.overrides).to eq([override])
+ end
+ end
+
describe "#lock" do
before do
- allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") }
- allow(Bundler).to receive(:default_gemfile) { Pathname.new("Gemfile") }
- allow(Bundler).to receive(:ui) { double("UI", :info => "", :debug => "") }
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_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, []) }
+ subject { Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, {}) }
+
+ context "when it's not possible to write to the file" do
it "raises an PermissionError with explanation" do
- expect(File).to receive(:open).with("Gemfile.lock", "wb").
- and_raise(Errno::EACCES)
- expect { subject.lock("Gemfile.lock") }.
+ allow(File).to receive(:open).and_call_original
+ expect(File).to receive(:open).with(bundled_app_lock, "wb").
+ and_raise(Errno::EACCES.new(bundled_app_lock.to_s))
+ expect { subject.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
- expect(File).to receive(:open).with("Gemfile.lock", "wb").
+ allow(File).to receive(:open).and_call_original
+ expect(File).to receive(:open).with(bundled_app_lock, "wb").
and_raise(Errno::EAGAIN)
- expect { subject.lock("Gemfile.lock") }.
+ expect { subject.lock }.
to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/)
end
end
+ context "when Bundler::Definition.no_lock is set to true" do
+ before { Bundler::Definition.no_lock = true }
+ after { Bundler::Definition.no_lock = false }
+
+ it "does not create a lockfile" do
+ subject.lock
+ expect(bundled_app_lock).not_to be_file
+ end
+ end
end
describe "detects changes" do
it "for a path gem with changes" do
- build_lib "foo", "1.0", :path => lib_path("foo")
+ build_lib "foo", "1.0", path: lib_path("foo")
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack", "1.0"
end
- bundle :install, :env => { "DEBUG" => 1 }
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ end
+
+ bundle :install, env: { "DEBUG" => "1" }
expect(out).to match(/re-resolving dependencies/)
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
PATH
remote: #{lib_path("foo")}
specs:
foo (1.0)
- rack (= 1.0)
+ myrack (= 1.0)
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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 "https://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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack", "1.0"
s.add_development_dependency "net-ssh", "1.0"
end
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ end
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo")}"
G
- bundle :check, :env => { "DEBUG" => 1 }
-
- expect(out).to match(/using resolution from the lockfile/)
- lockfile_should_be <<-G
+ expected_lockfile = <<~G
PATH
remote: #{lib_path("foo")}
specs:
foo (1.0)
- rack (= 1.0)
+ myrack (= 1.0)
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(lockfile).to eq(expected_lockfile)
+
+ bundle :check, env: { "DEBUG" => "1" }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(lockfile).to eq(expected_lockfile)
+ end
+
+ it "for a locked gem for another platform" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "only_java", platform: :jruby
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "only_java", "1.1", "java"
+ end
+ 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: https://gem.repo1/
+ specs:
+ only_java (1.1-java)
+
+ PLATFORMS
+ #{lockfile_platforms("java")}
+
+ DEPENDENCIES
+ only_java
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "for a rubygems gem" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "foo", "1.0"
+ end
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo"
G
- bundle :check, :env => { "DEBUG" => 1 }
+ bundle :check, env: { "DEBUG" => "1" }
expect(out).to match(/using resolution from the lockfile/)
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
foo (1.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
foo
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
end
describe "initialize" do
context "gem version promoter" do
- context "with lockfile" do
- before do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "foo"
- G
- end
-
- it "should get a locked specs list when updating all" do
- definition = Bundler::Definition.new(bundled_app("Gemfile.lock"), [], Bundler::SourceList.new, true)
- locked_specs = definition.gem_version_promoter.locked_specs
- expect(locked_specs.to_a.map(&:name)).to eq ["foo"]
- expect(definition.instance_variable_get("@locked_specs").empty?).to eq true
- end
- end
-
- context "without gemfile or lockfile" do
- it "should not attempt to parse empty lockfile contents" do
- definition = Bundler::Definition.new(nil, [], mock_source_list, true)
- expect(definition.gem_version_promoter.locked_specs.to_a).to eq []
+ context "eager unlock" do
+ let(:source_list) do
+ Bundler::SourceList.new.tap do |source_list|
+ source_list.add_global_rubygems_remote("https://gem.repo4")
+ end
end
- end
- context "eager unlock" do
before do
gemfile <<-G
- source "file://#{gem_repo4}"
+ source "https://gem.repo4"
gem 'isolated_owner'
gem 'shared_owner_a'
@@ -171,7 +248,7 @@ RSpec.describe Bundler::Definition do
lockfile <<-L
GEM
- remote: file://#{gem_repo4}
+ remote: https://gem.repo4
specs:
isolated_dep (2.0.1)
isolated_owner (1.0.1)
@@ -193,6 +270,8 @@ RSpec.describe Bundler::Definition do
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
@@ -201,13 +280,13 @@ RSpec.describe Bundler::Definition do
Bundler::Dependency.new("shared_owner_b", ">= 0")]
unlock_hash_for_bundle_install = {}
definition = Bundler::Definition.new(
- bundled_app("Gemfile.lock"),
+ bundled_app_lock,
updated_deps_in_gemfile,
- Bundler::SourceList.new,
+ source_list,
unlock_hash_for_bundle_install
)
locked = definition.send(:converge_locked_specs).map(&:name)
- expect(locked.include?("shared_dep")).to be_truthy
+ expect(locked).to include "shared_dep"
end
it "should not eagerly unlock shared dependency with bundle update conservative updating behavior" do
@@ -215,44 +294,68 @@ RSpec.describe Bundler::Definition do
Bundler::Dependency.new("shared_owner_a", ">= 0"),
Bundler::Dependency.new("shared_owner_b", ">= 0")]
definition = Bundler::Definition.new(
- bundled_app("Gemfile.lock"),
+ bundled_app_lock,
updated_deps_in_gemfile,
- Bundler::SourceList.new,
- :gems => ["shared_owner_a"], :lock_shared_dependencies => true
+ 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).to eq %w[isolated_dep isolated_owner shared_dep shared_owner_b]
expect(locked.include?("shared_dep")).to be_truthy
end
end
end
end
- describe "find_resolved_spec" do
- it "with no platform set in SpecSet" do
- ss = Bundler::SpecSet.new([build_stub_spec("a", "1.0"), build_stub_spec("b", "1.0")])
- dfn = Bundler::Definition.new(nil, [], mock_source_list, true)
- dfn.instance_variable_set("@specs", ss)
- found = dfn.find_resolved_spec(build_spec("a", "0.9", "ruby").first)
- expect(found.name).to eq "a"
- expect(found.version.to_s).to eq "1.0"
+ describe "#precompute_source_requirements_for_indirect_dependencies?" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") }
end
- end
- describe "find_indexed_specs" do
- it "with no platform set in indexed specs" do
- index = Bundler::Index.new
- %w(1.0.0 1.0.1 1.1.0).each {|v| index << build_stub_spec("foo", v) }
+ let(:sources) { Bundler::SourceList.new }
+ subject { Bundler::Definition.new(nil, [], sources, []) }
- dfn = Bundler::Definition.new(nil, [], mock_source_list, true)
- dfn.instance_variable_set("@index", index)
- found = dfn.find_indexed_specs(build_spec("foo", "0.9", "ruby").first)
- expect(found.length).to eq 3
+ before do
+ allow(sources).to receive(:non_global_rubygems_sources).and_return(non_global_rubygems_sources)
+ end
+
+ context "when all the scoped sources implement a dependency API" do
+ let(:non_global_rubygems_sources) do
+ [
+ double("non-global-source-0", "dependency_api_available?":true, to_s:"a"),
+ double("non-global-source-1", "dependency_api_available?":true, to_s:"b"),
+ ]
+ end
+
+ it "returns true without warning" do
+ expect(subject).not_to receive(:non_dependency_api_warning)
+
+ expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_truthy
+ end
end
- end
- def build_stub_spec(name, version)
- Bundler::StubSpecification.new(name, version, nil, nil)
+ context "when some scoped sources do not implement a dependency API" do
+ let(:non_global_rubygems_sources) do
+ [
+ double("non-global-source-0", "dependency_api_available?":true, to_s:"a"),
+ double("non-global-source-1", "dependency_api_available?":false, to_s:"b"),
+ double("non-global-source-2", "dependency_api_available?":false, to_s:"c"),
+ ]
+ end
+
+ it "returns false and warns about the non-API sources" do
+ expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
+Your Gemfile contains scoped sources that don't implement a dependency API, namely:
+
+ * b
+ * c
+
+Using the above gem servers may result in installing unexpected gems. To resolve this warning, make sure you use gem servers that implement dependency APIs, such as gemstash or geminabox gem servers.
+ W
+
+ expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_falsy
+ end
+ end
end
def mock_source_list
@@ -265,10 +368,6 @@ RSpec.describe Bundler::Definition do
[]
end
- def rubygems_remotes
- []
- end
-
def replace_sources!(arg)
nil
end
diff --git a/spec/bundler/bundler/dependency_spec.rb b/spec/bundler/bundler/dependency_spec.rb
new file mode 100644
index 0000000000..f930459571
--- /dev/null
+++ b/spec/bundler/bundler/dependency_spec.rb
@@ -0,0 +1,49 @@
+# 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
+
+ it "is on the current platform" do
+ engine = Gem.win_platform? ? "windows" : RUBY_ENGINE
+
+ dep = described_class.new(
+ "test_gem",
+ "1.0.0",
+ { "platforms" => "#{engine}_#{RbConfig::CONFIG["MAJOR"]}#{RbConfig::CONFIG["MINOR"]}" },
+ )
+
+ expect(dep.current_platform?).to be_truthy
+ end
+end
diff --git a/spec/bundler/bundler/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb
new file mode 100644
index 0000000000..f876827964
--- /dev/null
+++ b/spec/bundler/bundler/digest_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "openssl"
+require "bundler/digest"
+
+RSpec.describe Bundler::Digest do
+ context "SHA1" do
+ subject { Bundler::Digest }
+ let(:stdlib) { OpenSSL::Digest::SHA1 }
+
+ it "is compatible with stdlib" do
+ random_strings = ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"]
+
+ # https://www.rfc-editor.org/rfc/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
index 4f5eb6dc92..b6e67a312c 100644
--- a/spec/bundler/bundler/dsl_spec.rb
+++ b/spec/bundler/bundler/dsl_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Dsl do
before do
@@ -11,7 +10,7 @@ RSpec.describe Bundler::Dsl 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")
+ 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
@@ -26,48 +25,144 @@ RSpec.describe Bundler::Dsl do
expect { subject.git_source(:example) }.to raise_error(Bundler::InvalidOption)
end
- context "default hosts (git, gist)" do
- it "converts :github to :git" do
- subject.gem("sparks", :github => "indirect/sparks")
- github_uri = "git://github.com/indirect/sparks.git"
+ 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 "converts :gitlab PR to URI using https" do
+ subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5")
+ gitlab_uri = "https://gitlab.com/indirect/sparks.git"
+ expect(subject.dependencies.first.source.uri).to eq(gitlab_uri)
+ expect(subject.dependencies.first.source.ref).to eq("refs/merge-requests/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 :gitlab PR URI with a branch, ref or tag" do
+ expect do
+ subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", branch: "foo")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :branch option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`),
+ )
+
+ expect do
+ subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", ref: "foo")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :ref option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`),
+ )
+
+ expect do
+ subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", tag: "foo")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :tag option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/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
+
+ it "rejects :gitlab with :git" do
+ expect do
+ subject.gem("sparks", gitlab: "indirect/sparks", git: "https://gitlab.com/indirect/sparks.git")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :git option can't be used with `gitlab: "indirect/sparks"`),
+ )
+ end
+
+ context "default hosts" 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 numeric :gist to :git" do
- subject.gem("not-really-a-gem", :gist => 2_859_988)
- github_uri = "https://gist.github.com/2859988.git"
+ 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 :gist to :git" do
- subject.gem("not-really-a-gem", :gist => "2859988")
+ it "converts :gitlab to URI using https" do
+ subject.gem("sparks", gitlab: "indirect/sparks")
+ gitlab_uri = "https://gitlab.com/indirect/sparks.git"
+ expect(subject.dependencies.first.source.uri).to eq(gitlab_uri)
+ end
+
+ it "converts :gitlab shortcut to URI using https" do
+ subject.gem("sparks", gitlab: "rails")
+ gitlab_uri = "https://gitlab.com/rails/rails.git"
+ expect(subject.dependencies.first.source.uri).to eq(gitlab_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 'rails' to 'rails/rails'" do
- subject.gem("rails", :github => "rails")
- github_uri = "git://github.com/rails/rails.git"
+ 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")
+ 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")
+ 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, github, and gitlab" do
+ expect(subject.instance_variable_get(:@git_sources).keys.sort).to eq(%w[bitbucket gist github gitlab])
+ end
+ end
end
describe "#method_missing" do
it "raises an error for unknown DSL methods" do
- expect(Bundler).to receive(:read_file).with("Gemfile").
+ expect(Bundler).to receive(:read_file).with(git_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."
@@ -78,33 +173,64 @@ RSpec.describe Bundler::Dsl do
describe "#eval_gemfile" do
it "handles syntax errors with a useful message" do
- expect(Bundler).to receive(:read_file).with("Gemfile").and_return("}")
+ expect(Bundler).to receive(:read_file).with(git_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./)
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: (syntax error, unexpected tSTRING_DEND|(compile error - )?syntax error, unexpected '\}'|.+?unexpected '}', ignoring it\n). Bundler cannot continue./m)
end
it "distinguishes syntax errors from evaluation errors" do
- expect(Bundler).to receive(:read_file).with("Gemfile").and_return(
+ expect(Bundler).to receive(:read_file).with(git_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
+
+ it "populates __dir__ and __FILE__ correctly" do
+ abs_path = git_root.join("../fragment.rb").to_s
+ expect(Bundler).to receive(:read_file).with(abs_path).and_return(<<~RUBY)
+ @fragment_dir = __dir__
+ @fragment_file = __FILE__
+ RUBY
+ subject.eval_gemfile("../fragment.rb")
+ expect(subject.instance_variable_get(:@fragment_dir)).to eq(git_root.dirname.to_s)
+ expect(subject.instance_variable_get(:@fragment_file)).to eq(abs_path)
+ end
end
describe "#gem" do
- [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :mri, :mri_18, :mri_19,
- :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, :mri_25, :jruby, :rbx].each do |platform|
+ # 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, :ruby_34, :ruby_40, :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, :mri_34, :mri_40, :jruby, :rbx, :truffleruby].each do |platform|
it "allows #{platform} as a valid platform" do
- subject.gem("foo", :platform => platform)
+ 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"]}"
+
+ expect { subject.gem("foo", platform: platform) }.not_to raise_error
+ expect(Bundler.current_ruby.respond_to?("#{platform}?")).to be_truthy
+ end
it "rejects invalid platforms" do
- expect { subject.gem("foo", :platform => :bogus) }.
+ expect { subject.gem("foo", platform: :bogus) }.
to raise_error(Bundler::GemfileError, /is not a valid platform/)
end
+ it "warn for legacy windows platforms" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin, :x64_mingw will be removed in the future./)
+ subject.gem("foo", platforms: [:mswin, :jruby, :x64_mingw])
+ 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/)
@@ -146,54 +272,29 @@ RSpec.describe Bundler::Dsl do
end
it "rejects branch option on non-git gems" do
- expect { subject.gem("foo", :branch => "test") }.
+ 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 specifiying a branch on git gems" do
- subject.gem("foo", :branch => "test", :git => "http://mytestrepo")
+ 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 specifiying a branch on git gems with a git_source" do
+ 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")
+ subject.gem("foo", branch: "test", test_source: "bundler/bundler")
dep = subject.dependencies.last
expect(dep.name).to eq "foo"
end
end
- describe "#gemspec" do
- let(:spec) do
- Gem::Specification.new do |gem|
- gem.name = "example"
- gem.platform = platform
- end
- end
-
- before do
- allow(Dir).to receive(:[]).and_return(["spec_path"])
- allow(Bundler).to receive(:load_gemspec).with("spec_path").and_return(spec)
- allow(Bundler).to receive(:default_gemfile).and_return(Pathname.new("./Gemfile"))
- end
-
- context "with a ruby platform" do
- let(:platform) { "ruby" }
-
- it "keeps track of the ruby platforms in the dependency" do
- subject.gemspec
- expect(subject.dependencies.last.platforms).to eq(Bundler::Dependency::REVERSE_PLATFORM_MAP[Gem::Platform::RUBY])
- end
- end
-
- context "with a jruby platform" do
- let(:platform) { "java" }
-
- it "keeps track of the jruby platforms in the dependency" do
- allow(Gem::Platform).to receive(:local).and_return(java)
- subject.gemspec
- expect(subject.dependencies.last.platforms).to eq(Bundler::Dependency::REVERSE_PLATFORM_MAP[Gem::Platform::JAVA])
+ describe "#platforms" do
+ it "warn for legacy windows platforms" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin64, :mingw will be removed in the future./)
+ subject.platforms(:mswin64, :jruby, :mingw) do
+ subject.gem("foo")
end
end
end
@@ -206,7 +307,7 @@ RSpec.describe Bundler::Dsl do
# end
describe "#git" do
it "from a single repo" do
- rails_gems = %w(railties action_pack active_model)
+ 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
@@ -221,13 +322,13 @@ RSpec.describe Bundler::Dsl do
# end
describe "#github" do
it "from github" do
- spree_gems = %w(spree_core spree_api spree_backend)
+ 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("git://github.com/spree/spree.git")
+ expect(d.source.uri).to eq("https://github.com/spree/spree.git")
end
end
end
@@ -236,16 +337,16 @@ RSpec.describe Bundler::Dsl do
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./)
+ 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./m)
end
end
- describe "Runtime errors", :unless => Bundler.current_ruby.on_18? do
+ describe "Runtime errors" do
it "will raise a Bundler::GemfileError" do
- gemfile "s = 'foo'.freeze; s.strip!"
- expect { Bundler::Dsl.evaluate(bundled_app("Gemfile"), nil, true) }.
- to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: can't modify frozen String. Bundler cannot continue./i)
+ 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
@@ -257,7 +358,7 @@ RSpec.describe Bundler::Dsl do
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("dobry-pies", path: "foo")
subject.gem("foo")
end
@@ -265,4 +366,193 @@ RSpec.describe Bundler::Dsl do
end
end
end
+
+ describe "#source with cooldown" do
+ before do
+ allow(@rubygems).to receive(:add_remote)
+ end
+
+ it "accepts a non-negative integer" do
+ expect do
+ subject.source("https://rubygems.org", cooldown: 7)
+ end.not_to raise_error
+ end
+
+ it "accepts 0 as an explicit disable" do
+ expect do
+ subject.source("https://rubygems.org", cooldown: 0)
+ end.not_to raise_error
+ end
+
+ it "rejects a string" do
+ expect do
+ subject.source("https://rubygems.org", cooldown: "7")
+ end.to raise_error(Bundler::InvalidOption, /non-negative integer/)
+ end
+
+ it "rejects a float" do
+ expect do
+ subject.source("https://rubygems.org", cooldown: 7.5)
+ end.to raise_error(Bundler::InvalidOption, /non-negative integer/)
+ end
+
+ it "rejects a negative integer" do
+ expect do
+ subject.source("https://rubygems.org", cooldown: -7)
+ end.to raise_error(Bundler::InvalidOption, /non-negative integer/)
+ end
+
+ it "rejects an array" do
+ expect do
+ subject.source("https://rubygems.org", cooldown: [7])
+ end.to raise_error(Bundler::InvalidOption, /non-negative integer/)
+ end
+ end
+
+ describe "#override" do
+ it "stores an Override for a gem with a version: operation" do
+ subject.override("rails", version: ">= 8.0")
+
+ expect(subject.overrides.size).to eq(1)
+ override = subject.overrides.first
+ expect(override.target).to eq("rails")
+ expect(override.field).to eq(:version)
+ expect(override.operation).to eq(">= 8.0")
+ end
+
+ it "accepts :ignore_upper as the operation" do
+ subject.override("nokogiri", version: :ignore_upper)
+ expect(subject.overrides.first.operation).to eq(:ignore_upper)
+ end
+
+ it "accepts nil as the operation" do
+ subject.override("legacy", version: nil)
+ expect(subject.overrides.first.operation).to be_nil
+ end
+
+ it "appends to overrides across multiple statements" do
+ subject.override("rails", version: ">= 8.0")
+ subject.override("nokogiri", version: :ignore_upper)
+ expect(subject.overrides.map(&:target)).to eq(["rails", "nokogiri"])
+ end
+
+ it "is empty by default" do
+ expect(subject.overrides).to eq([])
+ end
+
+ it "raises ArgumentError when target is :all and version: is given" do
+ expect do
+ subject.override(:all, version: ">= 8.0")
+ end.to raise_error(ArgumentError, /`override :all, version:` is not allowed/)
+ end
+
+ it "rejects :all + version: even when other fields are also given" do
+ expect do
+ subject.override(:all, required_ruby_version: :ignore_upper, version: ">= 8.0")
+ end.to raise_error(ArgumentError, /`override :all, version:` is not allowed/)
+ end
+
+ it "does not record any override when :all + version: is rejected" do
+ expect do
+ subject.override(:all, version: ">= 8.0")
+ end.to raise_error(ArgumentError)
+ expect(subject.overrides).to eq([])
+ end
+
+ it "raises ArgumentError when target is neither :all nor a string" do
+ expect do
+ subject.override(:rails, version: ">= 8.0")
+ end.to raise_error(ArgumentError, /target must be :all or a gem name string/)
+ end
+
+ it "raises ArgumentError for an unsupported field" do
+ expect do
+ subject.override("rails", as: "y")
+ end.to raise_error(ArgumentError, /unsupported override field `as:`/)
+ end
+
+ it "stores an Override for a gem with a required_ruby_version: operation" do
+ subject.override("rails", required_ruby_version: :ignore_upper)
+ override = subject.overrides.first
+ expect(override.target).to eq("rails")
+ expect(override.field).to eq(:required_ruby_version)
+ expect(override.operation).to eq(:ignore_upper)
+ end
+
+ it "stores an Override for a gem with a required_rubygems_version: operation" do
+ subject.override("rails", required_rubygems_version: nil)
+ override = subject.overrides.first
+ expect(override.field).to eq(:required_rubygems_version)
+ expect(override.operation).to be_nil
+ end
+
+ it "stores an Override targeting :all with a metadata field" do
+ subject.override(:all, required_ruby_version: :ignore_upper)
+ override = subject.overrides.first
+ expect(override.target).to eq(:all)
+ expect(override.field).to eq(:required_ruby_version)
+ expect(override.operation).to eq(:ignore_upper)
+ end
+
+ it "stores an Override targeting :all with required_rubygems_version" do
+ subject.override(:all, required_rubygems_version: nil)
+ override = subject.overrides.first
+ expect(override.target).to eq(:all)
+ expect(override.field).to eq(:required_rubygems_version)
+ end
+
+ it "raises ArgumentError for a non-string, non-symbol, non-nil operation" do
+ expect do
+ subject.override("rails", version: 42)
+ end.to raise_error(ArgumentError, /override operation must be a String, Symbol, or nil/)
+ end
+
+ it "raises ArgumentError for an unsupported symbol operation" do
+ expect do
+ subject.override("rails", version: :explode)
+ end.to raise_error(ArgumentError, /unsupported override operation/)
+ end
+
+ it "raises ArgumentError for an unparsable version string" do
+ expect do
+ subject.override("rails", version: "not a version")
+ end.to raise_error(ArgumentError, /invalid override version requirement/)
+ end
+
+ it "does not record an override when the version string is invalid" do
+ expect do
+ subject.override("rails", version: "not a version")
+ end.to raise_error(ArgumentError)
+ expect(subject.overrides).to eq([])
+ end
+
+ it "rejects atomically when one field in a multi-field call is invalid" do
+ expect do
+ subject.override("rails", version: ">= 8.0", as: "y")
+ end.to raise_error(ArgumentError, /unsupported override field/)
+ expect(subject.overrides).to eq([])
+ end
+
+ it "raises ArgumentError when the same target and field are overridden twice" do
+ subject.override("rails", version: ">= 8.0")
+ expect do
+ subject.override("rails", version: :ignore_upper)
+ end.to raise_error(ArgumentError, /duplicate override for "rails" `version:`/)
+ end
+
+ it "keeps the original override when a duplicate is rejected" do
+ subject.override("rails", version: ">= 8.0")
+ expect do
+ subject.override("rails", version: :ignore_upper)
+ end.to raise_error(ArgumentError)
+ expect(subject.overrides.size).to eq(1)
+ expect(subject.overrides.first.operation).to eq(">= 8.0")
+ end
+
+ it "allows different targets with the same field" do
+ subject.override("rails", version: ">= 8.0")
+ subject.override("nokogiri", version: :ignore_upper)
+ expect(subject.overrides.size).to eq(2)
+ end
+ end
end
diff --git a/spec/bundler/bundler/endpoint_specification_spec.rb b/spec/bundler/bundler/endpoint_specification_spec.rb
index 0b8da840d2..4fbd59d48f 100644
--- a/spec/bundler/bundler/endpoint_specification_spec.rb
+++ b/spec/bundler/bundler/endpoint_specification_spec.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-require "spec_helper"
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 { described_class.new(name, version, platform, dependencies, metadata) }
+ subject(:spec) { described_class.new(name, version, platform, spec_fetcher, dependencies, metadata) }
describe "#build_dependency" do
let(:name) { "foo" }
@@ -33,22 +33,6 @@ RSpec.describe Bundler::EndpointSpecification do
)
end
end
-
- context "when there is an ill formed requirement" do
- before do
- allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) {
- raise ArgumentError.new("Ill-formed requirement [\"#<YAML::Syck::DefaultKey")
- }
- # Eliminate extra line break in rspec output due to `puts` in `#build_dependency`
- allow(subject).to receive(:puts) {}
- end
-
- it "should raise a Bundler::GemspecError with invalid gemspec message" do
- expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error(
- Bundler::GemspecError, /Unfortunately, the gem foo \(1\.0\.0\) has an invalid gemspec/
- )
- end
- end
end
describe "#parse_metadata" do
@@ -58,9 +42,82 @@ RSpec.describe Bundler::EndpointSpecification 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"}'))
+ and(a_string_including("The metadata was #{{ "rubygems" => ">\n" }.inspect}"))
)
end
end
+
+ context "when the metadata has created_at" do
+ let(:metadata) { { "created_at" => ["2026-05-12T10:00:00Z"] } }
+
+ it "parses created_at as a Time" do
+ expect(subject.created_at).to eq(Time.utc(2026, 5, 12, 10, 0, 0))
+ end
+ end
+
+ context "when the metadata has a string created_at (older rubygems shape)" do
+ let(:metadata) { { "created_at" => "2026-05-12T10:00:00Z" } }
+
+ it "still parses created_at" do
+ expect(subject.created_at).to eq(Time.utc(2026, 5, 12, 10, 0, 0))
+ end
+ end
+
+ context "when created_at is truncated (older rubygems splits on colons)" do
+ let(:metadata) { { "created_at" => "2026-05-12T10" } }
+
+ it "leaves created_at as nil instead of raising" do
+ expect(subject.created_at).to be_nil
+ end
+ end
+
+ context "when the metadata has no created_at" do
+ let(:metadata) { { "checksum" => ["abc"] } }
+ let(:spec_fetcher) { double(:spec_fetcher, uri: "https://rubygems.org") }
+
+ it "leaves created_at as nil" do
+ allow(Bundler::Checksum).to receive(:from_api).and_return(nil)
+ expect(subject.created_at).to be_nil
+ end
+ end
+
+ context "when the metadata is nil" do
+ it "leaves created_at as nil" do
+ expect(subject.created_at).to be_nil
+ 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
index 269c323ac6..2b7dbde217 100644
--- a/spec/bundler/bundler/env_spec.rb
+++ b/spec/bundler/bundler/env_spec.rb
@@ -1,65 +1,145 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/settings"
+require "openssl"
RSpec.describe Bundler::Env do
- let(:env) { described_class.new }
- let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil, nil) }
+ let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil) }
describe "#report" do
it "prints the environment" do
- out = env.report
+ 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(env.send(:ruby_version))
- expect(out).to include(env.send(:git_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 "gem 'rack', '1.0.0'"
+ gemfile "source 'https://gem.repo1'; gem 'myrack', '1.0.0'"
lockfile <<-L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
1.10.0
L
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
end
- let(:output) { env.report(:print_gemfile => true) }
+ 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'")
+ expect(output).to include("'myrack', '1.0.0'")
end
it "prints the lockfile" do
expect(output).to include("Gemfile.lock")
- expect(output).to include("rack (1.0.0)")
+ expect(output).to include("myrack (1.0.0)")
end
end
context "when there no Gemfile and print_gemfile is true" do
- let(:output) { env.report(:print_gemfile => true) }
+ 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)
+ <<~GEMSPEC
Gem::Specification.new do |gem|
gem.name = "foo"
gem.author = "Fumofu"
@@ -68,28 +148,89 @@ RSpec.describe Bundler::Env do
end
before do
- gemfile("gemspec")
+ gemfile("source 'https://gem.repo1'; gemspec")
- File.open(bundled_app.join("foo.gemspec"), "wb") do |f|
+ File.open(bundled_app("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 = env.report(:print_gemspecs => true)
+ 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
+ gemfile bundled_app("other/Gemfile-other"), "gem 'myrack'"
+ gemfile bundled_app("other/Gemfile"), "eval_gemfile 'Gemfile-other'"
+ gemfile bundled_app("Gemfile-alt"), <<-G
+ source "https://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(<<~ENV)
+ ## Gemfile
+
+ ### Gemfile
+
+ ```ruby
+ eval_gemfile #{bundled_app("Gemfile-alt").to_s.dump}
+ ```
+
+ ### Gemfile-alt
+
+ ```ruby
+ source "https://gem.repo1"
+ eval_gemfile "other/Gemfile"
+ ```
+
+ ### other/Gemfile
+
+ ```ruby
+ eval_gemfile 'Gemfile-other'
+ ```
+
+ ### other/Gemfile-other
+
+ ```ruby
+ gem 'myrack'
+ ```
+
+ ### 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).with("--version").
- and_return("git version 1.2.3 (Apple Git-BS)")
+ status = double("success?" => true)
+ expect(Open3).to receive(:capture3).with("git", "--version").
+ and_return(["git version 1.2.3 (Apple Git-BS)", "", status])
expect(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub)
- expect(env.report).to include("Git 1.2.3 (Apple Git-BS)")
+ expect(described_class.report).to include("Git 1.2.3 (Apple Git-BS)")
+ end
+ end
+
+ it "no longer reports the Tools section or external tool versions" do
+ report = described_class.report
+ expect(report).not_to include("Tools")
+ ["rbenv", "RVM", "chruby"].each do |tool|
+ expect(report).not_to include(tool)
end
end
end
diff --git a/spec/bundler/bundler/environment_preserver_spec.rb b/spec/bundler/bundler/environment_preserver_spec.rb
index 41d2650055..6c7066d0c6 100644
--- a/spec/bundler/bundler/environment_preserver_spec.rb
+++ b/spec/bundler/bundler/environment_preserver_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::EnvironmentPreserver do
let(:preserver) { described_class.new(env, ["foo"]) }
@@ -22,14 +21,18 @@ RSpec.describe Bundler::EnvironmentPreserver do
it "should not affect the original env" do
subject
- expect(env.keys.sort).to eq(%w(bar foo))
+ 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.key?("BUNDLER_ORIG_foo")).to eq(false)
+ it "should keep the original entry" do
+ expect(subject["foo"]).to be_empty
+ end
+
+ it "should still create backup entries" do
+ expect(subject).to have_key "BUNDLER_ORIG_foo"
end
end
@@ -72,8 +75,12 @@ RSpec.describe Bundler::EnvironmentPreserver do
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")
+ it "should restore the original value" do
+ expect(subject["foo"]).to be_empty
+ end
+
+ it "should delete the backup value" do
+ expect(subject.key?("BUNDLER_ORIG_foo")).to eq(false)
end
end
end
diff --git a/spec/bundler/bundler/errors_spec.rb b/spec/bundler/bundler/errors_spec.rb
new file mode 100644
index 0000000000..b62d85d32b
--- /dev/null
+++ b/spec/bundler/bundler/errors_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::IncorrectLockfileDependencies do
+ describe "#message" do
+ let(:spec) do
+ double("LazySpecification", full_name: "rubocop-1.82.0")
+ end
+
+ context "without dependency details" do
+ subject { described_class.new(spec) }
+
+ it "provides a basic error message" do
+ expect(subject.message).to include("Bundler found incorrect dependencies in the lockfile for rubocop-1.82.0")
+ expect(subject.message).to include("Please run `bundle install` to regenerate the lockfile.")
+ end
+ end
+
+ context "with dependency details" do
+ let(:actual_dependencies) do
+ [
+ Gem::Dependency.new("json", [">= 2.3", "< 4.0"]),
+ Gem::Dependency.new("parallel", ["~> 1.10"]),
+ Gem::Dependency.new("parser", [">= 3.3.0.2"]),
+ ]
+ end
+
+ let(:lockfile_dependencies) do
+ [
+ Gem::Dependency.new("json", [">= 2.3", "< 3.0"]),
+ Gem::Dependency.new("parallel", ["~> 1.10"]),
+ Gem::Dependency.new("parser", [">= 3.2.0.0"]),
+ ]
+ end
+
+ subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) }
+
+ it "shows only mismatched dependencies" do
+ message = subject.message
+
+ expect(message).to include("json: gemspec specifies")
+ expect(message).to include("parser: gemspec specifies")
+ expect(message).not_to include("parallel")
+ end
+ end
+
+ context "when gemspec has dependencies but lockfile has none" do
+ let(:actual_dependencies) do
+ [
+ Gem::Dependency.new("myrack-test", ["~> 1.0"]),
+ ]
+ end
+
+ let(:lockfile_dependencies) { [] }
+
+ subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) }
+
+ it "shows the dependency as not in lockfile" do
+ message = subject.message
+
+ expect(message).to include("myrack-test: gemspec specifies ~> 1.0, not in lockfile")
+ end
+ end
+
+ context "when gemspec has no dependencies but lockfile has some" do
+ let(:actual_dependencies) { [] }
+
+ let(:lockfile_dependencies) do
+ [
+ Gem::Dependency.new("unexpected", ["~> 1.0"]),
+ ]
+ end
+
+ subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) }
+
+ it "shows the dependency as not in gemspec" do
+ message = subject.message
+
+ expect(message).to include("unexpected: not in gemspec, lockfile has ~> 1.0")
+ end
+ end
+ end
+
+ describe "#status_code" do
+ let(:spec) { double("LazySpecification", full_name: "test-1.0.0") }
+ subject { described_class.new(spec) }
+
+ it "returns 41" do
+ expect(subject.status_code).to eq(41)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/base_spec.rb b/spec/bundler/bundler/fetcher/base_spec.rb
index 38b69429bc..b8c6b57b10 100644
--- a/spec/bundler/bundler/fetcher/base_spec.rb
+++ b/spec/bundler/bundler/fetcher/base_spec.rb
@@ -1,19 +1,19 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Fetcher::Base do
let(:downloader) { double(:downloader) }
let(:remote) { double(:remote) }
let(:display_uri) { "http://sample_uri.com" }
+ let(:gem_remote_fetcher) { nil }
class TestClass < described_class; end
- subject { TestClass.new(downloader, remote, display_uri) }
+ subject { TestClass.new(downloader, remote, display_uri, gem_remote_fetcher) }
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")
+ expect { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) }.to raise_error(RuntimeError, "Abstract class")
end
end
@@ -37,7 +37,7 @@ RSpec.describe Bundler::Fetcher::Base do
end
describe "#fetch_uri" do
- let(:remote_uri_obj) { URI("http://rubygems.org") }
+ let(:remote_uri_obj) { Gem::URI("http://rubygems.org") }
before { allow(subject).to receive(:remote_uri).and_return(remote_uri_obj) }
@@ -50,10 +50,10 @@ RSpec.describe Bundler::Fetcher::Base do
end
context "when the remote uri's host is not rubygems.org" do
- let(:remote_uri_obj) { URI("http://otherhost.org") }
+ let(:remote_uri_obj) { Gem::URI("http://otherhost.org") }
it "should return the remote uri" do
- expect(subject.fetch_uri).to eq(URI("http://otherhost.org"))
+ expect(subject.fetch_uri).to eq(Gem::URI("http://otherhost.org"))
end
end
diff --git a/spec/bundler/bundler/fetcher/compact_index_spec.rb b/spec/bundler/bundler/fetcher/compact_index_spec.rb
index e653c1ea43..aa536673d9 100644
--- a/spec/bundler/bundler/fetcher/compact_index_spec.rb
+++ b/spec/bundler/bundler/fetcher/compact_index_spec.rb
@@ -1,36 +1,41 @@
# frozen_string_literal: true
-require "spec_helper"
+
+# 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) { URI("http://sampleuri.com") }
- let(:remote) { double(:remote, :cache_slug => "lsjdf", :uri => display_uri) }
- let(:compact_index) { described_class.new(downloader, remote, display_uri) }
+ let(:response) { double(:response) }
+ let(:downloader) { double(:downloader, fetch: response) }
+ let(:display_uri) { Gem::URI("http://sampleuri.com") }
+ let(:remote) { double(:remote, cache_slug: "lsjdf", uri: display_uri) }
+ let(:gem_remote_fetcher) { nil }
+ let(:compact_index) { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) }
+ let(:compact_index_client) { double(:compact_index_client, available?: true, info: [["lskdjf", "1", nil, [], []]]) }
before do
+ allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified).and_return(true)
allow(compact_index).to receive(:log_specs) {}
+ allow(compact_index).to receive(:compact_index_client).and_return(compact_index_client)
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 {|thread| thread.status == "run" }
- expect(thread_count).to eq 1
+ 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)
+ 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
@@ -45,12 +50,22 @@ RSpec.describe Bundler::Fetcher::CompactIndex do
end
end
- context "when OpenSSL is FIPS-enabled", :ruby => ">= 2.0.0" do
- before { stub_const("OpenSSL::OPENSSL_FIPS", true) }
+ 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::MD5).to receive(:digest).
+ allow(OpenSSL::Digest).to receive(:digest).with("MD5", "").
and_raise(OpenSSL::Digest::DigestError)
end
diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb
index 134ca1bc57..501bc269a5 100644
--- a/spec/bundler/bundler/fetcher/dependency_spec.rb
+++ b/spec/bundler/bundler/fetcher/dependency_spec.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Fetcher::Dependency do
let(:downloader) { double(:downloader) }
- let(:remote) { double(:remote, :uri => URI("http://localhost:5000")) }
+ let(:remote) { double(:remote, uri: Gem::URI("http://localhost:5000")) }
let(:display_uri) { "http://sample_uri.com" }
+ let(:gem_remote_fetcher) { nil }
- subject { described_class.new(downloader, remote, display_uri) }
+ subject { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) }
describe "#available?" do
let(:dependency_api_uri) { double(:dependency_api_uri) }
@@ -64,7 +64,7 @@ RSpec.describe Bundler::Fetcher::Dependency do
end
describe "#specs" do
- let(:gem_names) { %w(foo bar) }
+ 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) }
@@ -103,7 +103,7 @@ RSpec.describe Bundler::Fetcher::Dependency do
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(: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
@@ -156,9 +156,9 @@ RSpec.describe Bundler::Fetcher::Dependency do
end
end
- shared_examples_for "the error suggests retrying with the full index" do
- it "should log the inability to fetch from API at debug level" do
- expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`")
+ 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
@@ -167,30 +167,26 @@ RSpec.describe Bundler::Fetcher::Dependency 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 suggests retrying with the full index"
+ 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 suggests retrying with the full index"
+ 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 "should log the inability to fetch from API 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
+ it_behaves_like "the error is logged"
end
end
describe "#dependency_specs" do
- let(:gem_names) { [%w(foo bar), %w(bundler rubocop)] }
+ 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) }
@@ -212,40 +208,52 @@ RSpec.describe Bundler::Fetcher::Dependency do
end
describe "#unmarshalled_dep_gems" do
- let(:gem_names) { [%w(foo bar), %w(bundler rubocop)] }
+ 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 }
+ let(:fetch_response) { double(:fetch_response, body: double(:body)) }
+ let(:rubygems_limit) { 100 }
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
+ 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(:load_marshal).with(fetch_response.body).and_return([unmarshalled_gems])
+ 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
+
+ it "should fetch as many dependencies as specified" do
+ allow(subject).to receive(:dependency_api_uri).with([%w[foo bar]]).and_return(dep_api_uri)
+ allow(subject).to receive(:dependency_api_uri).with([%w[bundler rubocop]]).and_return(dep_api_uri)
+
+ expect(downloader).to receive(:fetch).twice.with(dep_api_uri).and_return(fetch_response)
+ expect(Bundler).to receive(:safe_load_marshal).twice.with(fetch_response.body).and_return([unmarshalled_gems])
+
+ Bundler.settings.temporary(api_request_size: 1) do
+ expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems, unmarshalled_gems])
+ end
+ end
end
describe "#get_formatted_specs_and_deps" do
let(:gem_list) do
[
{
- :dependencies => {
+ dependencies: {
"resque" => "req3,req4",
},
- :name => "typhoeus",
- :number => "1.0.1",
- :platform => "ruby",
+ name: "typhoeus",
+ number: "1.0.1",
+ platform: "ruby",
},
{
- :dependencies => {
+ dependencies: {
"faraday" => "req1,req2",
},
- :name => "grape",
- :number => "2.0.2",
- :platform => "jruby",
+ name: "grape",
+ number: "2.0.2",
+ platform: "jruby",
},
]
end
@@ -254,15 +262,15 @@ RSpec.describe Bundler::Fetcher::Dependency 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))
+ expect(deps_list).to eq(%w[resque faraday])
end
end
describe "#dependency_api_uri" do
- let(:uri) { URI("http://gem-api.com") }
+ let(:uri) { Gem::URI("http://gem-api.com") }
context "with gem names" do
- let(:gem_names) { %w(foo bar bundler rubocop) }
+ let(:gem_names) { %w[foo bar bundler rubocop] }
before { allow(subject).to receive(:fetch_uri).and_return(uri) }
diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb
index 4dcd94b1b2..edf426328a 100644
--- a/spec/bundler/bundler/fetcher/downloader_spec.rb
+++ b/spec/bundler/bundler/fetcher/downloader_spec.rb
@@ -1,10 +1,9 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Fetcher::Downloader do
let(:connection) { double(:connection) }
let(:redirect_limit) { 5 }
- let(:uri) { URI("http://www.uri-to-fetch.com/api/v2/endpoint") }
+ let(:uri) { Gem::URI("http://www.uri-to-fetch.com/api/v2/endpoint") }
let(:options) { double(:options) }
subject { described_class.new(connection, redirect_limit) }
@@ -28,7 +27,7 @@ RSpec.describe Bundler::Fetcher::Downloader do
end
context "logging" do
- let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") }
+ let(:http_response) { Gem::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}")
@@ -36,68 +35,134 @@ RSpec.describe Bundler::Fetcher::Downloader do
end
end
- context "when the request response is a Net::HTTPRedirection" do
- let(:http_response) { Net::HTTPRedirection.new(httpv, 308, "Moved") }
+ context "when the request response is a Gem::Net::HTTPRedirection" do
+ let(:http_response) { Gem::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(URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
- expect(subject).to receive(:fetch).with(URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1)
+ expect(subject).to receive(:fetch).with(Gem::URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
+ expect(subject).to receive(:fetch).with(Gem::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) { URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+ let(:uri) { Gem::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(URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
- expect(subject).to receive(:fetch).with(URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1)
+ expect(subject).to receive(:fetch).with(Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
+ expect(subject).to receive(:fetch).with(Gem::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") }
+ context "when the request response is a Gem::Net::HTTPSuccess" do
+ let(:http_response) { Gem::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") }
+ context "when the request response is a Gem::Net::HTTPRequestEntityTooLarge" do
+ let(:http_response) { Gem::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") }
+ context "when the request response is a Gem::Net::HTTPUnauthorized" do
+ let(:http_response) { Gem::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 advice" 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 there are credentials provided in the request" do
+ let(:uri) { Gem::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 Gem::Net::HTTPForbidden" do
+ let(:http_response) { Gem::Net::HTTPForbidden.new("1.1", 403, "Forbidden") }
+ let(:uri) { Gem::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 Gem::Net::HTTPNotFound" do
+ let(:http_response) { Gem::Net::HTTPNotFound.new("1.1", 404, "Not Found") }
+
+ it "should raise a Bundler::Fetcher::FallbackError with Gem::Net::HTTPNotFound" do
+ expect { subject.fetch(uri, options, counter) }.
+ to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint")
+ end
+
+ context "when there are credentials provided in the request" do
+ let(:uri) { Gem::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, "Gem::Net::HTTPNotFound: http://username@www.uri-to-fetch.com/api/v2/endpoint")
+ end
+ end
end
- context "when the request response is a Net::HTTPNotFound" do
- let(:http_response) { Net::HTTPNotFound.new("1.1", 404, "Not Found") }
+ context "when the request response is a Gem::Net::HTTPRequestedRangeNotSatisfiable" do
+ let(:http_response) { Gem::Net::HTTPRequestedRangeNotSatisfiable.new("1.1", 416, "Range Not Satisfiable") }
+ let(:success_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") }
+ let(:options) { { "Range" => "bytes=1000-", "If-None-Match" => "some-etag" } }
+
+ before do
+ # First request returns 416, retry request returns success
+ allow(subject).to receive(:request).with(uri, options).and_return(http_response)
+ allow(subject).to receive(:request).with(uri, { "If-None-Match" => "some-etag" }).and_return(success_response)
+ end
+
+ # The 416 handler removes the Range header and retries without incrementing the counter.
+ # Importantly, it does NOT add Accept-Encoding header, which would break Ruby's
+ # automatic gzip decompression (see issue #9271 for details on that bug).
+ it "should retry the request without the Range header" do
+ expect(subject).to receive(:request).with(uri, options).ordered
+ expect(subject).to receive(:request).with(uri, hash_excluding("Range", "Accept-Encoding")).ordered
+ subject.fetch(uri, options, counter)
+ end
+
+ it "should preserve other headers on retry" do
+ expect(subject).to receive(:request).with(uri, options).ordered
+ expect(subject).to receive(:request).with(uri, hash_including("If-None-Match" => "some-etag")).ordered
+ subject.fetch(uri, options, counter)
+ end
- 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")
+ it "should return the successful response" do
+ result = subject.fetch(uri, options, counter)
+ expect(result).to eq(success_response)
end
end
context "when the request response is some other type" do
- let(:http_response) { Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") }
+ let(:http_response) { Gem::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")
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Gem::Net::HTTPBadGateway: Body with info")
end
end
end
@@ -107,7 +172,7 @@ RSpec.describe Bundler::Fetcher::Downloader do
let(:response) { double(:response) }
before do
- allow(Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get)
+ allow(Gem::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
@@ -119,62 +184,46 @@ RSpec.describe Bundler::Fetcher::Downloader do
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) { URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") }
+ let(:uri) { Gem::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") }
- it "should request basic authentication with the username and password" do
+ it "should request basic authentication with the username and password, and log the HTTP GET request to debug, without the password" do
expect(net_http_get).to receive(:basic_auth).with("username", "password$")
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://username@www.uri-to-fetch.com/api/v2/endpoint")
subject.request(uri, options)
end
end
context "that is all unescaped characters" do
- let(:uri) { 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
+ let(:uri) { Gem::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, and log the HTTP GET request to debug, without the password" do
expect(net_http_get).to receive(:basic_auth).with("username", "password")
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://username@www.uri-to-fetch.com/api/v2/endpoint")
subject.request(uri, options)
end
end
end
- context "and there is no password provided" do
- let(:uri) { URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") }
+ context "and it's used as the authentication token" do
+ let(:uri) { Gem::URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") }
- it "should request basic authentication with just the user" do
+ it "should request basic authentication with just the user, and log the HTTP GET request to debug, without the token" do
expect(net_http_get).to receive(:basic_auth).with("username", nil)
+ 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
end
- context "that contains cgi escaped characters" do
- let(:uri) { URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") }
+ context "and it's used as the authentication token, and contains cgi escaped characters" do
+ let(:uri) { Gem::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
+ it "should request basic authentication with the proper cgi compliant password user, and log the HTTP GET request to debug, without the token" do
expect(net_http_get).to receive(:basic_auth).with("username$", nil)
+ 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
end
end
- context "when the request response causes a NoMethodError" do
- before { allow(connection).to receive(:request).with(uri, net_http_get) { raise NoMethodError.new(message) } }
-
- context "and the error message is about use_ssl=" do
- let(:message) { "undefined method 'use_ssl='" }
-
- it "should raise a LoadError about openssl" do
- expect { subject.request(uri, options) }.to raise_error(LoadError, "cannot load such file -- openssl")
- end
- end
-
- context "and the error message is not about use_ssl=" do
- let(:message) { "undefined method 'undefined_method_call'" }
-
- it "should raise the original NoMethodError" do
- expect { subject.request(uri, options) }.to raise_error(NoMethodError, "undefined method 'undefined_method_call'")
- 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 } }
@@ -184,66 +233,68 @@ RSpec.describe Bundler::Fetcher::Downloader do
end
end
- context "when the request response causes an error included in HTTP_ERRORS" do
- let(:message) { nil }
- let(:error) { RuntimeError.new(message) }
+ context "when the request response causes an HTTP error" do
+ let(:message) { "error about network" }
+ let(:error) { error_class.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 about getaddrinfo issues" do
- let(:message) { "getaddrinfo: nodename nor servname provided for http://www.uri-to-fetch.com" }
+ context "that it's retryable" do
+ let(:error_class) { Gem::Timeout::Error }
- 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/)
+ 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
- end
-
- context "when error message is about neither host down or getaddrinfo" 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)")
+ "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)")
end
- context "when the there are credentials provided in the request" do
- let(:uri) { URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+ context "when there are credentials provided in the request" do
+ let(:uri) { Gem::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)")
+ "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)")
end
end
end
- context "when error message is about no route to host" do
+ context "when error is about the host being down" do
+ let(:error_class) { Gem::Net::HTTP::Persistent::Error }
+ 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 is about connection refused" do
+ let(:error_class) { Gem::Net::HTTP::Persistent::Error }
+ let(:message) { "connection refused 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 is about no route to host" do
+ let(:error_class) { SocketError }
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})")
+ 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
end
diff --git a/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb b/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb
new file mode 100644
index 0000000000..df1a58d843
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require "rubygems/remote_fetcher"
+require "bundler/fetcher/gem_remote_fetcher"
+require_relative "../../support/artifice/helpers/artifice"
+require "bundler/vendored_persistent.rb"
+
+RSpec.describe Bundler::Fetcher::GemRemoteFetcher do
+ describe "Parallel download" do
+ it "download using multiple connections from the pool" do
+ unless Bundler.rubygems.provides?(">= 4.0.0.dev")
+ skip "This example can only run when RubyGems supports multiple http connection pool"
+ end
+
+ require_relative "../../support/artifice/helpers/endpoint"
+ concurrent_ruby_path = Dir[scoped_base_system_gem_path.join("gems/concurrent-ruby-*/lib/concurrent-ruby")].first
+ $LOAD_PATH.unshift(concurrent_ruby_path)
+ require "concurrent-ruby"
+
+ require_rack_test
+ responses = []
+
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+ previous_client = Gem::Request::ConnectionPools.client
+ dummy_endpoint = Class.new(Endpoint) do
+ get "/foo" do
+ latch2.count_down
+ latch1.wait
+
+ responses << "foo"
+ end
+
+ get "/bar" do
+ responses << "bar"
+
+ latch1.count_down
+ end
+ end
+
+ Artifice.activate_with(dummy_endpoint)
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+
+ first_request = Thread.new do
+ subject.fetch_path("https://example.org/foo")
+ end
+ second_request = Thread.new do
+ latch2.wait
+ subject.fetch_path("https://example.org/bar")
+ end
+
+ [first_request, second_request].each(&:join)
+
+ expect(responses).to eq(["bar", "foo"])
+ ensure
+ Artifice.deactivate
+ Gem::Request::ConnectionPools.client = previous_client
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/index_spec.rb b/spec/bundler/bundler/fetcher/index_spec.rb
index b17e0d1727..a6a18efd98 100644
--- a/spec/bundler/bundler/fetcher/index_spec.rb
+++ b/spec/bundler/bundler/fetcher/index_spec.rb
@@ -1,14 +1,18 @@
# frozen_string_literal: true
-require "spec_helper"
+
+require "rubygems/remote_fetcher"
RSpec.describe Bundler::Fetcher::Index do
let(:downloader) { nil }
- let(:remote) { nil }
+ let(:remote) { double(:remote, uri: remote_uri) }
+ let(:remote_uri) { Gem::URI("http://#{userinfo}remote-uri.org") }
+ let(:userinfo) { "" }
let(:display_uri) { "http://sample_uri.com" }
let(:rubygems) { double(:rubygems) }
- let(:gem_names) { %w(foo bar) }
+ let(:gem_names) { %w[foo bar] }
+ let(:gem_remote_fetcher) { nil }
- subject { described_class.new(downloader, remote, display_uri) }
+ subject { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) }
before { allow(Bundler).to receive(:rubygems).and_return(rubygems) }
@@ -18,83 +22,54 @@ RSpec.describe Bundler::Fetcher::Index do
end
context "error handling" do
- shared_examples_for "the error is properly handled" do
- let(:remote_uri) { URI("http://remote-uri.org") }
- before do
- 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
+ before do
+ allow(rubygems).to receive(:fetch_all_remote_specs) { raise Gem::RemoteFetcher::FetchError.new(error_message, display_uri) }
+ end
- context "when a 401 response occurs" do
- let(:error_message) { "401" }
+ context "when certificate verify failed" do
+ let(:error_message) { "certificate verify failed" }
- 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
+ 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 403 response occurs" do
- let(:error_message) { "403" }
-
- 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 }
+ context "when a 401 response occurs" do
+ let(:error_message) { "401" }
- 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
+ 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
- context "any other message is returned" do
- let(:error_message) { "You get an error, you get an error!" }
+ context "and there was userinfo" do
+ let(:userinfo) { "user:pass@" }
- 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")
+ 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://user@remote-uri.org})
end
end
end
- context "when a Gem::RemoteFetcher::FetchError occurs" do
- before { allow(rubygems).to receive(:fetch_all_remote_specs) { raise Gem::RemoteFetcher::FetchError.new(error_message, nil) } }
+ context "when a 403 response occurs" do
+ let(:error_message) { "403" }
- it_behaves_like "the error is properly handled"
+ 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 "when a OpenSSL::SSL::SSLError occurs" do
- before { allow(rubygems).to receive(:fetch_all_remote_specs) { raise OpenSSL::SSL::SSLError.new(error_message) } }
+ context "any other message is returned" do
+ let(:error_message) { "You get an error, you get an error!" }
- it_behaves_like "the error is properly handled"
- end
+ before { allow(Bundler).to receive(:ui).and_return(double(trace: nil)) }
- context "when a Net::HTTPFatalError occurs" do
- before { allow(rubygems).to receive(:fetch_all_remote_specs) { raise Net::HTTPFatalError.new(error_message, 404) } }
-
- it_behaves_like "the error is properly handled"
+ 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
index 585768343f..e20f7e7c48 100644
--- a/spec/bundler/bundler/fetcher_spec.rb
+++ b/spec/bundler/bundler/fetcher_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/fetcher"
RSpec.describe Bundler::Fetcher do
- let(:uri) { URI("https://example.com") }
- let(:remote) { double("remote", :uri => uri, :original_uri => nil) }
+ let(:uri) { Gem::URI("https://example.com") }
+ let(:remote) { double("remote", uri: uri, original_uri: nil) }
subject(:fetcher) { Bundler::Fetcher.new(remote) }
@@ -26,7 +26,7 @@ RSpec.describe Bundler::Fetcher do
context "when Gem.configuration specifies http_proxy " do
let(:proxy) { "http://proxy-example2.com" }
before do
- allow(Bundler.rubygems.configuration).to receive(:[]).with(:http_proxy).and_return(proxy)
+ 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")
@@ -45,9 +45,9 @@ RSpec.describe Bundler::Fetcher do
end
context "when a rubygems source mirror is set" do
- let(:orig_uri) { URI("http://zombo.com") }
+ let(:orig_uri) { Gem::URI("http://zombo.com") }
let(:remote_with_mirror) do
- double("remote", :uri => uri, :original_uri => orig_uri, :anonymized_uri => uri)
+ double("remote", uri: uri, original_uri: orig_uri, anonymized_uri: uri)
end
let(:fetcher) { Bundler::Fetcher.new(remote_with_mirror) }
@@ -61,7 +61,7 @@ RSpec.describe Bundler::Fetcher do
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)
+ double("remote", uri: uri, original_uri: nil, anonymized_uri: uri)
end
let(:fetcher) { Bundler::Fetcher.new(remote_no_mirror) }
@@ -85,11 +85,56 @@ RSpec.describe Bundler::Fetcher do
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))
+ 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.)})
@@ -98,19 +143,118 @@ RSpec.describe Bundler::Fetcher do
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.match(%r{\Aci/}) }
- expect(ci_part).to match("jenkins")
+ with_env_vars("CI" => nil, "JENKINS_URL" => "foo") do
+ ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") }
+ cis = ci_part.split("/").last.split(",")
+ expect(cis).to include("jenkins")
+ expect(cis).not_to include("ci")
end
end
it "from many CI" do
- with_env_vars("TRAVIS" => "foo", "CI_NAME" => "my_ci") do
- ci_part = fetcher.user_agent.split(" ").find {|x| x.match(%r{\Aci/}) }
- expect(ci_part).to match("travis")
- expect(ci_part).to match("my_ci")
+ with_env_vars("CI" => "true", "SEMAPHORE" => nil, "TRAVIS" => "foo", "GITLAB_CI" => "gitlab", "CI_NAME" => "MY_ci") do
+ ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") }
+ cis = ci_part.split("/").last.split(",")
+ expect(cis).to include("ci", "gitlab", "my_ci", "travis")
+ expect(cis).not_to include("semaphore")
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(Gem::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
+
+ describe "#specs_with_retry" do
+ let(:downloader) { double(:downloader) }
+ let(:remote) { double(:remote, cache_slug: "slug", uri: uri, original_uri: nil, anonymized_uri: uri) }
+ let(:compact_index) { double(Bundler::Fetcher::CompactIndex, available?: true, api_fetcher?: true) }
+ let(:dependency) { double(Bundler::Fetcher::Dependency, available?: true, api_fetcher?: true) }
+ let(:index) { double(Bundler::Fetcher::Index, available?: true, api_fetcher?: false) }
+
+ before do
+ allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index)
+ allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency)
+ allow(Bundler::Fetcher::Index).to receive(:new).and_return(index)
+ end
+
+ it "picks the first fetcher that works" do
+ expect(compact_index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]])
+ expect(dependency).not_to receive(:specs)
+ expect(index).not_to receive(:specs)
+ fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems))
+ end
+
+ context "when APIs are not available" do
+ before do
+ allow(compact_index).to receive(:available?).and_return(false)
+ allow(dependency).to receive(:available?).and_return(false)
+ end
+
+ it "uses the index" do
+ expect(compact_index).not_to receive(:specs)
+ expect(dependency).not_to receive(:specs)
+ expect(index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]])
+
+ fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems))
+ end
+ end
+ end
+
+ describe "#api_fetcher?" do
+ let(:downloader) { double(:downloader) }
+ let(:remote) { double(:remote, cache_slug: "slug", uri: uri, original_uri: nil, anonymized_uri: uri) }
+ let(:compact_index) { double(Bundler::Fetcher::CompactIndex, available?: false, api_fetcher?: true) }
+ let(:dependency) { double(Bundler::Fetcher::Dependency, available?: false, api_fetcher?: true) }
+ let(:index) { double(Bundler::Fetcher::Index, available?: true, api_fetcher?: false) }
+
+ before do
+ allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index)
+ allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency)
+ allow(Bundler::Fetcher::Index).to receive(:new).and_return(index)
+ end
+
+ context "when an api fetcher is available" do
+ before do
+ allow(compact_index).to receive(:available?).and_return(true)
+ end
+
+ it "is truthy" do
+ expect(fetcher).to be_api_fetcher
+ end
+ end
+
+ context "when only the index fetcher is available" do
+ it "is falsey" do
+ expect(fetcher).not_to be_api_fetcher
+ end
+ end
+ end
end
diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb
index 19799d5495..426e3c856d 100644
--- a/spec/bundler/bundler/friendly_errors_spec.rb
+++ b/spec/bundler/bundler/friendly_errors_spec.rb
@@ -1,46 +1,31 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler"
require "bundler/friendly_errors"
-require "cgi"
+require "cgi/escape"
+require "cgi/util" unless defined?(CGI::EscapeExt)
RSpec.describe Bundler, "friendly errors" do
context "with invalid YAML in .gemrc" do
before do
- File.open(Gem.configuration.config_file_name, "w") do |f|
+ File.open(home(".gemrc"), "w") do |f|
f.write "invalid: yaml: hah"
end
end
after do
- FileUtils.rm(Gem.configuration.config_file_name)
- end
-
- it "reports a relevant friendly error message", :ruby => ">= 1.9", :rubygems => "< 2.5.0" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- bundle :install, :env => { "DEBUG" => true }
-
- expect(out).to include("Your RubyGems configuration")
- expect(out).to include("invalid YAML syntax")
- expect(out).to include("Psych::SyntaxError")
- expect(out).not_to include("ERROR REPORT TEMPLATE")
- expect(exitstatus).to eq(25) if exitstatus
+ FileUtils.rm(home(".gemrc"))
end
- it "reports a relevant friendly error message", :ruby => ">= 1.9", :rubygems => ">= 2.5.0" do
+ it "reports a relevant friendly error message" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle :install, :env => { "DEBUG" => true }
+ bundle :install, env: { "DEBUG" => "true" }
expect(err).to include("Failed to load #{home(".gemrc")}")
- expect(exitstatus).to eq(0) if exitstatus
end
end
@@ -109,7 +94,7 @@ RSpec.describe Bundler, "friendly errors" do
end
it "writes to Bundler.ui.trace" do
- expect(Bundler.ui).to receive(:trace).with(orig_error, nil, true)
+ expect(Bundler.ui).to receive(:trace).with(orig_error)
Bundler::FriendlyErrors.log_error(error)
end
end
@@ -117,35 +102,15 @@ RSpec.describe Bundler, "friendly errors" do
context "BundlerError" do
it "Bundler.ui receive error" do
error = Bundler::BundlerError.new
- expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true)
+ expect(Bundler.ui).to receive(:error).with(error.message, wrap: true)
Bundler::FriendlyErrors.log_error(error)
end
- it_behaves_like "Bundler.ui receive trace", Bundler::BundlerError.new
end
context "Thor::Error" do
it_behaves_like "Bundler.ui receive error", Bundler::Thor::Error.new
end
- context "LoadError" do
- let(:error) { LoadError.new("cannot load such file -- openssl") }
-
- it "Bundler.ui receive error" do
- expect(Bundler.ui).to receive(:error).with("\nCould not load OpenSSL.")
- Bundler::FriendlyErrors.log_error(error)
- end
-
- it "Bundler.ui receive warn" do
- expect(Bundler.ui).to receive(:warn).with(any_args, :wrap => true)
- Bundler::FriendlyErrors.log_error(error)
- end
-
- it "Bundler.ui receive trace" do
- expect(Bundler.ui).to receive(:trace).with(error)
- Bundler::FriendlyErrors.log_error(error)
- end
- end
-
context "Interrupt" do
it "Bundler.ui receive error" do
expect(Bundler.ui).to receive(:error).with("\nQuitting...")
@@ -157,7 +122,7 @@ RSpec.describe Bundler, "friendly errors" do
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)
+ expect(Bundler.ui).to receive(:error).with(error.message, wrap: true)
Bundler::FriendlyErrors.log_error(error)
end
end
@@ -166,17 +131,13 @@ RSpec.describe Bundler, "friendly errors" do
# Does nothing
end
- context "Java::JavaLang::OutOfMemoryError" do
- module Java
- module JavaLang
- class OutOfMemoryError < StandardError; end
- end
- end
-
+ context "Java::JavaLang::OutOfMemoryError", :jruby_only do
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)
+ install_gemfile <<-G, raise_on_error: false, env: { "JRUBY_OPTS" => "-J-Xmx32M" }, artifice: nil
+ source "https://gem.repo1"
+ G
+
+ expect(err).to include("JVM has run out of memory")
end
end
@@ -215,9 +176,9 @@ RSpec.describe Bundler, "friendly errors" do
describe "#request_issue_report_for" do
it "calls relevant methods for Bundler.ui" do
- expect(Bundler.ui).to receive(:info)
- expect(Bundler.ui).to receive(:error)
- expect(Bundler.ui).to receive(:warn)
+ 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
@@ -236,7 +197,7 @@ RSpec.describe Bundler, "friendly errors" 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/bundler/bundler/search?q=Exception+message&type=Issues")
+ expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/ruby/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
@@ -245,7 +206,7 @@ First line of the exception message
Second line of the exception message
END
- expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/bundler/bundler/search?q=First+line+of+the+exception+message&type=Issues")
+ expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/ruby/rubygems/search?q=First+line+of+the+exception+message&type=Issues")
end
it "generates the url without colons" do
@@ -254,7 +215,7 @@ 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/bundler/bundler/search?q=#{CGI.escape("Exception with colons ")}&type=Issues")
+ expect(issues_url).to eq("https://github.com/ruby/rubygems/search?q=#{CGI.escape("Exception with colons ")}&type=Issues")
end
it "removes information after - for Errono::EACCES" do
@@ -264,7 +225,7 @@ 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/bundler/bundler/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues")
+ expect(issues_url).to eq("https://github.com/ruby/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
index 498ed89447..b4ae2abdc5 100644
--- a/spec/bundler/bundler/gem_helper_spec.rb
+++ b/spec/bundler/bundler/gem_helper_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "rake"
require "bundler/gem_helper"
@@ -9,8 +9,15 @@ RSpec.describe Bundler::GemHelper do
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_config_global "gem.mit false"
+ bundle_config_global "gem.test false"
+ bundle_config_global "gem.coc false"
+ bundle_config_global "gem.linter false"
+ bundle_config_global "gem.ci false"
+ bundle_config_global "gem.changelog false"
+ git("config --global init.defaultBranch main")
bundle "gem #{app_name}"
+ prepare_gemspec(app_gemspec_path)
end
context "determining gemspec" do
@@ -29,15 +36,6 @@ RSpec.describe Bundler::GemHelper do
end
context "interpolates the name" do
- before do
- # Remove exception that prevents public pushes on older RubyGems versions
- if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
- content = File.read(app_gemspec_path)
- content.sub!(/raise "RubyGems 2\.0 or newer.*/, "")
- File.open(app_gemspec_path, "w") {|f| f.write(content) }
- end
- end
-
it "when there is only one gemspec" do
expect(subject.gemspec.name).to eq(app_name)
end
@@ -68,26 +66,29 @@ RSpec.describe Bundler::GemHelper do
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_gemspec_content) { remove_push_guard(File.read(app_gemspec_path)) }
+ 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
- def remove_push_guard(gemspec_content)
- # Remove exception that prevents public pushes on older RubyGems versions
- if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
- gemspec_content.sub!(/raise "RubyGems 2\.0 or newer.*/, "")
- end
- gemspec_content
- end
-
it "uses a shell UI for output" do
expect(Bundler.ui).to be_a(Bundler::UI::Shell)
end
@@ -105,13 +106,14 @@ RSpec.describe Bundler::GemHelper do
context "defines Rake tasks" do
let(:task_names) do
- %w(build install release release:guard_clean
- release:source_control_push release:rubygem_push)
+ %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
@@ -153,6 +155,68 @@ RSpec.describe Bundler::GemHelper do
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
@@ -162,7 +226,7 @@ RSpec.describe Bundler::GemHelper do
mock_confirm_message "#{app_name} (#{app_version}) installed."
subject.install_gem(nil, :local)
expect(app_gem_path).to exist
- gem_command! :list
+ installed_gems_list
expect(out).to include("#{app_name} (#{app_version})")
end
end
@@ -175,7 +239,7 @@ RSpec.describe Bundler::GemHelper do
FileUtils.touch app_gem_path
app_gem_path
end
- expect { subject.install_gem }.to raise_error(/Couldn't install gem/)
+ expect { subject.install_gem }.to raise_error(/Running `#{gem_bin} install #{app_gem_path}` failed/)
end
end
end
@@ -193,12 +257,11 @@ RSpec.describe Bundler::GemHelper do
end
before do
- Dir.chdir(app_path) do
- `git init`
- `git config user.email "you@example.com"`
- `git config user.name "name"`
- `git config push.default simple`
- end
+ git("init", app_path)
+ git("config user.email \"you@example.com\"", app_path)
+ git("config user.name \"name\"", app_path)
+ git("config commit.gpgsign false", app_path)
+ git("config push.default simple", app_path)
# silence messages
allow(Bundler.ui).to receive(:confirm)
@@ -212,35 +275,81 @@ RSpec.describe Bundler::GemHelper do
end
it "when there are uncommitted files" do
- Dir.chdir(app_path) { `git add .` }
+ git("add .", 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
- Dir.chdir(app_path) { `git commit -a -m "initial commit"` }
+ git("commit -a -m \"initial commit\"", 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
- Dir.chdir(gem_repo1) { `git init --bare` }
- Dir.chdir(app_path) do
- `git remote add origin file://#{gem_repo1}`
- `git commit -a -m "initial commit"`
+ git("remote add origin #{repo.path}", app_path)
+ git('commit -a -m "initial commit"', 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."
+
+ git("push -u origin main", 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
+ git("checkout -b v#{app_version}", app_path)
+ git("push -u origin v#{app_version}", 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
+ git("checkout -b module_function", app_path)
+
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ Rake.application["release"].invoke
end
end
- it "on releasing" do
- mock_build_message app_name, app_version
- mock_confirm_message "Tagged v#{app_version}."
- mock_confirm_message "Pushed git commits and tags."
- expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+ 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."
+
+ git("push -u origin main", app_path)
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+ end
- Dir.chdir(app_path) { sys_exec("git push -u origin master") }
+ it "prepends the custom prefix to the tag" do
+ mock_confirm_message "Tagged foo-v#{app_version}."
- Rake.application["release"].invoke
+ Rake.application["release"].invoke
+ end
end
it "even if tag already exists" do
@@ -248,13 +357,100 @@ RSpec.describe Bundler::GemHelper do
mock_confirm_message "Tag v#{app_version} has already been created."
expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
- Dir.chdir(app_path) do
- `git tag -a -m \"Version #{app_version}\" v#{app_version}`
- end
+ git("tag -a -m \"Version #{app_version}\" v#{app_version}", 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
+ git("init", app_path)
+ git("config user.email \"you@example.com\"", app_path)
+ git("config user.name \"name\"", app_path)
+ git("config push.gpgsign simple", 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)
+ allow(Bundler.user_home).to receive(:join).and_call_original
+ 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
index c7620e2620..0e1b7c9cc8 100644
--- a/spec/bundler/bundler/gem_version_promoter_spec.rb
+++ b/spec/bundler/bundler/gem_version_promoter_spec.rb
@@ -1,178 +1,162 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::GemVersionPromoter do
- context "conservative resolver" do
- def versions(result)
- result.flatten.map(&:version).map(&:to_s)
- end
-
- def make_instance(*args)
- @gvp = Bundler::GemVersionPromoter.new(*args).tap do |gvp|
- gvp.class.class_eval { public :filter_dep_specs, :sort_dep_specs }
+ 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 unlocking(options)
- make_instance(Bundler::SpecSet.new([]), ["foo"]).tap do |p|
- p.level = options[:level] if options[:level]
- p.strict = options[:strict] if options[:strict]
- end
+ def build_package(name, version, unlock)
+ Bundler::Resolver::Package.new(name, [], locked_specs: Bundler::SpecSet.new(build_spec(name, version)), unlock: unlock)
end
- def keep_locked(options)
- make_instance(Bundler::SpecSet.new([]), ["bar"]).tap do |p|
- p.level = options[:level] if options[:level]
- p.strict = options[:strict] if options[:strict]
- end
+ def sorted_versions(candidates:, current:, unlock: true)
+ gvp.sort_versions(
+ build_package("foo", current, unlock),
+ build_candidates(candidates)
+ ).flatten.map(&:version).map(&:to_s)
end
- def build_spec_group(name, version)
- Bundler::Resolver::SpecGroup.new(build_spec(name, version))
+ 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.8.0 1.7.15 1.7.9 1.7.8 1.7.7]
end
- # Rightmost (highest array index) in result is most preferred.
- # Leftmost (lowest array index) in result is least preferred.
- # `build_spec_group` has all version 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.
- context "filter specs (strict) level patch" do
- it "when keeping build_spec, keep current, next release" do
- keep_locked(:level => :patch)
- res = @gvp.filter_dep_specs(
- build_spec_group("foo", %w(1.7.8 1.7.9 1.8.0)),
- build_spec("foo", "1.7.8").first
- )
- expect(versions(res)).to eq %w(1.7.9 1.7.8)
+ 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[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0]
end
+ end
- it "when unlocking prefer next release first" do
- unlocking(:level => :patch)
- res = @gvp.filter_dep_specs(
- build_spec_group("foo", %w(1.7.8 1.7.9 1.8.0)),
- build_spec("foo", "1.7.8").first
- )
- expect(versions(res)).to eq %w(1.7.8 1.7.9)
- end
+ context "when strict" do
+ before { gvp.strict = true }
- it "when unlocking keep current when already at latest release" do
- unlocking(:level => :patch)
- res = @gvp.filter_dep_specs(
- build_spec_group("foo", %w(1.7.9 1.8.0 2.0.0)),
- build_spec("foo", "1.7.9").first
- )
- expect(versions(res)).to eq %w(1.7.9)
+ 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[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0]
+ end
end
- end
- context "filter specs (strict) level minor" do
- it "when unlocking favor next releases, remove minor and major increases" do
- unlocking(:level => :minor)
- res = @gvp.filter_dep_specs(
- build_spec_group("foo", %w(0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1)),
- build_spec("foo", "0.2.0").first
- )
- expect(versions(res)).to eq %w(0.2.0 0.3.0 0.3.1 0.9.0)
+ context "when level is minor" do
+ before { gvp.level = :minor }
+
+ it "sorts highest minor within same major in first position" 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.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0]
+ end
end
- it "when keep locked, keep current, then favor next release, remove minor and major increases" do
- keep_locked(:level => :minor)
- res = @gvp.filter_dep_specs(
- build_spec_group("foo", %w(0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1)),
- build_spec("foo", "0.2.0").first
- )
- expect(versions(res)).to eq %w(0.3.0 0.3.1 0.9.0 0.2.0)
+ context "when level is patch" do
+ before { gvp.level = :patch }
+
+ it "sorts highest patch within same minor in first position" 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.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0]
+ end
end
end
- context "sort specs (not strict) level patch" do
- it "when not unlocking, same order but make sure build_spec version is most preferred to stay put" do
- keep_locked(:level => :patch)
- res = @gvp.sort_dep_specs(
- build_spec_group("foo", %w(1.5.4 1.6.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 2.0.0 2.0.1)),
- build_spec("foo", "1.7.7").first
- )
- expect(versions(res)).to eq %w(1.5.4 1.6.5 1.7.6 2.0.0 2.0.1 1.8.0 1.8.1 1.7.8 1.7.9 1.7.7)
- end
+ context "when not strict" do
+ before { gvp.strict = false }
- it "when unlocking favor next release, then current over minor increase" do
- unlocking(:level => :patch)
- res = @gvp.sort_dep_specs(
- build_spec_group("foo", %w(1.7.7 1.7.8 1.7.9 1.8.0)),
- build_spec("foo", "1.7.8").first
- )
- expect(versions(res)).to eq %w(1.7.7 1.8.0 1.7.8 1.7.9)
+ 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[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0]
+ end
end
- it "when unlocking do proper integer comparison, not string" do
- unlocking(:level => :patch)
- res = @gvp.sort_dep_specs(
- build_spec_group("foo", %w(1.7.7 1.7.8 1.7.9 1.7.15 1.8.0)),
- build_spec("foo", "1.7.8").first
- )
- expect(versions(res)).to eq %w(1.7.7 1.8.0 1.7.8 1.7.9 1.7.15)
+ context "when level is minor" do
+ before { gvp.level = :minor }
+
+ it "favors minor upgrades, then patch upgrades, then major upgrades, then 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.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0]
+ end
end
- it "leave current when unlocking but already at latest release" do
- unlocking(:level => :patch)
- res = @gvp.sort_dep_specs(
- build_spec_group("foo", %w(1.7.9 1.8.0 2.0.0)),
- build_spec("foo", "1.7.9").first
- )
- expect(versions(res)).to eq %w(2.0.0 1.8.0 1.7.9)
+ context "when level is patch" do
+ before { gvp.level = :patch }
+
+ it "favors patch upgrades, then minor upgrades, then major upgrades, then 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.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0]
+ end
end
end
- context "sort specs (not strict) level minor" do
- it "when unlocking favor next release, then minor increase over current" do
- unlocking(:level => :minor)
- res = @gvp.sort_dep_specs(
- build_spec_group("foo", %w(0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1)),
- build_spec("foo", "0.2.0").first
- )
- expect(versions(res)).to eq %w(2.0.0 2.0.1 1.0.0 0.2.0 0.3.0 0.3.1 0.9.0)
+ 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[2.0.0 2.0.0.pre 1.8.1 1.8.1.pre 1.8.0 1.7.7.pre]
end
end
- context "level error handling" do
- subject { Bundler::GemVersionPromoter.new }
+ context "when not pre" do
+ before { gvp.pre = false }
- it "should raise if not major, minor or patch is passed" do
- expect { subject.level = :minjor }.to raise_error ArgumentError
+ 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[2.0.0 1.8.1 1.8.0 2.0.0.pre 1.8.1.pre 1.7.7.pre]
end
+ end
- it "should raise if invalid classes passed" do
- [123, nil].each do |value|
- expect { subject.level = value }.to raise_error ArgumentError
- end
+ context "when locking and not major" do
+ before { gvp.level = :minor }
+
+ it "keeps the current version first" 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", unlock: [])
+ expect(versions.first).to eq("0.3.0")
end
+ 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
+ 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 strings" do
- %w(major minor patch).each do |value|
- subject.level = value
- expect(subject.level).to eq value.to_sym
- 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
- context "debug output" do
- it "should not kerblooie on its own debug output" do
- gvp = unlocking(:level => :patch)
- dep = Bundler::DepProxy.new(dep("foo", "1.2.0").first, "ruby")
- result = gvp.send(:debug_format_result, dep, [build_spec_group("foo", "1.2.0"),
- build_spec_group("foo", "1.3.0")])
- expect(result.class).to eq Array
+ 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
diff --git a/spec/bundler/bundler/index_spec.rb b/spec/bundler/bundler/index_spec.rb
index 09b09e08fa..0f3f6e4944 100644
--- a/spec/bundler/bundler/index_spec.rb
+++ b/spec/bundler/bundler/index_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Index do
let(:specs) { [] }
diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb
index e2f30cdd70..dbd4e1d2c8 100644
--- a/spec/bundler/bundler/installer/gem_installer_spec.rb
+++ b/spec/bundler/bundler/installer/gem_installer_spec.rb
@@ -1,27 +1,46 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/installer/gem_installer"
RSpec.describe Bundler::GemInstaller do
- let(:installer) { instance_double("Installer") }
+ 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) }
+ let(:spec) { instance_double("Specification", name: "dummy", version: "0.0.1", loaded_from: "dummy", source: spec_source) }
+ let(:base_options) { { force: false, local: false, previous_spec: nil } }
subject { described_class.new(spec, installer) }
context "spec_settings is nil" do
- it "invokes install method with empty build_args", :rubygems => ">= 2" do
- allow(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => [])
+ it "invokes install method with empty build_args" do
+ allow(spec_source).to receive(:install).with(
+ spec,
+ base_options.merge(build_args: [])
+ )
subject.install_from_spec
end
end
context "spec_settings is build option" do
- it "invokes install method with build_args", :rubygems => ">= 2" do
- allow(Bundler.settings).to receive(:[]).with(:bin)
- allow(Bundler.settings).to receive(:[]).with(:inline)
+ it "invokes install method with build_args" do
+ allow(Bundler.settings).to receive(:[])
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"])
+ expect(spec_source).to receive(:install).with(
+ spec,
+ base_options.merge(build_args: ["--with-dummy-config=dummy"])
+ )
+ 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(:[])
+ 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,
+ base_options.merge(build_args: ["--with-dummy-config=dummy", "--with-another-dummy-config"])
+ )
subject.install_from_spec
end
end
diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb
index 7d2c441399..49bcb5310b 100644
--- a/spec/bundler/bundler/installer/parallel_installer_spec.rb
+++ b/spec/bundler/bundler/installer/parallel_installer_spec.rb
@@ -1,47 +1,79 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/installer/parallel_installer"
+require "bundler/rubygems_gem_installer"
+require "rubygems/remote_fetcher"
+require "bundler"
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 dependencies that are not on the overall installation list are the only ones not installed" do
- let(:all_specs) do
- [
- build_spec("alpha", "1.0") {|s| s.runtime "a", "1" },
- ].flatten
+ describe "priority queue" do
+ before do
+ require "support/artifice/compact_index"
+
+ @previous_client = Gem::Request::ConnectionPools.client
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+ Gem::RemoteFetcher.fetcher.close_all
+
+ build_repo2 do
+ build_gem "gem_with_extension", &:add_c_extension
+ build_gem "gem_without_extension"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ gem "gem_with_extension"
+ gem "gem_without_extension"
+ G
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ gem_with_extension (1.0)
+ gem_without_extension (1.0)
+
+ DEPENDENCIES
+ gem_with_extension
+ gem_without_extension
+ L
+
+ @old_ui = Bundler.ui
+ Bundler.ui = Bundler::UI::Silent.new
+ end
+
+ after do
+ Bundler.ui = @old_ui
+ Gem::Request::ConnectionPools.client = @previous_client
+ Artifice.deactivate
end
- it "prints a warning" do
- expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
-Your lockfile was created by an old Bundler that left some things out.
-You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile.
-The missing gems are:
-* a depended upon by alpha
- W
- subject.check_for_corrupt_lockfile
+ let(:definition) do
+ allow(Bundler).to receive(:root) { bundled_app }
+
+ definition = Bundler::Definition.build(bundled_app.join("Gemfile"), bundled_app.join("Gemfile.lock"), false)
+ definition.tap(&:setup_domain!)
end
+ let(:installer) { Bundler::Installer.new(bundled_app, definition) }
- context "when size > 1" do
- let(:size) { 500 }
-
- it "prints a warning and sets size to 1" do
- expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
-Your lockfile was created by an old Bundler that left some things out.
-Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing 500 at a time.
-You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile.
-The missing gems are:
-* a depended upon by alpha
- W
- subject.check_for_corrupt_lockfile
- expect(subject.size).to eq(1)
+ it "queues native extensions in priority" do
+ parallel_installer = Bundler::ParallelInstaller.new(installer, definition.specs, 2, false, true)
+ worker_pool = parallel_installer.send(:worker_pool)
+ expected = 6 # Enqueue to download bundler and the 2 gems. Enqueue to install Bundler and the 2 gems.
+
+ expect(worker_pool).to receive(:enq).exactly(expected).times.and_wrap_original do |original_enq, spec, opts|
+ unless opts.nil? # Enqueued for download, no priority
+ if spec.name == "gem_with_extension"
+ expect(opts).to eq({ priority: true })
+ else
+ expect(opts).to eq({ priority: false })
+ end
+ end
+
+ opts ||= {}
+ original_enq.call(spec, **opts)
end
+
+ parallel_installer.call
end
end
end
diff --git a/spec/bundler/bundler/installer/spec_installation_spec.rb b/spec/bundler/bundler/installer/spec_installation_spec.rb
index 1e368ab7c5..57868766d9 100644
--- a/spec/bundler/bundler/installer/spec_installation_spec.rb
+++ b/spec/bundler/bundler/installer/spec_installation_spec.rb
@@ -1,16 +1,19 @@
# frozen_string_literal: true
-require "spec_helper"
+
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
- a_spec
+ def build_spec(name, extensions: [])
+ spec = Object.new
+ spec.define_singleton_method(:name) { name }
+ spec.define_singleton_method(:full_name) { "#{name}-1.0" }
+ spec.define_singleton_method(:extensions) { extensions }
+ spec.define_singleton_method(:dependencies) { [] }
+ spec
end
+ let!(:dep) { build_spec("I like tests") }
+
describe "#ready_to_enqueue?" do
context "when in enqueued state" do
it "is falsey" do
@@ -35,27 +38,51 @@ RSpec.describe Bundler::ParallelInstaller::SpecInstallation do
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)]
+ it "returns true when all dependencies are installed" do
+ alpha = described_class.new(build_spec("alpha"))
+ alpha.dependencies = []
+
+ beta = described_class.new(build_spec("beta"))
+ beta.dependencies = [alpha]
+
+ gamma = described_class.new(build_spec("gamma"))
+ gamma.dependencies = [beta]
+
+ expect(gamma.dependencies_installed?({})).to be_falsey
+ expect(gamma.dependencies_installed?({ "beta" => true })).to be_falsey
+ expect(gamma.dependencies_installed?({ "alpha" => true, "beta" => true })).to be_truthy
+ end
+ end
+
+ describe "#ready_to_install?" do
+ context "when spec has no extensions" do
+ it "returns true regardless of dependencies" do
+ beta = described_class.new(build_spec("beta"))
+ beta.dependencies = []
+
spec = described_class.new(dep)
- allow(spec).to receive(:all_dependencies).and_return(dependencies)
- expect(spec.dependencies_installed?(all_specs)).to be_truthy
+ spec.state = :downloaded
+ spec.dependencies = [beta]
+
+ expect(spec.ready_to_install?({})).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
+ context "when spec has extensions" do
+ it "returns true when all dependencies are installed" do
+ alpha = described_class.new(build_spec("alpha"))
+ alpha.dependencies = []
+
+ beta = described_class.new(build_spec("beta"))
+ beta.dependencies = [alpha]
+
+ gamma = described_class.new(build_spec("gamma", extensions: ["ext/Rakefile"]))
+ gamma.state = :downloaded
+ gamma.dependencies = [beta]
+
+ expect(gamma.ready_to_install?({})).to be_falsey
+ expect(gamma.ready_to_install?({ "beta" => true })).to be_falsey
+ expect(gamma.ready_to_install?({ "alpha" => true, "beta" => true })).to be_truthy
end
end
end
diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb
index 17bb447194..7364ab98e5 100644
--- a/spec/bundler/bundler/lockfile_parser_spec.rb
+++ b/spec/bundler/bundler/lockfile_parser_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/lockfile_parser"
RSpec.describe Bundler::LockfileParser do
- let(:lockfile_contents) { strip_whitespace(<<-L) }
+ let(:lockfile_contents) { <<~L }
GIT
remote: https://github.com/alloy/peiji-san.git
revision: eca485d8dc95f12aaec1a434b49d295c7e91844b
@@ -22,6 +22,9 @@ RSpec.describe Bundler::LockfileParser do
peiji-san!
rake
+ CHECKSUMS
+ rake (10.3.2) sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8
+
RUBY VERSION
ruby 2.1.3p242
@@ -33,13 +36,13 @@ RSpec.describe Bundler::LockfileParser 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"
+ "BUNDLED WITH", "CHECKSUMS", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION"
)
end
end
describe ".unknown_sections_in_lockfile" do
- let(:lockfile_contents) { strip_whitespace(<<-L) }
+ let(:lockfile_contents) { <<~L }
UNKNOWN ATTR
UNKNOWN ATTR 2
@@ -60,7 +63,7 @@ RSpec.describe Bundler::LockfileParser do
it "returns the same as > 1.0" do
expect(subject).to contain_exactly(
- described_class::BUNDLED, described_class::RUBY, described_class::PLUGIN
+ described_class::BUNDLED, described_class::CHECKSUMS, described_class::RUBY, described_class::PLUGIN
)
end
end
@@ -70,7 +73,7 @@ RSpec.describe Bundler::LockfileParser do
it "returns the same as for the release version" do
expect(subject).to contain_exactly(
- described_class::RUBY, described_class::PLUGIN
+ described_class::CHECKSUMS, described_class::RUBY, described_class::PLUGIN
)
end
end
@@ -91,4 +94,210 @@ RSpec.describe Bundler::LockfileParser do
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"), Gem::Platform::RUBY),
+ Bundler::LazySpecification.new("rake", v("10.3.2"), Gem::Platform::RUBY),
+ ]
+ end
+ let(:platforms) { [Gem::Platform::RUBY] }
+ let(:bundler_version) { Gem::Version.new("1.12.0.rc.2") }
+ let(:ruby_version) { "ruby 2.1.3p242" }
+ let(:lockfile_path) { Bundler.default_lockfile.relative_path_from(Dir.pwd) }
+ let(:rake_sha256_checksum) do
+ Bundler::Checksum.from_lock(
+ "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8",
+ "#{lockfile_path}:20:17"
+ )
+ end
+ let(:rake_checksums) { [rake_sha256_checksum] }
+
+ shared_examples_for "parsing" do
+ it "parses correctly" do
+ expect(subject.valid?).to be(true)
+ 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
+ rake_spec = specs.last
+ checksums = subject.sources.last.checksum_store.to_lock(specs.last)
+ expect(checksums).to eq("#{rake_spec.lock_name} #{rake_checksums.map(&:to_lock).sort.join(",")}")
+ 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
+
+ context "when the checksum is urlsafe base64 encoded" do
+ let(:lockfile_contents) do
+ super().sub(
+ "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8",
+ "sha256=gUgow08TFdfnt-gpUYRXfMTpabrWFWrAadAtY_WNgug="
+ )
+ end
+ include_examples "parsing"
+ end
+
+ context "when the checksum is of an unknown algorithm" do
+ let(:rake_sha512_checksum) do
+ Bundler::Checksum.from_lock(
+ "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==",
+ "#{lockfile_path}:20:17"
+ )
+ end
+ let(:lockfile_contents) do
+ super().sub(
+ "sha256=",
+ "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==,sha256="
+ )
+ end
+ let(:rake_checksums) { [rake_sha256_checksum, rake_sha512_checksum] }
+ include_examples "parsing"
+ end
+
+ context "when the content does not contain any recognized lockfile sections" do
+ let(:lockfile_contents) { "hello world\nlorem ipsum\n" }
+
+ it "does not raise, is not valid, and deprecates" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
+ /does not appear to be a valid lockfile.*future version of Bundler/m
+ )
+ parser = described_class.new(lockfile_contents)
+ expect(parser.valid?).to be(false)
+ expect(parser.specs).to eq([])
+ expect(parser.dependencies).to eq({})
+ end
+
+ it "does not raise when strict: true, and still deprecates" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
+ /does not appear to be a valid lockfile.*future version of Bundler/m
+ )
+ parser = described_class.new(lockfile_contents, strict: true)
+ expect(parser.valid?).to be(false)
+ expect(parser.specs).to eq([])
+ expect(parser.dependencies).to eq({})
+ end
+ end
+
+ context "when the content looks like a Gemfile DSL" do
+ let(:lockfile_contents) { <<~G }
+ source "https://rubygems.org"
+ gem "rake"
+ G
+
+ it "does not raise, is not valid, and deprecates" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
+ /does not appear to be a valid lockfile.*future version of Bundler/m
+ )
+ parser = described_class.new(lockfile_contents)
+ expect(parser.valid?).to be(false)
+ expect(parser.specs).to eq([])
+ expect(parser.dependencies).to eq({})
+ end
+
+ it "does not raise when strict: true, and still deprecates" do
+ expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(
+ /does not appear to be a valid lockfile.*future version of Bundler/m
+ )
+ parser = described_class.new(lockfile_contents, strict: true)
+ expect(parser.valid?).to be(false)
+ expect(parser.specs).to eq([])
+ expect(parser.dependencies).to eq({})
+ end
+ end
+
+ context "when the content is empty" do
+ let(:lockfile_contents) { "" }
+
+ it "does not raise and is valid" do
+ expect { subject }.not_to raise_error
+ expect(subject.valid?).to be(true)
+ end
+ end
+
+ context "when lockfile_path is given" do
+ it "uses the provided path in error messages instead of looking up Bundler.default_lockfile" do
+ expect(Bundler::SharedHelpers).not_to receive(:relative_lockfile_path)
+ parser = described_class.new(lockfile_contents, lockfile_path: "custom/path.lock")
+ expect(parser.valid?).to be(true)
+ rake_spec = parser.specs.last
+ checksums = parser.sources.last.checksum_store.to_lock(rake_spec)
+ expected_checksum = Bundler::Checksum.from_lock(
+ "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8",
+ "custom/path.lock:20:17"
+ )
+ expect(checksums).to eq("#{rake_spec.lock_name} #{expected_checksum.to_lock}")
+ end
+
+ it "raises with the provided path when the lockfile contains merge conflicts" do
+ expect do
+ described_class.new("<<<<<<<\n", lockfile_path: "custom/path.lock")
+ end.to raise_error(Bundler::LockfileError, %r{custom/path\.lock contains merge conflicts})
+ end
+ end
+
+ context "when CHECKSUMS has duplicate checksums in the lockfile that don't match" do
+ let(:bad_checksum) { "sha256=c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11" }
+ let(:lockfile_contents) { super().split(/(?<=CHECKSUMS\n)/m).insert(1, " rake (10.3.2) #{bad_checksum}\n").join }
+
+ it "raises a security error" do
+ expect { subject }.to raise_error(Bundler::SecurityError) do |e|
+ expect(e.message).to match <<~MESSAGE
+ Bundler found mismatched checksums. This is a potential security risk.
+ rake (10.3.2) #{bad_checksum}
+ from the lockfile CHECKSUMS at #{lockfile_path}:20:17
+ rake (10.3.2) #{rake_sha256_checksum.to_lock}
+ from the lockfile CHECKSUMS at #{lockfile_path}:21:17
+
+ To resolve this issue you can either:
+ 1. remove the matching checksum in #{lockfile_path}:21:17
+ 2. run `bundle install`
+ or if you are sure that the new checksum from the lockfile CHECKSUMS at #{lockfile_path}:21:17 is correct:
+ 1. remove the matching checksum in #{lockfile_path}:20:17
+ 2. run `bundle install`
+
+ To ignore checksum security warnings, disable checksum validation with
+ `bundle config set --local disable_checksum_validation true`
+ MESSAGE
+ end
+ end
+ end
+ end
end
diff --git a/spec/bundler/bundler/mirror_spec.rb b/spec/bundler/bundler/mirror_spec.rb
index 9051a80465..ba1c6ed413 100644
--- a/spec/bundler/bundler/mirror_spec.rb
+++ b/spec/bundler/bundler/mirror_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/mirror"
RSpec.describe Bundler::Settings::Mirror do
@@ -36,12 +36,12 @@ RSpec.describe Bundler::Settings::Mirror do
it "takes a string for the uri but returns an uri object" do
mirror.uri = "http://localhost:9292"
- expect(mirror.uri).to eq(URI("http://localhost:9292"))
+ expect(mirror.uri).to eq(Gem::URI("http://localhost:9292"))
end
it "takes an uri object for the uri" do
- mirror.uri = URI("http://localhost:9293")
- expect(mirror.uri).to eq(URI("http://localhost:9293"))
+ mirror.uri = Gem::URI("http://localhost:9293")
+ expect(mirror.uri).to eq(Gem::URI("http://localhost:9293"))
end
context "without a uri" do
@@ -145,7 +145,7 @@ RSpec.describe Bundler::Settings::Mirror do
end
RSpec.describe Bundler::Settings::Mirrors do
- let(:localhost_uri) { URI("http://localhost:9292") }
+ let(:localhost_uri) { Gem::URI("http://localhost:9292") }
context "with a just created mirror" do
let(:mirrors) do
@@ -260,7 +260,7 @@ RSpec.describe Bundler::Settings::Mirrors 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(URI("http://whatever.com/"))
+ expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/"))
end
end
end
@@ -270,7 +270,7 @@ RSpec.describe Bundler::Settings::Mirrors do
context "without a fallback timeout" do
it "returns the uri that is not mirrored" do
- expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
+ expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/"))
end
it "returns localhost for rubygems.org" do
@@ -282,11 +282,11 @@ RSpec.describe Bundler::Settings::Mirrors 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(URI("http://whatever.com/"))
+ expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/"))
end
it "returns rubygems.org for rubygems.org" do
- expect(mirrors.for("http://rubygems.org/").uri).to eq(URI("http://rubygems.org/"))
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(Gem::URI("http://rubygems.org/"))
end
end
end
@@ -298,13 +298,15 @@ RSpec.describe Bundler::Settings::TCPSocketProbe do
context "with a listening TCP Server" do
def with_server_and_mirror
- server = TCPServer.new("127.0.0.1", 0)
- mirror = Bundler::Settings::Mirror.new("http://localhost:#{server.addr[1]}", 1)
+ 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
diff --git a/spec/bundler/bundler/override_spec.rb b/spec/bundler/bundler/override_spec.rb
new file mode 100644
index 0000000000..ad8be75520
--- /dev/null
+++ b/spec/bundler/bundler/override_spec.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+RSpec.describe "MatchMetadata override-aware checks" do
+ let(:spec_class) do
+ Class.new do
+ include Bundler::MatchMetadata
+ attr_accessor :name
+ def initialize(name, ruby_req, rubygems_req)
+ @name = name
+ @required_ruby_version = ruby_req
+ @required_rubygems_version = rubygems_req
+ end
+ end
+ end
+
+ it "matches_current_metadata? ignores overrides (strict path)" do
+ spec = spec_class.new("rails", Gem::Requirement.new("< #{Gem.ruby_version}"), Gem::Requirement.default)
+ overrides = [Bundler::Override.new("rails", :required_ruby_version, :ignore_upper)]
+ # Strict method MUST NOT apply overrides; guards SelfManager and other generic callers.
+ expect(spec.matches_current_metadata?).to be(false)
+ expect(spec.matches_current_metadata_with_overrides?(overrides)).to be(true)
+ end
+
+ it "matches_current_ruby_with_overrides? returns the strict result for an empty override list" do
+ spec = spec_class.new("rails", Gem::Requirement.new(">= #{Gem.ruby_version}"), Gem::Requirement.default)
+ expect(spec.matches_current_ruby_with_overrides?([])).to be(true)
+ expect(spec.matches_current_ruby_with_overrides?(nil)).to be(true)
+ end
+
+ it "matches_current_rubygems_with_overrides? honors :all override" do
+ spec = spec_class.new("rails", Gem::Requirement.default, Gem::Requirement.new("< #{Gem.rubygems_version}"))
+ overrides = [Bundler::Override.new(:all, :required_rubygems_version, :ignore_upper)]
+ expect(spec.matches_current_rubygems_with_overrides?(overrides)).to be(true)
+ end
+end
+
+RSpec.describe "LazySpecification override propagation" do
+ let(:overrides) { [Bundler::Override.new("rails", :required_ruby_version, :ignore_upper)] }
+
+ it "carries overrides forward from a source LazySpec via from_spec" do
+ src = Bundler::LazySpecification.new("rails", "8.0", Gem::Platform::RUBY)
+ src.overrides = overrides
+ derived = Bundler::LazySpecification.from_spec(src)
+ expect(derived.overrides).to eq(overrides)
+ end
+
+ it "does not call respond_to? on the source spec, avoiding gemspec lazy load" do
+ # If from_spec used respond_to?(:overrides), a RemoteSpec source would
+ # force-load the backing gemspec. Use a stand-in object whose
+ # respond_to? raises to prove it is never asked.
+ src = Object.new
+ src.define_singleton_method(:name) { "rails" }
+ src.define_singleton_method(:version) { Gem::Version.new("8.0") }
+ src.define_singleton_method(:platform) { Gem::Platform::RUBY }
+ src.define_singleton_method(:source) { nil }
+ src.define_singleton_method(:runtime_dependencies) { [] }
+ src.define_singleton_method(:required_ruby_version) { Gem::Requirement.default }
+ src.define_singleton_method(:required_rubygems_version) { Gem::Requirement.default }
+ src.define_singleton_method(:respond_to?) {|*| raise "from_spec must not call respond_to?" }
+ expect { Bundler::LazySpecification.from_spec(src) }.not_to raise_error
+ end
+end
+
+RSpec.describe Bundler::Override do
+ describe ".find_for" do
+ it "returns the matching override by target and field" do
+ a = described_class.new("rails", :version, ">= 8.0")
+ b = described_class.new("nokogiri", :version, :ignore_upper)
+ expect(described_class.find_for([a, b], "rails", :version)).to be(a)
+ end
+
+ it "returns nil when no override matches the target" do
+ a = described_class.new("rails", :version, ">= 8.0")
+ expect(described_class.find_for([a], "sinatra", :version)).to be_nil
+ end
+
+ it "returns nil when no override matches the field" do
+ a = described_class.new("rails", :version, ">= 8.0")
+ expect(described_class.find_for([a], "rails", :required_ruby_version)).to be_nil
+ end
+
+ it "returns nil for an empty overrides list" do
+ expect(described_class.find_for([], "rails", :version)).to be_nil
+ end
+
+ it "falls back to an :all override on the same field" do
+ a = described_class.new(:all, :required_ruby_version, :ignore_upper)
+ expect(described_class.find_for([a], "rails", :required_ruby_version)).to be(a)
+ end
+
+ it "prefers a per-gem override over a matching :all override" do
+ per_gem = described_class.new("rails", :required_ruby_version, ">= 3.4")
+ all_target = described_class.new(:all, :required_ruby_version, :ignore_upper)
+ expect(described_class.find_for([all_target, per_gem], "rails", :required_ruby_version)).to be(per_gem)
+ end
+
+ it "does not fall back to :all when the field differs" do
+ a = described_class.new(:all, :required_ruby_version, :ignore_upper)
+ expect(described_class.find_for([a], "rails", :required_rubygems_version)).to be_nil
+ end
+ end
+
+ describe "#apply_to" do
+ context "when operation is a version spec string" do
+ it "replaces the existing requirement entirely" do
+ override = described_class.new("rails", :version, ">= 8.0")
+ result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0"))
+ expect(result).to eq(Gem::Requirement.new(">= 8.0"))
+ end
+
+ it "ignores the existing requirement regardless of its content" do
+ override = described_class.new("rails", :version, "= 1.0")
+ result = override.apply_to(Gem::Requirement.new(">= 99.0"))
+ expect(result).to eq(Gem::Requirement.new("= 1.0"))
+ end
+ end
+
+ context "when operation is :ignore_upper" do
+ it "removes < and <= operators" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0"))
+ expect(result).to eq(Gem::Requirement.new(">= 1.0"))
+ end
+
+ it "keeps >, >=, = operators" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ result = override.apply_to(Gem::Requirement.new("> 1.0", "<= 2.0"))
+ expect(result).to eq(Gem::Requirement.new("> 1.0"))
+ end
+
+ it "converts ~> to >= preserving the lower bound" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ result = override.apply_to(Gem::Requirement.new("~> 1.5"))
+ expect(result).to eq(Gem::Requirement.new(">= 1.5"))
+ end
+
+ it "preserves != exclusion constraints" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ result = override.apply_to(Gem::Requirement.new(">= 1.0", "!= 1.5.0", "< 2.0"))
+ expect(result).to eq(Gem::Requirement.new(">= 1.0", "!= 1.5.0"))
+ end
+
+ it "returns the default requirement when only upper bounds remain" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ result = override.apply_to(Gem::Requirement.new("< 2.0"))
+ expect(result).to eq(Gem::Requirement.default)
+ end
+
+ it "returns the default requirement when the input is nil" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ expect(override.apply_to(nil)).to eq(Gem::Requirement.default)
+ end
+
+ it "returns the default requirement when the input is already the default" do
+ override = described_class.new("rails", :version, :ignore_upper)
+ expect(override.apply_to(Gem::Requirement.default)).to eq(Gem::Requirement.default)
+ end
+ end
+
+ context "when operation is nil" do
+ it "returns the default requirement" do
+ override = described_class.new("rails", :version, nil)
+ result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0"))
+ expect(result).to eq(Gem::Requirement.default)
+ end
+ end
+
+ context "when operation is unsupported" do
+ it "raises ArgumentError" do
+ override = described_class.new("rails", :version, 42)
+ expect { override.apply_to(Gem::Requirement.default) }.to raise_error(ArgumentError, /unsupported override operation/)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/api/source_spec.rb b/spec/bundler/bundler/plugin/api/source_spec.rb
index 4dbb993b89..ae02e08bea 100644
--- a/spec/bundler/bundler/plugin/api/source_spec.rb
+++ b/spec/bundler/bundler/plugin/api/source_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin::API::Source do
let(:uri) { "uri://to/test" }
@@ -37,7 +36,7 @@ RSpec.describe Bundler::Plugin::API::Source do
context "install_path" do
let(:uri) { "uri://to/a/repository-name" }
- let(:hash) { Digest::SHA1.hexdigest(uri) }
+ let(:hash) { Digest(:SHA1).hexdigest(uri) }
let(:install_path) { Pathname.new "/bundler/install/path" }
before do
@@ -52,7 +51,7 @@ RSpec.describe Bundler::Plugin::API::Source do
context "to_lock" do
it "returns the string with remote and type" do
- expected = strip_whitespace <<-L
+ expected = <<~L
PLUGIN SOURCE
remote: #{uri}
type: #{type}
@@ -68,7 +67,7 @@ RSpec.describe Bundler::Plugin::API::Source do
end
it "includes them" do
- expected = strip_whitespace <<-L
+ expected = <<~L
PLUGIN SOURCE
remote: #{uri}
type: #{type}
@@ -80,4 +79,10 @@ RSpec.describe Bundler::Plugin::API::Source do
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
index e40b9adb0f..58fb908572 100644
--- a/spec/bundler/bundler/plugin/api_spec.rb
+++ b/spec/bundler/bundler/plugin/api_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin::API do
context "plugin declarations" do
@@ -60,15 +59,15 @@ RSpec.describe Bundler::Plugin::API do
# A test of delegation
it "provides the Bundler's functions" do
- expect(Bundler).to receive(:an_unkown_function).once
+ expect(Bundler).to receive(:an_unknown_function).once
- api.an_unkown_function
+ api.an_unknown_function
end
it "includes Bundler::SharedHelpers' functions" do
- expect(Bundler::SharedHelpers).to receive(:an_unkown_helper).once
+ expect(Bundler::SharedHelpers).to receive(:an_unknown_helper).once
- api.an_unkown_helper
+ api.an_unknown_helper
end
context "#tmp" do
diff --git a/spec/bundler/bundler/plugin/dsl_spec.rb b/spec/bundler/bundler/plugin/dsl_spec.rb
index cd15b6ea9d..235a549735 100644
--- a/spec/bundler/bundler/plugin/dsl_spec.rb
+++ b/spec/bundler/bundler/plugin/dsl_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin::DSL do
DSL = Bundler::Plugin::DSL
@@ -24,16 +23,16 @@ RSpec.describe Bundler::Plugin::DSL 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") {}
+ 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 declataions" do
+ 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") {}
+ 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..77e5fdb74c
--- /dev/null
+++ b/spec/bundler/bundler/plugin/events_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Events do
+ context "plugin events" do
+ before do
+ @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] }
+ Bundler::Plugin::Events.send :reset
+ end
+
+ after do
+ Bundler::Plugin::Events.send(:reset)
+ Hash[@old_constants].each do |name, value|
+ Bundler::Plugin::Events.send(:define, name, value)
+ end
+ end
+
+ 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
index 24b9a408ff..a28934269b 100644
--- a/spec/bundler/bundler/plugin/index_spec.rb
+++ b/spec/bundler/bundler/plugin/index_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin::Index do
Index = Bundler::Plugin::Index
before do
- gemfile ""
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ gemfile "source 'https://gem.repo1'"
path = lib_path(plugin_name)
index.register_plugin("new-plugin", path.to_s, [path.join("lib").to_s], commands, sources, hooks)
end
@@ -22,7 +22,7 @@ RSpec.describe Bundler::Plugin::Index do
expect(index.plugin_path(plugin_name)).to eq(lib_path(plugin_name))
end
- it "load_paths is available for retrival" do
+ 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
@@ -87,7 +87,24 @@ RSpec.describe Bundler::Plugin::Index do
expect(new_index.hook_plugins("after-bar")).to eq([plugin_name])
end
- context "that are not registered", :focused do
+ 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
@@ -107,11 +124,11 @@ RSpec.describe Bundler::Plugin::Index do
describe "global index" do
before do
- Dir.chdir(tmp) do
- Bundler::Plugin.reset!
- path = lib_path("gplugin")
- index.register_plugin("gplugin", path.to_s, [path.join("lib").to_s], [], ["glb_source"], [])
- end
+ 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
@@ -123,7 +140,7 @@ RSpec.describe Bundler::Plugin::Index do
describe "after conflict" do
let(:commands) { ["foo"] }
let(:sources) { ["bar"] }
- let(:hooks) { ["hoook"] }
+ let(:hooks) { ["thehook"] }
shared_examples "it cleans up" do
it "the path" do
@@ -139,7 +156,7 @@ RSpec.describe Bundler::Plugin::Index do
end
it "the hook" do
- expect(index.hook_plugins("xhoook")).to be_empty
+ expect(index.hook_plugins("xthehook")).to be_empty
end
end
@@ -147,7 +164,7 @@ RSpec.describe Bundler::Plugin::Index do
before do
expect do
path = lib_path("cplugin")
- index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["xbar"], ["xhoook"])
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["xbar"], ["xthehook"])
end.to raise_error(Index::CommandConflict)
end
@@ -158,7 +175,7 @@ RSpec.describe Bundler::Plugin::Index do
before do
expect do
path = lib_path("cplugin")
- index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["xfoo"], ["bar"], ["xhoook"])
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["xfoo"], ["bar"], ["xthehook"])
end.to raise_error(Index::SourceConflict)
end
@@ -169,11 +186,90 @@ RSpec.describe Bundler::Plugin::Index do
before do
expect do
path = lib_path("cplugin")
- index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["bar"], ["xhoook"])
+ 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 "relative plugin paths" do
+ let(:plugin_name) { "relative-plugin" }
+
+ before do
+ Bundler::Plugin.reset!
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+
+ plugin_root = Bundler::Plugin.root
+ FileUtils.mkdir_p(plugin_root)
+
+ path = plugin_root.join(plugin_name)
+ FileUtils.mkdir_p(path.join("lib"))
+
+ index.register_plugin(plugin_name, path.to_s, [path.join("lib").to_s], [], [], [])
+ end
+
+ it "stores plugin paths relative to the plugin root" do
+ require "yaml"
+ data = YAML.load_file(index.index_file)
+
+ expect(data["plugin_paths"][plugin_name]).to eq(plugin_name)
+ expect(data["load_paths"][plugin_name]).to eq([File.join(plugin_name, "lib")])
+ end
+
+ it "expands relative paths to absolute on load" do
+ require "bundler/yaml_serializer"
+
+ plugin_root = Bundler::Plugin.root
+
+ relative_index = {
+ "commands" => {},
+ "hooks" => {},
+ "load_paths" => { plugin_name => [File.join(plugin_name, "lib")] },
+ "plugin_paths" => { plugin_name => plugin_name },
+ "sources" => {},
+ }
+
+ File.open(index.index_file, "w") {|f| f.puts Bundler::YAMLSerializer.dump(relative_index) }
+
+ new_index = Index.new
+ expect(new_index.plugin_path(plugin_name)).to eq(plugin_root.join(plugin_name))
+ expect(new_index.load_paths(plugin_name)).to eq([plugin_root.join(plugin_name, "lib").to_s])
+ end
+
+ it "keeps paths outside the plugin root as absolute" do
+ outside_path = tmp.join("outside", "external-plugin")
+ FileUtils.mkdir_p(outside_path.join("lib"))
+
+ index.register_plugin("external-plugin", outside_path.to_s, [outside_path.join("lib").to_s], [], [], [])
+
+ require "yaml"
+ data = YAML.load_file(index.index_file)
+
+ expect(data["plugin_paths"]["external-plugin"]).to eq(outside_path.to_s)
+ expect(data["load_paths"]["external-plugin"]).to eq([outside_path.join("lib").to_s])
+ end
+
+ it "reads legacy index files with absolute paths" do
+ require "bundler/yaml_serializer"
+
+ plugin_root = Bundler::Plugin.root
+ absolute_path = plugin_root.join(plugin_name).to_s
+
+ legacy_index = {
+ "commands" => {},
+ "hooks" => {},
+ "load_paths" => { plugin_name => [File.join(absolute_path, "lib")] },
+ "plugin_paths" => { plugin_name => absolute_path },
+ "sources" => {},
+ }
+
+ File.open(index.index_file, "w") {|f| f.puts Bundler::YAMLSerializer.dump(legacy_index) }
+
+ new_index = Index.new
+ expect(new_index.plugin_path(plugin_name)).to eq(Pathname.new(absolute_path))
+ expect(new_index.load_paths(plugin_name)).to eq([File.join(absolute_path, "lib")])
+ end
+ end
end
diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb
index e8d5941e33..c200a98afa 100644
--- a/spec/bundler/bundler/plugin/installer_spec.rb
+++ b/spec/bundler/bundler/plugin/installer_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin::Installer do
subject(:installer) { Bundler::Plugin::Installer.new }
@@ -7,7 +6,7 @@ RSpec.describe Bundler::Plugin::Installer do
describe "cli install" do
it "uses Gem.sources when non of the source is provided" do
sources = double(:sources)
- allow(Bundler).to receive_message_chain("rubygems.sources") { sources }
+ allow(Gem).to receive(:sources) { sources }
allow(installer).to receive(:install_rubygems).
with("new-plugin", [">= 0"], sources).once
@@ -21,7 +20,15 @@ RSpec.describe Bundler::Plugin::Installer do
allow(installer).to receive(:install_git).
and_return("new-plugin" => spec)
- expect(installer.install(["new-plugin"], :git => "https://some.ran/dom")).
+ 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_git).
+ and_return("new-plugin" => spec)
+
+ expect(installer.install(["new-plugin"], git: "/phony/path/repo")).
to eq("new-plugin" => spec)
end
@@ -29,7 +36,7 @@ RSpec.describe Bundler::Plugin::Installer do
allow(installer).to receive(:install_rubygems).
and_return("new-plugin" => spec)
- expect(installer.install(["new-plugin"], :source => "https://some.ran/dom")).
+ expect(installer.install(["new-plugin"], source: "https://some.ran/dom")).
to eq("new-plugin" => spec)
end
end
@@ -40,17 +47,47 @@ RSpec.describe Bundler::Plugin::Installer do
build_plugin "re-plugin"
build_plugin "ma-plugin"
end
+
+ @previous_ui = Bundler.ui
+ Bundler.ui = Bundler::UI::Silent.new
+ end
+
+ after do
+ Bundler.ui = @previous_ui
end
context "git plugins" do
before do
- build_git "ga-plugin", :path => lib_path("ga-plugin") do |s|
+ 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: 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 "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"], :git => "file://#{lib_path("ga-plugin")}")
+ installer.install(["ga-plugin"], git: lib_path("ga-plugin").to_s)
end
it "returns the installed spec after installing" do
@@ -58,7 +95,7 @@ RSpec.describe Bundler::Plugin::Installer do
expect(spec.full_name).to eq "ga-plugin-1.0"
end
- it "has expected full gem path" do
+ 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)
@@ -67,14 +104,14 @@ RSpec.describe Bundler::Plugin::Installer do
context "rubygems plugins" do
let(:result) do
- installer.install(["re-plugin"], :source => "file://#{gem_repo2}")
+ 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
+ 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
@@ -82,7 +119,7 @@ RSpec.describe Bundler::Plugin::Installer do
context "multiple plugins" do
let(:result) do
- installer.install(["re-plugin", "ma-plugin"], :source => "file://#{gem_repo2}")
+ installer.install(["re-plugin", "ma-plugin"], source: file_uri_for(gem_repo2))
end
it "returns the installed spec after installing " do
@@ -90,7 +127,7 @@ RSpec.describe Bundler::Plugin::Installer do
expect(result["ma-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
end
- it "has expected full_gem)path" do
+ 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
diff --git a/spec/bundler/bundler/plugin/source_list_spec.rb b/spec/bundler/bundler/plugin/source_list_spec.rb
index 86cc4ac4ed..64a1233dd1 100644
--- a/spec/bundler/bundler/plugin/source_list_spec.rb
+++ b/spec/bundler/bundler/plugin/source_list_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin::SourceList do
SourceList = Bundler::Plugin::SourceList
diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb
index 5bbb7384c8..b379594c6f 100644
--- a/spec/bundler/bundler/plugin_spec.rb
+++ b/spec/bundler/bundler/plugin_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Plugin do
Plugin = Bundler::Plugin
@@ -10,11 +9,11 @@ RSpec.describe Bundler::Plugin do
let(:spec2) { double(:spec2) }
before do
- build_lib "new-plugin", :path => lib_path("new-plugin") do |s|
+ 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|
+ build_lib "another-plugin", path: lib_path("another-plugin") do |s|
s.write "plugins.rb"
end
@@ -33,6 +32,29 @@ RSpec.describe Bundler::Plugin do
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" } }
@@ -43,6 +65,8 @@ RSpec.describe Bundler::Plugin do
end
it "passes the name and options to installer" do
+ allow(index).to receive(:up_to_date?).
+ with(spec)
allow(installer).to receive(:install).with(["new-plugin"], opts) do
{ "new-plugin" => spec }
end.once
@@ -51,6 +75,8 @@ RSpec.describe Bundler::Plugin do
end
it "validates the installed plugin" do
+ allow(index).to receive(:up_to_date?).
+ with(spec)
allow(subject).
to receive(:validate_plugin!).with(lib_path("new-plugin")).once
@@ -58,6 +84,8 @@ RSpec.describe Bundler::Plugin do
end
it "registers the plugin with index" do
+ allow(index).to receive(:up_to_date?).
+ with(spec)
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
@@ -74,6 +102,7 @@ RSpec.describe Bundler::Plugin do
end.once
allow(subject).to receive(:validate_plugin!).twice
+ allow(index).to receive(:up_to_date?).twice
allow(index).to receive(:register_plugin).twice
subject.install ["new-plugin", "another-plugin"], opts
end
@@ -83,11 +112,12 @@ RSpec.describe Bundler::Plugin do
describe "evaluate gemfile for plugins" do
let(:definition) { double("definition") }
let(:builder) { double("builder") }
- let(:gemfile) { bundled_app("Gemfile") }
+ 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
@@ -108,7 +138,7 @@ RSpec.describe Bundler::Plugin do
end
before do
- allow(index).to receive(:installed?) { nil }
+ allow(index).to receive(:up_to_date?) { 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
@@ -195,7 +225,7 @@ RSpec.describe Bundler::Plugin do
end
end
- describe "#source_from_lock" do
+ describe "#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" }
@@ -206,86 +236,132 @@ RSpec.describe Bundler::Plugin do
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)
+ expect(subject.from_lock(opts)).to be(s_instance)
end
end
describe "#root" do
context "in app dir" do
before do
- gemfile ""
+ 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"))
+ expect(subject.root).to eq(bundled_app(".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
- Dir.chdir tmp
- expect(subject.root).to eq(home.join(".bundle/plugin"))
+ expect(subject.root).to eq(home(".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|
+ build_lib "foo-plugin", path: path do |s|
s.write "plugins.rb", code
end
- allow(index).to receive(:hook_plugins).with(event).
+ @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] }
+ 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
+ after do
+ Bundler::Plugin::Events.send(:reset)
+ Hash[@old_constants].each do |name, value|
+ Bundler::Plugin::Events.send(:define, name, value)
+ end
+ end
+
let(:code) { <<-RUBY }
Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" }
RUBY
- let(:event) { "event-1" }
+ 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
- out = capture(:stdout) do
- Plugin.hook("event-1")
- end.strip
-
- expect(out).to eq("hook for event 1")
+ 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("event-1") {}
- Bundler::Plugin::API.hook("event-2") {}
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT1) {}
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT2) {}
puts "loaded"
RUBY
- let(:event) { /event-1|event-2/ }
-
it "evals plugins.rb once" do
- out = capture(:stdout) do
- Plugin.hook("event-1")
- Plugin.hook("event-2")
- end.strip
-
- expect(out).to eq("loaded")
+ 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("#{event}") { |&blk| blk.call }
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT1) { |&blk| blk.call }
RUBY
it "is passed to the hook" do
- out = capture(:stdout) do
- Plugin.hook("event-1") { puts "win" }
- end.strip
+ expect do
+ Plugin.hook(Bundler::Plugin::Events::EVENT1) { puts "win" }
+ end.to output("win\n").to_stdout
+ end
+ end
+
+ context "the plugin load_path is invalid" do
+ before do
+ allow(index).to receive(:load_paths).with("foo-plugin").
+ and_return(["invalid-file-name1", "invalid-file-name2"])
+ end
+
+ it "outputs a useful warning" do
+ msg =
+ "The following plugin paths don't exist: invalid-file-name1, invalid-file-name2.\n\n" \
+ "This can happen if the plugin was " \
+ "installed with a different version of Ruby that has since been uninstalled.\n\n" \
+ "If you would like to reinstall the plugin, run:\n\n" \
+ "bundler plugin uninstall foo-plugin && bundler plugin install foo-plugin\n\n" \
+ "Continuing without installing plugin foo-plugin.\n"
+
+ expect(Bundler.ui).to receive(:warn).with(msg)
+
+ Plugin.hook(Bundler::Plugin::Events::EVENT1)
- expect(out).to eq("win")
+ expect(subject.loaded?("foo-plugin")).to be_falsey
end
end
end
diff --git a/spec/bundler/bundler/psyched_yaml_spec.rb b/spec/bundler/bundler/psyched_yaml_spec.rb
deleted file mode 100644
index 18e40d6b5a..0000000000
--- a/spec/bundler/bundler/psyched_yaml_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-require "bundler/psyched_yaml"
-
-RSpec.describe "Bundler::YamlLibrarySyntaxError" do
- it "is raised on YAML parse errors" do
- expect { YAML.parse "{foo" }.to raise_error(Bundler::YamlLibrarySyntaxError)
- end
-end
diff --git a/spec/bundler/bundler/remote_specification_spec.rb b/spec/bundler/bundler/remote_specification_spec.rb
index 644814c563..f35b231d58 100644
--- a/spec/bundler/bundler/remote_specification_spec.rb
+++ b/spec/bundler/bundler/remote_specification_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::RemoteSpecification do
let(:name) { "foo" }
@@ -18,7 +17,7 @@ RSpec.describe Bundler::RemoteSpecification do
end
describe "#fetch_platform" do
- let(:remote_spec) { double(:remote_spec, :platform => "jruby") }
+ let(:remote_spec) { double(:remote_spec, platform: "jruby") }
before { allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) }
@@ -46,7 +45,7 @@ RSpec.describe Bundler::RemoteSpecification do
let(:platform) { "jruby" }
it "should return the spec name, version, and platform" do
- expect(subject.full_name).to eq("foo-1.0.0-jruby")
+ expect(subject.full_name).to eq("foo-1.0.0-java")
end
end
end
@@ -114,7 +113,7 @@ RSpec.describe Bundler::RemoteSpecification do
context "comparing a non sortable object" do
let(:other) { Object.new }
- let(:remote_spec) { double(:remote_spec, :platform => "jruby") }
+ let(:remote_spec) { double(:remote_spec, platform: "jruby") }
before do
allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec)
@@ -128,8 +127,8 @@ RSpec.describe Bundler::RemoteSpecification do
end
describe "#__swap__" do
- let(:spec) { double(:spec, :dependencies => []) }
- let(:new_spec) { double(:new_spec, :dependencies => [], :runtime_dependencies => []) }
+ let(:spec) { double(:spec, dependencies: []) }
+ let(:new_spec) { double(:new_spec, dependencies: [], runtime_dependencies: []) }
before { subject.instance_variable_set(:@_remote_specification, spec) }
@@ -158,7 +157,7 @@ RSpec.describe Bundler::RemoteSpecification do
describe "method missing" do
context "and is present in Gem::Specification" do
- let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+ let(:remote_spec) { double(:remote_spec, authors: "abcd") }
before do
allow(subject).to receive(:_remote_specification).and_return(remote_spec)
@@ -173,7 +172,7 @@ RSpec.describe Bundler::RemoteSpecification do
describe "respond to missing?" do
context "and is present in Gem::Specification" do
- let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+ let(:remote_spec) { double(:remote_spec, authors: "abcd") }
before do
allow(subject).to receive(:_remote_specification).and_return(remote_spec)
diff --git a/spec/bundler/bundler/resolver/candidate_spec.rb b/spec/bundler/bundler/resolver/candidate_spec.rb
new file mode 100644
index 0000000000..aefad3316e
--- /dev/null
+++ b/spec/bundler/bundler/resolver/candidate_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Resolver::Candidate do
+ it "compares fine" do
+ version1 = described_class.new("1.12.5", priority: -1)
+ version2 = described_class.new("1.12.5", priority: 1)
+
+ expect(version2 > version1).to be true
+
+ version1 = described_class.new("1.12.5")
+ version2 = described_class.new("1.12.5")
+
+ expect(version2 == version1).to be true
+
+ version1 = described_class.new("1.12.5", priority: 1)
+ version2 = described_class.new("1.12.5", priority: -1)
+
+ expect(version2 < version1).to be true
+ end
+end
diff --git a/spec/bundler/bundler/resolver/cooldown_spec.rb b/spec/bundler/bundler/resolver/cooldown_spec.rb
new file mode 100644
index 0000000000..37ec158cba
--- /dev/null
+++ b/spec/bundler/bundler/resolver/cooldown_spec.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Resolver do
+ let(:resolver) { described_class.allocate }
+
+ def remote(cooldown:)
+ instance_double(Bundler::Source::Rubygems::Remote, effective_cooldown: cooldown)
+ end
+
+ def spec(created_at:, remote:, name: "myrack", version: "1.0.0")
+ Struct.new(:name, :version, :created_at, :remote).new(name, Gem::Version.new(version), created_at, remote)
+ end
+
+ describe "#filter_cooldown" do
+ let(:now) { Time.now }
+
+ context "with a 7-day cooldown" do
+ let(:r) { remote(cooldown: 7) }
+
+ it "rejects versions published within the window" do
+ recent = spec(version: "1.1.0", created_at: now - (2 * 86_400), remote: r)
+ old = spec(version: "1.0.0", created_at: now - (30 * 86_400), remote: r)
+
+ expect(resolver.send(:filter_cooldown, [recent, old])).to eq([old])
+ end
+
+ it "keeps versions published exactly at the threshold" do
+ boundary = spec(created_at: now - (7 * 86_400), remote: r)
+
+ expect(resolver.send(:filter_cooldown, [boundary])).to eq([boundary])
+ end
+
+ it "leaves rolling-delay history intact" do
+ # 7-day cooldown with frequent releases must still expose an older candidate.
+ in_cooldown = spec(version: "1.2.0", created_at: now - 86_400, remote: r)
+ also_in_cooldown = spec(version: "1.1.0", created_at: now - (3 * 86_400), remote: r)
+ eligible = spec(version: "1.0.0", created_at: now - (10 * 86_400), remote: r)
+
+ result = resolver.send(:filter_cooldown, [in_cooldown, also_in_cooldown, eligible])
+
+ expect(result).to eq([eligible])
+ end
+
+ it "drops every spec sharing an excluded [name, version] tuple" do
+ # The cooldown check is by version, not per-spec: a StubSpecification for an
+ # in-cooldown release would otherwise slip through on local install paths.
+ endpoint = spec(version: "2.0.0", created_at: now - 86_400, remote: r)
+ local_stub = Struct.new(:name, :version).new("myrack", Gem::Version.new("2.0.0"))
+ eligible = spec(version: "1.0.0", created_at: now - (30 * 86_400), remote: r)
+
+ result = resolver.send(:filter_cooldown, [endpoint, local_stub, eligible])
+
+ expect(result).to eq([eligible])
+ end
+
+ it "keeps stub-only versions that no endpoint marks as in cooldown" do
+ # If no remote spec carries created_at for a version, cooldown cannot judge it;
+ # the stub stays in.
+ local_only = Struct.new(:name, :version).new("myrack", Gem::Version.new("2.0.0"))
+ eligible = spec(version: "1.0.0", created_at: now - (30 * 86_400), remote: r)
+
+ result = resolver.send(:filter_cooldown, [local_only, eligible])
+
+ expect(result).to eq([local_only, eligible])
+ end
+ end
+
+ context "when created_at is missing (blank metadata)" do
+ it "keeps the spec regardless of cooldown" do
+ s = spec(created_at: nil, remote: remote(cooldown: 7))
+
+ expect(resolver.send(:filter_cooldown, [s])).to eq([s])
+ end
+ end
+
+ context "when the remote has no cooldown" do
+ it "keeps every spec" do
+ s = spec(created_at: now - 3600, remote: remote(cooldown: nil))
+
+ expect(resolver.send(:filter_cooldown, [s])).to eq([s])
+ end
+ end
+
+ context "when cooldown is 0" do
+ it "keeps every spec (escape hatch)" do
+ s = spec(created_at: now - 3600, remote: remote(cooldown: 0))
+
+ expect(resolver.send(:filter_cooldown, [s])).to eq([s])
+ end
+ end
+
+ context "when the spec does not respond to created_at" do
+ it "keeps the spec" do
+ bare = Struct.new(:version).new("1.0.0")
+
+ expect(resolver.send(:filter_cooldown, [bare])).to eq([bare])
+ end
+ end
+
+ context "when the spec has no remote" do
+ it "keeps the spec" do
+ s = spec(created_at: now - 86_400, remote: nil)
+
+ expect(resolver.send(:filter_cooldown, [s])).to eq([s])
+ end
+ end
+
+ it "returns the same array when input is empty" do
+ expect(resolver.send(:filter_cooldown, [])).to eq([])
+ end
+ end
+
+ describe "#cooldown_hint" do
+ let(:now) { Time.now }
+ let(:r) { remote(cooldown: 7) }
+
+ it "returns nil when no spec is excluded" do
+ expect(resolver.send(:cooldown_hint, [])).to be_nil
+ end
+
+ it "returns nil when every spec is outside the cooldown window" do
+ eligible = [spec(created_at: now - (30 * 86_400), remote: r)]
+
+ expect(resolver.send(:cooldown_hint, eligible)).to be_nil
+ end
+
+ it "mentions the count and the bypass flag for one excluded version" do
+ excluded = [spec(created_at: now - 86_400, remote: r)]
+
+ hint = resolver.send(:cooldown_hint, excluded)
+
+ expect(hint).to match(/1 version excluded by the cooldown setting/)
+ expect(hint).to match(/--cooldown 0/)
+ end
+
+ it "uses plural wording when multiple versions are excluded" do
+ excluded = %w[1.0.0 1.1.0 1.2.0].map {|v| spec(version: v, created_at: now - 86_400, remote: r) }
+
+ expect(resolver.send(:cooldown_hint, excluded)).to match(/3 versions excluded/)
+ end
+
+ it "counts each unique version once even when multiple spec instances share it" do
+ duplicates = Array.new(3) { spec(created_at: now - 86_400, remote: r) }
+
+ expect(resolver.send(:cooldown_hint, duplicates)).to match(/1 version excluded/)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/retry_spec.rb b/spec/bundler/bundler/retry_spec.rb
index 525f05d327..5c84d0bea5 100644
--- a/spec/bundler/bundler/retry_spec.rb
+++ b/spec/bundler/bundler/retry_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Retry do
it "return successful result if no errors" do
@@ -13,7 +12,7 @@ RSpec.describe Bundler::Retry do
end
it "returns the first valid result" do
- jobs = [proc { raise "foo" }, proc { :bar }, proc { raise "foo" }]
+ jobs = [proc { raise "job 1 failed" }, proc { :bar }, proc { raise "job 2 failed" }]
attempts = 0
result = Bundler::Retry.new(nil, nil, 3).attempt do
attempts += 1
@@ -69,7 +68,7 @@ RSpec.describe Bundler::Retry 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(Bundler.ui).to receive(:warn).with(failure_message, true)
expect do
Bundler::Retry.new("test", [], 1).attempt do
@@ -79,4 +78,113 @@ RSpec.describe Bundler::Retry do
end
end
end
+
+ context "exponential backoff" do
+ it "can be disabled by setting base_delay to 0" do
+ attempts = 0
+ expect do
+ Bundler::Retry.new("test", [], 2, base_delay: 0).attempt do
+ attempts += 1
+ raise "error"
+ end
+ end.to raise_error(StandardError)
+
+ # Verify no sleep was called (implicitly - if sleep was called, timing would be different)
+ expect(attempts).to eq(3)
+ end
+
+ it "is enabled by default with 1 second base delay" do
+ original_base_delay = Bundler::Retry.default_base_delay
+ Bundler::Retry.default_base_delay = 1.0
+
+ attempts = 0
+ sleep_times = []
+
+ allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay|
+ sleep_times << delay
+ end
+
+ expect do
+ Bundler::Retry.new("test", [], 2, jitter: 0).attempt do
+ attempts += 1
+ raise "error"
+ end
+ end.to raise_error(StandardError)
+
+ expect(attempts).to eq(3)
+ expect(sleep_times.length).to eq(2)
+ # First retry: 1.0 * 2^0 = 1.0
+ expect(sleep_times[0]).to eq(1.0)
+ # Second retry: 1.0 * 2^1 = 2.0
+ expect(sleep_times[1]).to eq(2.0)
+ ensure
+ Bundler::Retry.default_base_delay = original_base_delay
+ end
+
+ it "sleeps with exponential backoff when base_delay is set" do
+ attempts = 0
+ sleep_times = []
+
+ allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay|
+ sleep_times << delay
+ end
+
+ expect do
+ Bundler::Retry.new("test", [], 2, base_delay: 1.0, jitter: 0).attempt do
+ attempts += 1
+ raise "error"
+ end
+ end.to raise_error(StandardError)
+
+ expect(attempts).to eq(3)
+ expect(sleep_times.length).to eq(2)
+ # First retry: 1.0 * 2^0 = 1.0
+ expect(sleep_times[0]).to eq(1.0)
+ # Second retry: 1.0 * 2^1 = 2.0
+ expect(sleep_times[1]).to eq(2.0)
+ end
+
+ it "respects max_delay" do
+ sleep_times = []
+
+ allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay|
+ sleep_times << delay
+ end
+
+ expect do
+ Bundler::Retry.new("test", [], 3, base_delay: 10.0, max_delay: 15.0, jitter: 0).attempt do
+ raise "error"
+ end
+ end.to raise_error(StandardError)
+
+ # First retry: 10.0 * 2^0 = 10.0
+ expect(sleep_times[0]).to eq(10.0)
+ # Second retry: 10.0 * 2^1 = 20.0, capped at 15.0
+ expect(sleep_times[1]).to eq(15.0)
+ # Third retry: 10.0 * 2^2 = 40.0, capped at 15.0
+ expect(sleep_times[2]).to eq(15.0)
+ end
+
+ it "adds jitter to delay" do
+ sleep_times = []
+
+ allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay|
+ sleep_times << delay
+ end
+
+ expect do
+ Bundler::Retry.new("test", [], 2, base_delay: 1.0, jitter: 0.5).attempt do
+ raise "error"
+ end
+ end.to raise_error(StandardError)
+
+ expect(sleep_times.length).to eq(2)
+ # First retry should be between 1.0 and 1.5 (base + jitter)
+ expect(sleep_times[0]).to be >= 1.0
+ expect(sleep_times[0]).to be <= 1.5
+ # Second retry should be between 2.0 and 2.5
+ expect(sleep_times[1]).to be >= 2.0
+ expect(sleep_times[1]).to be <= 2.5
+ end
+ end
end
diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb
index 3e0ec9d7f0..45a37c5795 100644
--- a/spec/bundler/bundler/ruby_dsl_spec.rb
+++ b/spec/bundler/bundler/ruby_dsl_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/ruby_dsl"
RSpec.describe Bundler::RubyDsl do
@@ -7,28 +7,37 @@ RSpec.describe Bundler::RubyDsl do
include Bundler::RubyDsl
attr_reader :ruby_version
+ attr_accessor :gemfile
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 }
+ { patchlevel: patchlevel,
+ engine: engine,
+ engine_version: engine_version }
end
+ let(:project_root) { Pathname.new("/path/to/project") }
+ let(:gemfile) { project_root.join("Gemfile") }
+ before { allow(Bundler).to receive(:root).and_return(project_root) }
let(:invoke) do
proc do
- args = Array(ruby_version) + [options]
+ args = []
+ args << ruby_version_arg if ruby_version_arg
+ args << options
+
dsl.ruby(*args)
end
end
subject do
+ dsl.gemfile = gemfile
invoke.call
dsl.ruby_version
end
@@ -59,10 +68,19 @@ RSpec.describe Bundler::RubyDsl do
it_behaves_like "it stores the ruby version"
end
+ context "with a preview version" do
+ let(:ruby_version) { "3.3.0-preview2" }
+
+ it "stores the version" do
+ expect(subject.versions).to eq(Array("3.3.0.preview2"))
+ expect(subject.gem_version.version).to eq("3.3.0.preview2")
+ end
+ 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)
+ expect { subject }.to raise_error(Bundler::InvalidArgumentError)
end
end
@@ -91,5 +109,140 @@ RSpec.describe Bundler::RubyDsl do
it_behaves_like "it stores the ruby version"
end
end
+
+ context "with a file option" do
+ let(:file) { ".ruby-version" }
+ let(:ruby_version_file_path) { gemfile.dirname.join(file) }
+ let(:options) do
+ { file: file,
+ patchlevel: patchlevel,
+ engine: engine,
+ engine_version: engine_version }
+ end
+ let(:ruby_version_arg) { nil }
+ let(:file_content) { "#{version}\n" }
+
+ before do
+ allow(Bundler).to receive(:read_file) do |path|
+ raise Errno::ENOENT, <<~ERROR unless path == ruby_version_file_path
+ #{file} not found in specs:
+ expected: #{ruby_version_file_path}
+ received: #{path}
+ ERROR
+ file_content
+ end
+ end
+
+ it_behaves_like "it stores the ruby version"
+
+ context "with the Gemfile ruby file: path is relative to the Gemfile in a subdir" do
+ let(:gemfile) { project_root.join("subdir", "Gemfile") }
+ let(:file) { "../.ruby-version" }
+ let(:ruby_version_file_path) { gemfile.dirname.join(file) }
+
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with bundler root in a subdir of the project" do
+ let(:project_root) { Pathname.new("/path/to/project/subdir") }
+ let(:gemfile) { project_root.parent.join("Gemfile") }
+
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with the ruby- prefix in the file" do
+ let(:file_content) { "ruby-#{version}\n" }
+
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "and a version" do
+ let(:ruby_version_arg) { version }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Bundler::GemfileError, "Do not pass version argument when using :file option")
+ end
+ end
+
+ context "with a @gemset" do
+ let(:file_content) { "ruby-#{version}@gemset\n" }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Bundler::InvalidArgumentError, "2.0.0@gemset is not a valid requirement on the Ruby version")
+ end
+ end
+
+ context "with a mise.toml file format" do
+ let(:file) { "mise.toml" }
+ let(:ruby_version_arg) { nil }
+ let(:file_content) do
+ <<~TOML
+ [tools]
+ ruby = #{quote}#{version}#{quote}
+ TOML
+ end
+
+ context "with double quotes" do
+ let(:quote) { '"' }
+
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with single quotes" do
+ let(:quote) { "'" }
+
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with mismatched quotes" do
+ let(:file_content) do
+ <<~TOML
+ [tools]
+ ruby = "#{version}'
+ TOML
+ end
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Bundler::InvalidArgumentError, "= is not a valid requirement on the Ruby version")
+ end
+ end
+ end
+
+ context "with a .tool-versions file format" do
+ let(:file) { ".tool-versions" }
+ let(:ruby_version_arg) { nil }
+ let(:file_content) do
+ <<~TOOLS
+ nodejs 18.16.0
+ ruby #{version} # This is a comment
+ pnpm 8.6.12
+ TOOLS
+ end
+
+ it_behaves_like "it stores the ruby version"
+
+ context "with extra spaces and a very cozy comment" do
+ let(:file_content) do
+ <<~TOOLS
+ nodejs 18.16.0
+ ruby #{version}# This is a cozy comment
+ pnpm 8.6.12
+ TOOLS
+ end
+
+ it_behaves_like "it stores the ruby version"
+ end
+ end
+
+ context "when the file does not exist" do
+ let(:ruby_version_file_path) { nil }
+ let(:ruby_version_arg) { nil }
+ let(:file) { "nonexistent.txt" }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Bundler::GemfileError, /Could not find version file nonexistent.txt/)
+ end
+ end
+ end
end
end
diff --git a/spec/bundler/bundler/ruby_version_spec.rb b/spec/bundler/bundler/ruby_version_spec.rb
index f77ec606fc..0d41ec9901 100644
--- a/spec/bundler/bundler/ruby_version_spec.rb
+++ b/spec/bundler/bundler/ruby_version_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/ruby_version"
RSpec.describe "Bundler::RubyVersion and its subclasses" do
@@ -100,7 +100,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
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)")
+ expect(subject.to_s).to eq("ruby 2.0.0 (jruby 2.0.1)")
end
context "no patchlevel" do
@@ -115,7 +115,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" 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")
+ expect(subject.to_s).to eq("ruby 2.0.0")
end
end
@@ -137,7 +137,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
end
end
- context "the versions, pathlevels, engines, and engine_versions match" do
+ shared_examples_for "the versions, engines, and engine_versions match" do
it "should return true" do
expect(subject).to eq(other_ruby_version)
end
@@ -152,7 +152,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
context "the patchlevels do not match" do
let(:other_patchlevel) { "21" }
- it_behaves_like "two ruby versions are not equal"
+ it_behaves_like "the versions, engines, and engine_versions match"
end
context "the engines do not match" do
@@ -228,9 +228,9 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
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])
+ shared_examples_for "even there is a difference in the patchlevels" do
+ it "should return nil" do
+ expect(ruby_version.diff(other_ruby_version)).to be_nil
end
end
@@ -287,10 +287,10 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
it_behaves_like "there is a difference in the engine versions"
end
- context "detects patchlevel discrepancies last" do
+ context "ignores patchlevel discrepancies last" do
let(:other_patchlevel) { "643" }
- it_behaves_like "there is a difference in the patchlevels"
+ it_behaves_like "even there is a difference in the patchlevels"
end
context "successfully matches gem requirements" do
@@ -355,7 +355,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
let(:other_engine) { "ruby" }
let(:other_engine_version) { "2.0.5" }
- it_behaves_like "there is a difference in the patchlevels"
+ it_behaves_like "even there is a difference in the patchlevels"
end
context "successfully detects bad gem requirements with engine versions" do
@@ -389,7 +389,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
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"
+ it_behaves_like "even there is a difference in the patchlevels"
end
end
end
@@ -399,8 +399,22 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
let(:bundler_system_ruby_version) { subject }
- before do
- Bundler::RubyVersion.instance_variable_set("@ruby_version", nil)
+ 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
@@ -413,77 +427,65 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
end
describe "#version" do
- it "should return a copy of the value of RUBY_VERSION" do
- expect(subject.versions).to eq([RUBY_VERSION])
- expect(subject.versions.first).to_not be(RUBY_VERSION)
+ 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
- context "RUBY_ENGINE is defined" do
- before { stub_const("RUBY_ENGINE", "jruby") }
- before { stub_const("JRUBY_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
+ before { stub_const("RUBY_ENGINE", "jruby") }
+ before { stub_const("RUBY_ENGINE_VERSION", "2.1.1") }
- context "RUBY_ENGINE is not defined" do
- before { stub_const("RUBY_ENGINE", nil) }
-
- it "should return the string 'ruby'" do
- expect(subject.engine).to eq("ruby")
- end
+ 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
- stub_const("RUBY_VERSION", "2.2.4")
- allow(Bundler).to receive(:ruby_engine).and_return("ruby")
+ allow(Gem).to receive(:ruby_version).and_return(Gem::Version.new("2.2.4"))
+ stub_const("RUBY_ENGINE", "ruby")
end
- it "should return a copy of the value of RUBY_VERSION" do
+ 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"])
- expect(bundler_system_ruby_version.engine_versions.first).to_not be(RUBY_VERSION)
end
end
context "engine is rbx" do
before do
stub_const("RUBY_ENGINE", "rbx")
- stub_const("Rubinius::VERSION", "2.0.0")
+ stub_const("RUBY_ENGINE_VERSION", "2.0.0")
end
- it "should return a copy of the value of Rubinius::VERSION" do
+ 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(Rubinius::VERSION)
+ 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("JRUBY_VERSION", "2.1.1")
+ stub_const("RUBY_ENGINE_VERSION", "2.1.1")
end
- it "should return a copy of the value of JRUBY_VERSION" do
+ 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(JRUBY_VERSION)
+ 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")
- allow(Bundler).to receive(:ruby_engine).and_return("not_supported_ruby_engine")
+ stub_const("RUBY_ENGINE_VERSION", "1.2.3")
end
- it "should raise a BundlerError with a 'not recognized' message" do
- expect { bundler_system_ruby_version.engine_versions }.to raise_error(Bundler::BundlerError, "RUBY_ENGINE value not_supported_ruby_engine is not recognized")
+ it "returns RUBY_ENGINE_VERSION" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["1.2.3"])
end
end
end
@@ -494,31 +496,5 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do
end
end
end
-
- describe "#to_gem_version_with_patchlevel" do
- shared_examples_for "the patchlevel is omitted" do
- it "does not include a patch level" do
- expect(subject.to_gem_version_with_patchlevel.to_s).to eq(version)
- end
- end
-
- context "with nil patch number" do
- let(:patchlevel) { nil }
-
- it_behaves_like "the patchlevel is omitted"
- end
-
- context "with negative patch number" do
- let(:patchlevel) { -1 }
-
- it_behaves_like "the patchlevel is omitted"
- end
-
- context "with a valid patch number" do
- it "uses the specified patchlevel as patchlevel" do
- expect(subject.to_gem_version_with_patchlevel.to_s).to eq("#{version}.#{patchlevel}")
- end
- end
- end
end
end
diff --git a/spec/bundler/bundler/rubygems_ext_spec.rb b/spec/bundler/bundler/rubygems_ext_spec.rb
new file mode 100644
index 0000000000..0fc528f78c
--- /dev/null
+++ b/spec/bundler/bundler/rubygems_ext_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require "bundler/rubygems_ext"
+
+RSpec.describe Gem::SplitCompactIndexEntryOnFirstColon do
+ # Reproduces the RubyGems < 4.0.13 `Gem::Resolver::APISet::GemParser` that
+ # split each compact index entry on every colon, corrupting metadata values
+ # that themselves contain colons.
+ let(:legacy_parser_class) do
+ Class.new do
+ def parse_dependency(string)
+ dependency = string.split(":")
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
+ dependency[0] = -dependency[0]
+ dependency
+ end
+ end
+ end
+
+ before { legacy_parser_class.prepend(described_class) }
+
+ it "preserves colon-bearing metadata values such as created_at timestamps" do
+ parser = legacy_parser_class.new
+
+ expect(parser.send(:parse_dependency, "created_at:2026-05-12T10:00:00Z")).to eq(["created_at", ["2026-05-12T10:00:00Z"]])
+ end
+
+ it "still parses ordinary name:requirement entries" do
+ parser = legacy_parser_class.new
+
+ expect(parser.send(:parse_dependency, "myrack:>= 1.0")).to eq(["myrack", [">= 1.0"]])
+ end
+
+ it "keeps parse_dependency private" do
+ parser = legacy_parser_class.new
+
+ expect { parser.parse_dependency("created_at:x") }.to raise_error(NoMethodError, /private method/)
+ end
+end
diff --git a/spec/bundler/bundler/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb
index 38ff9dae7e..a2c63a7ca0 100644
--- a/spec/bundler/bundler/rubygems_integration_spec.rb
+++ b/spec/bundler/bundler/rubygems_integration_spec.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::RubygemsIntegration do
- it "uses the same chdir lock as rubygems", :rubygems => "2.1" 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|
@@ -16,30 +11,18 @@ RSpec.describe Bundler::RubygemsIntegration do
end
subject { Bundler.rubygems.validate(spec) }
- it "skips overly-strict gemspec validation", :rubygems => "< 1.7" do
- expect(spec).to_not receive(:validate)
+ it "validates for resolution" do
+ expect(spec).to receive(:validate_for_resolution)
subject
end
- it "validates with packaging mode disabled", :rubygems => "1.7" do
- expect(spec).to receive(:validate).with(false)
- subject
- end
-
- it "should set a summary to avoid an overly-strict error", :rubygems => "~> 1.7.0" do
- spec.summary = nil
- expect { subject }.not_to raise_error
- expect(spec.summary).to eq("")
- end
-
context "with an invalid spec" do
before do
- expect(spec).to receive(:validate).with(false).
+ expect(spec).to receive(:validate_for_resolution).
and_raise(Gem::InvalidSpecificationException.new("TODO is not an author"))
end
- it "should raise a Gem::InvalidSpecificationException and produce a helpful warning message",
- :rubygems => "1.7" do
+ 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")
@@ -47,68 +30,96 @@ RSpec.describe Bundler::RubygemsIntegration do
end
end
- describe "#configuration" do
- it "handles Gem::SystemExitException errors" do
- allow(Gem).to receive(:configuration) { raise Gem::SystemExitException.new(1) }
- expect { Bundler.rubygems.configuration }.to raise_error(Gem::SystemExitException)
- end
- end
-
- describe "#download_gem", :rubygems => ">= 2.0" do
+ describe "#download_gem" do
let(:bundler_retry) { double(Bundler::Retry) }
- let(:retry) { double("Bundler::Retry") }
- let(:uri) { URI.parse("https://foo.bar") }
- let(:path) { Gem.path.first }
+ let(:cache_dir) { "#{Gem.path.first}/cache" }
let(:spec) do
- spec = Bundler::RemoteSpecification.new("Foo", Gem::Version.new("2.5.2"),
- Gem::Platform::RUBY, nil)
+ 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 "succesfully 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(:download).with(spec, uri, path)
+ context "when uri is public" do
+ let(:uri) { Gem::URI.parse("https://foo.bar") }
+
+ it "successfully downloads gem with retries" do
+ 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, fetcher)
+ end
+ end
+
+ context "when uri contains userinfo part" do
+ let(:uri) { Gem::URI.parse("https://#{userinfo}@foo.bar") }
+
+ context "with user and password" do
+ let(:userinfo) { "user:password" }
+
+ it "successfully downloads gem with retries with filtered log" do
+ expect(Bundler::Retry).to receive(:new).with("download gem from https://user:REDACTED@foo.bar/").
+ 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, fetcher)
+ end
+ end
+
+ context "with token [as user]" do
+ let(:userinfo) { "token" }
- Bundler.rubygems.download_gem(spec, uri, path)
+ it "successfully downloads gem with retries with filtered log" do
+ expect(Bundler::Retry).to receive(:new).with("download gem from https://REDACTED@foo.bar/").
+ 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, fetcher)
+ end
+ end
end
end
- describe "#fetch_all_remote_specs", :rubygems => ">= 2.0" do
- let(:uri) { URI("https://example.com") }
+ 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) { URI("http://zombo.com") }
- let(:remote_with_mirror) { double("remote", :uri => uri, :original_uri => orig_uri) }
+ let(:orig_uri) { Gem::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))
+ result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror, fetcher)
+ 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) }
+ 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))
+ result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror, fetcher)
+ 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(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, fetcher) }.to raise_error(Bundler::MarshalError, /unexpected class/i)
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..b252ba59a0
--- /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, <<~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, <<~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, <<~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
index 7302da5421..5e1aaaa555 100644
--- a/spec/bundler/bundler/settings_spec.rb
+++ b/spec/bundler/bundler/settings_spec.rb
@@ -1,17 +1,23 @@
# frozen_string_literal: true
-require "spec_helper"
+
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
+ context "root is nil" 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")
+ before do
+ allow(Pathname).to receive(:new).and_call_original
+ allow(Pathname).to receive(:new).with(".bundle").and_return home(".bundle")
+ end
+
+ it "works" do
+ subject.set_local("foo", "bar")
+
+ expect(subject["foo"]).to eq("bar")
end
end
end
@@ -27,7 +33,7 @@ RSpec.describe Bundler::Settings do
"gem.mit" => "false",
"gem.test" => "minitest",
"thingy" => <<-EOS.tr("\n", " "),
---asdf --fdsa --ty=oh man i hope this doesnt break bundler because
+--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
@@ -40,7 +46,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
before do
hash.each do |key, value|
- settings[key] = value
+ settings.set_local key, value
end
end
@@ -64,13 +70,10 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
describe "#global_config_file" do
context "when $HOME is not accessible" do
- context "when $TMPDIR is not writable" do
- it "does not raise" do
- expect(Bundler.rubygems).to receive(:user_home).twice.and_return(nil)
- expect(FileUtils).to receive(:mkpath).twice.with(File.join(Dir.tmpdir, "bundler", "home")).and_raise(Errno::EROFS, "Read-only file system @ dir_s_mkdir - /tmp/bundler")
+ 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
+ expect(subject.send(:global_config_file)).to be_nil
end
end
end
@@ -100,12 +103,12 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
context "when is boolean" do
it "returns a boolean" do
- settings[:frozen] = "true"
+ 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["ignore_messages.foobar"] = "true"
+ settings.set_local "ignore_messages.foobar", "true"
expect(settings["ignore_messages.foobar"]).to be true
end
end
@@ -113,40 +116,67 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
context "when is number" do
it "returns a number" do
- settings[:ssl_verify_mode] = "1"
+ settings.set_local :ssl_verify_mode, "1"
expect(settings[:ssl_verify_mode]).to be 1
end
+
+ it "coerces cooldown to integer" do
+ settings.set_local :cooldown, "7"
+ expect(settings[:cooldown]).to be 7
+ end
end
- context "when it's not possible to write to the file" do
+ context "when it's not possible to create the settings directory" do
it "raises an PermissionError with explanation" do
- expect(FileUtils).to receive(:mkdir_p).with(settings.send(:local_config_file).dirname).
- and_raise(Errno::EACCES)
- expect { settings[:frozen] = "1" }.
- to raise_error(Bundler::PermissionError, /config/)
+ settings_dir = settings.send(:local_config_file).dirname
+ expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings_dir).
+ and_raise(Errno::EACCES.new(settings_dir.to_s))
+ expect { settings.set_local :frozen, "1" }.
+ to raise_error(Bundler::PermissionError, /#{settings_dir}/)
end
end
end
describe "#temporary" do
it "reset after used" do
- Bundler.settings[:no_install] = true
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
- Bundler.settings.temporary(:no_install => false) do
+ 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
+ context "when it's not possible to write to create the settings directory" do
it "raises an PermissionError with explanation" do
- expect(FileUtils).to receive(:mkdir_p).with(settings.send(:global_config_file).dirname).
- and_raise(Errno::EACCES)
+ settings_dir = settings.send(:global_config_file).dirname
+ expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings_dir).
+ and_raise(Errno::EACCES.new(settings_dir.to_s))
expect { settings.set_global(:frozen, "1") }.
- to raise_error(Bundler::PermissionError, %r{\.bundle/config})
+ to raise_error(Bundler::PermissionError, /#{settings_dir}/)
end
end
end
@@ -154,7 +184,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
describe "#pretty_values_for" do
it "prints the converted value rather than the raw string" do
bool_key = described_class::BOOL_KEYS.first
- settings[bool_key] = false
+ settings.set_local(bool_key, "false")
expect(subject.pretty_values_for(bool_key)).to eq [
"Set for your local app (#{bundled_app("config")}): false",
]
@@ -162,7 +192,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
end
describe "#mirror_for" do
- let(:uri) { URI("https://rubygems.org/") }
+ let(:uri) { Gem::URI("https://rubygems.org/") }
context "with no configured mirror" do
it "returns the original URI" do
@@ -175,9 +205,9 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
end
context "with a configured mirror" do
- let(:mirror_uri) { URI("https://rubygems-mirror.org/") }
+ let(:mirror_uri) { Gem::URI("https://example-mirror.rubygems.org/") }
- before { settings["mirror.https://rubygems.org/"] = mirror_uri.to_s }
+ 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)
@@ -194,11 +224,27 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
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) { Gem::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) { URI("https://gemserver.example.org/") }
+ let(:uri) { Gem::URI("https://gemserver.example.org/") }
let(:credentials) { "username:password" }
context "with no configured credentials" do
@@ -208,7 +254,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
end
context "with credentials configured by URL" do
- before { settings["https://gemserver.example.org/"] = credentials }
+ before { settings.set_local "https://gemserver.example.org/", credentials }
it "returns the configured credentials" do
expect(settings.credentials_for(uri)).to eq(credentials)
@@ -216,7 +262,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
end
context "with credentials configured by hostname" do
- before { settings["gemserver.example.org"] = credentials }
+ before { settings.set_local "gemserver.example.org", credentials }
it "returns the configured credentials" do
expect(settings.credentials_for(uri)).to eq(credentials)
@@ -226,59 +272,109 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
describe "URI normalization" do
it "normalizes HTTP URIs in credentials configuration" do
- settings["http://gemserver.example.org"] = "username:password"
+ 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["https://gemserver.example.org"] = "username:password"
+ 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["mirror.http://rubygems.org"] = "http://rubygems-mirror.org"
+ settings.set_local "mirror.http://rubygems.org", "http://example-mirror.rubygems.org"
expect(settings.all).to include("mirror.http://rubygems.org/")
end
it "normalizes HTTPS URIs in mirror configuration" do
- settings["mirror.https://rubygems.org"] = "http://rubygems-mirror.org"
+ settings.set_local "mirror.https://rubygems.org", "http://example-mirror.rubygems.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["local.httparty"] = home("httparty")
+ 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["local.httpsmarty"] = home("httpsmarty")
+ settings.set_local "local.httpsmarty", home("httpsmarty")
expect(settings.all).to include("local.httpsmarty")
end
it "reads older keys without trailing slashes" do
- settings["mirror.https://rubygems.org"] = "http://rubygems-mirror.org"
+ settings.set_local "mirror.https://rubygems.org", "http://example-mirror.rubygems.org"
expect(settings.mirror_for("https://rubygems.org/")).to eq(
- URI("http://rubygems-mirror.org/")
+ Gem::URI("http://example-mirror.rubygems.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 dashes" do
- config("BUNDLE_MY__PERSONAL.RACK" => "~/Work/git/rack")
- expect(settings["my.personal.rack"]).to eq("~/Work/git/rack")
+ it "converts older keys without double underscore" do
+ bundle_config("BUNDLE_MY__PERSONAL.MYRACK" => "~/Work/git/myrack")
+ expect(settings["my.personal.myrack"]).to eq("~/Work/git/myrack")
+ end
+
+ it "converts older keys without trailing slashes and double underscore" do
+ bundle_config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://example-mirror.rubygems.org")
+ expect(settings["mirror.https://rubygems.org/"]).to eq("http://example-mirror.rubygems.org")
end
- it "converts older keys without trailing slashes and double dashes" do
- config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://rubygems-mirror.org")
- expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org")
+ it "ignores commented out keys" do
+ create_file bundled_app(".bundle/config"), <<~C
+ # BUNDLE_MY-PERSONAL-SERVER__ORG: my-personal-server.org
+ C
+
+ expect(Bundler.ui).not_to receive(:warn)
+ expect(settings.all).to be_empty
+ end
+
+ it "converts older keys with dashes" do
+ bundle_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")
+ bundle_config("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://example-mirror.rubygems.org")
+ expect(settings["mirror.https://rubygems.org/"]).to eq("http://example-mirror.rubygems.org")
+ end
+ end
+
+ describe "default_cli_command validation" do
+ it "accepts 'install' as a valid value" do
+ expect { settings.set_local("default_cli_command", "install") }.not_to raise_error
+ end
+
+ it "accepts 'cli_help' as a valid value" do
+ expect { settings.set_local("default_cli_command", "cli_help") }.not_to raise_error
+ end
+
+ it "rejects invalid values" do
+ expect { settings.set_local("default_cli_command", "invalid") }.to raise_error(
+ Bundler::InvalidOption,
+ /Setting `default_cli_command` to "invalid" failed:\n - default_cli_command must be either 'install' or 'cli_help'\n - must be one of: install, cli_help/
+ )
+ end
+
+ it "accepts nil values" do
+ expect { settings.set_local("default_cli_command", nil) }.not_to raise_error
end
end
end
diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb
index d3b93b56d0..41115aa667 100644
--- a/spec/bundler/bundler/shared_helpers_spec.rb
+++ b/spec/bundler/bundler/shared_helpers_spec.rb
@@ -1,21 +1,19 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::SharedHelpers do
- let(:ext_lock_double) { double(:ext_lock) }
-
before do
- allow(Bundler.rubygems).to receive(:ext_lock).and_return(ext_lock_double)
- allow(ext_lock_double).to receive(:synchronize) {|&block| block.call }
+ pwd_stub
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") }
+ 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)
@@ -31,6 +29,16 @@ RSpec.describe Bundler::SharedHelpers do
)
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
@@ -51,7 +59,7 @@ RSpec.describe Bundler::SharedHelpers do
before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) }
- it "returns the lock file path" do
+ it "returns the lockfile path" do
expect(subject.default_lockfile).to eq(expected_lockfile_path)
end
end
@@ -65,10 +73,10 @@ RSpec.describe Bundler::SharedHelpers do
end
context ".bundle is global .bundle" do
- let(:global_rubygems_dir) { Pathname.new("#{bundled_app}") }
+ let(:global_rubygems_dir) { Pathname.new(bundled_app) }
before do
- Dir.mkdir ".bundle"
+ Dir.mkdir bundled_app(".bundle")
allow(Bundler.rubygems).to receive(:user_home).and_return(global_rubygems_dir)
end
@@ -82,7 +90,7 @@ RSpec.describe Bundler::SharedHelpers do
let(:expected_bundle_dir_path) { Pathname.new("#{bundled_app}/.bundle") }
before do
- Dir.mkdir ".bundle"
+ Dir.mkdir bundled_app(".bundle")
allow(Bundler.rubygems).to receive(:user_home).and_return(global_rubygems_dir)
end
@@ -100,9 +108,10 @@ RSpec.describe Bundler::SharedHelpers do
shared_examples_for "correctly determines whether to return a Gemfile path" do
context "currently in directory with a Gemfile" do
- before { File.new("Gemfile", "w") }
+ before { FileUtils.touch(bundled_app_gemfile) }
+ after { FileUtils.rm(bundled_app_gemfile) }
- it "returns path of the bundle gemfile" do
+ it "returns path of the bundle Gemfile" do
expect(subject.in_bundle?).to eq("#{bundled_app}/Gemfile")
end
end
@@ -138,22 +147,24 @@ RSpec.describe Bundler::SharedHelpers do
describe "#chdir" do
let(:op_block) { proc { Dir.mkdir "nested_dir" } }
- before { Dir.mkdir "chdir_test_dir" }
+ before { Dir.mkdir bundled_app("chdir_test_dir") }
it "executes the passed block while in the specified directory" do
- subject.chdir("chdir_test_dir", &op_block)
- expect(Pathname.new("chdir_test_dir/nested_dir")).to exist
+ 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(bundled_app)
+ expect(subject.pwd).to eq(git_root.to_s)
end
end
describe "#with_clean_git_env" do
- let(:with_clean_git_env_block) { proc { Dir.mkdir "with_clean_git_env_test_dir" } }
+ 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"
@@ -162,20 +173,20 @@ RSpec.describe Bundler::SharedHelpers do
it "executes the passed block" do
subject.with_clean_git_env(&with_clean_git_env_block)
- expect(Pathname.new("with_clean_git_env_test_dir")).to exist
+ 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 "git_dir_test_dir" unless ENV["GIT_DIR"].nil?
- Dir.mkdir "git_work_tree_test_dir" unless ENV["GIT_WORK_TREE"].nil?
+ 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(Pathname.new("git_dir_test_dir")).to_not exist
- expect(Pathname.new("git_work_tree_test_dir")).to_not exist
+ expect(bundled_app("git_dir_test_dir")).to_not exist
+ expect(bundled_app("git_work_tree_test_dir")).to_not exist
end
end
@@ -215,7 +226,7 @@ RSpec.describe Bundler::SharedHelpers do
end
shared_examples_for "ENV['PATH'] gets set correctly" do
- before { Dir.mkdir ".bundle" }
+ before { Dir.mkdir bundled_app(".bundle") }
it "ensures bundle bin path is in ENV['PATH']" do
subject.set_bundle_environment
@@ -227,7 +238,14 @@ RSpec.describe Bundler::SharedHelpers do
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("-rbundler/setup")
+ expect(ENV["RUBYOPT"].split(" ")).to start_with("-r#{install_path}/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
+ subject.set_bundle_environment
+ expect(ENV["BUNDLER_SETUP"]).to eq("#{source_lib_dir}/bundler/setup")
end
end
@@ -235,37 +253,78 @@ RSpec.describe Bundler::SharedHelpers do
let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
before do
- allow(Bundler::SharedHelpers).to receive(:bundler_ruby_lib).and_return(ruby_lib_path)
+ 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)
+ expect(rubylib).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 "exits if bundle path contains the path seperator" do
- stub_const("File::PATH_SEPARATOR", ":".freeze)
+ 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
+
+ expect(rubylib.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 a ':', which is the " \
+ "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 include ':'.\nYour current bundle " \
+ "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") }
@@ -295,25 +354,46 @@ RSpec.describe Bundler::SharedHelpers do
it "ENV['PATH'] should only contain one instance of bundle bin path" do
subject.set_bundle_environment
- paths = (ENV["PATH"]).split(File::PATH_SEPARATOR)
+ 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") }
+ context "when bundler install path is standard" do
+ let(:install_path) { source_lib_dir }
- it_behaves_like "ENV['RUBYOPT'] gets set correctly"
- end
+ context "ENV['RUBYOPT'] does not exist" do
+ before { ENV.delete("RUBYOPT") }
- 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
- it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ 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
end
- context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do
- before { ENV["RUBYOPT"] = "-rbundler/setup" }
+ context "when bundler install path contains special characters" do
+ let(:install_path) { "/opt/ruby3.3.0-preview2/lib/ruby/3.3.0+0" }
+
+ before do
+ ENV["RUBYOPT"] = "-r#{install_path}/bundler/setup"
+ allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup")
+ allow(Gem).to receive(:bin_path).and_return("#{install_path}/bundler/setup")
+ end
+
+ it "ensures -rbundler/setup is not duplicated" do
+ subject.set_bundle_environment
+ expect(ENV["RUBYOPT"].split(" ").grep(%r{-r.*/bundler/setup}).length).to eq(1)
+ end
it_behaves_like "ENV['RUBYOPT'] gets set correctly"
end
@@ -336,6 +416,18 @@ RSpec.describe Bundler::SharedHelpers do
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(exedir.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" }
@@ -347,8 +439,7 @@ RSpec.describe Bundler::SharedHelpers do
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)
+ expect(rubylib.count(ruby_lib_path)).to eq(1)
end
end
end
@@ -358,13 +449,13 @@ RSpec.describe Bundler::SharedHelpers do
let(:file_op_block) { proc {|path| FileUtils.mkdir_p(path) } }
it "performs the operation in the passed block" do
- subject.filesystem_access("./test_dir", &file_op_block)
- expect(Pathname.new("test_dir")).to exist
+ 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 } }
+ let(:file_op_block) { proc {|_path| raise Errno::EACCES.new("/path") } }
it "raises a PermissionError" do
expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
@@ -393,7 +484,7 @@ RSpec.describe Bundler::SharedHelpers do
end
end
- context "system throws Errno::ENOTSUP", :ruby => "1.9" do
+ context "system throws Errno::ENOTSUP" do
let(:file_op_block) { proc {|_path| raise Errno::ENOTSUP } }
it "raises a OperationNotSupportedError" do
@@ -419,33 +510,9 @@ RSpec.describe Bundler::SharedHelpers do
it "raises a GenericSystemCallError" do
expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
- Bundler::GenericSystemCallError, /error accessing.+underlying.+Shields down/m
+ Bundler::GenericSystemCallError, /error creating.+underlying.+Shields down/m
)
end
end
end
-
- describe "#const_get_safely" do
- module TargetNamespace
- VALID_CONSTANT = 1
- end
-
- context "when the namespace does have the requested constant" do
- it "returns the value of the requested constant" do
- expect(subject.const_get_safely(:VALID_CONSTANT, TargetNamespace)).to eq(1)
- end
- end
-
- context "when the requested constant is passed as a string" do
- it "returns the value of the requested constant" do
- expect(subject.const_get_safely("VALID_CONSTANT", TargetNamespace)).to eq(1)
- end
- end
-
- context "when the namespace does not have the requested constant" do
- it "returns nil" do
- expect(subject.const_get_safely("INVALID_CONSTANT", TargetNamespace)).to be_nil
- 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
index 34fe21e9fb..1f10ca4b07 100644
--- a/spec/bundler/bundler/source/git/git_proxy_spec.rb
+++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb
@@ -1,81 +1,146 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Source::Git::GitProxy do
- let(:uri) { "https://github.com/bundler/bundler.git" }
- subject { described_class.new(Pathname("path"), uri, "HEAD") }
+ let(:path) { Pathname("path") }
+ let(:uri) { "https://github.com/ruby/rubygems.git" }
+ let(:ref) { nil }
+ let(:branch) { nil }
+ let(:tag) { nil }
+ let(:options) { { "ref" => ref, "branch" => branch, "tag" => tag }.compact }
+ let(:revision) { nil }
+ let(:git_source) { nil }
+ let(:clone_result) { double(Process::Status, success?: true) }
+ let(:fail_result) { double(Process::Status, success?: false) }
+ let(:base_clone_args) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch"] }
+ let(:base_fetch_args) { ["fetch", "--force", "--quiet", "--no-tags", "--depth", "1"] }
+ subject(:git_proxy) { described_class.new(path, uri, options, revision, git_source) }
+
+ context "with explicit ref" do
+ context "with branch only" do
+ let(:branch) { "main" }
+ it "sets explicit ref to branch" do
+ expect(git_proxy.explicit_ref).to eq(branch)
+ end
+ end
+
+ context "with ref only" do
+ let(:ref) { "HEAD" }
+ it "sets explicit ref to ref" do
+ expect(git_proxy.explicit_ref).to eq(ref)
+ end
+ end
+
+ context "with tag only" do
+ let(:tag) { "v1.0" }
+ it "sets explicit ref to ref" do
+ expect(git_proxy.explicit_ref).to eq(tag)
+ end
+ end
+
+ context "with tag and branch" do
+ let(:tag) { "v1.0" }
+ let(:branch) { "main" }
+ it "raises error" do
+ expect { git_proxy }.to raise_error(Bundler::Source::Git::AmbiguousGitReference)
+ end
+ end
+
+ context "with tag and ref" do
+ let(:tag) { "v1.0" }
+ let(:ref) { "HEAD" }
+ it "raises error" do
+ expect { git_proxy }.to raise_error(Bundler::Source::Git::AmbiguousGitReference)
+ end
+ end
+
+ context "with branch and ref" do
+ let(:branch) { "main" }
+ let(:ref) { "HEAD" }
+ it "honors ref over branch" do
+ expect(git_proxy.explicit_ref).to eq(ref)
+ end
+ end
+ end
context "with configured credentials" do
it "adds username and password to URI" do
- Bundler.settings[uri] = "u:p"
- expect(subject).to receive(:git_retry).with(match("https://u:p@github.com/bundler/bundler.git"))
- subject.checkout
+ Bundler.settings.temporary(uri => "u:p") do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/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["github.com"] = "u:p"
- expect(subject).to receive(:git_retry).with(match("https://u:p@github.com/bundler/bundler.git"))
- subject.checkout
+ Bundler.settings.temporary("github.com" => "u:p") do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/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["https://u:p@github.com/bundler/bundler-mismatch.git"] = "u:p"
- expect(subject).to receive(:git_retry).with(match(uri))
- subject.checkout
+ Bundler.settings.temporary("https://u:p@github.com/ruby/rubygems-mismatch.git" => "u:p") do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).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["github.com"] = "u:p"
- original = "https://orig:info@github.com/bundler/bundler.git"
- subject = described_class.new(Pathname("path"), original, "HEAD")
- expect(subject).to receive(:git_retry).with(match(original))
- subject.checkout
+ Bundler.settings.temporary("github.com" => "u:p") do
+ original = "https://orig:info@github.com/ruby/rubygems.git"
+ git_proxy = described_class.new(Pathname("path"), original, options)
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", original, path.to_s], nil).and_return(["", "", clone_result])
+ git_proxy.checkout
+ end
end
end
describe "#version" do
context "with a normal version number" do
before do
- expect(subject).to receive(:git).with("--version").
+ expect(described_class).to receive(:full_version).
and_return("git version 1.2.3")
end
it "returns the git version number" do
- expect(subject.version).to eq("1.2.3")
+ expect(git_proxy.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
+ expect { Gem::Version.create git_proxy.version }.not_to raise_error
end
end
context "with a OSX version number" do
before do
- expect(subject).to receive(:git).with("--version").
+ expect(described_class).to receive(:full_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")
+ expect(git_proxy.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
+ expect { Gem::Version.create git_proxy.version }.not_to raise_error
end
end
context "with a msysgit version number" do
before do
- expect(subject).to receive(:git).with("--version").
+ expect(described_class).to receive(:full_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")
+ expect(git_proxy.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
+ expect { Gem::Version.create git_proxy.version }.not_to raise_error
end
end
end
@@ -83,34 +148,206 @@ RSpec.describe Bundler::Source::Git::GitProxy do
describe "#full_version" do
context "with a normal version number" do
before do
- expect(subject).to receive(:git).with("--version").
- and_return("git version 1.2.3")
+ status = double("success?" => true)
+ expect(Open3).to receive(:capture3).with("git", "--version").
+ and_return(["git version 1.2.3", "", status])
end
it "returns the git version number" do
- expect(subject.full_version).to eq("1.2.3")
+ expect(git_proxy.full_version).to eq("1.2.3")
end
end
context "with a OSX version number" do
before do
- expect(subject).to receive(:git).with("--version").
- and_return("git version 1.2.3 (Apple Git-BS)")
+ status = double("success?" => true)
+ expect(Open3).to receive(:capture3).with("git", "--version").
+ and_return(["git version 1.2.3 (Apple Git-BS)", "", status])
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)")
+ expect(git_proxy.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).with("--version").
- and_return("git version 1.2.3.msysgit.0")
+ status = double("success?" => true)
+ expect(Open3).to receive(:capture3).with("git", "--version").
+ and_return(["git version 1.2.3.msysgit.0", "", status])
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")
+ expect(git_proxy.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
+
+ context "URI is HTTP" do
+ let(:uri) { "http://github.com/ruby/rubygems.git" }
+ let(:clone_args_without_depth) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--single-branch"] }
+
+ it "retries clone without --depth when dumb http transport fails" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "dumb http transport does not support shallow capabilities", fail_result])
+ expect(git_proxy).to receive(:capture).with([*clone_args_without_depth, "--", uri, path.to_s], nil).and_return(["", "", clone_result])
+
+ subject.checkout
+ end
+ end
+
+ describe "#installed_to?" do
+ let(:destination) { "install/dir" }
+ let(:destination_dir_exists) { true }
+ let(:children) { ["gem.gemspec", "README.me", ".git", "Rakefile"] }
+
+ before do
+ allow(Dir).to receive(:exist?).with(destination).and_return(destination_dir_exists)
+ allow(Dir).to receive(:children).with(destination).and_return(children)
+ end
+
+ context "when destination dir exists with children other than just .git" do
+ it "returns true" do
+ expect(git_proxy.installed_to?(destination)).to be true
+ end
+ end
+
+ context "when destination dir does not exist" do
+ let(:destination_dir_exists) { false }
+
+ it "returns false" do
+ expect(git_proxy.installed_to?(destination)).to be false
+ end
+ end
+
+ context "when destination dir is empty" do
+ let(:children) { [] }
+
+ it "returns false" do
+ expect(git_proxy.installed_to?(destination)).to be false
+ end
+ end
+
+ context "when destination dir has only .git directory" do
+ let(:children) { [".git"] }
+
+ it "returns false" do
+ expect(git_proxy.installed_to?(destination)).to be false
+ end
+ end
+ end
+
+ describe "#checkout" do
+ context "when the repository isn't cloned" do
+ before do
+ allow(path).to receive(:exist?).and_return(false)
+ end
+
+ it "clones the repository" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+
+ context "when the repository is cloned" do
+ before do
+ allow(path).to receive(:exist?).and_return(true)
+ end
+
+ context "with a locked revision" do
+ let(:revision) { Digest::SHA1.hexdigest("ruby") }
+
+ context "when the revision exists locally" do
+ it "uses the cached revision" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:git).with("cat-file", "-e", revision, dir: path).and_return(true)
+ subject.checkout
+ end
+ end
+
+ context "when the revision doesn't exist locally" do
+ it "fetches the specific revision" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:git).with("cat-file", "-e", revision, dir: path).and_raise(Bundler::GitError)
+ expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "#{revision}:refs/#{revision}-sha"], path).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+ end
+
+ context "with no explicit ref" do
+ it "fetches the HEAD revision" do
+ parsed_revision = Digest::SHA1.hexdigest("ruby")
+ allow(git_proxy).to receive(:git_local).with("rev-parse", "--abbrev-ref", "HEAD", dir: path).and_return(parsed_revision)
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+
+ context "with a commit ref" do
+ let(:ref) { Digest::SHA1.hexdigest("ruby") }
+
+ context "when the revision exists locally" do
+ it "uses the cached revision" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:git).with("cat-file", "-e", ref, dir: path).and_return(true)
+ subject.checkout
+ end
+ end
+
+ context "when the revision doesn't exist locally" do
+ it "fetches the specific revision" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:git).with("cat-file", "-e", ref, dir: path).and_raise(Bundler::GitError)
+ expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "#{ref}:refs/#{ref}-sha"], path).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+ end
+
+ context "with a non-commit ref" do
+ let(:ref) { "HEAD" }
+
+ it "fetches all revisions" do
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--", uri, "refs/*:refs/*"], path).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+
+ context "URI is HTTP" do
+ let(:uri) { "http://github.com/ruby/rubygems.git" }
+
+ it "retries fetch without --depth when dumb http transport fails" do
+ parsed_revision = Digest::SHA1.hexdigest("ruby")
+ allow(git_proxy).to receive(:git_local).with("rev-parse", "--abbrev-ref", "HEAD", dir: path).and_return(parsed_revision)
+ allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(git_proxy).to receive(:capture).with([*base_fetch_args, "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "dumb http transport does not support shallow capabilities", fail_result])
+ expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "", clone_result])
+ subject.checkout
+ end
end
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..14e91c6bdc
--- /dev/null
+++ b/spec/bundler/bundler/source/git_spec.rb
@@ -0,0 +1,123 @@
+# 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
+
+ describe "#locked_revision_checked_out?" do
+ let(:revision) { "abc" }
+ let(:git_proxy_revision) { revision }
+ let(:git_proxy_installed) { true }
+ let(:git_proxy) { subject.send(:git_proxy) }
+ let(:options) do
+ {
+ "uri" => uri,
+ "revision" => revision,
+ }
+ end
+
+ before do
+ allow(git_proxy).to receive(:revision).and_return(git_proxy_revision)
+ allow(git_proxy).to receive(:installed_to?).with(subject.install_path).and_return(git_proxy_installed)
+ end
+
+ context "when the locked revision is checked out" do
+ it "returns true" do
+ expect(subject.send(:locked_revision_checked_out?)).to be true
+ end
+ end
+
+ context "when no revision is provided" do
+ let(:options) do
+ { "uri" => uri }
+ end
+
+ it "returns falsey value" do
+ expect(subject.send(:locked_revision_checked_out?)).to be_falsey
+ end
+ end
+
+ context "when the git proxy revision is different than the git revision" do
+ let(:git_proxy_revision) { revision.next }
+
+ it "returns falsey value" do
+ expect(subject.send(:locked_revision_checked_out?)).to be_falsey
+ end
+ end
+
+ context "when the gem hasn't been installed" do
+ let(:git_proxy_installed) { false }
+
+ it "returns falsey value" do
+ expect(subject.send(:locked_revision_checked_out?)).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/rubygems/remote_spec.rb b/spec/bundler/bundler/source/rubygems/remote_spec.rb
index 54394fc0ca..27430d4a3b 100644
--- a/spec/bundler/bundler/source/rubygems/remote_spec.rb
+++ b/spec/bundler/bundler/source/rubygems/remote_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/source/rubygems/remote"
RSpec.describe Bundler::Source::Rubygems::Remote do
@@ -8,11 +8,11 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
before do
- allow(Digest::MD5).to receive(:hexdigest).with(duck_type(:to_s)) {|string| "MD5HEX(#{string})" }
+ allow(Digest(:MD5)).to receive(:hexdigest).with(duck_type(:to_s)) {|string| "MD5HEX(#{string})" }
end
- let(:uri_no_auth) { URI("https://gems.example.com") }
- let(:uri_with_auth) { URI("https://#{credentials}@gems.example.com") }
+ let(:uri_no_auth) { Gem::URI("https://gems.example.com") }
+ let(:uri_with_auth) { Gem::URI("https://#{credentials}@gems.example.com") }
let(:credentials) { "username:password" }
context "when the original URI has no credentials" do
@@ -22,8 +22,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
it "applies configured credentials" do
- Bundler.settings[uri_no_auth.to_s] = credentials
- expect(remote(uri_no_auth).uri).to eq(uri_with_auth)
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials) do
+ expect(remote(uri_no_auth).uri).to eq(uri_with_auth)
+ end
end
end
@@ -33,8 +34,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
it "does not apply given credentials" do
- Bundler.settings[uri_no_auth.to_s] = credentials
- expect(remote(uri_no_auth).anonymized_uri).to eq(uri_no_auth)
+ 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
@@ -44,8 +46,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
it "only applies the given user" do
- Bundler.settings[uri_no_auth.to_s] = credentials
- expect(remote(uri_no_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ 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
@@ -57,7 +60,7 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
it "does not apply configured credentials" do
- Bundler.settings[uri_no_auth.to_s] = "other:stuff"
+ Bundler.settings.temporary(uri_no_auth.to_s => "other:stuff")
expect(remote(uri_with_auth).uri).to eq(uri_with_auth)
end
end
@@ -68,7 +71,7 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
it "does not apply given credentials" do
- Bundler.settings[uri_no_auth.to_s] = "other:stuff"
+ Bundler.settings.temporary(uri_no_auth.to_s => "other:stuff")
expect(remote(uri_with_auth).anonymized_uri).to eq(uri_no_auth)
end
end
@@ -79,18 +82,18 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
it "does not apply given credentials" do
- Bundler.settings[uri_with_auth.to_s] = credentials
+ 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) { URI("https://SeCrEt-ToKeN@gem.fury.io/me/") }
+ let(:uri) { Gem::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(URI("https://gem.fury.io/me/"))
+ expect(remote(uri).anonymized_uri).to eq(Gem::URI("https://gem.fury.io/me/"))
end
end
@@ -102,11 +105,13 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
context "when a mirror with inline credentials is configured for the URI" do
- let(:uri) { URI("https://rubygems.org/") }
- let(:mirror_uri_with_auth) { URI("https://username:password@rubygems-mirror.org/") }
- let(:mirror_uri_no_auth) { URI("https://rubygems-mirror.org/") }
+ let(:uri) { Gem::URI("https://rubygems.org/") }
+ let(:mirror_uri_with_auth) { Gem::URI("https://username:password@example-mirror.rubygems.org/") }
+ let(:mirror_uri_no_auth) { Gem::URI("https://example-mirror.rubygems.org/") }
+
+ before { Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_with_auth.to_s) }
- before { Bundler.settings["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)
@@ -126,13 +131,18 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
context "when a mirror with configured credentials is configured for the URI" do
- let(:uri) { URI("https://rubygems.org/") }
- let(:mirror_uri_with_auth) { URI("https://#{credentials}@rubygems-mirror.org/") }
- let(:mirror_uri_no_auth) { URI("https://rubygems-mirror.org/") }
+ let(:uri) { Gem::URI("https://rubygems.org/") }
+ let(:mirror_uri_with_auth) { Gem::URI("https://#{credentials}@example-mirror.rubygems.org/") }
+ let(:mirror_uri_no_auth) { Gem::URI("https://example-mirror.rubygems.org/") }
before do
- Bundler.settings["mirror.https://rubygems.org/"] = mirror_uri_no_auth.to_s
- Bundler.settings[mirror_uri_no_auth.to_s] = credentials
+ 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
@@ -159,4 +169,39 @@ RSpec.describe Bundler::Source::Rubygems::Remote do
end
end
end
+
+ describe "#cooldown" do
+ it "is nil by default" do
+ expect(remote(uri_no_auth).cooldown).to be_nil
+ end
+
+ it "returns the value passed to the constructor" do
+ r = Bundler::Source::Rubygems::Remote.new(uri_no_auth, cooldown: 7)
+ expect(r.cooldown).to eq(7)
+ end
+ end
+
+ describe "#effective_cooldown" do
+ it "returns the per-remote value when no override is set" do
+ r = Bundler::Source::Rubygems::Remote.new(uri_no_auth, cooldown: 7)
+ expect(r.effective_cooldown).to eq(7)
+ end
+
+ it "returns nil when neither override nor per-remote value is set" do
+ expect(remote(uri_no_auth).effective_cooldown).to be_nil
+ end
+
+ it "settings override per-remote value" do
+ r = Bundler::Source::Rubygems::Remote.new(uri_no_auth, cooldown: 7)
+ Bundler.settings.temporary(cooldown: 14) do
+ expect(r.effective_cooldown).to eq(14)
+ end
+ end
+
+ it "settings override even when per-remote value is absent" do
+ Bundler.settings.temporary(cooldown: 14) do
+ expect(remote(uri_no_auth).effective_cooldown).to eq(14)
+ end
+ end
+ end
end
diff --git a/spec/bundler/bundler/source/rubygems_spec.rb b/spec/bundler/bundler/source/rubygems_spec.rb
index b8f9f09c20..feb787498e 100644
--- a/spec/bundler/bundler/source/rubygems_spec.rb
+++ b/spec/bundler/bundler/source/rubygems_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Source::Rubygems do
before do
@@ -31,4 +30,75 @@ RSpec.describe Bundler::Source::Rubygems do
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
+
+ describe "#clear_cache" do
+ it "invalidates memoized indexes so subsequent reads rebuild them" do
+ source = described_class.new
+
+ first_specs = source.specs
+ first_installed = source.send(:installed_specs)
+ first_default = source.send(:default_specs)
+ first_cached = source.send(:cached_specs)
+
+ expect(source.specs).to equal(first_specs)
+ expect(source.send(:installed_specs)).to equal(first_installed)
+ expect(source.send(:default_specs)).to equal(first_default)
+ expect(source.send(:cached_specs)).to equal(first_cached)
+
+ source.clear_cache
+
+ expect(source.specs).not_to equal(first_specs)
+ expect(source.send(:installed_specs)).not_to equal(first_installed)
+ expect(source.send(:default_specs)).not_to equal(first_default)
+ expect(source.send(:cached_specs)).not_to equal(first_cached)
+ end
+
+ it "reflects newly-discovered installed gems after clear_cache" do
+ source = described_class.new
+ foo = Gem::Specification.new("foo", "1.0.0")
+ bar = Gem::Specification.new("bar", "1.0.0")
+
+ allow(Bundler.rubygems).to receive(:installed_specs).and_return([foo])
+ expect(source.send(:installed_specs).search("bar")).to be_empty
+
+ allow(Bundler.rubygems).to receive(:installed_specs).and_return([foo, bar])
+ expect(source.send(:installed_specs).search("bar")).to be_empty
+
+ source.clear_cache
+
+ expect(source.send(:installed_specs).search("bar")).not_to be_empty
+ end
+ end
+
+ describe "log debug information" do
+ it "log the time spent downloading and installing a gem" do
+ build_repo2 do
+ build_gem "warning"
+ end
+
+ gemfile_content = <<~G
+ source "https://gem.repo2"
+ gem "warning"
+ G
+
+ stdout = install_gemfile(gemfile_content, env: { "DEBUG" => "1" })
+
+ expect(stdout).to match(/Downloaded warning in: \d+\.\d+s/)
+ expect(stdout).to match(/Installed warning in: \d+\.\d+s/)
+ end
+ end
end
diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb
index 6a23c8bcbf..61bd99b063 100644
--- a/spec/bundler/bundler/source_list_spec.rb
+++ b/spec/bundler/bundler/source_list_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::SourceList do
before do
@@ -12,7 +11,8 @@ RSpec.describe Bundler::SourceList do
subject(:source_list) { Bundler::SourceList.new }
- let(:rubygems_aggregate) { Bundler::Source::Rubygems.new }
+ let(:global_rubygems_source) { Bundler::Source::Rubygems.new }
+ let(:metadata_source) { Bundler::Source::Metadata.new }
describe "adding sources" do
before do
@@ -75,7 +75,7 @@ RSpec.describe Bundler::SourceList 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 git.allow_insecure true`, " \
+ "Disable this warning with `bundle config set --local git.allow_insecure true`, " \
"or switch to the `https` protocol to keep your data secure."
end
@@ -85,7 +85,7 @@ RSpec.describe Bundler::SourceList do
end
it "ignores git protocols on request" do
- Bundler.settings["git.allow_insecure"] = true
+ 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
@@ -115,18 +115,25 @@ RSpec.describe Bundler::SourceList do
end
end
- describe "#add_rubygems_remote" do
- before do
- @returned_source = source_list.add_rubygems_remote("https://rubygems.org/")
+ describe "#add_global_rubygems_remote" do
+ let!(:returned_source) { source_list.add_global_rubygems_remote("https://rubygems.org/") }
+
+ it "returns the global rubygems source" do
+ expect(returned_source).to be_instance_of(Bundler::Source::Rubygems)
end
- it "returns the aggregate rubygems source" do
- expect(@returned_source).to be_instance_of(Bundler::Source::Rubygems)
+ it "adds the provided remote to the beginning of the global source" do
+ source_list.add_global_rubygems_remote("https://othersource.org")
+ expect(returned_source.remotes).to eq [
+ Gem::URI("https://othersource.org/"),
+ Gem::URI("https://rubygems.org/"),
+ ]
end
- it "adds the provided remote to the beginning of the aggregate source" do
- source_list.add_rubygems_remote("https://othersource.org")
- expect(@returned_source.remotes.first).to eq(URI("https://othersource.org/"))
+ it "records the per-remote cooldown when supplied" do
+ source_list.add_global_rubygems_remote("https://othersource.org", cooldown: 7)
+ expect(returned_source.cooldown_for(Gem::URI("https://othersource.org/"))).to eq(7)
+ expect(returned_source.cooldown_for(Gem::URI("https://rubygems.org/"))).to be_nil
end
end
@@ -155,21 +162,21 @@ RSpec.describe Bundler::SourceList do
end
describe "#all_sources" do
- it "includes the aggregate rubygems source when rubygems sources have been added" do
+ it "includes the global 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
+ expect(source_list.all_sources).to include global_rubygems_source
end
- it "includes the aggregate rubygems source when no rubygems sources have been added" do
+ it "includes the global 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
+ expect(source_list.all_sources).to include global_rubygems_source
end
it "returns sources of the same type in the reverse order that they were added" do
@@ -203,29 +210,30 @@ RSpec.describe Bundler::SourceList do
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,
+ global_rubygems_source,
+ metadata_source,
]
end
end
describe "#path_sources" do
it "returns an empty array when no path sources have been added" do
- source_list.add_rubygems_remote("https://rubygems.org")
+ 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_rubygems_remote("https://fifth-rubygems.org")
+ source_list.add_global_rubygems_remote("https://fifth-rubygems.org")
source_list.add_path_source("path" => "/third/path/to/gem")
- source_list.add_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_global_rubygems_remote("https://fourth-rubygems.org")
source_list.add_path_source("path" => "/second/path/to/gem")
- source_list.add_rubygems_remote("https://third-rubygems.org")
+ 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_rubygems_remote("https://second-rubygems.org")
+ source_list.add_global_rubygems_remote("https://second-rubygems.org")
source_list.add_path_source("path" => "/first/path/to/gem")
- source_list.add_rubygems_remote("https://first-rubygems.org")
+ 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 [
@@ -238,7 +246,7 @@ RSpec.describe Bundler::SourceList do
describe "#git_sources" do
it "returns an empty array when no git sources have been added" do
- source_list.add_rubygems_remote("https://rubygems.org")
+ 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
@@ -246,15 +254,15 @@ RSpec.describe Bundler::SourceList do
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_rubygems_remote("https://fifth-rubygems.org")
+ source_list.add_global_rubygems_remote("https://fifth-rubygems.org")
source_list.add_path_source("path" => "/third/path/to/gem")
- source_list.add_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_global_rubygems_remote("https://fourth-rubygems.org")
source_list.add_path_source("path" => "/second/path/to/gem")
- source_list.add_rubygems_remote("https://third-rubygems.org")
+ 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_rubygems_remote("https://second-rubygems.org")
+ source_list.add_global_rubygems_remote("https://second-rubygems.org")
source_list.add_path_source("path" => "/first/path/to/gem")
- source_list.add_rubygems_remote("https://first-rubygems.org")
+ 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 [
@@ -267,7 +275,7 @@ RSpec.describe Bundler::SourceList do
describe "#plugin_sources" do
it "returns an empty array when no plugin sources have been added" do
- source_list.add_rubygems_remote("https://rubygems.org")
+ 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
@@ -277,13 +285,13 @@ RSpec.describe Bundler::SourceList 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_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_global_rubygems_remote("https://fourth-rubygems.org")
source_list.add_path_source("path" => "/second/path/to/gem")
- source_list.add_rubygems_remote("https://third-rubygems.org")
+ 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_rubygems_remote("https://second-rubygems.org")
+ source_list.add_global_rubygems_remote("https://second-rubygems.org")
source_list.add_path_source("path" => "/first/path/to/gem")
- source_list.add_rubygems_remote("https://first-rubygems.org")
+ 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 [
@@ -295,19 +303,19 @@ RSpec.describe Bundler::SourceList do
end
describe "#rubygems_sources" do
- it "includes the aggregate rubygems source when rubygems sources have been added" do
+ it "includes the global 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
+ expect(source_list.rubygems_sources).to include global_rubygems_source
end
- it "returns only the aggregate rubygems source when no rubygems sources have been added" do
+ it "returns only the global 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]
+ expect(source_list.rubygems_sources).to eq [global_rubygems_source]
end
it "returns rubygems sources in the reverse order that they were added" do
@@ -329,7 +337,7 @@ RSpec.describe Bundler::SourceList do
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,
+ global_rubygems_source,
]
end
end
@@ -337,7 +345,7 @@ RSpec.describe Bundler::SourceList do
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_rubygems_remote("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
@@ -354,7 +362,7 @@ RSpec.describe Bundler::SourceList do
end
describe "#lock_sources" do
- it "combines the rubygems sources into a single instance, removing duplicate remotes from the end" 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")
@@ -368,7 +376,9 @@ RSpec.describe Bundler::SourceList do
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"),
@@ -378,12 +388,11 @@ RSpec.describe Bundler::SourceList do
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("remotes" => [
- "https://duplicate-rubygems.org",
- "https://first-rubygems.org",
- "https://second-rubygems.org",
- "https://third-rubygems.org",
- ]),
+ 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
@@ -414,7 +423,7 @@ RSpec.describe Bundler::SourceList do
end
describe "#cached!" do
- let(:rubygems_source) { source_list.add_rubygems_remote("https://rubygems.org") }
+ 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") }
@@ -427,7 +436,7 @@ RSpec.describe Bundler::SourceList do
end
describe "#remote!" do
- let(:rubygems_source) { source_list.add_rubygems_remote("https://rubygems.org") }
+ 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") }
@@ -438,4 +447,29 @@ RSpec.describe Bundler::SourceList do
source_list.remote!
end
end
+
+ describe "#clear_cache" do
+ let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) }
+
+ it "calls #clear_cache on all rubygems sources" do
+ expect(rubygems_source).to receive(:clear_cache)
+ expect(source_list.global_rubygems_source).to receive(:clear_cache)
+ source_list.clear_cache
+ 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
index 08d1698fcd..01b57ce9e8 100644
--- a/spec/bundler/bundler/source_spec.rb
+++ b/spec/bundler/bundler/source_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::Source do
class ExampleSource < Bundler::Source
@@ -22,7 +21,7 @@ RSpec.describe Bundler::Source do
end
describe "#version_message" do
- let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6", :platform => rb) }
+ let(:spec) { double(:spec, name: "nokogiri", version: ">= 1.6", platform: Gem::Platform::RUBY) }
shared_examples_for "the lockfile specs are not relevant" do
it "should return a string with the spec name and version" do
@@ -31,95 +30,105 @@ RSpec.describe Bundler::Source do
end
context "when there are locked gems" do
- let(:locked_gems) { double(:locked_gems) }
-
- before { allow(Bundler).to receive(:locked_gems).and_return(locked_gems) }
-
context "that contain the relevant gem spec" do
- before do
- specs = double(:specs)
- allow(locked_gems).to receive(:specs).and_return(specs)
- allow(specs).to receive(:find).and_return(locked_gem)
- end
-
context "without a version" do
- let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => nil) }
+ 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") }
+ 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") }
+ let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "< 1.5") }
- context "with color" do
- before { Bundler.ui = Bundler::UI::Shell.new }
+ 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)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m")
+ 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)).to eq("nokogiri >= 1.6 (was < 1.5)")
+ 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") }
+ let(:spec) { double(:spec, name: "nokogiri", version: "1.6.1", platform: Gem::Platform::RUBY) }
+ 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 "with color" do
- before { Bundler.ui = Bundler::UI::Shell.new }
+ 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)).to eq("nokogiri 1.6.1\e[33m (was 1.7.0)\e[0m")
+ 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") }
+ let(:spec) { double(:spec, name: "nokogiri", version: "1.7.1", platform: Gem::Platform::RUBY) }
+ let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "1.7.0") }
- context "with color" do
- before { Bundler.ui = Bundler::UI::Shell.new }
+ 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)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m")
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m")
end
end
- end
- end
- context "that do not contain the relevant gem spec" do
- before do
- specs = double(:specs)
- allow(locked_gems).to receive(:specs).and_return(specs)
- allow(specs).to receive(:find).and_return(nil)
- end
+ context "without color" do
+ around do |example|
+ with_ui(Bundler::UI::Shell.new("no-color" => true)) do
+ example.run
+ end
+ end
- it_behaves_like "the lockfile specs are not relevant"
+ 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
-
- context "when there are no locked gems" do
- before { allow(Bundler).to receive(:locked_gems).and_return(nil) }
-
- it_behaves_like "the lockfile specs are not relevant"
- end
end
describe "#can_lock?" do
context "when the passed spec's source is equivalent" do
- let(:spec) { double(:spec, :source => subject) }
+ let(:spec) { double(:spec, source: subject) }
it "should return true" do
expect(subject.can_lock?(spec)).to be_truthy
@@ -127,7 +136,7 @@ RSpec.describe Bundler::Source do
end
context "when the passed spec's source is not equivalent" do
- let(:spec) { double(:spec, :source => double(:other_source)) }
+ let(:spec) { double(:spec, source: double(:other_source)) }
it "should return false" do
expect(subject.can_lock?(spec)).to be_falsey
@@ -152,4 +161,14 @@ RSpec.describe Bundler::Source do
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
index 8f7c27f065..1e1ceadf26 100644
--- a/spec/bundler/bundler/spec_set_spec.rb
+++ b/spec/bundler/bundler/spec_set_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::SpecSet do
let(:specs) do
@@ -17,6 +16,7 @@ RSpec.describe Bundler::SpecSet do
build_spec("e", "1.0.0.pre.1"),
].flatten
end
+
subject { described_class.new(specs) }
context "enumerable methods" do
@@ -29,15 +29,120 @@ RSpec.describe Bundler::SpecSet do
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
+
+ it "returns nil when the name is not present" do
+ spec = described_class.new(specs).find_by_name_and_platform("missing", platform)
+ expect(spec).to be_nil
+ end
+
+ it "returns nil when the name exists but no spec is installable on the requested platform" do
+ incompatible_platform = Gem::Platform.new("java")
+ incompatible_spec = build_spec("a", "1.0", incompatible_platform).first
+
+ spec = described_class.new([incompatible_spec]).find_by_name_and_platform("a", platform)
+ expect(spec).to be_nil
+ end
+
+ it "returns the first installable spec for the given name in insertion order" do
+ later_platform_spec = build_spec("b", "3.0", platform).first
+ specs = [
+ platform_spec,
+ later_platform_spec,
+ ]
+
+ spec = described_class.new(specs).find_by_name_and_platform("b", platform)
+ expect(spec).to eq platform_spec
+ end
+ end
+
describe "#to_a" do
it "returns the specs in order" do
- expect(subject.to_a.map(&:full_name)).to eq %w(
+ 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
+
+ it "puts rake first when present" do
+ specs = [
+ build_spec("a", "1.0") {|s| s.dep "rake", ">= 0" },
+ build_spec("rake", "13.0"),
+ ].flatten
+
+ expect(described_class.new(specs).to_a.map(&:full_name)).to eq %w[
+ rake-13.0
+ a-1.0
+ ]
+ end
+ end
+
+ describe "#complete_platform" do
+ let(:platform) { Gem::Platform.new("x86_64-linux") }
+
+ let(:platform_variant) do
+ build_spec("needs_old_ruby", "1.0", platform).first.tap do |s|
+ s.required_ruby_version = Gem::Requirement.new("< #{Gem.ruby_version}")
+ end
+ end
+
+ let(:lazy_spec) do
+ lazy = Bundler::LazySpecification.new("needs_old_ruby", Gem::Version.new("1.0"), Gem::Platform::RUBY)
+ lazy.required_ruby_version = Gem::Requirement.new("< #{Gem.ruby_version}")
+ source = double("source")
+ source_specs = double("source_specs")
+ allow(source).to receive(:specs).and_return(source_specs)
+ allow(source_specs).to receive(:search).
+ with(["needs_old_ruby", Gem::Version.new("1.0")]).and_return([platform_variant])
+ lazy.source = source
+ lazy
+ end
+
+ it "rejects a platform variant whose strict metadata is incompatible when no override is attached" do
+ set = described_class.new([lazy_spec])
+ expect(set.send(:complete_platform, platform)).to be(false)
+ end
+
+ it "accepts a platform variant when the LazySpec carries an override that allows it" do
+ lazy_spec.overrides = [Bundler::Override.new("needs_old_ruby", :required_ruby_version, :ignore_upper)]
+ set = described_class.new([lazy_spec])
+ expect(set.send(:complete_platform, platform)).to be(true)
+ end
+
+ it "carries overrides onto a synthesized LazySpec so a follow-up complete_platform still honors them" do
+ override = Bundler::Override.new("needs_old_ruby", :required_ruby_version, :ignore_upper)
+ lazy_spec.overrides = [override]
+ second_platform = Gem::Platform.new("aarch64-linux")
+ second_variant = build_spec("needs_old_ruby", "1.0", second_platform).first.tap do |s|
+ s.required_ruby_version = Gem::Requirement.new("< #{Gem.ruby_version}")
+ end
+ allow(lazy_spec.source.specs).to receive(:search).
+ with(["needs_old_ruby", Gem::Version.new("1.0")]).and_return([platform_variant, second_variant])
+
+ set = described_class.new([lazy_spec])
+ expect(set.send(:complete_platform, platform)).to be(true)
+ # The synthesized x86_64-linux variant is now in the set. If lookup
+ # picks it as exemplar for the next platform check, the override list
+ # must still be reachable via its overrides accessor.
+ synthesized = set.to_a.find {|s| s.platform == platform }
+ expect(synthesized.overrides).to eq([override])
+ expect(set.send(:complete_platform, second_platform)).to be(true)
end
end
end
diff --git a/spec/bundler/bundler/specifications/foo.gemspec b/spec/bundler/bundler/specifications/foo.gemspec
new file mode 100644
index 0000000000..19b7724e81
--- /dev/null
+++ b/spec/bundler/bundler/specifications/foo.gemspec
@@ -0,0 +1,13 @@
+# rubocop:disable Style/FrozenStringLiteralComment
+# stub: foo 1.0.0 ruby lib
+
+# The first line would be '# -*- encoding: utf-8 -*-' in a real stub gemspec
+
+Gem::Specification.new do |s|
+ s.name = "foo"
+ s.version = "1.0.0"
+ s.loaded_from = __FILE__
+ s.extensions = "ext/foo"
+ s.required_ruby_version = ">= 3.2.0"
+end
+# rubocop:enable Style/FrozenStringLiteralComment
diff --git a/spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb b/spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb
deleted file mode 100644
index 66853a6815..0000000000
--- a/spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-require "bundler/ssl_certs/certificate_manager"
-
-RSpec.describe Bundler::SSLCerts::CertificateManager do
- let(:rubygems_path) { root }
- let(:stub_cert) { File.join(root.to_s, "lib", "rubygems", "ssl_certs", "rubygems.org", "ssl-cert.pem") }
- let(:rubygems_certs_dir) { File.join(root.to_s, "lib", "rubygems", "ssl_certs", "rubygems.org") }
-
- subject { described_class.new(rubygems_path) }
-
- # Pretend bundler root is rubygems root
- before do
- # Backing up rubygems ceriticates
- FileUtils.mv(rubygems_certs_dir, rubygems_certs_dir + ".back") if !!(ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"])
-
- FileUtils.mkdir_p(rubygems_certs_dir)
- FileUtils.touch(stub_cert)
- end
-
- after do
- rubygems_dir = File.join(root.to_s, "lib", "rubygems")
- FileUtils.rm_rf(rubygems_certs_dir)
-
- # Restore rubygems certificates
- FileUtils.mv(rubygems_certs_dir + ".back", rubygems_certs_dir) if !!(ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"])
- end
-
- describe "#update_from" do
- let(:cert_manager) { double(:cert_manager) }
-
- before { allow(described_class).to receive(:new).with(rubygems_path).and_return(cert_manager) }
-
- it "should update the certs through a new certificate manager" do
- allow(cert_manager).to receive(:update!)
- expect(described_class.update_from!(rubygems_path)).to be_nil
- end
- end
-
- describe "#initialize" do
- it "should set bundler_cert_path as path of the subdir with bundler ssl certs" do
- expect(subject.bundler_cert_path).to eq(File.join(root, "lib/bundler/ssl_certs"))
- end
-
- it "should set bundler_certs as the paths of the bundler ssl certs" do
- expect(subject.bundler_certs).to include(File.join(root, "lib/bundler/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem"))
- expect(subject.bundler_certs).to include(File.join(root, "lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem"))
- end
-
- context "when rubygems_path is not nil" do
- it "should set rubygems_certs" do
- expect(subject.rubygems_certs).to include(File.join(root, "lib", "rubygems", "ssl_certs", "rubygems.org", "ssl-cert.pem"))
- end
- end
- end
-
- describe "#up_to_date?" do
- context "when bundler certs and rubygems certs are the same" do
- before do
- bundler_certs = Dir[File.join(root.to_s, "lib", "bundler", "ssl_certs", "**", "*.pem")]
- FileUtils.rm(stub_cert)
- FileUtils.cp(bundler_certs, rubygems_certs_dir)
- end
-
- it "should return true" do
- expect(subject).to be_up_to_date
- end
- end
-
- context "when bundler certs and rubygems certs are not the same" do
- it "should return false" do
- expect(subject).to_not be_up_to_date
- end
- end
- end
-
- describe "#update!" do
- context "when certificate manager is not up to date" do
- before do
- allow(subject).to receive(:up_to_date?).and_return(false)
- allow(FileUtils).to receive(:rm)
- allow(FileUtils).to receive(:cp)
- end
-
- it "should remove the current bundler certs" do
- expect(FileUtils).to receive(:rm).with(subject.bundler_certs)
- subject.update!
- end
-
- it "should copy the rubygems certs into bundler certs" do
- expect(FileUtils).to receive(:cp).with(subject.rubygems_certs, subject.bundler_cert_path)
- subject.update!
- end
-
- it "should return nil" do
- expect(subject.update!).to be_nil
- end
- end
-
- context "when certificate manager is up to date" do
- before { allow(subject).to receive(:up_to_date?).and_return(true) }
-
- it "should return nil" do
- expect(subject.update!).to be_nil
- end
- end
- end
-
- describe "#connect_to" do
- let(:host) { "http://www.host.com" }
- let(:http) { Net::HTTP.new(host, 443) }
- let(:cert_store) { OpenSSL::X509::Store.new }
- let(:http_header_response) { double(:http_header_response) }
-
- before do
- allow(Net::HTTP).to receive(:new).with(host, 443).and_return(http)
- allow(OpenSSL::X509::Store).to receive(:new).and_return(cert_store)
- allow(http).to receive(:head).with("/").and_return(http_header_response)
- end
-
- it "should use ssl for the http request" do
- expect(http).to receive(:use_ssl=).with(true)
- subject.connect_to(host)
- end
-
- it "use verify peer mode" do
- expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
- subject.connect_to(host)
- end
-
- it "set its cert store as a OpenSSL::X509::Store populated with bundler certs" do
- expect(cert_store).to receive(:add_file).at_least(:once)
- expect(http).to receive(:cert_store=).with(cert_store)
- subject.connect_to(host)
- end
-
- it "return the headers of the request response" do
- expect(subject.connect_to(host)).to eq(http_header_response)
- end
- end
-end
diff --git a/spec/bundler/bundler/stub_specification_spec.rb b/spec/bundler/bundler/stub_specification_spec.rb
index f1ddf43bb4..f2faa2ea64 100644
--- a/spec/bundler/bundler/stub_specification_spec.rb
+++ b/spec/bundler/bundler/stub_specification_spec.rb
@@ -1,25 +1,82 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::StubSpecification do
- let(:gemspec) do
- Gem::Specification.new do |s|
+ 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
- end
- let(:with_bundler_stub_spec) do
described_class.from_stub(gemspec)
end
- if Bundler.rubygems.provides?(">= 2.1")
- 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)
+ 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 "#gem_build_complete_path" do
+ it "StubSpecification should have equal gem_build_complete_path as Specification" do
+ spec_path = File.join(File.dirname(__FILE__), "specifications", "foo.gemspec")
+ spec = Gem::Specification.load(spec_path)
+ gem_stub = Gem::StubSpecification.new(spec_path, File.dirname(__FILE__),"","")
+
+ stub = described_class.from_stub(gem_stub)
+ expect(stub.gem_build_complete_path).to eq spec.gem_build_complete_path
+ 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 #{RUBY_ENGINE == "jruby" ? "false" : "true"} if not manually_installed?" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ stub.installed_by_version = Gem::Version.new(1)
+ if RUBY_ENGINE == "jruby"
+ expect(stub.missing_extensions?).to be false
+ else
+ expect(stub.missing_extensions?).to be true
end
end
end
+
+ describe "#activated?" do
+ it "returns true after activation" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+
+ expect(stub.activated?).to be_falsey
+ stub.activated = true
+ expect(stub.activated?).to be true
+ end
+
+ it "returns true after activation if the underlying stub is a `Gem::StubSpecification`" do
+ spec_path = File.join(File.dirname(__FILE__), "specifications", "foo.gemspec")
+ gem_stub = Gem::StubSpecification.new(spec_path, File.dirname(__FILE__),"","")
+ stub = described_class.from_stub(gem_stub)
+
+ expect(stub.activated?).to be_falsey
+ stub.activated = true
+ expect(stub.activated?).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..83f147191e
--- /dev/null
+++ b/spec/bundler/bundler/ui/shell_spec.rb
@@ -0,0 +1,112 @@
+# 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
+
+ context "when output_stream is :stderr" do
+ before { subject.output_stream = :stderr }
+ it "prints to stderr" do
+ expect { subject.info("info") }.to output("info\n").to_stderr
+ end
+ 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
+
+ context "when output_stream is :stderr" do
+ before { subject.output_stream = :stderr }
+ it "prints to stderr" do
+ expect { subject.confirm("confirm") }.to output("confirm\n").to_stderr
+ end
+ end
+ end
+
+ describe "#warn" do
+ before { subject.level = "warn" }
+ it "prints to stderr, implicitly adding a newline" do
+ expect { subject.warn("warning") }.to output("warning\n").to_stderr
+ end
+ it "can be told not to emit a newline" do
+ expect { subject.warn("warning", false) }.to output("warning").to_stderr
+ end
+ end
+
+ describe "#debug" do
+ it "prints to stdout" do
+ expect { subject.debug("debug") }.to output("debug\n").to_stdout
+ end
+
+ context "when output_stream is :stderr" do
+ before { subject.output_stream = :stderr }
+ it "prints to stderr" do
+ expect { subject.debug("debug") }.to output("debug\n").to_stderr
+ end
+ 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
+
+ describe "threads" do
+ it "is thread safe when using with_level" do
+ stop_thr1 = false
+ stop_thr2 = false
+
+ expect(subject.level).to eq("debug")
+
+ thr1 = Thread.new do
+ subject.silence do
+ sleep(0.1) until stop_thr1
+ end
+
+ stop_thr2 = true
+ end
+
+ thr2 = Thread.new do
+ subject.silence do
+ stop_thr1 = true
+ sleep(0.1) until stop_thr2
+ end
+ end
+
+ [thr1, thr2].each(&:join)
+
+ expect(subject.level).to eq("debug")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ui_spec.rb b/spec/bundler/bundler/ui_spec.rb
index fc76eb1ee7..6df0d2e290 100644
--- a/spec/bundler/bundler/ui_spec.rb
+++ b/spec/bundler/bundler/ui_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::UI do
describe Bundler::UI::Silent do
- it "has the same instance methods as Shell", :ruby => ">= 1.9" do
+ it "has the same instance methods as Shell" do
shell = Bundler::UI::Shell
methods = proc do |cls|
cls.instance_methods.map do |i|
@@ -14,7 +13,7 @@ RSpec.describe Bundler::UI do
expect(methods.call(described_class)).to eq(methods.call(shell))
end
- it "has the same instance class as Shell", :ruby => ">= 1.9" do
+ it "has the same instance class as Shell" do
shell = Bundler::UI::Shell
methods = proc do |cls|
cls.methods.map do |i|
diff --git a/spec/bundler/bundler/uri_credentials_filter_spec.rb b/spec/bundler/bundler/uri_credentials_filter_spec.rb
index 1dd01b4be0..641f0addb4 100644
--- a/spec/bundler/bundler/uri_credentials_filter_spec.rb
+++ b/spec/bundler/bundler/uri_credentials_filter_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe Bundler::URICredentialsFilter do
subject { described_class }
@@ -17,7 +16,7 @@ RSpec.describe Bundler::URICredentialsFilter 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(URI("https://x-oauth-basic@github.com/company/private-repo").to_s)
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://x-oauth-basic@github.com/company/private-repo").to_s)
end
it_behaves_like "original type of uri is maintained"
@@ -27,7 +26,17 @@ RSpec.describe Bundler::URICredentialsFilter 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(URI("https://x@github.com/company/private-repo").to_s)
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://x@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "specified without empty username" do
+ let(:credentials) { "oauth_token@" }
+
+ it "returns the uri without the oauth token" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://github.com/company/private-repo").to_s)
end
it_behaves_like "original type of uri is maintained"
@@ -38,7 +47,7 @@ RSpec.describe Bundler::URICredentialsFilter do
let(:credentials) { "username1:hunter3@" }
it "returns the uri without the password" do
- expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://username1@github.com/company/private-repo").to_s)
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://username1@github.com/company/private-repo").to_s)
end
it_behaves_like "original type of uri is maintained"
@@ -56,7 +65,7 @@ RSpec.describe Bundler::URICredentialsFilter do
end
context "uri is a uri object" do
- let(:uri) { URI("https://#{credentials}github.com/company/private-repo") }
+ let(:uri) { Gem::URI("https://#{credentials}github.com/company/private-repo") }
it_behaves_like "sensitive credentials in uri are filtered out"
end
@@ -91,7 +100,7 @@ RSpec.describe Bundler::URICredentialsFilter do
describe "#credential_filtered_string" do
let(:str_to_filter) { "This is a git message containing a uri #{uri}!" }
let(:credentials) { "" }
- let(:uri) { URI("https://#{credentials}github.com/company/private-repo") }
+ let(:uri) { Gem::URI("https://#{credentials}github.com/company/private-repo") }
context "with a uri that contains credentials" do
let(:credentials) { "oauth_token:x-oauth-basic@" }
diff --git a/spec/bundler/bundler/uri_normalizer_spec.rb b/spec/bundler/bundler/uri_normalizer_spec.rb
new file mode 100644
index 0000000000..1308e86014
--- /dev/null
+++ b/spec/bundler/bundler/uri_normalizer_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::URINormalizer do
+ describe ".normalize_suffix" do
+ context "when trailing_slash is true" do
+ it "adds a trailing slash when missing" do
+ expect(described_class.normalize_suffix("https://example.com", trailing_slash: true)).to eq("https://example.com/")
+ end
+
+ it "keeps the trailing slash when present" do
+ expect(described_class.normalize_suffix("https://example.com/", trailing_slash: true)).to eq("https://example.com/")
+ end
+ end
+
+ context "when trailing_slash is false" do
+ it "removes a trailing slash when present" do
+ expect(described_class.normalize_suffix("https://example.com/", trailing_slash: false)).to eq("https://example.com")
+ end
+
+ it "keeps the value unchanged when no trailing slash exists" do
+ expect(described_class.normalize_suffix("https://example.com", trailing_slash: false)).to eq("https://example.com")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/version_ranges_spec.rb b/spec/bundler/bundler/version_ranges_spec.rb
deleted file mode 100644
index f746aa88ad..0000000000
--- a/spec/bundler/bundler/version_ranges_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-require "bundler/version_ranges"
-
-RSpec.describe Bundler::VersionRanges do
- describe ".empty?" do
- shared_examples_for "empty?" do |exp, *req|
- it "returns #{exp} for #{req}" do
- r = Gem::Requirement.new(*req)
- ranges = described_class.for(r)
- expect(described_class.empty?(*ranges)).to eq(exp), "expected `#{r}` #{exp ? "" : "not "}to be empty"
- end
- end
-
- include_examples "empty?", false
- include_examples "empty?", false, "!= 1"
- include_examples "empty?", false, "!= 1", "= 2"
- include_examples "empty?", false, "!= 1", "> 1"
- include_examples "empty?", false, "!= 1", ">= 1"
- include_examples "empty?", false, "= 1", ">= 0.1", "<= 1.1"
- include_examples "empty?", false, "= 1", ">= 1", "<= 1"
- include_examples "empty?", false, "= 1", "~> 1"
- include_examples "empty?", false, ">= 0.z", "= 0"
- include_examples "empty?", false, ">= 0"
- include_examples "empty?", false, ">= 1.0.0", "< 2.0.0"
- include_examples "empty?", false, "~> 1"
- include_examples "empty?", false, "~> 2.0", "~> 2.1"
- include_examples "empty?", true, "!= 1", "< 2", "> 2"
- include_examples "empty?", true, "!= 1", "<= 1", ">= 1"
- include_examples "empty?", true, "< 2", "> 2"
- include_examples "empty?", true, "= 1", "!= 1"
- include_examples "empty?", true, "= 1", "= 2"
- include_examples "empty?", true, "= 1", "~> 2"
- include_examples "empty?", true, ">= 0", "<= 0.a"
- include_examples "empty?", true, "~> 2.0", "~> 3"
- end
-end
diff --git a/spec/bundler/bundler/worker_spec.rb b/spec/bundler/bundler/worker_spec.rb
index fbfe6ddab3..2ad2845e37 100644
--- a/spec/bundler/bundler/worker_spec.rb
+++ b/spec/bundler/bundler/worker_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/worker"
RSpec.describe Bundler::Worker do
@@ -19,4 +19,71 @@ RSpec.describe Bundler::Worker do
end
end
end
+
+ describe "priority queue" do
+ it "process elements from the priority queue first" do
+ processed_elements = []
+
+ function = proc do |element, _|
+ processed_elements << element
+ end
+
+ worker = described_class.new(1, "Spec Worker", function)
+ worker.instance_variable_set(:@threads, []) # Prevent the enqueueing from starting work.
+ worker.enq("Normal element")
+ worker.enq("Priority element", priority: true)
+ worker.send(:create_threads)
+
+ worker.stop
+
+ expect(processed_elements).to eq(["Priority element", "Normal element"])
+ 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
index c28db59223..9ff1579b76 100644
--- a/spec/bundler/bundler/yaml_serializer_spec.rb
+++ b/spec/bundler/bundler/yaml_serializer_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "bundler/yaml_serializer"
RSpec.describe Bundler::YAMLSerializer do
@@ -9,7 +9,7 @@ RSpec.describe Bundler::YAMLSerializer do
it "works for simple hash" do
hash = { "Q" => "Where does Thursday come before Wednesday? In the dictionary. :P" }
- expected = strip_whitespace <<-YAML
+ expected = <<~YAML
---
Q: "Where does Thursday come before Wednesday? In the dictionary. :P"
YAML
@@ -24,7 +24,7 @@ RSpec.describe Bundler::YAMLSerializer do
},
}
- expected = strip_whitespace <<-YAML
+ expected = <<~YAML
---
nice-one:
read_ahead: "All generalizations are false, including this one"
@@ -45,7 +45,7 @@ RSpec.describe Bundler::YAMLSerializer do
},
}
- expected = strip_whitespace <<-YAML
+ expected = <<~YAML
---
nested_hash:
contains_array:
@@ -57,11 +57,24 @@ RSpec.describe Bundler::YAMLSerializer do
expect(serializer.dump(hash)).to eq(expected)
end
+
+ it "handles empty array" do
+ hash = {
+ "empty_array" => [],
+ }
+
+ expected = <<~YAML
+ ---
+ empty_array: []
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
end
describe "#load" do
it "works for simple hash" do
- yaml = strip_whitespace <<-YAML
+ yaml = <<~YAML
---
Jon: "Air is free dude!"
Jack: "Yes.. until you buy a bag of chips!"
@@ -76,7 +89,7 @@ RSpec.describe Bundler::YAMLSerializer do
end
it "works for nested hash" do
- yaml = strip_whitespace <<-YAML
+ yaml = <<~YAML
---
baa:
baa: "black sheep"
@@ -98,15 +111,15 @@ RSpec.describe Bundler::YAMLSerializer do
end
it "handles colon in key/value" do
- yaml = strip_whitespace <<-YAML
- BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://rubygems-mirror.org
+ yaml = <<~YAML
+ BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://example-mirror.rubygems.org
YAML
- expect(serializer.load(yaml)).to eq("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org")
+ expect(serializer.load(yaml)).to eq("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://example-mirror.rubygems.org")
end
it "handles arrays inside hashes" do
- yaml = strip_whitespace <<-YAML
+ yaml = <<~YAML
---
nested_hash:
contains_array:
@@ -127,7 +140,7 @@ RSpec.describe Bundler::YAMLSerializer do
end
it "handles windows-style CRLF line endings" do
- yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n")
+ yaml = <<~YAML.gsub("\n", "\r\n")
---
nested_hash:
contains_array:
@@ -148,6 +161,34 @@ RSpec.describe Bundler::YAMLSerializer do
expect(serializer.load(yaml)).to eq(hash)
end
+
+ it "handles empty array" do
+ yaml = <<~YAML
+ ---
+ empty_array: []
+ YAML
+
+ hash = {
+ "empty_array" => [],
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "skip commented out words" do
+ yaml = <<~YAML
+ ---
+ foo: bar
+ buzz: foo # bar
+ YAML
+
+ hash = {
+ "foo" => "bar",
+ "buzz" => "foo",
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
end
describe "against yaml lib" do
@@ -156,6 +197,7 @@ RSpec.describe Bundler::YAMLSerializer 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" => [
diff --git a/spec/bundler/cache/cache_path_spec.rb b/spec/bundler/cache/cache_path_spec.rb
index ec6d6e312a..2a280ea858 100644
--- a/spec/bundler/cache/cache_path_spec.rb
+++ b/spec/bundler/cache/cache_path_spec.rb
@@ -1,34 +1,32 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle package" do
before do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
context "with --cache-path" do
it "caches gems at given path" do
- bundle :package, "cache-path" => "vendor/cache-foo"
- expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist
+ bundle :cache, "cache-path" => "vendor/cache-foo"
+ expect(bundled_app("vendor/cache-foo/myrack-1.0.0.gem")).to exist
end
end
context "with config cache_path" do
it "caches gems at given path" do
- bundle "config cache_path vendor/cache-foo"
- bundle :package
- expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist
+ bundle_config "cache_path vendor/cache-foo"
+ bundle :cache
+ expect(bundled_app("vendor/cache-foo/myrack-1.0.0.gem")).to exist
end
end
- context "when given an absolute path" do
- it "exits with non-zero status" do
- bundle :package, "cache-path" => "/tmp/cache-foo"
- expect(out).to match(/must be relative/)
- expect(exitstatus).to eq(15) if exitstatus
+ context "with absolute --cache-path" do
+ it "caches gems at given path" do
+ bundle :cache, "cache-path" => bundled_app("vendor/cache-foo")
+ expect(bundled_app("vendor/cache-foo/myrack-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
index 7828c87fec..198279d84c 100644
--- a/spec/bundler/cache/gems_spec.rb
+++ b/spec/bundler/cache/gems_spec.rb
@@ -1,26 +1,26 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle cache" do
- describe "when there are only gemsources" do
+ shared_examples_for "when there are only gemsources" do
before :each do
gemfile <<-G
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
- system_gems "rack-1.0.0"
+ system_gems "myrack-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
+ expect(bundled_app("vendor/cache/myrack-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")
+ build_gem "omg", path: bundled_app("vendor/cache")
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "omg"
G
@@ -28,118 +28,167 @@ RSpec.describe "bundle cache" do
end
it "uses the cache as a source when installing gems with --local" do
- system_gems []
+ system_gems [], path: default_bundle_path
bundle "install --local"
- expect(the_bundle).to include_gems("rack 1.0.0")
+ expect(the_bundle).to include_gems("myrack 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'"
+ build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
end
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- expect(the_bundle).to include_gems("rack 1.0.0")
+ expect(the_bundle).to include_gems("myrack 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"
+ system_gems "myrack-1.0.0", path: default_bundle_path
gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
- s.write "lib/rack.rb", "RACK = 'FAIL'"
+ build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
end
- bundle "install --local"
- expect(the_bundle).to include_gems("rack 1.0.0")
+ bundle :install, local: true
+ expect(the_bundle).to include_gems("myrack 1.0.0")
end
it "creates a lockfile" do
- cache_gems "rack-1.0.0"
+ cache_gems "myrack-1.0.0"
gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
bundle "cache"
- expect(bundled_app("Gemfile.lock")).to exist
+ expect(bundled_app_lock).to exist
end
end
- describe "when there is a built-in gem", :ruby => "2.0" do
+ context "using system gems" do
+ before { bundle_config "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 "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 "builtin_gem", "1.0.2"
+ build_gem "json", default_json_version, to_system: true, default: true
+ end
+
+ context "when a remote gem is available for caching" do
+ before do
+ build_repo2 do
+ build_gem "json", default_json_version
+ end
end
- build_gem "builtin_gem", "1.0.2", :to_system => true do |s|
- s.summary = "This builtin_gem is bundled with Ruby"
+ it "uses remote gems when installing" do
+ install_gemfile %(source "https://gem.repo2"; gem 'json', '#{default_json_version}'), verbose: true
+ expect(out).to include("Installing json #{default_json_version}")
end
- FileUtils.rm("#{system_gem_path}/cache/builtin_gem-1.0.2.gem")
- end
+ it "does not use remote gems when installing with --local flag" do
+ install_gemfile %(source "https://gem.repo2"; gem 'json', '#{default_json_version}'), verbose: true, local: true
+ expect(out).to include("Using json #{default_json_version}")
+ end
- it "uses builtin gems" do
- install_gemfile %(gem 'builtin_gem', '1.0.2')
- expect(the_bundle).to include_gems("builtin_gem 1.0.2")
- end
+ it "does not use remote gems when installing with --prefer-local flag" do
+ install_gemfile %(source "https://gem.repo2"; gem 'json', '#{default_json_version}'), verbose: true, "prefer-local": true
+ expect(out).to include("Using json #{default_json_version}")
+ end
- it "caches remote and builtin gems" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'builtin_gem', '1.0.2'
- gem 'rack', '1.0.0'
- G
+ it "caches remote and builtin gems" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem 'json', '#{default_json_version}'
+ gem 'myrack', '1.0.0'
+ G
- bundle :cache
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
- expect(bundled_app("vendor/cache/builtin_gem-1.0.2.gem")).to exist
- end
+ bundle :cache
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ 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"
+ it "caches builtin gems when cache_all_platforms is set" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "json"
+ G
+
+ bundle_config "cache_all_platforms true"
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist
end
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'builtin_gem_2', '1.0.2'
- G
+ it "doesn't make remote request after caching the gem" do
+ build_gem "builtin_gem_2", "1.0.2", path: bundled_app("vendor/cache"), default: true
- bundle "install --local"
- expect(the_bundle).to include_gems("builtin_gem_2 1.0.2")
+ install_gemfile <<-G
+ source "https://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
end
- it "errors if the builtin gem isn't available to cache" do
- install_gemfile <<-G
- gem 'builtin_gem', '1.0.2'
- G
+ context "when a remote gem is not available for caching" do
+ it "warns, but uses builtin gems when installing to system gems" do
+ bundle_config "path.system true"
+ install_gemfile %(source "https://gem.repo1"; gem 'json', '#{default_json_version}'), verbose: true
+ expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached")
+ expect(out).to include("Using json #{default_json_version}")
+ end
- bundle :cache
- expect(exitstatus).to_not eq(0) if exitstatus
- expect(out).to include("builtin_gem-1.0.2 is built in to Ruby, and can't be cached")
+ it "errors when explicitly caching" do
+ bundle_config "path.system true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem 'json', '#{default_json_version}'
+ G
+
+ bundle :cache, raise_on_error: false
+ expect(last_command).to be_failure
+ expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached")
+ end
end
end
describe "when there are also git sources" do
before do
build_git "foo"
- system_gems "rack-1.0.0"
+ system_gems "myrack-1.0.0"
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
- gem 'rack'
+ gem 'myrack'
G
end
@@ -149,49 +198,57 @@ RSpec.describe "bundle cache" do
system_gems []
bundle "install --local"
- expect(the_bundle).to include_gems("rack 1.0.0", "foo 1.0")
+ expect(the_bundle).to include_gems("myrack 1.0.0", "foo 1.0")
end
it "should not explode if the lockfile is not present" do
- FileUtils.rm(bundled_app("Gemfile.lock"))
+ FileUtils.rm(bundled_app_lock)
bundle :cache
- expect(bundled_app("Gemfile.lock")).to exist
+ expect(bundled_app_lock).to exist
end
end
describe "when previously cached" do
- before :each do
+ let :setup_main_repo do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack"
+ source "https://gem.repo2"
+ gem "myrack"
gem "actionpack"
G
bundle :cache
- expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("myrack-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
+ setup_main_repo
+ FileUtils.rm_rf cached_gem("myrack-1.0.0")
bundle :install
expect(out).to include("Updating files in vendor/cache")
- expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("myrack-1.0.0")).to exist
end
it "adds and removes when gems are updated" do
- update_repo2
- bundle "update"
- expect(cached_gem("rack-1.2")).to exist
- expect(cached_gem("rack-1.0.0")).not_to exist
+ setup_main_repo
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+ end
+
+ bundle "update", all: true
+ expect(cached_gem("myrack-1.2")).to exist
+ expect(cached_gem("myrack-1.0.0")).not_to exist
end
it "adds new gems and dependencies" do
+ setup_main_repo
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "rails"
G
expect(cached_gem("rails-2.3.2")).to exist
@@ -199,24 +256,26 @@ RSpec.describe "bundle cache" do
end
it "removes .gems for removed gems and dependencies" do
+ setup_main_repo
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack"
+ source "https://gem.repo2"
+ gem "myrack"
G
- expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("myrack-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"
+ setup_main_repo
+ build_git "myrack"
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack", :git => "#{lib_path("rack-1.0")}"
+ source "https://gem.repo2"
+ gem "myrack", :git => "#{lib_path("myrack-1.0")}"
gem "actionpack"
G
- expect(cached_gem("rack-1.0.0")).not_to exist
+ expect(cached_gem("myrack-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
@@ -224,7 +283,7 @@ RSpec.describe "bundle cache" do
it "doesn't remove gems that are for another platform" do
simulate_platform "java" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "platform_specific"
G
@@ -232,37 +291,111 @@ RSpec.describe "bundle cache" do
expect(cached_gem("platform_specific-1.0-java")).to exist
end
- simulate_new_machine
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
- G
+ pristine_system_gems
+
+ simulate_platform "x86-darwin-100" do
+ install_gemfile <<-G
+ source "https://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
+ expect(cached_gem("platform_specific-1.0-x86-darwin-100")).to exist
+ expect(cached_gem("platform_specific-1.0-java")).to exist
+ end
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
+ it "doesn't remove gems cached gems that don't match their remote counterparts, but also refuses to install and prints an error" do
+ setup_main_repo
+ cached_myrack = cached_gem("myrack-1.0.0")
+ FileUtils.rm_rf cached_myrack
+ build_gem "myrack", "1.0.0",
+ path: cached_myrack.parent,
+ rubygems_version: "1.3.2"
+
+ FileUtils.rm_r default_bundle_path
+ default_system_gems
+
+ FileUtils.rm bundled_app_lock
+ bundle :install, raise_on_error: false
+
+ expect(err).to eq <<~E.strip
+ Bundler found mismatched checksums. This is a potential security risk.
+ #{checksum_to_lock(gem_repo2, "myrack", "1.0.0")}
+ from the API at https://gem.repo2/
+ #{checksum_from_package(cached_myrack, "myrack", "1.0.0")}
+ from the gem at #{cached_myrack}
+
+ If you trust the API at https://gem.repo2/, to resolve this issue you can:
+ 1. remove the gem at #{cached_myrack}
+ 2. run `bundle install`
+
+ To ignore checksum security warnings, disable checksum validation with
+ `bundle config set --local disable_checksum_validation true`
+ E
+
+ expect(cached_gem("myrack-1.0.0")).to exist
+ end
+ it "raises an error when a cached gem is altered and produces a different checksum than the remote gem" do
+ setup_main_repo
+ FileUtils.rm_rf cached_gem("myrack-1.0.0")
+ build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache")
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ end
+
+ FileUtils.rm_r default_bundle_path
+ default_system_gems
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+ #{checksums}
+ L
+
+ bundle :install, raise_on_error: false
+ expect(exitstatus).to eq(37)
+ expect(err).to include("Bundler found mismatched checksums.")
+ expect(err).to include("1. remove the gem at #{cached_gem("myrack-1.0.0")}")
+
+ expect(cached_gem("myrack-1.0.0")).to exist
+ FileUtils.rm_rf cached_gem("myrack-1.0.0")
bundle :install
- expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("myrack-1.0.0")).to exist
+ end
+
+ it "installs a modified gem with a non-matching checksum when the API implementation does not provide checksums" do
+ setup_main_repo
+ FileUtils.rm_rf cached_gem("myrack-1.0.0")
+ build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache")
+ pristine_system_gems
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+ L
+
+ bundle :install, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(cached_gem("myrack-1.0.0")).to exist
end
it "handles directories and non .gem files in the cache" do
+ setup_main_repo
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
+ setup_main_repo
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
bundle "cache"
bundle "install"
@@ -270,9 +403,10 @@ RSpec.describe "bundle cache" do
end
it "does not warn about all if it doesn't have any git/path dependency" do
+ setup_main_repo
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
bundle "cache"
expect(out).not_to match(/\-\-all/)
@@ -280,9 +414,10 @@ RSpec.describe "bundle cache" do
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")
+ path: bundled_app("vendor/cache")
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo-bundler"
G
diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb
index 31b3816a3b..f0976ecac7 100644
--- a/spec/bundler/cache/git_spec.rb
+++ b/spec/bundler/cache/git_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "git base name" do
it "base_name should strip private repo uris" do
@@ -13,203 +12,500 @@ RSpec.describe "git base name" do
end
end
-%w(cache package).each do |cmd|
- RSpec.describe "bundle #{cmd} with git" do
- it "copies repository to vendor cache and uses it" do
- git = build_git "foo"
- ref = git.ref_for("master", 11)
+RSpec.describe "bundle cache with git" do
+ it "does not copy repository to vendor cache when cache_all set to false" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
- bundle "#{cmd} --all"
- 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
+ bundle_config "cache_all false"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).not_to exist
- FileUtils.rm_rf lib_path("foo-1.0")
- expect(the_bundle).to include_gems "foo 1.0"
- end
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
- it "copies repository to vendor cache and uses it even when installed with bundle --path" do
- git = build_git "foo"
- ref = git.ref_for("master", 11)
+ it "copies repository to vendor cache and uses it" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
- bundle "install --path vendor/bundle"
- bundle "#{cmd} --all"
+ 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
- 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_r 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 "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle_config "path vendor/bundle"
+ bundle "install"
+ 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_r lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
- FileUtils.rm_rf lib_path("foo-1.0")
- expect(the_bundle).to include_gems "foo 1.0"
+ it "runs twice without exploding" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :cache
+ bundle :cache
+
+ expect(out).to include "Updating files in vendor/cache"
+ FileUtils.rm_r 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 "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :cache
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
end
- it "runs twice without exploding" do
- build_git "foo"
+ ref = git.ref_for("main", 11)
+ expect(ref).not_to eq(old_ref)
+
+ bundle "update", all: true
+ bundle :cache
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ 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_r lib_path("foo-1.0")
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
- bundle "#{cmd} --all"
- bundle "#{cmd} --all"
+ it "tracks updates when specifying the gem" do
+ git = build_git "foo"
+ old_ref = git.ref_for("main", 11)
- expect(err).to lack_errors
- FileUtils.rm_rf lib_path("foo-1.0")
- expect(the_bundle).to include_gems "foo 1.0"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :cache
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
end
- it "tracks updates" do
- git = build_git "foo"
- old_ref = git.ref_for("master", 11)
+ ref = git.ref_for("main", 11)
+ expect(ref).not_to eq(old_ref)
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ bundle "update foo"
- bundle "#{cmd} --all"
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist
- update_git "foo" do |s|
- s.write "lib/foo.rb", "puts :CACHE"
- end
+ FileUtils.rm_r lib_path("foo-1.0")
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
- ref = git.ref_for("master", 11)
- expect(ref).not_to eq(old_ref)
+ it "uses the local repository to generate the cache" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
- bundle "update"
- bundle "#{cmd} --all"
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :main
+ G
- expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
- expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist
+ bundle %(config set local.foo #{lib_path("foo-1.0")})
+ bundle "install"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/foo-invalid-#{ref}")).to exist
- FileUtils.rm_rf lib_path("foo-1.0")
- run "require 'foo'"
- expect(out).to eq("CACHE")
+ # Updating the local still uses the local.
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :LOCAL"
end
- it "tracks updates when specifying the gem" do
- git = build_git "foo"
- old_ref = git.ref_for("master", 11)
+ run "require 'foo'"
+ expect(out).to eq("LOCAL")
+ end
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ it "can use gems after copying install folder to a different machine with git not installed" do
+ build_git "foo"
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle_config "path vendor/bundle"
+ bundle :install
+
+ pristine_system_gems
+ with_path_as "" do
+ bundle_config "deployment true"
+ bundle "install --local"
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+ end
- bundle "#{cmd} --all"
+ it "can install after bundle cache without cloning remote repositories" do
+ build_git "foo"
- update_git "foo" do |s|
- s.write "lib/foo.rb", "puts :CACHE"
- end
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :cache, "all-platforms" => true
- ref = git.ref_for("master", 11)
- expect(ref).not_to eq(old_ref)
+ pristine_system_gems
+ bundle_config "frozen true"
+ bundle "install --local --verbose"
+ expect(out).to_not include("Fetching")
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
- bundle "update foo"
+ it "can install after bundle cache without cloning remote repositories even without the original cache" do
+ build_git "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
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :cache, "all-platforms" => true
+
+ pristine_system_gems
+ bundle_config "frozen true"
+ bundle "install --local --verbose"
+ expect(out).to_not include("Fetching")
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+
+ it "can install after bundle cache without cloning remote repositories with only git tracked files" do
+ build_git "foo"
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :cache, "all-platforms" => true
- FileUtils.rm_rf lib_path("foo-1.0")
- run "require 'foo'"
- expect(out).to eq("CACHE")
+ pristine_system_gems
+ bundle_config "frozen true"
+
+ # Remove untracked files (including the empty refs dir in the cache)
+ Dir.chdir(bundled_app) do
+ system(*%W[git init --quiet])
+ system(*%W[git add --all])
+ system(*%W[git clean -d --force --quiet])
end
- it "uses the local repository to generate the cache" do
- git = build_git "foo"
- ref = git.ref_for("master", 11)
+ bundle "install --local --verbose"
+ expect(out).to_not include("Fetching")
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
- gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :master
- G
+ it "installs properly a bundler 2.5.17-2.5.23 cache as a bare repository without cloning remote repositories" do
+ git = build_git "foo"
- bundle %(config local.foo #{lib_path("foo-1.0")})
- bundle "install"
- bundle "#{cmd} --all"
+ short_ref = git.ref_for("main", 11)
+ cache_dir = bundled_app("vendor/cache/foo-1.0-#{short_ref}")
- expect(bundled_app("vendor/cache/foo-invalid-#{ref}")).to exist
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle_config "global_gem_cache false"
+ bundle_config "path vendor/bundle"
+ bundle :install
- # Updating the local still uses the local.
- update_git "foo" do |s|
- s.write "lib/foo.rb", "puts :LOCAL"
- end
+ # Simulate old cache by copying the real cache folder to vendor/cache
+ FileUtils.mkdir_p bundled_app("vendor/cache")
+ FileUtils.cp_r "#{Dir.glob(vendored_gems("cache/bundler/git/foo-1.0-*")).first}/.", cache_dir
+ FileUtils.rm_r bundled_app("vendor/bundle")
+
+ bundle "install --local --verbose"
+ expect(err).to include("Installing from cache in old \"bare repository\" format for compatibility")
+
+ expect(out).to_not include("Fetching")
- run "require 'foo'"
- expect(out).to eq("LOCAL")
+ # leaves old cache alone
+ expect(cache_dir.join("lib/foo.rb")).not_to exist
+ expect(cache_dir.join("HEAD")).to exist
+
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+
+ it "migrates a bundler 2.5.17-2.5.23 cache as a bare repository when not running with --local" do
+ git = build_git "foo"
+
+ short_ref = git.ref_for("main", 11)
+ cache_dir = bundled_app("vendor/cache/foo-1.0-#{short_ref}")
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle_config "global_gem_cache false"
+ bundle_config "path vendor/bundle"
+ bundle :install
+
+ # Simulate old cache by copying the real cache folder to vendor/cache
+ FileUtils.mkdir_p bundled_app("vendor/cache")
+ FileUtils.cp_r "#{Dir.glob(vendored_gems("cache/bundler/git/foo-1.0-*")).first}/.", cache_dir
+ FileUtils.rm_r bundled_app("vendor/bundle")
+
+ bundle "install --verbose"
+ expect(out).to include("Fetching")
+
+ # migrates old cache alone
+ expect(cache_dir.join("lib/foo.rb")).to exist
+ expect(cache_dir.join("HEAD")).not_to exist
+
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+
+ it "migrates a bundler 2.5.17-2.5.23 cache as a bare repository when running `bundle cache`, even if gems already installed" do
+ git = build_git "foo"
+
+ short_ref = git.ref_for("main", 11)
+ cache_dir = bundled_app("vendor/cache/foo-1.0-#{short_ref}")
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle_config "global_gem_cache false"
+ bundle_config "path vendor/bundle"
+ bundle :install
+
+ # Simulate old cache by copying the real cache folder to vendor/cache
+ FileUtils.mkdir_p bundled_app("vendor/cache")
+ FileUtils.cp_r "#{Dir.glob(vendored_gems("cache/bundler/git/foo-1.0-*")).first}/.", cache_dir
+
+ bundle "cache"
+
+ # migrates old cache alone
+ expect(cache_dir.join("lib/foo.rb")).to exist
+ expect(cache_dir.join("HEAD")).not_to exist
+
+ expect(the_bundle).to include_gem "foo 1.0"
+ 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
- it "copies repository to vendor cache, including submodules" do
- build_git "submodule", "1.0"
+ git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
+ git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")
- git = build_git "has_submodule", "1.0" do |s|
- s.add_dependency "submodule"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
end
+ G
- Dir.chdir(lib_path("has_submodule-1.0")) do
- sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
- `git commit -m "submodulator"`
- end
+ ref = git.ref_for("main", 11)
+ bundle :cache
- install_gemfile <<-G
- git "#{lib_path("has_submodule-1.0")}", :submodules => true do
- gem "has_submodule"
- end
- G
+ 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
- ref = git.ref_for("master", 11)
- bundle "#{cmd} --all"
+ it "caches pre-evaluated gemspecs" do
+ git = build_git "foo"
- 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
+ # 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 "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :cache
- it "displays warning message when detecting git repo in Gemfile" do
- build_git "foo"
+ 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
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ it "can install after bundle cache with git not installed" do
+ build_git "foo"
- bundle "#{cmd}"
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :cache, "all-platforms" => true, :install => false
- expect(out).to include("Your Gemfile contains path and git dependencies.")
+ pristine_system_gems
+ with_path_as "" do
+ bundle_config "deployment true"
+ bundle :install, local: true
+ expect(the_bundle).to include_gem "foo 1.0"
end
+ end
- it "does not display warning message if cache_all is set in bundle config" do
- build_git "foo"
+ it "can install after bundle cache generated with an older Bundler that kept checkouts in the cache" do
+ git = build_git("foo")
+ locked_revision = git.ref_for("main")
+ path_revision = git.ref_for("main", 11)
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
+ git_path = lib_path("foo-1.0")
- bundle "#{cmd} --all"
- bundle "#{cmd}"
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{git_path}'
+ G
+ lockfile <<~L
+ GIT
+ remote: #{git_path}/
+ revision: #{locked_revision}
+ specs:
+ foo (1.0)
- expect(out).not_to include("Your Gemfile contains path and git dependencies.")
- end
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
- it "caches pre-evaluated gemspecs" do
- git = build_git "foo"
+ DEPENDENCIES
+ 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") }
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
- install_gemfile <<-G
- gem "foo", :git => '#{lib_path("foo-1.0")}'
- G
- bundle "#{cmd} --all"
+ # Simulate an old incorrect situation where vendor/cache would be the install location of git gems
+ FileUtils.mkdir_p bundled_app("vendor/cache")
+ FileUtils.cp_r git_path, bundled_app("vendor/cache/foo-1.0-#{path_revision}")
+ FileUtils.rm_r bundled_app("vendor/cache/foo-1.0-#{path_revision}/.git")
+
+ bundle :install, env: { "BUNDLE_DEPLOYMENT" => "true", "BUNDLE_CACHE_ALL" => "true" }
+ end
- ref = git.ref_for("master", 11)
- gemspec = bundled_app("vendor/cache/foo-1.0-#{ref}/foo.gemspec").read
- expect(gemspec).to_not match("`echo bob`")
+ it "respects the --no-install flag" do
+ git = build_git "foo", &:add_c_extension
+ ref = git.ref_for("main", 11)
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ # 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 4 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
+
+ it "doesn't fail when git gem has extensions and an empty cache folder is present before bundle install" do
+ build_git "puma" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.executables = "puma"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/puma.rb", "w") do |f|
+ f.puts "PUMA = 'YES'"
+ end
+ end
+ RUBY
end
+
+ FileUtils.mkdir_p(bundled_app("vendor/cache"))
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "puma", :git => "#{lib_path("puma-1.0")}"
+ G
+
+ bundle "exec puma"
+
+ expect(out).to eq("YES")
end
end
diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb
index bbce448759..42648aea1f 100644
--- a/spec/bundler/cache/path_spec.rb
+++ b/spec/bundler/cache/path_spec.rb
@@ -1,140 +1,121 @@
# frozen_string_literal: true
-require "spec_helper"
-%w(cache package).each do |cmd|
- RSpec.describe "bundle #{cmd} with path" do
- it "is no-op when the path is within the bundle" do
- build_lib "foo", :path => bundled_app("lib/foo")
+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
- gem "foo", :path => '#{bundled_app("lib/foo")}'
- G
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :path => '#{bundled_app("lib/foo")}'
+ G
- bundle "#{cmd} --all"
- 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
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- G
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
- bundle "#{cmd} --all"
- expect(bundled_app("vendor/cache/foo-1.0")).to exist
- expect(bundled_app("vendor/cache/foo-1.0/.bundlecache")).to be_file
+ it "copies when the path is outside the bundle " do
+ build_lib "foo"
- FileUtils.rm_rf lib_path("foo-1.0")
- expect(the_bundle).to include_gems "foo 1.0"
- end
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
- it "copies when the path is outside the bundle and the paths intersect" do
- libname = File.basename(Dir.pwd) + "_gem"
- libpath = File.join(File.dirname(Dir.pwd), libname)
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0/.bundlecache")).to be_file
- build_lib libname, :path => libpath
-
- install_gemfile <<-G
- gem "#{libname}", :path => '#{libpath}'
- G
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
- bundle "#{cmd} --all"
- expect(bundled_app("vendor/cache/#{libname}")).to exist
- expect(bundled_app("vendor/cache/#{libname}/.bundlecache")).to be_file
+ 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)
- FileUtils.rm_rf libpath
- expect(the_bundle).to include_gems "#{libname} 1.0"
- end
+ build_lib libname, path: libpath
- it "updates the path on each cache" do
- build_lib "foo"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "#{libname}", :path => '#{libpath}'
+ G
- install_gemfile <<-G
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- G
+ bundle :cache
+ expect(bundled_app("vendor/cache/#{libname}")).to exist
+ expect(bundled_app("vendor/cache/#{libname}/.bundlecache")).to be_file
- bundle "#{cmd} --all"
+ expect(the_bundle).to include_gems "#{libname} 1.0"
+ end
- build_lib "foo" do |s|
- s.write "lib/foo.rb", "puts :CACHE"
- end
+ it "updates the path on each cache" do
+ build_lib "foo"
- bundle "#{cmd} --all"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
- expect(bundled_app("vendor/cache/foo-1.0")).to exist
- FileUtils.rm_rf lib_path("foo-1.0")
+ bundle :cache
- run "require 'foo'"
- expect(out).to eq("CACHE")
+ build_lib "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
end
- it "removes stale entries cache" do
- build_lib "foo"
+ bundle :cache
- install_gemfile <<-G
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- G
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
- bundle "#{cmd} --all"
-
- install_gemfile <<-G
- gem "bar", :path => '#{lib_path("bar-1.0")}'
- G
-
- bundle "#{cmd} --all"
- expect(bundled_app("vendor/cache/bar-1.0")).not_to exist
- end
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
- it "raises a warning without --all" do
- build_lib "foo"
+ it "removes stale entries cache" do
+ build_lib "foo"
- install_gemfile <<-G
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- G
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
- bundle cmd
- expect(out).to match(/please pass the \-\-all flag/)
- expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
- end
+ bundle :cache
- it "stores the given flag" do
- build_lib "foo"
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
- install_gemfile <<-G
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- G
+ build_lib "bar"
- bundle "#{cmd} --all"
- build_lib "bar"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "bar", :path => '#{lib_path("bar-1.0")}'
+ G
- install_gemfile <<-G
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- gem "bar", :path => '#{lib_path("bar-1.0")}'
- G
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ end
- bundle cmd
- expect(bundled_app("vendor/cache/bar-1.0")).to exist
- end
+ it "does not cache path gems if cache_all is set to false" do
+ build_lib "foo"
- it "can rewind chosen configuration" do
- build_lib "foo"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+ bundle_config "cache_all false"
- install_gemfile <<-G
- 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
- bundle "#{cmd} --all"
- build_lib "baz"
+ it "caches path gems by default" do
+ build_lib "foo"
- gemfile <<-G
- gem "foo", :path => '#{lib_path("foo-1.0")}'
- gem "baz", :path => '#{lib_path("baz-1.0")}'
- G
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
- bundle "#{cmd} --no-all"
- expect(bundled_app("vendor/cache/baz-1.0")).not_to exist
- end
+ bundle :cache
+ expect(err).to be_empty
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
end
end
diff --git a/spec/bundler/cache/platform_spec.rb b/spec/bundler/cache/platform_spec.rb
index ed80c949aa..71c0eaee8e 100644
--- a/spec/bundler/cache/platform_spec.rb
+++ b/spec/bundler/cache/platform_spec.rb
@@ -1,13 +1,12 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle cache with multiple platforms" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
platforms :mri, :rbx do
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
end
platforms :jruby do
@@ -17,9 +16,9 @@ RSpec.describe "bundle cache with multiple platforms" do
lockfile <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
activesupport (2.3.5)
PLATFORMS
@@ -27,28 +26,24 @@ RSpec.describe "bundle cache with multiple platforms" do
java
DEPENDENCIES
- rack (1.0.0)
+ myrack (1.0.0)
activesupport (2.3.5)
G
- cache_gems "rack-1.0.0", "activesupport-2.3.5"
+ cache_gems "myrack-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(exitstatus).to eq 0 if exitstatus
-
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/myrack-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"
-
- expect(exitstatus).to eq 0 if exitstatus
+ bundle "update", all: true
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/myrack-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
index 4931402c33..162650f2e5 100644
--- a/spec/bundler/commands/add_spec.rb
+++ b/spec/bundler/commands/add_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle add" do
before :each do
@@ -10,100 +9,440 @@ RSpec.describe "bundle add" do
build_gem "bar", "0.12.3"
build_gem "cat", "0.12.3.pre"
build_gem "dog", "1.1.3.pre"
+ build_gem "lemur", "3.1.1.pre.2023.1.1"
end
- install_gemfile <<-G
- source "file://#{gem_repo2}"
+ build_git "foo", "2.0"
+
+ gemfile <<-G
+ source "https://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
+
+ context "when Gemfile is empty, and frozen mode is set" do
+ it "shows error" do
+ gemfile 'source "https://gem.repo2"'
+ bundle "add bar", raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" }
+
+ expect(err).to include("Frozen mode is set, but there's no lockfile")
+ end
+ end
+
describe "without version specified" do
- it "version requirement becomes ~> major.minor.patch when resolved version is < 1.0" 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(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
+ 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(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
+ 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(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
+ 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(bundled_app_gemfile.read).to match(/gem "dog", ">= 1.1.pre"/)
expect(the_bundle).to include_gems "dog 1.1.3.pre"
end
+
+ it "version requirement becomes >= major.minor.pre.tail when resolved version has a very long tail pre version" do
+ bundle "add 'lemur'"
+ # the trailing pre purposely matches the release version to ensure that subbing the release doesn't change the pre.version"
+ expect(bundled_app_gemfile.read).to match(/gem "lemur", ">= 3.1.pre.2023.1.1"/)
+ expect(the_bundle).to include_gems "lemur 3.1.1.pre.2023.1.1"
+ 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(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
- bundle "add 'foo' --version='< 3.0, > 1.1'"
- expect(bundled_app("Gemfile").read).to match(/gem "foo", "< 3.0", "> 1.1"/)
+ 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(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(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://#{gem_repo2}'"
- expect(bundled_app("Gemfile").read).to match(%r{gem "foo", "~> 2.0", :source => "file:\/\/#{gem_repo2}"})
+ bundle "add 'foo' --source='https://gem.repo2'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", source: "https://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
+ before do
+ build_git "rake", "13.0"
+ git("config --global url.file://#{lib_path("rake-13.0")}.insteadOf https://github.com/ruby/rake.git")
+ end
+
+ it "adds dependency with specified github source" do
+ bundle "add rake --github=ruby/rake"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake"})
+ end
+
+ it "adds dependency with specified github source and branch" do
+ bundle "add rake --github=ruby/rake --branch=main"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", branch: "main"})
+ end
+
+ it "adds dependency with specified github source and ref" do
+ ref = revision_for(lib_path("rake-13.0"))
+ bundle "add rake --github=ruby/rake --ref=#{ref}"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", ref: "#{ref}"})
+ end
+
+ it "adds dependency with specified github source and glob" do
+ bundle "add rake --github=ruby/rake --glob='./*.gemspec'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"})
+ end
+
+ it "adds dependency with specified github source, branch and glob" do
+ bundle "add rake --github=ruby/rake --branch=main --glob='./*.gemspec'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", branch: "main", glob: "\.\/\*\.gemspec"})
+ end
+
+ it "adds dependency with specified github source, ref and glob" do
+ ref = revision_for(lib_path("rake-13.0"))
+ bundle "add rake --github=ruby/rake --ref=#{ref} --glob='./*.gemspec'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", ref: "#{ref}", glob: "\.\/\*\.gemspec"})
+ end
+ end
+
+ describe "with --git and --glob" do
+ it "adds dependency with specified git source" do
+ bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", glob: "\./\*\.gemspec"})
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --git and --branch and --glob" 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 --glob='./*.gemspec'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", branch: "test", glob: "\./\*\.gemspec"})
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --git and --ref and --glob" 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"))} --glob='./*.gemspec'"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2\.0", git: "#{lib_path("foo-2.0")}", ref: "#{revision_for(lib_path("foo-2.0"))}", glob: "\./\*\.gemspec"})
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with mismatched pair in --git/--github, --branch/--ref" do
+ describe "with --git and --github" do
+ it "throws error" do
+ bundle "add 'foo' --git x --github y", raise_on_error: false
+
+ expect(err).to include("You cannot specify `--git` and `--github` at the same time.")
+ end
+ end
+
+ describe "with --branch and --ref with --git" do
+ it "throws error" do
+ bundle "add 'foo' --branch x --ref y --git file://git", raise_on_error: false
+
+ expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.")
+ end
+ end
+
+ describe "with --branch but without --git or --github" do
+ it "throws error" do
+ bundle "add 'foo' --branch x", raise_on_error: false
+
+ expect(err).to include("You cannot specify `--branch` unless `--git` or `--github` is specified.")
+ end
+ end
+
+ describe "with --ref but without --git or --github" do
+ it "throws error" do
+ bundle "add 'foo' --ref y", raise_on_error: false
+
+ expect(err).to include("You cannot specify `--ref` unless `--git` or `--github` is specified.")
+ end
+ 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://#{gem_repo2}' -g='development' -v='~>1.0'"
- expect(bundled_app("Gemfile").read).to match(%r{gem "foo", "~> 1.0", :group => \[:development\], :source => "file:\/\/#{gem_repo2}"})
+ bundle "add 'foo' -s='https://gem.repo2' -g='development' -v='~>1.0'"
+ expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 1.0", group: :development, source: "https://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'"
- expect(out).to match("Invalid gem requirement pattern '~>1 . 0'")
+ 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 "add 'werk_it'"
- expect(out).to match("Could not find gem 'werk_it' in any of the gem sources listed in your Gemfile.")
+ bundle_config "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://#{gem_repo2}'"
- expect(out).to match("Could not find gem 'werk_it' in rubygems repository")
+ bundle "add 'werk_it' -s='https://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'"
- expect(out).to include("Could not reach host badhostasdf. Check your network connection and try again.")
+ bundle "add 'baz' --source='http://badhostasdf'", raise_on_error: false, artifice: "fail"
+ 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 "ignores option" 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 --pessimistic" do
+ it "adds pessimistic version" do
+ bundle "add 'foo' --pessimistic"
+ expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --quiet option" do
+ it "is quiet when there are no warnings" do
+ bundle "add 'foo' --quiet"
+ expect(out).to be_empty
+ expect(err).to be_empty
+ end
+
+ it "still displays warning and errors" do
+ create_file("add_with_warning.rb", <<~RUBY)
+ require "#{lib_dir}/bundler"
+ require "#{lib_dir}/bundler/cli"
+ require "#{lib_dir}/bundler/cli/add"
- bundle "add 'baz' --source='file://does/not/exist'"
- expect(out).to include("Could not fetch specs from file://does/not/exist/")
+ module RunWithWarning
+ def run
+ super
+ rescue
+ Bundler.ui.warn "This is a warning"
+ raise
+ end
+ end
+
+ Bundler::CLI::Add.prepend(RunWithWarning)
+ RUBY
+
+ bundle "add 'non-existing-gem' --quiet", raise_on_error: false, env: { "RUBYOPT" => "-r#{bundled_app("add_with_warning.rb")}" }
+ expect(out).to be_empty
+ expect(err).to include("Could not find gem 'non-existing-gem'")
+ expect(err).to include("This is a warning")
+ 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 optimistic 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 --pessimistic and --strict" do
+ it "throws error" do
+ bundle "add 'foo' --strict --pessimistic", raise_on_error: false
+
+ expect(err).to include("You cannot specify `--strict` and `--pessimistic` 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 "https://gem.repo2"
+ gem "myrack", "1.0"
+ G
+
+ bundle "add 'myrack' --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 myrack`. 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 "https://gem.repo2"
+ gem "myrack", "1.0"
+ G
+
+ bundle "add 'myrack'", 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 myrack`. 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 "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle "add 'myrack' --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 myrack`.")
+ 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 'myrack' --version=1.0.0"
+ expect(bundled_app("vendor/cache/myrack-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
index cb0999348e..af4d24a9e8 100644
--- a/spec/bundler/commands/binstubs_spec.rb
+++ b/spec/bundler/commands/binstubs_spec.rb
@@ -1,74 +1,98 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "binstubs rack"
+ bundle "binstubs myrack"
- expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/myrackup")).to exist
end
it "does not install other binstubs" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "rails"
G
bundle "binstubs rails"
- expect(bundled_app("bin/rackup")).not_to exist
+ expect(bundled_app("bin/myrackup")).not_to exist
expect(bundled_app("bin/rails")).to exist
end
it "does install multiple binstubs" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "rails"
G
- bundle "binstubs rails rack"
+ bundle "binstubs rails myrack"
- expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/myrackup")).to exist
expect(bundled_app("bin/rails")).to exist
end
- it "displays an error when used without any gem" do
+ it "allows installing all binstubs" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "rails"
G
- bundle "binstubs"
- expect(exitstatus).to eq(1) if exitstatus
- expect(out).to include("`bundle binstubs` needs at least one gem to run.")
+ bundle :binstubs, all: true
+
+ expect(bundled_app("bin/rails")).to exist
+ expect(bundled_app("bin/rake")).to exist
end
- it "does not bundle the bundler binary" do
+ it "allows installing binstubs for all platforms" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "binstubs bundler"
+ bundle "binstubs myrack --all-platforms"
- expect(bundled_app("bin/bundle")).not_to exist
- expect(out).to include("Sorry, Bundler can only be run via Rubygems.")
+ expect(bundled_app("bin/myrackup")).to exist
+ expect(bundled_app("bin/myrackup.cmd")).to exist
+ end
+
+ it "displays an error when used without any gem" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ 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 "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "binstubs myrack", all: true, raise_on_error: false
+ expect(last_command).to be_failure
+ expect(err).to include("Cannot specify --all with specific gems")
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)
+ build_git "foo", "1.0", path: lib_path("foo") do |s|
+ s.executables = %w[foo]
end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo")}"
G
@@ -80,10 +104,11 @@ RSpec.describe "bundle binstubs <gem>" do
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)
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.executables = %w[foo]
end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo")}"
G
@@ -95,13 +120,25 @@ RSpec.describe "bundle binstubs <gem>" do
it "sets correct permissions for binstubs" do
with_umask(0o002) do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "binstubs myrack"
+ binary = bundled_app("bin/myrackup")
+ 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 "https://gem.repo1"
+ gem "myrack"
G
- bundle "binstubs rack"
- binary = bundled_app("bin/rackup")
- expect(File.stat(binary).mode.to_s(8)).to eq("100775")
+ bundle "binstubs myrack --shebang jruby"
+ expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env jruby\n")
end
end
end
@@ -109,94 +146,123 @@ RSpec.describe "bundle binstubs <gem>" do
context "when the gem doesn't exist" do
it "displays an error with correct status" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
G
- bundle "binstubs doesnt_exist"
+ bundle "binstubs doesnt_exist", raise_on_error: false
- expect(exitstatus).to eq(7) if exitstatus
- expect(out).to include("Could not find gem 'doesnt_exist'.")
+ 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
+ context "with the binstubs dir configured" do
+ before do
+ bundle_config "bin exec"
+ end
+
+ it "creates the binstubs in the configured dir" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "binstubs rack --path exec"
+ bundle "binstubs myrack"
- expect(bundled_app("exec/rackup")).to exist
+ expect(bundled_app("exec/myrackup")).to exist
end
+ end
- it "setting is saved for bundle install" do
+ context "with --standalone option" do
+ before do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "rails"
G
+ end
- bundle "binstubs rack --path exec"
- bundle :install
+ it "generates a standalone binstub" do
+ bundle "binstubs myrack --standalone"
+ expect(bundled_app("bin/myrackup")).to exist
+ end
- expect(bundled_app("exec/rails")).to exist
+ it "generates a binstub that does not depend on rubygems or bundler" do
+ bundle "binstubs myrack --standalone"
+ expect(File.read(bundled_app("bin/myrackup"))).to_not include("Gem.bin_path")
end
- end
- context "after installing with --standalone" do
- before do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- bundle "install --standalone"
+ it "generates a standalone binstub at the given path when configured" do
+ bundle_config "bin foo"
+ bundle "binstubs myrack --standalone"
+ expect(bundled_app("foo/myrackup")).to exist
end
- it "includes the standalone path" do
- bundle "binstubs rack --standalone"
- 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", path.realpath)
+ context "when specified --all-platforms option" do
+ it "generates standalone binstubs for all platforms" do
+ bundle "binstubs myrack --standalone --all-platforms"
+ expect(bundled_app("bin/myrackup")).to exist
+ expect(bundled_app("bin/myrackup.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/myrackup")).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.open(bundled_app("bin/myrackup"), "wb") do |file|
file.print "OMG"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "binstubs rack"
+ bundle "binstubs myrack"
- expect(bundled_app("bin/rackup")).to exist
- expect(File.read(bundled_app("bin/rackup"))).to eq("OMG")
- expect(out).to include("Skipped rackup")
- expect(out).to include("overwrite skipped stubs, use --force")
+ expect(bundled_app("bin/myrackup")).to exist
+ expect(File.read(bundled_app("bin/myrackup"))).to eq("OMG")
+ expect(err).to include("Skipped myrackup")
+ 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.open(bundled_app("bin/myrackup"), "wb") do |file|
file.print "OMG"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "binstubs rack --force"
+ bundle "binstubs myrack --force"
- expect(bundled_app("bin/rackup")).to exist
- expect(File.read(bundled_app("bin/rackup"))).not_to eq("OMG")
+ expect(bundled_app("bin/myrackup")).to exist
+ expect(File.read(bundled_app("bin/myrackup"))).not_to eq("OMG")
end
end
end
@@ -204,58 +270,64 @@ RSpec.describe "bundle binstubs <gem>" do
context "when the gem has no bins" do
it "suggests child gems if they have bins" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack-obama"
G
- bundle "binstubs rack-obama"
- expect(out).to include("rack-obama has no executables")
- expect(out).to include("rack has: rackup")
+ bundle "binstubs myrack-obama"
+ expect(err).to include("myrack-obama has no executables")
+ expect(err).to include("myrack has: myrackup")
end
it "works if child gems don't have bins" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "actionpack"
G
bundle "binstubs actionpack"
- expect(out).to include("no executables for the gem 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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "with_development_dependency"
G
bundle "binstubs with_development_dependency"
- expect(out).to include("no executables for the gem 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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "config 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"
+ bundle_config "auto_install 1"
+ bundle "binstubs myrack"
+ expect(out).to include("Installing myrack 1.0.0")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "does nothing when already up to date" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "config auto_install 1"
- bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => 1 }
- expect(out).not_to include("Installing rack 1.0.0")
+ bundle_config "auto_install 1"
+ bundle "binstubs myrack", env: { "BUNDLE_INSTALL" => "1" }
+ expect(out).not_to include("Installing myrack 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..e223d07f7f
--- /dev/null
+++ b/spec/bundler/commands/cache_spec.rb
@@ -0,0 +1,620 @@
+# 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 "https://gem.repo1"
+ gem "myrack"
+ 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 "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle "cache --gemfile=NotGemfile"
+
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+ end
+
+ context "with cache_all configured" do
+ context "without a gemspec" do
+ it "caches all dependencies except bundler itself" do
+ gemfile <<-D
+ source "https://gem.repo1"
+ gem 'myrack'
+ gem 'bundler'
+ D
+
+ bundle_config "cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/myrack-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 "https://gem.repo1"
+ gem 'myrack'
+ gemspec
+ D
+
+ bundle_config "cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/myrack-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 "https://gem.repo1"
+ gem 'myrack'
+ gemspec
+ D
+
+ bundle_config "cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/myrack-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 "https://gem.repo1"
+ gem 'myrack'
+ gemspec :name => 'mygem'
+ gemspec :name => 'mygem_test'
+ D
+
+ bundle_config "cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/myrack-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 --no-install" do
+ it "puts the gems in vendor/cache but does not install them" do
+ gemfile <<-D
+ source "https://gem.repo1"
+ gem 'myrack'
+ D
+
+ bundle "cache --no-install"
+
+ expect(the_bundle).not_to include_gems "myrack 1.0.0"
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ end
+
+ it "does not prevent installing gems with bundle install" do
+ gemfile <<-D
+ source "https://gem.repo1"
+ gem 'myrack'
+ D
+
+ bundle "cache --no-install"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "does not prevent installing gems with bundle update" do
+ gemfile <<-D
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ D
+
+ bundle "cache --no-install"
+ bundle "update --all"
+
+ expect(the_bundle).to include_gems "myrack 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 "https://gem.repo1"
+ gem 'myrack', :platforms => [:ruby_20, :windows_20]
+ D
+
+ bundle "cache --all-platforms"
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ end
+
+ it "prints a warn when using legacy windows rubies" do
+ gemfile <<-D
+ source "https://gem.repo1"
+ gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20]
+ D
+
+ bundle "cache --all-platforms", raise_on_error: false
+ expect(err).to include("will be removed in the future")
+ expect(bundled_app("vendor/cache/myrack-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 "without wo"
+ install_gemfile <<-G, artifice: "compact_index_extra_api"
+ source "https://main.repo"
+ gem "myrack"
+ group :wo do
+ gem "weakling"
+ gem "uninstallable", :source => "https://main.repo/extra"
+ end
+ G
+
+ bundle :cache, "all-platforms" => true, artifice: "compact_index_extra_api"
+ 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 "myrack 1.0"
+ expect(the_bundle).not_to include_gems "weakling", "uninstallable"
+
+ bundle_config "without wo"
+ bundle :install, artifice: "compact_index_extra_api"
+ expect(the_bundle).to include_gem "myrack 1.0"
+ expect(the_bundle).not_to include_gems "weakling"
+ end
+
+ it "does not fail to cache gems in excluded groups when there's a lockfile but gems not previously installed" do
+ bundle_config "without wo"
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ group :wo do
+ gem "weakling"
+ end
+ G
+
+ bundle :lock
+ bundle :cache, "all-platforms" => true
+ expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist
+ end
+ end
+
+ context "with frozen configured" do
+ let(:app_cache) { bundled_app("vendor/cache") }
+
+ before do
+ bundle_config "frozen true"
+ end
+
+ it "tries to install but fails when the lockfile is out of sync" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ myrack-obama
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ bundle :cache, raise_on_error: false
+ expect(exitstatus).to eq(16)
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have deleted from the Gemfile")
+ expect(err).to include("* myrack-obama")
+ bundle "env"
+ expect(out).to include("frozen")
+ end
+
+ it "caches gems without installing when lockfile is in sync, and --no-install is passed, even if vendor/cache directory is initially empty" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ FileUtils.mkdir_p app_cache
+
+ bundle "cache --no-install"
+ expect(out).not_to include("Installing myrack 1.0.0")
+ expect(out).to include("Fetching myrack 1.0.0")
+ expect(app_cache.join("myrack-1.0.0.gem")).to exist
+ end
+
+ it "completes a partial cache when lockfile is in sync, even if the already cached gem is no longer available remotely" do
+ build_repo4 do
+ build_gem "foo", "1.0.0"
+ end
+
+ build_gem "bar", "1.0.0", path: bundled_app("vendor/cache")
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "foo"
+ gem "bar"
+ G
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ foo (1.0.0)
+ bar (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ bar
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "cache --no-install"
+ expect(out).to include("Fetching foo 1.0.0")
+ expect(out).not_to include("Fetching bar 1.0.0")
+ expect(app_cache.join("foo-1.0.0.gem")).to exist
+ expect(app_cache.join("bar-1.0.0.gem")).to exist
+ 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 myrack' }"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ gem "racc"
+ G
+ end
+
+ it "installs them properly from cache to a different path" do
+ bundle "cache"
+ bundle_config "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 "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle :cache
+ pristine_system_gems
+ FileUtils.rm_r gem_repo2
+
+ bundle "install --local"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "does not hit the remote at all in frozen mode" do
+ build_repo2
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle :cache
+ pristine_system_gems
+ FileUtils.rm_r gem_repo2
+
+ bundle_config "deployment true"
+ bundle_config "path vendor/bundle"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "does not hit the remote at all in non frozen mode either" do
+ build_repo2
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle :cache
+ pristine_system_gems
+ FileUtils.rm_r gem_repo2
+
+ bundle_config "path vendor/bundle"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "does not hit the remote at all when cache_all_platforms configured" do
+ build_repo2
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle :cache
+ pristine_system_gems
+ FileUtils.rm_r gem_repo2
+
+ bundle_config "cache_all_platforms true"
+ bundle_config "path vendor/bundle"
+ bundle "install --local"
+ expect(out).not_to include("Fetching gem metadata")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "uses cached gems for secondary sources when cache_all_platforms configured" do
+ build_repo4 do
+ build_gem "foo", "1.0.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "foo", "1.0.0" do |s|
+ s.platform = "arm64-darwin"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ source "https://gem.repo4" do
+ gem "foo"
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ foo (1.0.0-x86_64-linux)
+ foo (1.0.0-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle_config "cache_all_platforms true"
+ bundle_config "path vendor/bundle"
+ bundle :cache, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+
+ # simulate removal of all remote gems
+ empty_repo4
+
+ # delete compact index cache
+ FileUtils.rm_r home(".bundle/cache/compact_index")
+
+ bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+
+ expect(the_bundle).to include_gems "foo 1.0.0 x86_64-linux"
+ end
+ end
+
+ it "does not reinstall already-installed gems" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ bundle :cache
+
+ build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") do |s|
+ s.write "lib/myrack.rb", "raise 'omg'"
+ end
+
+ bundle :install
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems "myrack 1.0"
+ end
+
+ it "ignores cached gems for the wrong platform" do
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
+ bundle :cache
+ end
+
+ pristine_system_gems
+
+ bundle_config "force_ruby_platform true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
+ expect(the_bundle).to include_gems("platform_specific 1.0 ruby")
+ end
+
+ it "keeps gems that are locked and cached for the current platform, even if incompatible with the current ruby" do
+ build_repo4 do
+ build_gem "bcrypt_pbkdf", "1.1.1"
+ build_gem "bcrypt_pbkdf", "1.1.1" do |s|
+ s.platform = "arm64-darwin"
+ s.required_ruby_version = "< #{current_ruby_minor}"
+ end
+ end
+
+ app_cache = bundled_app("vendor/cache")
+ FileUtils.mkdir_p app_cache
+ FileUtils.cp gem_repo4("gems/bcrypt_pbkdf-1.1.1-arm64-darwin.gem"), app_cache
+ FileUtils.cp gem_repo4("gems/bcrypt_pbkdf-1.1.1.gem"), app_cache
+
+ bundle_config "cache_all_platforms true"
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ bcrypt_pbkdf (1.1.1)
+ bcrypt_pbkdf (1.1.1-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin
+ ruby
+
+ DEPENDENCIES
+ bcrypt_pbkdf
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "arm64-darwin-23" do
+ install_gemfile <<~G, verbose: true
+ source "https://gem.repo4"
+ gem "bcrypt_pbkdf"
+ G
+
+ expect(out).to include("Updating files in vendor/cache")
+ expect(err).to be_empty
+ expect(app_cache.join("bcrypt_pbkdf-1.1.1-arm64-darwin.gem")).to exist
+ expect(app_cache.join("bcrypt_pbkdf-1.1.1.gem")).to exist
+ end
+ end
+
+ it "does not update the cache if --no-cache is passed" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ 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
index 532be07c3f..7fe6897ae3 100644
--- a/spec/bundler/commands/check_spec.rb
+++ b/spec/bundler/commands/check_spec.rb
@@ -1,154 +1,200 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle check" do
it "returns success when the Gemfile is satisfied" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
bundle :check
- expect(exitstatus).to eq(0) if exitstatus
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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
- Dir.chdir tmp
- bundle "check --gemfile bundled_app/Gemfile"
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
- FileUtils.rm("Gemfile.lock")
+ FileUtils.rm(bundled_app_lock)
bundle "check"
- expect(bundled_app("Gemfile.lock")).to exist
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
- FileUtils.rm("Gemfile.lock")
+ FileUtils.rm(bundled_app_lock)
bundle "check --dry-run"
- expect(bundled_app("Gemfile.lock")).not_to exist
+ 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"]
+ it "prints an error that shows missing gems" do
+ system_gems ["rails-2.3.2"], path: default_bundle_path
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
- bundle :check
- expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ bundle :check, raise_on_error: false
+ expect(err).to include("The following gems are missing")
+ expect(err).to include(" * rake (#{rake_version})")
+ expect(err).to include(" * actionpack (2.3.2)")
+ expect(err).to include(" * activerecord (2.3.2)")
+ expect(err).to include(" * actionmailer (2.3.2)")
+ expect(err).to include(" * activeresource (2.3.2)")
+ expect(err).to include(" * activesupport (2.3.2)")
+ expect(err).to include("Install missing gems with `bundle install`")
end
- it "prints a generic error if a Gemfile.lock does not exist and a toplevel dependency does not exist" do
+ it "prints an error that shows missing gems if a Gemfile.lock does not exist and a toplevel dependency is missing" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
- bundle :check
- expect(exitstatus).to be > 0 if exitstatus
- expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ bundle :check, raise_on_error: false
+ expect(exitstatus).to be > 0
+ expect(err).to include("The following gems are missing")
+ expect(err).to include(" * rails (2.3.2)")
+ expect(err).to include(" * rake (#{rake_version})")
+ expect(err).to include(" * actionpack (2.3.2)")
+ expect(err).to include(" * activerecord (2.3.2)")
+ expect(err).to include(" * actionmailer (2.3.2)")
+ expect(err).to include(" * activeresource (2.3.2)")
+ expect(err).to include(" * activesupport (2.3.2)")
+ expect(err).to include("Install missing gems with `bundle install`")
end
- it "prints a generic message if you changed your lockfile" do
+ it "prints a generic error if gem git source is not checked out" do
+ build_git "foo", path: lib_path("foo")
+
+ bundle_config "path vendor/bundle"
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rails'
+ source "https://gem.repo1"
+ gem "foo", git: "#{lib_path("foo")}"
G
+
+ FileUtils.rm_r bundled_app("vendor/bundle")
+ bundle :check, raise_on_error: false
+ expect(exitstatus).to eq 1
+ 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://#{gem_repo1}"
- gem 'rails_fail'
+ source "https://gem.repo2"
+ gem 'rails'
G
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2"
gem "rails"
- gem "rails_fail"
+ gem "rails_pinned_to_old_activesupport"
G
- bundle :check
- expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ 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" do
- gemfile <<-G
- source "file://#{gem_repo1}"
+ it "uses the without setting" do
+ bundle_config "without foo"
+ install_gemfile <<-G
+ source "https://gem.repo1"
group :foo do
- gem "rack"
+ gem "myrack"
end
G
- bundle "install --without foo"
bundle "check"
- expect(exitstatus).to eq(0) if exitstatus
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://#{gem_repo1}"
- gem "rack", :group => :foo
+ source "https://gem.repo1"
+ gem "myrack", :group => :foo
G
- bundle "install --without foo"
+ bundle_config "without foo"
+ bundle :install
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "check"
- expect(out).to include("* rack (1.0.0)")
- expect(exitstatus).to eq(1) if exitstatus
+ bundle "check", raise_on_error: false
+ expect(err).to include("* myrack (1.0.0)")
+ expect(exitstatus).to eq(1)
end
- it "ignores missing gems restricted to other platforms" do
- system_gems "rack-1.0.0"
+ it "ensures that gems are actually installed and not just cached in applications' cache" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle_config "path vendor/bundle"
+ bundle :cache
+
+ uninstall_gem("myrack", env: { "GEM_HOME" => vendored_gems.to_s })
+
+ bundle "check", raise_on_error: false
+ expect(err).to include("* myrack (1.0.0)")
+ expect(exitstatus).to eq(1)
+ end
+ it "ignores missing gems restricted to other platforms" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
platforms :#{not_local_tag} do
gem "activesupport"
end
G
+ system_gems "myrack-1.0.0", path: default_bundle_path
+
lockfile <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
activesupport (2.3.5)
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{local}
+ #{generic_local_platform}
#{not_local}
DEPENDENCIES
- rack
+ myrack
activesupport
G
@@ -157,29 +203,29 @@ RSpec.describe "bundle check" do
end
it "works with env conditionals" do
- system_gems "rack-1.0.0"
-
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
env :NOT_GOING_TO_BE_SET do
gem "activesupport"
end
G
+ system_gems "myrack-1.0.0", path: default_bundle_path
+
lockfile <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
activesupport (2.3.5)
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{local}
+ #{generic_local_platform}
#{not_local}
DEPENDENCIES
- rack
+ myrack
activesupport
G
@@ -188,134 +234,341 @@ RSpec.describe "bundle check" do
end
it "outputs an error when the default Gemfile is not found" do
- bundle :check
- expect(exitstatus).to eq(10) if exitstatus
- expect(out).to include("Could not locate Gemfile")
+ 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
- expect(exitstatus).to eq(10) if exitstatus
- expect(out).not_to include("Unfortunately, a fatal error has occurred. ")
+ bundle :check, raise_on_error: false
+ expect(exitstatus).to eq(10)
+ expect(err).not_to include("Unfortunately, a fatal error has occurred. ")
end
- it "should not crash when called multiple times on a new machine" do
- gemfile <<-G
- gem 'rails', '3.0.0.beta3'
- gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git'
+ it "fails when there's no lockfile and frozen is set" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo"
G
- simulate_new_machine
- bundle "check"
- last_out = out
- 3.times do
+ bundle_config "deployment true"
+ bundle "install"
+ FileUtils.rm(bundled_app_lock)
+
+ bundle :check, raise_on_error: false
+ expect(last_command).to be_failure
+ end
+
+ describe "when locked" do
+ before :each do
+ system_gems "myrack-1.0.0"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+ end
+
+ it "returns success when the Gemfile is satisfied" do
+ bundle :install
bundle :check
- expect(out).to eq(last_out)
- expect(err).to lack_errors
+ 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
+ FileUtils.rm_r default_bundle_path
+ default_system_gems
+ bundle :check, raise_on_error: false
+ expect(err).to match(/The following gems are missing/)
+ expect(err).to include("* myrack (1.0")
end
end
- it "fails when there's no lock file and frozen is set" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "foo"
- G
+ describe "when locked with multiple dependents with different requirements" do
+ before :each do
+ build_repo4 do
+ build_gem "depends_on_myrack" do |s|
+ s.add_dependency "myrack", ">= 1.0"
+ end
+ build_gem "also_depends_on_myrack" do |s|
+ s.add_dependency "myrack", "~> 1.0"
+ end
+ build_gem "myrack"
+ end
- bundle "install"
- bundle "install --deployment"
- FileUtils.rm(bundled_app("Gemfile.lock"))
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "depends_on_myrack"
+ gem "also_depends_on_myrack"
+ G
- bundle :check
- expect(exitstatus).not_to eq(0) if exitstatus
+ 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("* myrack (1.0").once
+ end
end
- context "--path" do
- before do
+ describe "when locked under multiple platforms" do
+ before :each do
+ build_repo4 do
+ build_gem "myrack"
+ end
+
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
+ source "https://gem.repo4"
+ gem "myrack"
G
- bundle "install --path vendor/bundle"
- FileUtils.rm_rf(bundled_app(".bundle"))
- end
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
- it "returns success" do
- bundle "check --path vendor/bundle"
- expect(exitstatus).to eq(0) if exitstatus
- expect(out).to include("The Gemfile's dependencies are satisfied")
+ PLATFORMS
+ ruby
+ #{local_platform}
+
+ DEPENDENCIES
+ myrack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
- it "should write to .bundle/config" do
- bundle "check --path vendor/bundle"
- bundle "check"
- expect(exitstatus).to eq(0) if exitstatus
+ 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("* myrack (1.0").once
end
end
- context "--path vendor/bundle after installing gems in the default directory" do
- it "returns false" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
+ describe "when using only scoped rubygems sources" do
+ before do
+ gemfile <<~G
+ source "https://gem.repo2"
+ source "https://gem.repo1" do
+ gem "myrack"
+ end
G
+ end
- bundle "check --path vendor/bundle"
- expect(exitstatus).to eq(1) if exitstatus
- expect(out).to match(/The following gems are missing/)
+ it "returns success when the Gemfile is satisfied" do
+ system_gems "myrack-1.0.0", path: default_bundle_path
+ bundle :check, artifice: "compact_index"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
end
end
- describe "when locked" do
- before :each do
- system_gems "rack-1.0.0"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0"
+ describe "when using only scoped rubygems sources with indirect dependencies" do
+ before do
+ build_repo4 do
+ build_gem "depends_on_myrack" do |s|
+ s.add_dependency "myrack"
+ end
+
+ build_gem "myrack"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo1"
+ source "https://gem.repo4" do
+ gem "depends_on_myrack"
+ end
G
end
- it "returns success when the Gemfile is satisfied" do
- bundle :install
- bundle :check
- expect(exitstatus).to eq(0) if exitstatus
+ it "returns success when the Gemfile is satisfied and generates a correct lockfile" do
+ system_gems "depends_on_myrack-1.0", "myrack-1.0", gem_repo: gem_repo4, path: default_bundle_path
+ bundle :check, artifice: "compact_index"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "depends_on_myrack", "1.0"
+ c.checksum gem_repo4, "myrack", "1.0"
+ end
+
expect(out).to include("The Gemfile's dependencies are satisfied")
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ depends_on_myrack (1.0)
+ myrack
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ depends_on_myrack!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
+ end
- it "shows what is missing with the current Gemfile if it is not satisfied" do
- simulate_new_machine
- bundle :check
- expect(out).to match(/The following gems are missing/)
- expect(out).to include("* rack (1.0")
+ 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("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("bundle-check-issue")
+ end
+
+ it "does not corrupt lockfile when changing version" do
+ version_file = tmp("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("bundle-check-issue")
+
+ lockfile = File.read(tmp("bundle-check-issue/Gemfile.lock"))
+
+ checksums = checksums_section_when_enabled(lockfile) do |c|
+ c.checksum gem_repo4, "awesome_print", "1.0"
+ c.no_checksum "bundle-check-issue", "9999"
+ c.checksum gem_repo2, "dex-dispatch-engine", "1.0"
+ end
+
+ expect(lockfile).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!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with scoped and unscoped sources" do
+ it "does not corrupt lockfile" do
+ build_repo2 do
+ build_gem "foo"
+ build_gem "wadus"
+ build_gem("baz") {|s| s.add_dependency "wadus" }
+ end
+
+ build_repo4 do
+ build_gem "bar"
+ end
+
+ bundle_config "path.system true"
+
+ # Add all gems to ensure all gems are installed so that a bundle check
+ # would be successful
+ install_gemfile(<<-G, artifice: "compact_index_extra")
+ source "https://gem.repo2"
+
+ source "https://gem.repo4" do
+ gem "bar"
+ end
+
+ gem "foo"
+ gem "baz"
+ G
+
+ original_lockfile = lockfile
+
+ # Remove "baz" gem from the Gemfile, and bundle install again to generate
+ # a functional lockfile with no "baz" dependency or "wadus" transitive
+ # dependency
+ install_gemfile(<<-G, artifice: "compact_index_extra")
+ source "https://gem.repo2"
+
+ source "https://gem.repo4" do
+ gem "bar"
+ end
+
+ gem "foo"
+ G
+
+ # Add back "baz" gem back to the Gemfile, but _crucially_ we do not perform a
+ # bundle install
+ gemfile(gemfile + 'gem "baz"')
+
+ bundle :check, verbose: true
+
+ # Bundle check should succeed and restore the lockfile to its original
+ # state
+ expect(lockfile).to eq(original_lockfile)
end
end
describe "BUNDLED WITH" do
def lock_with(bundler_version = nil)
- lock = <<-L
+ lock = <<~L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
L
if bundler_version
- lock += "\n BUNDLED WITH\n #{bundler_version}\n"
+ lock += "\nBUNDLED WITH\n #{bundler_version}\n"
end
lock
end
before do
+ bundle_config "path vendor/bundle"
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
@@ -323,25 +576,25 @@ RSpec.describe "bundle check" do
it "does not change the lock" do
lockfile lock_with(nil)
bundle :check
- lockfile_should_be lock_with(nil)
+ expect(lockfile).to eq lock_with(nil)
end
end
context "is newer" do
- it "does not change the lock but warns" do
+ it "does not change the lock and does not warn" do
lockfile lock_with(Bundler::VERSION.succ)
bundle :check
- expect(out).to include("the running version of Bundler (#{Bundler::VERSION}) is older than the version that created the lockfile (#{Bundler::VERSION.succ})")
- expect(err).to lack_errors
- lockfile_should_be lock_with(Bundler::VERSION.succ)
+ 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
- lockfile lock_with("1.10.1")
+ system_gems "bundler-1.18.0"
+ lockfile lock_with("1.18.0")
bundle :check
- lockfile_should_be lock_with("1.10.1")
+ expect(lockfile).to eq lock_with("1.18.0")
end
end
end
diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb
index 02d96a0ff7..c77859d378 100644
--- a/spec/bundler/commands/clean_spec.rb
+++ b/spec/bundler/commands/clean_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle clean" do
def should_have_gems(*gems)
@@ -20,16 +19,18 @@ RSpec.describe "bundle clean" do
it "removes unused gems that are different" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
G
@@ -39,89 +40,95 @@ RSpec.describe "bundle clean" do
expect(out).to include("Removing foo (1.0)")
- should_have_gems "thin-1.0", "rack-1.0.0"
+ should_have_gems "thin-1.0", "myrack-1.0.0"
should_not_have_gems "foo-1.0"
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
it "removes old version of gem if unused" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "0.9.1"
+ gem "myrack", "0.9.1"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
gem "foo"
G
bundle "install"
bundle :clean
- expect(out).to include("Removing rack (0.9.1)")
+ expect(out).to include("Removing myrack (0.9.1)")
- should_have_gems "foo-1.0", "rack-1.0.0"
- should_not_have_gems "rack-0.9.1"
+ should_have_gems "foo-1.0", "myrack-1.0.0"
+ should_not_have_gems "myrack-0.9.1"
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
it "removes new version of gem if unused" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "0.9.1"
+ gem "myrack", "0.9.1"
gem "foo"
G
- bundle "install"
+ bundle "update myrack"
bundle :clean
- expect(out).to include("Removing rack (1.0.0)")
+ expect(out).to include("Removing myrack (1.0.0)")
- should_have_gems "foo-1.0", "rack-0.9.1"
- should_not_have_gems "rack-1.0.0"
+ should_have_gems "foo-1.0", "myrack-0.9.1"
+ should_not_have_gems "myrack-1.0.0"
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
it "removes gems in bundle without groups" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo"
group :test_group do
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
end
G
- bundle "install --path vendor/bundle"
- bundle "install --without test_group"
+ bundle_config "path vendor/bundle"
+ bundle "install"
+ bundle_config "without test_group"
+ bundle "install"
bundle :clean
- expect(out).to include("Removing rack (1.0.0)")
+ expect(out).to include("Removing myrack (1.0.0)")
should_have_gems "foo-1.0"
- should_not_have_gems "rack-1.0.0"
+ should_not_have_gems "myrack-1.0.0"
- expect(vendored_gems("bin/rackup")).to_not exist
+ expect(vendored_gems("bin/myrackup")).to_not exist
end
it "does not remove cached git dir if it's being used" do
@@ -130,42 +137,45 @@ RSpec.describe "bundle clean" do
git_path = lib_path("foo-1.0")
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
git "#{git_path}", :ref => "#{revision}" do
gem "foo"
end
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
bundle :clean
- digest = Digest::SHA1.hexdigest(git_path.to_s)
- expect(vendored_gems("cache/bundler/git/foo-1.0-#{digest}")).to exist
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ cache_path = vendored_gems("cache/bundler/git/foo-1.0-#{digest}")
+ expect(cache_path).to exist
end
it "removes unused git gems" do
- build_git "foo", :path => lib_path("foo")
+ build_git "foo", path: lib_path("foo")
git_path = lib_path("foo")
revision = revision_for(git_path)
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
git "#{git_path}", :ref => "#{revision}" do
gem "foo"
end
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
G
bundle "install"
@@ -173,60 +183,90 @@ RSpec.describe "bundle clean" do
expect(out).to include("Removing foo (#{revision[0..11]})")
- expect(vendored_gems("gems/rack-1.0.0")).to exist
+ expect(vendored_gems("gems/myrack-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)
+ 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("specifications/myrack-1.0.0.gemspec")).to exist
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
- it "removes old git gems" do
- build_git "foo-bar", :path => lib_path("foo-bar")
+ 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 "https://gem.repo1"
+
+ gem "myrack", "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 "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 on bundle update" do
+ build_git "foo-bar", path: lib_path("foo-bar")
revision = revision_for(lib_path("foo-bar"))
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
git "#{lib_path("foo-bar")}" do
gem "foo-bar"
end
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
- update_git "foo", :path => lib_path("foo-bar")
+ update_git "foo-bar", path: lib_path("foo-bar")
revision2 = revision_for(lib_path("foo-bar"))
- bundle "update"
+ 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("gems/myrack-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("specifications/myrack-1.0.0.gemspec")).to exist
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).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|
+ 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 "https://gem.repo1"
gem "activesupport", :git => "#{lib_path("rails")}", :ref => '#{revision}'
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
bundle :clean
expect(out).to include("")
@@ -234,193 +274,220 @@ RSpec.describe "bundle clean" do
end
it "does not remove git sources that are in without groups" do
- build_git "foo", :path => lib_path("foo")
+ build_git "foo", path: lib_path("foo")
git_path = lib_path("foo")
revision = revision_for(git_path)
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
group :test do
git "#{git_path}", :ref => "#{revision}" do
gem "foo"
end
end
G
- bundle "install --path vendor/bundle --without test"
+ bundle_config "path vendor/bundle"
+ bundle_config "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)
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
group :development do
gem "foo"
end
G
- bundle "install --path vendor/bundle --without development"
+ bundle_config "path vendor/bundle"
+ bundle_config "without development"
+ bundle "install"
bundle :clean
- expect(exitstatus).to eq(0) if exitstatus
end
it "displays an error when used without --path" do
+ bundle_config "path.system true"
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
G
- bundle :clean
+ bundle :clean, raise_on_error: false
- expect(exitstatus).to eq(1) if exitstatus
- expect(out).to include("--force")
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "foo"
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://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"))
+ FileUtils.rm(vendored_gems("bin/myrackup"))
+ FileUtils.rm_r(vendored_gems("gems/thin-1.0"))
+ FileUtils.rm_r(vendored_gems("gems/myrack-1.0.0"))
bundle :clean
- should_not_have_gems "thin-1.0", "rack-1.0"
+ should_not_have_gems "thin-1.0", "myrack-1.0"
should_have_gems "foo-1.0"
- expect(vendored_gems("bin/rackup")).not_to exist
+ expect(vendored_gems("bin/myrackup")).not_to exist
end
it "does not call clean automatically when using system gems" do
- gemfile <<-G
- source "file://#{gem_repo1}"
+ bundle_config "path.system true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "thin"
- gem "rack"
+ gem "myrack"
G
- bundle :install
- gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
- bundle :install
- sys_exec "gem list"
- expect(out).to include("rack (1.0.0)")
- expect(out).to include("thin (1.0)")
+ installed_gems_list
+ expect(out).to include("myrack (1.0.0)").and include("thin (1.0)")
end
- it "--clean should override the bundle setting on install" do
- gemfile <<-G
- source "file://#{gem_repo1}"
-
- gem "thin"
- gem "rack"
- G
- bundle "install --path vendor/bundle --clean"
+ it "does not clean on bundle update when path has not been set" do
+ build_repo2
- gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G
+ source "https://gem.repo2"
- gem "rack"
+ gem "foo"
G
- bundle "install"
- should_have_gems "rack-1.0.0"
- should_not_have_gems "thin-1.0"
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle "update", all: true
+
+ files = Pathname.glob(default_bundle_path("*", "*"))
+ files.map! {|f| f.to_s.sub(default_bundle_path.to_s, "") }
+ expected_files = %W[
+ /bin/bundle
+ /bin/bundler
+ /cache/bundler-#{Bundler::VERSION}.gem
+ /cache/foo-1.0.1.gem
+ /cache/foo-1.0.gem
+ /gems/bundler-#{Bundler::VERSION}
+ /gems/foo-1.0
+ /gems/foo-1.0.1
+ /specifications/bundler-#{Bundler::VERSION}.gemspec
+ /specifications/foo-1.0.1.gemspec
+ /specifications/foo-1.0.gemspec
+ ]
+ expected_files += ["/bin/bundle.bat", "/bin/bundler.bat"] if Gem.win_platform?
+
+ expect(files.sort).to eq(expected_files.sort)
end
- it "--clean should override the bundle setting on update" do
+ it "will automatically clean on bundle update when path has not been set", bundler: "5" do
build_repo2
- gemfile <<-G
- source "file://#{gem_repo2}"
+ install_gemfile <<-G
+ source "https://gem.repo2"
gem "foo"
G
- bundle "install --path vendor/bundle --clean"
update_repo2 do
build_gem "foo", "1.0.1"
end
- bundle "update"
+ bundle "update", all: true
- should_have_gems "foo-1.0.1"
- should_not_have_gems "foo-1.0"
+ files = Pathname.glob(local_gem_path("*", "*"))
+ files.map! {|f| f.to_s.sub(local_gem_path.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
+ it "does not clean automatically when path configured" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
- gem "rack"
+ gem "myrack"
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
bundle "install"
- should_have_gems "rack-1.0.0", "thin-1.0"
+ should_have_gems "myrack-1.0.0", "thin-1.0"
end
- it "does not clean on bundle update with --path" do
+ it "does not clean on bundle update when path configured" do
build_repo2
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "foo"
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
update_repo2 do
build_gem "foo", "1.0.1"
end
- bundle :update
+ 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
+ it "does not clean on bundle update when installing to system gems" do
+ bundle_config "path.system true"
+
build_repo2
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "foo"
G
@@ -429,66 +496,69 @@ RSpec.describe "bundle clean" do
update_repo2 do
build_gem "foo", "1.0.1"
end
- bundle :update
+ bundle :update, all: true
- sys_exec "gem list"
+ installed_gems_list
expect(out).to include("foo (1.0.1, 1.0)")
end
it "cleans system gems when --force is used" do
+ bundle_config "path.system true"
+
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo"
- gem "rack"
+ gem "myrack"
G
bundle :install
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
bundle :install
bundle "clean --force"
expect(out).to include("Removing foo (1.0)")
- sys_exec "gem list"
+ installed_gems_list
expect(out).not_to include("foo (1.0)")
- expect(out).to include("rack (1.0.0)")
+ expect(out).to include("myrack (1.0.0)")
end
- describe "when missing permissions" do
+ 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, default_bundle_path("cache"))
+ FileUtils.chmod(0o755, system_cache_path)
end
it "returns a helpful error message" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo"
- gem "rack"
+ gem "myrack"
G
bundle :install
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
bundle :install
- system_cache_path = default_bundle_path("cache")
FileUtils.chmod(0o500, system_cache_path)
- bundle :clean, :force => true
+ bundle :clean, force: true, raise_on_error: false
- expect(out).to include(system_gem_path.to_s)
- expect(out).to include("grant write permissions")
+ expect(err).to include(system_gem_path.to_s)
+ expect(err).to include("grant write permissions")
- sys_exec "gem list"
+ installed_gems_list
expect(out).to include("foo (1.0)")
- expect(out).to include("rack (1.0.0)")
+ expect(out).to include("myrack (1.0.0)")
end
end
@@ -497,23 +567,23 @@ RSpec.describe "bundle clean" do
revision = revision_for(lib_path("foo-1.0"))
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
# mimic 7 length git revisions in Gemfile.lock
- gemfile_lock = File.read(bundled_app("Gemfile.lock")).split("\n")
+ 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
- File.open(bundled_app("Gemfile.lock"), "w") do |file|
- file.print gemfile_lock.join("\n")
- end
+ lockfile(bundled_app_lock, gemfile_lock.join("\n"))
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
bundle :clean
@@ -523,8 +593,9 @@ RSpec.describe "bundle clean" do
end
it "when using --force on system gems, it doesn't remove binaries" do
- build_repo2
- update_repo2 do
+ bundle_config "path.system true"
+
+ build_repo2 do
build_gem "bindir" do |s|
s.bindir = "exe"
s.executables = "foo"
@@ -532,7 +603,7 @@ RSpec.describe "bundle clean" do
end
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "bindir"
G
@@ -542,11 +613,28 @@ RSpec.describe "bundle clean" do
sys_exec "foo"
- expect(exitstatus).to eq(0) if exitstatus
expect(out).to eq("1.0")
end
- it "doesn't blow up on path gems without a .gempsec" do
+ it "when using --force, it doesn't remove default gem binaries" do
+ 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
+
+ install_gemfile <<-G
+ source "https://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")
@@ -555,30 +643,31 @@ RSpec.describe "bundle clean" do
end
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo"
gem "bar", "1.0", :path => "#{relative_path}"
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle "install"
bundle :clean
-
- expect(exitstatus).to eq(0) if exitstatus
end
it "doesn't remove gems in dry-run mode with path set" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
G
@@ -590,54 +679,56 @@ RSpec.describe "bundle clean" do
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"
+ should_have_gems "thin-1.0", "myrack-1.0.0", "foo-1.0"
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
it "doesn't remove gems in dry-run mode with no path set" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
G
bundle :install
- bundle "configuration --delete path"
-
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"
+ should_have_gems "thin-1.0", "myrack-1.0.0", "foo-1.0"
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
it "doesn't store dry run as a config setting" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
- bundle "config dry_run false"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
+ bundle_config "dry_run false"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
G
@@ -649,55 +740,197 @@ RSpec.describe "bundle clean" do
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_have_gems "thin-1.0", "myrack-1.0.0"
should_not_have_gems "foo-1.0"
- expect(vendored_gems("bin/rackup")).to exist
+ expect(vendored_gems("bin/myrackup")).to exist
end
it "performs an automatic bundle install" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "foo"
G
- bundle "install --path vendor/bundle --no-clean"
+ bundle_config "path vendor/bundle"
+ bundle_config "clean false"
+ bundle "install"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "thin"
gem "weakling"
G
- bundle "config auto_install 1"
+ bundle_config "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_have_gems "thin-1.0", "myrack-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", :ruby_repo, :rubygems => "2.2" do
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
G
- bundle! "install --path vendor/bundle"
+ bundle_config "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 eq("")
+ 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 "https://gem.repo1"
+
+ gem "thin"
+ gem "very_simple_binary"
+ gem "simple_binary"
+ G
+
+ bundle_config "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 "https://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 "https://gem.repo1"
+
+ gem "thin"
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
+ G
+
+ bundle_config "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 "https://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 "https://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 "https://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 "without development"
+ bundle_config "path vendor/bundle"
+ bundle "install", verbose: true
+ 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
+
+ it "does not remove the bundler version currently running" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ G
+
+ bundle_config "path vendor/bundle"
+ bundle "install"
+
+ version = Bundler.gem_version.to_s
+ # Simulate that the locked bundler version is installed in the bundle path
+ # by creating the gem directory and gemspec (as would happen after bundle install with that version)
+ Pathname(vendored_gems("cache/bundler-#{version}.gem")).tap do |path|
+ FileUtils.touch(path)
+ end
+ FileUtils.touch(vendored_gems("gems/bundler-#{version}"))
+ Pathname(vendored_gems("specifications/bundler-#{version}.gemspec")).tap do |path|
+ path.write(<<~GEMSPEC)
+ Gem::Specification.new do |s|
+ s.name = "bundler"
+ s.version = "#{version}"
+ s.authors = ["bundler team"]
+ s.summary = "The best way to manage your application's dependencies"
+ end
+ GEMSPEC
+ end
+
+ should_have_gems "bundler-#{version}"
+
+ bundle :clean
+
+ should_have_gems "bundler-#{version}"
+ end
end
diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb
index a3ca696ec1..e8ab32ca5d 100644
--- a/spec/bundler/commands/config_spec.rb
+++ b/spec/bundler/commands/config_spec.rb
@@ -1,19 +1,11 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe ".bundle/config" do
- before :each do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0.0"
- G
- end
-
describe "config" do
- before { bundle "config foo bar" }
+ before { bundle "config set foo bar" }
it "prints a detailed report of local and user configuration" do
- bundle "config"
+ 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")
@@ -22,69 +14,136 @@ RSpec.describe ".bundle/config" do
context "given --parseable flag" do
it "prints a minimal report of local and user configuration" do
- bundle "config --parseable"
+ 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 --local foo bar2"
- bundle "config --parseable"
+ 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 --parseable", :env => { "BUNDLE_FOO" => "bar3" }
+ bundle "config list --parseable", env: { "BUNDLE_FOO" => "bar3" }
expect(out).to include("foo=bar3")
end
end
end
end
- describe "BUNDLE_APP_CONFIG" do
+ describe "location with a gemfile" do
+ before :each do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "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 "install --path vendor/bundle"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "can provide a relative path with the environment variable" do
FileUtils.mkdir_p bundled_app("omg")
- Dir.chdir bundled_app("omg")
ENV["BUNDLE_APP_CONFIG"] = "../foo"
- bundle "install --path vendor/bundle"
+ 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"
+ expect(the_bundle).to include_gems "myrack 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 "does not list global settings as local" do
+ bundle "config set --global foo bar"
+ bundle "config list", dir: home
+
+ expect(out).to include("for the current user")
+ expect(out).not_to include("for your local app")
+ 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) { bundle :install }
+ before(:each) do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ G
+ end
it "is the default" do
- bundle "config foo global"
+ 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 --global foo global"
- run! "puts Bundler.settings[:foo]"
+ 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 --local foo local"
+ bundle "config set --local foo local"
- bundle "config --global foo global"
+ bundle "config set --global foo global"
expect(out).to match(/Your application has set foo to "local"/)
run "puts Bundler.settings[:foo]"
@@ -92,30 +151,28 @@ RSpec.describe ".bundle/config" do
end
it "has lower precedence than env" do
- begin
- ENV["BUNDLE_FOO"] = "env"
+ ENV["BUNDLE_FOO"] = "env"
- bundle "config --global foo global"
- expect(out).to match(/You have a bundler environment variable for foo set to "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
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("env")
+ ensure
+ ENV.delete("BUNDLE_FOO")
end
it "can be deleted" do
- bundle "config --global foo global"
- bundle "config --delete foo"
+ 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 --global foo previous"
- bundle "config --global foo global"
+ 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]"
@@ -123,8 +180,8 @@ RSpec.describe ".bundle/config" do
end
it "does not warn when using the same value twice" do
- bundle "config --global foo value"
- bundle "config --global foo value"
+ 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]"
@@ -132,22 +189,22 @@ RSpec.describe ".bundle/config" do
end
it "expands the path at time of setting" do
- bundle "config --global local.foo .."
+ bundle "config set --global local.foo .."
run "puts Bundler.settings['local.foo']"
- expect(out).to eq(File.expand_path(Dir.pwd + "/.."))
+ expect(out).to eq(File.expand_path(bundled_app.to_s + "/.."))
end
it "saves with parseable option" do
- bundle "config --global --parseable foo value"
+ 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 --global foo value" }
+ before { bundle "config set --global foo value" }
it "prints the current value in a parseable format" do
- bundle "config --global --parseable foo value2"
+ bundle "config set --global --parseable foo value2"
expect(out).to eq "foo=value2"
run "puts Bundler.settings['foo']"
expect(out).to eq("value2")
@@ -156,37 +213,40 @@ RSpec.describe ".bundle/config" do
end
describe "local" do
- before(:each) { bundle :install }
+ before(:each) do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ G
+ end
it "can also be set explicitly" do
- bundle "config --local foo local"
+ bundle "config set --local foo local"
run "puts Bundler.settings[:foo]"
expect(out).to eq("local")
end
it "has higher precedence than env" do
- begin
- ENV["BUNDLE_FOO"] = "env"
- bundle "config --local foo local"
-
- run "puts Bundler.settings[:foo]"
- expect(out).to eq("local")
- ensure
- ENV.delete("BUNDLE_FOO")
- end
+ 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 --local foo local"
- bundle "config --delete foo"
+ 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 --local foo previous"
- bundle "config --local foo local"
+ 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]"
@@ -194,14 +254,14 @@ RSpec.describe ".bundle/config" do
end
it "expands the path at time of setting" do
- bundle "config --local local.foo .."
+ bundle "config set --local local.foo .."
run "puts Bundler.settings['local.foo']"
- expect(out).to eq(File.expand_path(Dir.pwd + "/.."))
+ expect(out).to eq(File.expand_path(bundled_app.to_s + "/.."))
end
it "can be deleted with parseable option" do
- bundle "config --local foo value"
- bundle "config --delete --parseable foo"
+ 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")
@@ -209,7 +269,12 @@ RSpec.describe ".bundle/config" do
end
describe "env" do
- before(:each) { bundle :install }
+ before(:each) do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ G
+ end
it "can set boolean properties via the environment" do
ENV["BUNDLE_FROZEN"] = "true"
@@ -248,39 +313,45 @@ RSpec.describe ".bundle/config" do
describe "parseable option" do
it "prints an empty string" do
- bundle "config foo --parseable"
+ bundle "config get foo --parseable", raise_on_error: false
expect(out).to eq ""
+ expect(last_command).to be_failure
end
it "only prints the value of the config" do
- bundle "config foo local"
- bundle "config foo --parseable"
+ 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 --global bar value"
- bundle "config bar --parseable"
+ bundle "config set --global bar value"
+ bundle "config get bar --parseable"
expect(out).to eq "bar=value"
end
- it "preferes local config over global" do
- bundle "config --local bar value2"
- bundle "config --global bar value"
- bundle "config bar --parseable"
+ 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) { bundle :install }
+ before(:each) do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ G
+ end
it "configures mirrors using keys with `mirror.`" do
- bundle "config --local mirror.http://gems.example.org http://gem-mirror.example.org"
+ 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}"
@@ -288,36 +359,42 @@ end
E
expect(out).to eq("http://gems.example.org/ => http://gem-mirror.example.org/")
end
+
+ it "allows configuring fallback timeout for each mirror, and does not duplicate configs", rubygems: ">= 3.5.12" do
+ bundle "config set --global mirror.https://rubygems.org.fallback_timeout 1"
+ bundle "config set --global mirror.https://rubygems.org.fallback_timeout 2"
+ expect(File.read(home(".bundle/config"))).to include("BUNDLE_MIRROR").once
+ end
end
describe "quoting" do
- before(:each) { gemfile "# no gems" }
+ before(:each) { gemfile "source 'https://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 foo something\\'"
+ bundle "config set foo something\\'"
run "puts Bundler.settings[:foo]"
expect(out).to eq("something'")
end
- it "doesn't return quotes around values", :ruby => "1.9" do
- bundle "config foo '1'"
- run "puts Bundler.settings.send(:global_config_file).read"
+ 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", :if => (RUBY_VERSION >= "2.1") do
+ 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 bar baz"
+ bundle "config set bar baz"
run "puts Bundler.settings.send(:local_config_file).read"
# Starting in Ruby 2.1, YAML automatically adds double quotes
@@ -326,12 +403,12 @@ E
end
it "doesn't duplicate quotes around long wrapped values" do
- bundle "config foo #{long_string}"
+ bundle "config set foo #{long_string}"
run "puts Bundler.settings[:foo]"
expect(out).to eq(long_string)
- bundle "config bar baz"
+ bundle "config set bar baz"
run "puts Bundler.settings[:foo]"
expect(out).to eq(long_string)
@@ -339,7 +416,12 @@ E
end
describe "very long lines" do
- before(:each) { bundle :install }
+ before(:each) do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ G
+ end
let(:long_string) do
"--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \
@@ -352,34 +434,213 @@ E
end
it "doesn't wrap values" do
- bundle "config foo #{long_string}"
+ 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 foo #{long_string_without_special_characters}"
+ 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 be_empty
+ expect(err).to be_empty
+
+ ruby(<<~RUBY)
+ require "bundler"
+ 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 "bundler"
+ 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", raise_on_error: false
+ 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`"
+ expect(last_command).to be_failure
+
+ 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", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+ expect(last_command).to be_failure
+
+ 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", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+ expect(last_command).to be_failure
+
+ 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", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+ expect(last_command).to be_failure
+
+ 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 a default Gemfile exists" do
+ before do
+ gemfile <<-G
+ source "https://gem.repo1"
+ G
+
+ gemfile bundled_app("foo/bar_gemfile"), <<-G
+ source "https://gem.repo1"
+ G
+ end
+
+ it "reports the local gemfile setting without promoting it to the environment" do
+ bundle "config set gemfile foo/bar_gemfile"
+
+ bundle "config list"
+ expect(out).to include("Set for your local app (#{bundled_app(".bundle/config")}): \"foo/bar_gemfile\"")
+ expect(out).not_to include("Set via BUNDLE_GEMFILE")
+ end
+
+ it "unsets the local gemfile setting from the app config" do
+ bundle "config set gemfile foo/bar_gemfile"
+
+ bundle "config unset gemfile"
+ bundle "config get gemfile", raise_on_error: false
+
+ expect(out).to include("You have not configured a value for `gemfile`")
+ expect(File.read(bundled_app(".bundle/config"))).not_to include("BUNDLE_GEMFILE")
+ end
+ end
+
context "when only the non-default Gemfile exists" do
it "persists the gemfile location to .bundle/config" do
- File.open(bundled_app("NotGemfile"), "w") do |f|
- f.write <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
- G
- end
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
- bundle "config --local gemfile #{bundled_app("NotGemfile")}"
- expect(File.exist?(".bundle/config")).to eq(true)
+ bundle "config set --local gemfile #{bundled_app("NotGemfile")}"
+ expect(File.exist?(bundled_app(".bundle/config"))).to eq(true)
- bundle "config"
+ bundle "config list"
expect(out).to include("NotGemfile")
end
end
end
+
+RSpec.describe "setting lockfile via config" do
+ it "persists the lockfile location to .bundle/config" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle "config set --local gemfile #{bundled_app("NotGemfile")}"
+ bundle "config set --local lockfile #{bundled_app("ReallyNotGemfile.lock")}"
+ expect(File.exist?(bundled_app(".bundle/config"))).to eq(true)
+
+ bundle "config list"
+ expect(out).to include("NotGemfile")
+ expect(out).to include("ReallyNotGemfile.lock")
+ end
+end
diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb
index de14b6db5f..a44f607546 100644
--- a/spec/bundler/commands/console_spec.rb
+++ b/spec/bundler/commands/console_spec.rb
@@ -1,107 +1,214 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "bundle console" do
+RSpec.describe "bundle console", readline: true do
before :each do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport", :group => :test
- gem "rack_middleware", :group => :development
- G
- end
+ 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
- it "starts IRB with the default group loaded" do
- bundle "console" do |input, _, _|
- input.puts("puts RACK")
- input.puts("exit")
+ build_dummy_irb
end
- expect(out).to include("0.9.1")
end
- it "uses IRB as default console" do
- bundle "console" do |input, _, _|
- input.puts("__method__")
- input.puts("exit")
+ context "when the library requires a non-existent file" do
+ before do
+ build_lib "loadfuuu", "1.0.0" do |s|
+ s.write "lib/loadfuuu.rb", "require_relative 'loadfuuu/bar'"
+ s.write "lib/loadfuuu/bar.rb", "require 'not-in-bundle'"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "irb"
+ path "#{lib_path}" do
+ gem "loadfuuu", require: true
+ end
+ G
end
- expect(out).to include(":irb_binding")
- end
- it "starts another REPL if configured as such" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "pry"
- G
- bundle "config console pry"
+ it "does not show the bug report template" do
+ bundle("console", raise_on_error: false) do |input, _, _|
+ input.puts("exit")
+ end
- bundle "console" do |input, _, _|
- input.puts("__method__")
- input.puts("exit")
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
end
- expect(out).to include(":__pry__")
end
- it "falls back to IRB if the other REPL isn't available" do
- bundle "config console pry"
- # make sure pry isn't there
+ context "when the library references a non-existent constant" do
+ before do
+ build_lib "loadfuuu", "1.0.0" do |s|
+ s.write "lib/loadfuuu.rb", "Some::NonExistent::Constant"
+ end
- bundle "console" do |input, _, _|
- input.puts("__method__")
- input.puts("exit")
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "irb"
+ path "#{lib_path}" do
+ gem "loadfuuu", require: true
+ end
+ G
end
- expect(out).to include(":irb_binding")
- end
- it "doesn't load any other groups" do
- bundle "console" do |input, _, _|
- input.puts("puts ACTIVESUPPORT")
- input.puts("exit")
+ it "does not show the bug report template" do
+ bundle("console", raise_on_error: false) do |input, _, _|
+ input.puts("exit")
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
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")
+ context "when the library does not have any errors" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "irb"
+ gem "myrack"
+ gem "activesupport", :group => :test
+ gem "myrack_middleware", :group => :development
+ G
+ end
+
+ it "starts IRB with the default group loaded" do
+ bundle "console" do |input, _, _|
+ input.puts("puts MYRACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "uses IRB as default console" do
+ skip "Does not work in a ruby-core context if irb is in the default $LOAD_PATH because it enables the real IRB, not our dummy one" if ruby_core? && Gem.ruby_version < Gem::Version.new("3.5.0.a")
+
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
input.puts("exit")
end
- expect(out).to include("2.3.5")
+ expect(out).to include("__irb__")
end
- it "loads the default group" do
- bundle "console test" do |input, _, _|
- input.puts("puts RACK")
+ it "starts another REPL if configured as such" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "irb"
+ gem "pry"
+ G
+ bundle_config "console pry"
+
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
input.puts("exit")
end
- expect(out).to include("0.9.1")
+ expect(out).to include(":__pry__")
end
- it "doesn't load other groups" do
- bundle "console test" do |input, _, _|
- input.puts("puts RACK_MIDDLEWARE")
+ it "falls back to IRB if the other REPL isn't available" do
+ skip "Does not work in a ruby-core context if irb is in the default $LOAD_PATH because it enables the real IRB, not our dummy one" if ruby_core? && Gem.ruby_version < Gem::Version.new("3.5.0.a")
+
+ bundle_config "console pry"
+ # make sure pry isn't there
+
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
+ input.puts("exit")
+ end
+ expect(out).to include("__irb__")
+ end
+
+ it "does not try IRB twice if no console is configured and IRB is not available" do
+ create_file("irb.rb", "raise LoadError, 'irb is not available'")
+
+ bundle("console", env: { "RUBYOPT" => "-I#{bundled_app} #{ENV["RUBYOPT"]}" }, raise_on_error: false) do |input, _, _|
+ input.puts("puts ACTIVESUPPORT")
+ input.puts("exit")
+ end
+ expect(err).not_to include("falling back to irb")
+ expect(err).to include("irb is not available")
+ 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
- end
- it "performs an automatic bundle install" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport", :group => :test
- gem "rack_middleware", :group => :development
- gem "foo"
- G
-
- bundle "config auto_install 1"
- bundle :console do |input, _, _|
- input.puts("puts 'hello'")
- input.puts("exit")
+ 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 MYRACK")
+ 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 MYRACK_MIDDLEWARE")
+ input.puts("exit")
+ end
+ expect(out).to include("NameError")
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "irb"
+ gem "myrack"
+ gem "activesupport", :group => :test
+ gem "myrack_middleware", :group => :development
+ gem "foo"
+ G
+
+ bundle_config "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
- 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
index 7c6e48ce19..d350b4b3d1 100644
--- a/spec/bundler/commands/doctor_spec.rb
+++ b/spec/bundler/commands/doctor_spec.rb
@@ -1,14 +1,21 @@
# frozen_string_literal: true
-require "spec_helper"
+
+require "find"
require "stringio"
require "bundler/cli"
require "bundler/cli/doctor"
+require "bundler/cli/doctor/diagnose"
RSpec.describe "bundle doctor" do
before(:each) do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
@stdout = StringIO.new
- [:error, :warn].each do |method|
+ [:error, :warn, :info].each do |method|
allow(Bundler.ui).to receive(method).and_wrap_original do |m, message|
m.call message
@stdout.puts message
@@ -16,49 +23,163 @@ RSpec.describe "bundle doctor" do
end
end
- it "exits with no message if the installed gem has no C extensions" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ it "succeeds on a sane installation" do
+ bundle :doctor
+ end
- bundle :install
- Bundler::CLI::Doctor.new({}).run
- expect(@stdout.string).to be_empty
+ 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(:writable?).and_call_original
+ allow(File).to receive(:readable?).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 }
+
+ # The following lines are for `Gem::PathSupport#initialize`.
+ allow(File).to receive(:exist?).with(Gem.default_dir)
+ allow(File).to receive(:writable?).with(Gem.default_dir)
+ allow(File).to receive(:writable?).with(File.expand_path("..", Gem.default_dir))
+ end
+
+ it "exits with a success message if the installed gem has no C extensions" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ allow(doctor).to receive(:lookup_with_fiddle).and_return(false)
+ expect { doctor.run }.not_to raise_error
+ expect(@stdout.string).to include("No issues")
+ end
+
+ it "exits with a success message if the installed gem's C extension dylib breakage is fine" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/myrack/myrack.bundle"]
+ expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"]
+ allow(doctor).to receive(:lookup_with_fiddle).with("/usr/lib/libSystem.dylib").and_return(false)
+ expect { doctor.run }.not_to raise_error
+ expect(@stdout.string).to include("No issues")
+ end
+
+ it "parses otool output correctly" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ expect(doctor).to receive(:`).with("/usr/bin/otool -L fake").and_return("/home/gem/ruby/3.4.3/gems/blake3-rb-1.5.4.4/lib/digest/blake3/blake3_ext.bundle:\n\t (compatibility version 0.0.0, current version 0.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)")
+ expect(doctor.dylibs_darwin("fake")).to eq(["/usr/lib/libSystem.B.dylib"])
+ end
+
+ it "exits with a message if one of the linked libraries is missing" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/myrack/myrack.bundle"]
+ expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"]
+ allow(doctor).to receive(:lookup_with_fiddle).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(true)
+ expect { doctor.run }.to raise_error(Bundler::ProductionError, <<~E.strip), @stdout.string
+ The following gems are missing OS dependencies:
+ * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ * myrack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ E
+ end
end
- it "exits with no message if the installed gem's C extension dylib breakage is fine" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ 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
- bundle :install
- 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(File).to receive(:exist?).and_call_original
- allow(File).to receive(:exist?).with("/usr/lib/libSystem.dylib").and_return(true)
- doctor.run
- expect(@stdout.string).to be_empty
+ it "exits with an error if home contains files that are not readable/writable" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ allow(doctor).to receive(:lookup_with_fiddle).and_return(false)
+ expect { doctor.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
- it "exits with a message if one of the linked libraries is missing" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ 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(:writable?).and_call_original
+ allow(File).to receive(:readable?).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" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ allow(doctor).to receive(:lookup_with_fiddle).and_return(false)
+ 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 { doctor.run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are not readable by the current user. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+
+ it "exits without an error if home contains files that are not writable" do
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ allow(doctor).to receive(:lookup_with_fiddle).and_return(false)
+ allow(@stat).to receive(:uid) { Process.uid }
+ allow(File).to receive(:writable?).with(@unwritable_file) { false }
+ allow(File).to receive(:readable?).with(@unwritable_file) { true }
+ expect { doctor.run }.not_to raise_error
+ expect(@stdout.string).not_to include(
+ "Files exist in the Bundler home that are not readable by the current user. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).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
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ allow(doctor).to receive(:lookup_with_fiddle).and_return(false)
+ allow(File).to receive(:writable?).with(@unwritable_file) { false }
+ allow(File).to receive(:readable?).with(@unwritable_file) { false }
+ expect { doctor.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. 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
+ doctor = Bundler::CLI::Doctor::Diagnose.new({})
+ allow(doctor).to receive(:lookup_with_fiddle).and_return(false)
+ allow(File).to receive(:writable?).with(@unwritable_file) { true }
+ allow(File).to receive(:readable?).with(@unwritable_file) { true }
+ expect { doctor.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. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+ end
+ end
- bundle :install
- 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(File).to receive(:exist?).and_call_original
- allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false)
- expect { doctor.run }.to raise_error Bundler::ProductionError, strip_whitespace(<<-E).strip
- 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
+ context "when home contains filenames with special characters" do
+ it "escape filename before command execute" do
+ doctor = Bundler::CLI::Doctor::Diagnose.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
index 7736adefe1..aa35685be8 100644
--- a/spec/bundler/commands/exec_spec.rb
+++ b/spec/bundler/commands/exec_spec.rb
@@ -1,65 +1,127 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle exec" do
- let(:system_gems_to_install) { %w(rack-1.0.0 rack-0.9.1) }
- before :each do
- system_gems(system_gems_to_install)
+ it "works with --gemfile flag" do
+ system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
+
+ gemfile "CustomGemfile", <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ G
+
+ bundle "exec --gemfile CustomGemfile myrackup"
+ expect(out).to eq("1.0.0")
end
it "activates the correct gem" do
+ system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
+
gemfile <<-G
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
- bundle "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("0.9.1")
end
+ it "works and prints no warnings when HOME is not writable" do
+ system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+ G
+
+ bundle "exec myrackup", 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("1.0.0")
end
- it "works when running from a random directory", :ruby_repo do
+ it "works when running from a random directory" do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec 'cd #{tmp("gems")} && rackup'"
+ bundle "exec 'cd #{tmp("gems")} && myrackup'"
- expect(out).to include("1.0.0")
+ expect(out).to eq("1.0.0")
end
it "works when exec'ing something else" do
- install_gemfile 'gem "rack"'
+ install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
bundle "exec echo exec"
expect(out).to eq("exec")
end
it "works when exec'ing to ruby" do
- install_gemfile 'gem "rack"'
+ install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
bundle "exec ruby -e 'puts %{hi}'"
expect(out).to eq("hi")
end
+ it "works when exec'ing to rubygems" do
+ install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
+ 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 \"https://gem.repo1\"; gem \"myrack\""
+ bundle "exec sh -c '#{gem_cmd} --version'"
+ expect(out).to eq(Gem::VERSION)
+ end
+
+ it "works when exec'ing back to bundler to run a remote resolve" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+ G
+
+ bundle "exec bundle lock", env: { "BUNDLER_VERSION" => Bundler::VERSION }
+
+ expect(out).to include("Writing lockfile")
+ end
+
+ it "respects custom process title when loading through ruby" do
+ skip "https://github.com/ruby/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
+ gemfile "Gemfile", "source \"https://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 'gem "rack"'
+ install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
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 'gem "rack"'
+ install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
bundle "exec echo --verbose"
expect(out).to eq("--verbose")
end
it "handles --keep-file-descriptors" do
+ skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform?
+
require "tempfile"
command = Tempfile.new("io-test")
@@ -70,38 +132,35 @@ RSpec.describe "bundle exec" do
else
require 'tempfile'
io = Tempfile.new("io-test-fd")
- args = %W[#{Gem.ruby} -I#{lib} #{bindir.join("bundle")} exec --keep-file-descriptors #{Gem.ruby} #{command.path} \#{io.to_i}]
- args << { io.to_i => io } if RUBY_VERSION >= "2.0"
+ 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 ""
- sys_exec("#{Gem.ruby} #{command.path}")
+ install_gemfile "source \"https://gem.repo1\""
+ in_bundled_app "#{Gem.ruby} #{command.path}"
- if Bundler.current_ruby.ruby_2?
- expect(out).to eq("")
- else
- expect(out).to eq("Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec.")
- end
-
- expect(err).to lack_errors
+ expect(out).to be_empty
+ expect(err).to be_empty
end
it "accepts --keep-file-descriptors" do
- install_gemfile ""
+ install_gemfile "source \"https://gem.repo1\""
bundle "exec --keep-file-descriptors echo foobar"
- expect(err).to lack_errors
+ expect(err).to be_empty
end
it "can run a command named --verbose" do
- install_gemfile 'gem "rack"'
- File.open("--verbose", "w") do |f|
+ skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
+ File.open(bundled_app("--verbose"), "w") do |f|
f.puts "#!/bin/sh"
f.puts "echo foobar"
end
- File.chmod(0o744, "--verbose")
+ File.chmod(0o744, bundled_app("--verbose"))
with_path_as(".") do
bundle "exec -- --verbose"
end
@@ -110,135 +169,225 @@ RSpec.describe "bundle exec" do
it "handles different versions in different bundles" do
build_repo2 do
- build_gem "rack_two", "1.0.0" do |s|
- s.executables = "rackup"
+ build_gem "myrack_two", "1.0.0" do |s|
+ s.executables = "myrackup"
end
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
- Dir.chdir bundled_app2 do
- install_gemfile bundled_app2("Gemfile"), <<-G
- source "file://#{gem_repo2}"
- gem "rack_two", "1.0.0"
- G
- end
+ install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
+ source "https://gem.repo2"
+ gem "myrack_two", "1.0.0"
+ G
- bundle! "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("0.9.1")
- Dir.chdir bundled_app2 do
- bundle! "exec rackup"
- expect(out).to eq("1.0.0")
+ bundle "exec myrackup", dir: bundled_app2
+ expect(out).to eq("1.0.0")
+ end
+
+ context "with default gems" do
+ # TODO: Switch to ERB::VERSION once Ruby 3.4 support is dropped, so all
+ # supported rubies include an `erb` gem version where `ERB::VERSION` is
+ # public
+ let(:default_erb_version) { ruby "require 'erb/version'; puts ERB.const_get(:VERSION)" }
+
+ context "when not specified in Gemfile" do
+ before do
+ install_gemfile "source \"https://gem.repo1\""
+ end
+
+ it "uses version provided by ruby" do
+ bundle "exec erb --version"
+
+ expect(stdboth).to eq(default_erb_version)
+ end
+ end
+
+ context "when specified in Gemfile directly" do
+ let(:specified_erb_version) { "2.0.0" }
+
+ before do
+ build_repo2 do
+ build_gem "erb", specified_erb_version do |s|
+ s.executables = "erb"
+ end
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "erb", "#{specified_erb_version}"
+ G
+ end
+
+ it "uses version specified" do
+ bundle "exec erb --version"
+
+ expect(stdboth).to eq(specified_erb_version)
+ end
+ end
+
+ context "when specified in Gemfile indirectly" do
+ let(:indirect_erb_version) { "2.0.0" }
+
+ before do
+ build_repo2 do
+ build_gem "erb", indirect_erb_version do |s|
+ s.executables = "erb"
+ end
+
+ build_gem "gem_depending_on_old_erb" do |s|
+ s.add_dependency "erb", indirect_erb_version
+ end
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "gem_depending_on_old_erb"
+ G
+ end
+
+ it "uses resolved version" do
+ bundle "exec erb --version"
+
+ expect(stdboth).to eq(indirect_erb_version)
+ end
+ end
+ end
+
+ it "warns about executable conflicts" do
+ build_repo2 do
+ build_gem "myrack_two", "1.0.0" do |s|
+ s.executables = "myrackup"
+ end
end
+
+ bundle_config_global "path.system true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+ G
+
+ install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
+ source "https://gem.repo2"
+ gem "myrack_two", "1.0.0"
+ G
+
+ bundle "exec myrackup"
+
+ expect(last_command.stderr).to eq(
+ "Bundler is using a binstub that was created for a different gem (myrack).\n" \
+ "You should run `bundle binstub myrack_two` to work around a system/bundle conflict."
+ )
end
it "handles gems installed with --without" do
- install_gemfile <<-G, :without => :middleware
- source "file://#{gem_repo1}"
- gem "rack" # rack 0.9.1 and 1.0 exist
+ bundle_config "without middleware"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack" # myrack 0.9.1 and 1.0 exist
group :middleware do
- gem "rack_middleware" # rack_middleware depends on rack 0.9.1
+ gem "myrack_middleware" # myrack_middleware depends on myrack 0.9.1
end
G
- bundle "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("0.9.1")
- expect(the_bundle).not_to include_gems "rack_middleware 1.0"
+ expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
end
it "does not duplicate already exec'ed RUBYOPT" do
+ create_file("echoopt", "#!/usr/bin/env ruby\nprint ENV['RUBYOPT']")
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- rubyopt = ENV["RUBYOPT"]
- rubyopt = "-rbundler/setup #{rubyopt}"
+ bundler_setup_opt = "-r#{lib_dir}/bundler/setup"
+
+ rubyopt = opt_add(bundler_setup_opt, ENV["RUBYOPT"])
- bundle "exec 'echo $RUBYOPT'"
- expect(out).to have_rubyopts(rubyopt)
+ bundle "exec echoopt"
+ expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
- bundle "exec 'echo $RUBYOPT'", :env => { "RUBYOPT" => rubyopt }
- expect(out).to have_rubyopts(rubyopt)
+ bundle "exec echoopt", env: { "RUBYOPT" => rubyopt }
+ expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
end
- it "does not duplicate already exec'ed RUBYLIB", :ruby_repo do
+ it "does not duplicate already exec'ed RUBYLIB" do
+ create_file("echolib", "#!/usr/bin/env ruby\nprint ENV['RUBYLIB']")
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
rubylib = ENV["RUBYLIB"]
- rubylib = "#{rubylib}".split(File::PATH_SEPARATOR).unshift "#{bundler_path}"
+ rubylib = rubylib.to_s.split(File::PATH_SEPARATOR).unshift lib_dir.to_s
rubylib = rubylib.uniq.join(File::PATH_SEPARATOR)
- bundle "exec 'echo $RUBYLIB'"
+ bundle "exec echolib"
expect(out).to include(rubylib)
- bundle "exec 'echo $RUBYLIB'", :env => { "RUBYLIB" => rubylib }
+ bundle "exec echolib", env: { "RUBYLIB" => rubylib }
expect(out).to include(rubylib)
end
it "errors nicely when the argument doesn't exist" do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec foobarbaz"
- expect(exitstatus).to eq(127) if exitstatus
- expect(out).to include("bundler: command not found: foobarbaz")
- expect(out).to include("Install missing gem executables with `bundle install`")
+ 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec touch foo"
- bundle "exec ./foo"
- expect(exitstatus).to eq(126) if exitstatus
- expect(out).to include("bundler: not executable: ./foo")
+ bundled_app("foo").write("")
+ 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec"
- expect(exitstatus).to eq(128) if exitstatus
- expect(out).to include("bundler: exec needs a command to run")
+ 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", :ruby_repo, :rubygems => ">= 2.5.2" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "with_license"
- G
- [true, false].each do |l|
- bundle! "config disable_exec_load #{l}"
- bundle "exec rackup"
- 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
+ it "raises a helpful error when exec'ing to something outside of the bundle" do
+ system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
- # Different error message on old RG versions (before activate_bin_path) because they
- # called `Kernel#gem` directly
- it "raises a helpful error when exec'ing to something outside of the bundle", :rubygems => "< 2.5.2" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "with_license"
+ bundle_config "clean false" # want to keep the myrackup binstub
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo"
G
[true, false].each do |l|
- bundle! "config disable_exec_load #{l}"
- bundle "exec rackup"
- expect(err).to include "rack is not part of the bundle. Add it to your Gemfile."
+ bundle_config "disable_exec_load #{l}"
+ bundle "exec myrackup", raise_on_error: false
+ expect(err).to include "can't find executable myrackup for gem myrack. myrack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?"
end
end
@@ -250,14 +399,14 @@ RSpec.describe "bundle exec" do
describe "when #{exec} is used" do
before(:each) do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
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
@@ -278,6 +427,7 @@ RSpec.describe "bundle exec" do
it "shows executable's man page when the executable has a -" do
FileUtils.mv(bundled_app("print_args"), bundled_app("docker-template"))
+ FileUtils.mv(bundled_app("print_args.bat"), bundled_app("docker-template.bat")) if Gem.win_platform?
bundle "#{exec} docker-template build discourse --help"
expect(out).to eq('args: ["build", "discourse", "--help"]')
end
@@ -292,39 +442,39 @@ RSpec.describe "bundle exec" do
expect(out).to eq('args: ["-h"]')
end
- it "shows bundle-exec's man page when --help is between exec and the executable", :ruby_repo do
+ it "shows bundle-exec's man page when --help is between exec and the executable" do
with_fake_man do
- bundle "#{exec} --help cat"
+ bundle "#{exec} --help echo"
end
- expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when --help is before exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when -h is before exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when --help is after exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when -h is after exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
end
end
@@ -334,30 +484,31 @@ RSpec.describe "bundle exec" do
describe "run from a random directory" do
before(:each) do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
- it "works when unlocked", :ruby_repo do
- bundle "exec 'cd #{tmp("gems")} && rackup'"
+ it "works when unlocked" do
+ bundle "exec 'cd #{tmp("gems")} && myrackup'"
expect(out).to eq("1.0.0")
- expect(out).to include("1.0.0")
end
- it "works when locked", :ruby_repo do
+ it "works when locked" do
expect(the_bundle).to be_locked
- bundle "exec 'cd #{tmp("gems")} && rackup'"
- expect(out).to include("1.0.0")
+ bundle "exec 'cd #{tmp("gems")} && myrackup'"
+ 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|
+ build_lib "fizz", path: home("fizz") do |s|
s.executables = "fizz"
end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "fizz", :path => "#{File.expand_path(home("fizz"))}"
G
end
@@ -382,6 +533,7 @@ RSpec.describe "bundle exec" do
end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}"
G
end
@@ -400,11 +552,12 @@ RSpec.describe "bundle exec" do
describe "from gems bundled via :git with no gemspec" do
before(:each) do
- build_git "fizz_no_gemspec", :gemspec => false do |s|
+ build_git "fizz_no_gemspec", gemspec: false do |s|
s.executables = "fizz_no_gemspec"
end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}"
G
end
@@ -424,18 +577,61 @@ RSpec.describe "bundle exec" do
it "performs an automatic bundle install" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
gem "foo"
G
- bundle "config auto_install 1"
- bundle "exec rackup"
+ bundle_config "auto_install 1"
+ bundle "exec myrackup", artifice: "compact_index"
expect(out).to include("Installing foo 1.0")
end
+ it "performs an automatic bundle install with git gems" do
+ build_git "foo" do |s|
+ s.executables = "foo"
+ end
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle_config "auto_install 1"
+ bundle "exec foo", artifice: "compact_index"
+ expect(out).to include("Fetching myrack 0.9.1")
+ expect(out).to include("Fetching #{lib_path("foo-1.0")}")
+ expect(out.lines).to end_with("1.0")
+ end
+
+ it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do
+ 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 "auto_install 1"
+ bundle_config "path vendor/bundle"
+
+ gemfile <<~G
+ source "https://gem.repo4"
+ gem "fastlane"
+ G
+
+ bundle "exec fastlane", artifice: "compact_index"
+ 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", :rubygems => ">= 1.7.2" do
+ it "outputs the gemspec validation errors" do
build_lib "foo"
gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
@@ -446,24 +642,28 @@ RSpec.describe "bundle exec" do
s.version = '1.0'
s.summary = 'TODO: Add summary'
s.authors = 'Me'
+ s.rubygems_version = nil
end
G
end
- install_gemfile <<-G
+ gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo-1.0")}"
G
- bundle "exec irb"
+ bundle "exec erb", 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')
+ expect(err).to match(/missing value for attribute rubygems_version|rubygems_version must not be nil/)
end
end
describe "with gems bundled for deployment" do
it "works when calling bundler from another script" do
gemfile <<-G
+ source "https://gem.repo1"
+
module Monkey
def bin_path(a,b,c)
raise Gem::GemNotFoundException.new('Fail')
@@ -471,70 +671,146 @@ RSpec.describe "bundle exec" do
end
Bundler.rubygems.extend(Monkey)
G
- bundle "install --deployment"
- bundle "exec ruby -e '`#{bindir.join("bundler")} -v`; puts $?.success?'"
+ bundle_config "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
+ describe "bundle exec gem uninstall" do
+ before do
+ build_repo4 do
+ build_gem "foo"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "foo"
+ G
+ end
+
+ it "works" do
+ bundle "exec #{gem_cmd} uninstall foo"
+ expect(out).to eq("Successfully uninstalled foo-1.0")
+ end
+ end
+
+ describe "running gem commands in presence of rubygems plugins" do
+ before do
+ build_repo4 do
+ build_gem "foo" do |s|
+ s.write "lib/rubygems_plugin.rb", "puts 'FAIL'"
+ end
+ end
+
+ system_gems "foo-1.0", path: default_bundle_path, gem_repo: gem_repo4
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ G
+ end
+
+ it "does not load plugins outside of the bundle" do
+ bundle "exec #{gem_cmd} -v"
+ expect(out).not_to include("FAIL")
+ 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 }
+ let(:executable) { <<~RUBY.strip }
#{shebang}
- require "rack"
+ require "myrack"
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 "MYRACK: \#{MYRACK}"
+ if Gem.win_platform?
+ process_title = "ruby"
+ else
+ process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip
+ end
puts "PROCESS: \#{process_title}"
RUBY
before do
- path.open("w") {|f| f << executable }
- path.chmod(0o755)
+ create_file(bundled_app(path), executable)
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
let(:exec) { "EXEC: load" }
let(:args) { "ARGS: #{path} arg1 arg2" }
- let(:rack) { "RACK: 1.0.0" }
+ let(:myrack) { "MYRACK: 1.0.0" }
let(:process) do
- title = "PROCESS: #{path}"
- title += " arg1 arg2" if RUBY_VERSION >= "2.1"
- title
+ if Gem.win_platform?
+ "PROCESS: ruby"
+ else
+ "PROCESS: #{path} arg1 arg2"
+ end
end
let(:exit_code) { 0 }
- let(:expected) { [exec, args, rack, process].join("\n") }
+ let(:expected) { [exec, args, myrack, process].join("\n") }
let(:expected_err) { "" }
- subject { bundle "exec #{path} arg1 arg2" }
+ subject { bundle "exec #{path} arg1 arg2", raise_on_error: false }
- shared_examples_for "it runs" do
- it "like a normally executed executable" do
- subject
- expect(exitstatus).to eq(exit_code) if exitstatus
- expect(err).to eq(expected_err)
- expect(out).to eq(expected)
- end
+ it "runs" do
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
end
- it_behaves_like "it runs"
-
context "the executable exits explicitly" do
let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" }
context "with exit 0" do
- it_behaves_like "it runs"
+ it "runs" do
+ 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_behaves_like "it runs"
+
+ it "runs" do
+ 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/ruby/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
@@ -542,66 +818,148 @@ RSpec.describe "bundle exec" do
let(:executable) { "" }
let(:exit_code) { 0 }
- let(:expected) { "#{path} is empty" }
- let(:expected_err) { "" }
- if LessThanProc.with(RUBY_VERSION).call("1.9")
- # Kernel#exec in ruby < 1.9 will raise Errno::ENOEXEC if the command content is empty,
- # even if the command is set as an executable.
- pending "Kernel#exec is different"
- else
- it_behaves_like "it runs"
+ let(:expected_err) { "#{path} is empty" }
+ let(:expected) { "" }
+
+ it "runs" do
+ # it's empty, so `create_file` won't add executable permission and bat scripts on Windows
+ bundled_app(path).chmod(0o755)
+ path.sub_ext(".bat").write <<~SCRIPT if Gem.win_platform?
+ @ECHO OFF
+ @"ruby.exe" "%~dpn0" %*
+ SCRIPT
+
+ 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) { super() << "\nbundler: failed to load command: #{path} (#{path})" }
let(:expected_err) do
- "RuntimeError: ERROR\n #{path}:10" +
- (Bundler.current_ruby.ruby_18? ? "" : ":in `<top (required)>'")
+ /\Abundler: failed to load command: #{Regexp.quote(path.to_s)} \(#{Regexp.quote(path.to_s)}\)\n#{Regexp.quote(path.to_s)}:[0-9]+:in [`']<top \(required\)>': ERROR \(RuntimeError\)/
+ end
+
+ it "runs like a normally executed executable" do
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to match(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
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
end
- it_behaves_like "it runs"
end
- context "when the file uses the current ruby shebang", :ruby_repo do
+ context "when the file uses the current ruby shebang" do
let(:shebang) { "#!#{Gem.ruby}" }
- it_behaves_like "it runs"
+
+ it "runs" do
+ 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" do
before do
+ system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
+
gemfile <<-G
- gem 'rack', '2'
+ source "https://gem.repo1"
+ gem 'myrack', '2'
G
ENV["BUNDLER_FORCE_TTY"] = "true"
end
let(:exit_code) { Bundler::GemNotFound.new.status_code }
- let(:expected) { <<-EOS.strip }
-\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m
-\e[33mRun `bundle install` to install missing gems.\e[0m
+ let(:expected) { "" }
+ let(:expected_err) { <<~EOS.strip }
+ Could not find gem 'myrack (= 2)' in locally installed gems.
+
+ The source contains the following gems matching 'myrack':
+ * myrack-0.9.1
+ * myrack-1.0.0
+ Run `bundle install` to install missing gems.
EOS
- it_behaves_like "it runs"
+ it "runs" do
+ 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
+ gemfile "CustomGemfile", <<-G
+ source "https://gem.repo1"
+ gem 'myrack', '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
+ 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_behaves_like "it runs"
+ it "runs" do
+ 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" }
+ let(:process) do
+ if Gem.win_platform?
+ "PROCESS: ruby"
+ else
+ "PROCESS: ruby #{path} arg1 arg2"
+ end
+ end
before do
- bundle "config disable_exec_load true"
+ bundle_config "disable_exec_load true"
end
- it_behaves_like "it runs"
+ it "runs" do
+ 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
@@ -611,126 +969,304 @@ RSpec.describe "bundle exec" do
puts "__FILE__: #{__FILE__.inspect}"
RUBY
- let(:expected) { super() + <<-EOS.chomp }
+ context "when the path is absolute" do
+ let(:expected) { super() + <<~EOS.chomp }
-$0: #{path.to_s.inspect}
-__FILE__: #{path.to_s.inspect}
- EOS
+ $0: #{path.to_s.inspect}
+ __FILE__: #{path.to_s.inspect}
+ EOS
- it_behaves_like "it runs"
+ it "runs" do
+ 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" do
let(:path) { super().relative_path_from(bundled_app) }
+ let(:expected) { super() + <<~EOS.chomp }
- if LessThanProc.with(RUBY_VERSION).call("1.9")
- pending "relative paths have ./ __FILE__"
- else
- it_behaves_like "it runs"
+ $0: #{path.to_s.inspect}
+ __FILE__: #{path.to_s.inspect}
+ EOS
+
+ it "runs" do
+ 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(Pathname.pwd)}") }
-
- if LessThanProc.with(RUBY_VERSION).call("< 1.9")
- pending "relative paths with ./ have absolute __FILE__"
- else
- it_behaves_like "it runs"
+ let(:path) { Pathname.new("./#{super().relative_path_from(bundled_app)}") }
+ let(:expected) { super() + <<~EOS.chomp }
+
+ $0: #{path.to_s.inspect}
+ __FILE__: #{File.expand_path(path, bundled_app).inspect}
+ EOS
+
+ it "runs" do
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
end
end
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
+ context "signal handling" do
+ let(:test_signals) do
+ open3_reserved_signals = %w[CHLD CLD PIPE]
+ reserved_signals = %w[SEGV BUS ILL FPE ABRT IOT 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) { <<~RUBY }
+ #{shebang}
+ begin
+ Thread.new do
+ puts 'Started' # For process sync
+ STDOUT.flush
+ sleep 1 # ignore quality_spec
+ raise RuntimeError, "Didn't receive expected INT"
+ end.join
+ rescue Interrupt
+ puts "foo"
+ end
+ RUBY
- it "receives the signal" do
- skip "popen3 doesn't provide a way to get pid " unless RUBY_VERSION >= "1.9.3"
+ it "receives the signal" do
+ skip "https://github.com/ruby/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
- bundle("exec #{path}") do |_, o, thr|
- o.gets # Consumes 'Started' and ensures that thread has started
- Process.kill("INT", thr.pid)
+ expect(out).to eq("foo")
end
+ end
- expect(out).to eq("foo")
+ context "signals not being trapped by bunder" do
+ let(:executable) { <<~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/ruby/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", :ruby_repo do
- let(:system_gems_to_install) { super() << :bundler }
-
- context "with shared gems disabled" do
+ context "nested bundle exec" do
+ context "when bundle in a local path" do
before do
+ skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform?
+
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle :install, :system_bundler => true, :path => "vendor/bundler"
+ bundle_config "path vendor/bundler"
+ bundle :install
end
- it "overrides disable_shared_gems so bundler can be found" do
+ it "correctly shells out" do
file = bundled_app("file_that_bundle_execs.rb")
- create_file(file, <<-RB)
+ create_file(file, <<-RUBY)
#!#{Gem.ruby}
puts `bundle exec echo foo`
- RB
+ RUBY
file.chmod(0o777)
- bundle! "exec #{file}", :system_bundler => true
+ 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/ruby/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile "source \"https://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
+ build_repo2 do
+ build_gem "rails", "6.1.0" do |s|
+ s.executables = "rails"
+ end
+ end
+
+ bundle_config "path vendor/bundle"
+ bundle_config "gemfile gemfiles/myrack_6_1.gemfile"
+
+ gemfile(bundled_app("gemfiles/myrack_6_1.gemfile"), <<~RUBY)
+ source "https://gem.repo2"
+
+ gem "rails", "6.1.0"
+ RUBY
+
+ # A Gemfile needs to be in the root to trick bundler's root resolution
+ gemfile "source 'https://gem.repo1'"
+
+ bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ 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
+
+ it "can still find gems after a nested subprocess when using bundler (with a final r) executable" do
+ script = bundled_app("bin/myscript")
+
+ create_file(script, <<~RUBY)
+ #!#{Gem.ruby}
+
+ puts `bundler 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 }
it "only leaves the default gem in the stdlib available" do
- skip "openssl isn't a default gem" if expected.empty?
+ default_openssl_version = ruby "require 'openssl'; puts OpenSSL::VERSION"
- install_gemfile! "" # must happen before installing the broken system gem
+ skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile "source \"https://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", <<-RB)
- raise "custom openssl should not be loaded, it's not in the gemfile!"
- RB
+ s.write("lib/openssl.rb", <<-RUBY)
+ raise ArgumentError, "custom openssl should not be loaded"
+ RUBY
end
end
- system_gems(:bundler, "openssl-#{openssl_version}", :gem_repo => gem_repo4)
+ system_gems("openssl-#{openssl_version}", gem_repo: gem_repo4)
file = bundled_app("require_openssl.rb")
- create_file(file, <<-RB)
+ create_file(file, <<-RUBY)
#!/usr/bin/env ruby
require "openssl"
puts OpenSSL::VERSION
warn Gem.loaded_specs.values.map(&:full_name)
- RB
+ RUBY
file.chmod(0o777)
+ env = { "PATH" => path }
aggregate_failures do
- expect(bundle!("exec #{file}", :system_bundler => true, :artifice => nil)).to eq(expected)
- expect(bundle!("exec bundle exec #{file}", :system_bundler => true, :artifice => nil)).to eq(expected)
- expect(bundle!("exec ruby #{file}", :system_bundler => true, :artifice => nil)).to eq(expected)
- expect(run!(file.read, :no_lib => true, :artifice => nil)).to eq(expected)
+ expect(bundle("exec #{file}", env: env)).to eq(default_openssl_version)
+ expect(bundle("exec bundle exec #{file}", env: env)).to eq(default_openssl_version)
+ expect(bundle("exec ruby #{file}", env: env)).to eq(default_openssl_version)
+ expect(run(file.read, artifice: nil, env: env)).to eq(default_openssl_version)
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}")
+ 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 "path .bundle"
+ install_gemfile <<-G
+ source "https://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_r 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..5883b8a63a
--- /dev/null
+++ b/spec/bundler/commands/fund_spec.rb
@@ -0,0 +1,118 @@
+# 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 "https://gem.repo2"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ gem 'myrack-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("myrack-obama")
+ end
+
+ it "does not consider fund information for gem dependencies" do
+ install_gemfile <<-G
+ source "https://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 "does not consider fund information for uninstalled optional dependencies" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ group :whatever, optional: true do
+ gem 'has_funding_and_other_metadata'
+ end
+ gem 'has_funding'
+ gem 'myrack-obama'
+ G
+
+ bundle "fund"
+
+ expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
+ expect(out).to_not include("has_funding_and_other_metadata")
+ expect(out).to_not include("myrack-obama")
+ end
+
+ it "considers fund information for installed optional dependencies" do
+ bundle_config "with whatever"
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ group :whatever, optional: true do
+ gem 'has_funding_and_other_metadata'
+ end
+ gem 'has_funding'
+ gem 'myrack-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("myrack-obama")
+ end
+
+ it "prints message if none of the gems have fund information" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem 'myrack-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 "https://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
index 6faeed058e..f9ad9fff14 100644
--- a/spec/bundler/commands/help_spec.rb
+++ b/spec/bundler/commands/help_spec.rb
@@ -1,43 +1,37 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle help" do
- # Rubygems 1.4+ no longer load gem plugins so this test is no longer needed
- it "complains if older versions of bundler are installed", :rubygems => "< 1.4" do
- system_gems "bundler-0.8.1"
-
- bundle "help"
- expect(err).to include("older than 0.9")
- expect(err).to include("running `gem cleanup bundler`.")
- end
-
- it "uses mann when available", :ruby_repo do
+ it "uses man when available" do
with_fake_man do
bundle "help gemfile"
end
- expect(out).to eq(%(["#{root}/man/gemfile.5"]))
+ expect(out).to eq(%(["#{man_dir}/gemfile.5"]))
end
- it "prefixes bundle commands with bundle- when finding the groff files", :ruby_repo do
+ it "prefixes bundle commands with bundle- when finding the man files" do
with_fake_man do
bundle "help install"
end
- expect(out).to eq(%(["#{root}/man/bundle-install.1"]))
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
end
- it "simply outputs the txt file when there is no man on the path", :ruby_repo do
- with_path_as("") do
- bundle "help install"
+ it "prexifes bundle commands with bundle- and resolves aliases when finding the man files" do
+ with_fake_man do
+ bundle "help package"
end
- expect(out).to match(/BUNDLE-INSTALL/)
+ expect(out).to eq(%(["#{man_dir}/bundle-cache.1"]))
end
- it "still outputs the old help for commands that do not have man pages yet" do
- bundle "help version"
- expect(out).to include("Prints the bundler's version information")
+ 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 "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
@@ -46,54 +40,53 @@ RSpec.describe "bundle help" do
bundle "help testtasks"
end
- expect(exitstatus).to be_zero if exitstatus
expect(out).to eq("--help")
end
- it "is called when the --help flag is used after the command", :ruby_repo do
+ 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(%(["#{root}/man/bundle-install.1"]))
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
end
- it "is called when the --help flag is used before the command", :ruby_repo do
+ 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(%(["#{root}/man/bundle-install.1"]))
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
end
- it "is called when the -h flag is used before the command", :ruby_repo do
+ 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(%(["#{root}/man/bundle-install.1"]))
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
end
- it "is called when the -h flag is used after the command", :ruby_repo do
+ 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(%(["#{root}/man/bundle-install.1"]))
+ 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"
+ bundle "instill -h", raise_on_error: false
end
- expect(out).to include('Could not find command "instill".')
+ expect(err).to include('Could not find command "instill".')
end
- it "is called when only using the --help flag", :ruby_repo do
+ it "is called when only using the --help flag" do
with_fake_man do
bundle "--help"
end
- expect(out).to eq(%(["#{root}/man/bundle.1"]))
+ expect(out).to eq(%(["#{man_dir}/bundle.1"]))
with_fake_man do
bundle "-h"
end
- expect(out).to eq(%(["#{root}/man/bundle.1"]))
+ 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
index cdfea983dc..a26b1696fb 100644
--- a/spec/bundler/commands/info_spec.rb
+++ b/spec/bundler/commands/info_spec.rb
@@ -1,41 +1,118 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle info" do
- context "info from specific gem in gemfile" 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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "rails"
+ gem "has_metadata"
+ gem "thin"
G
end
- it "prints information about the current gem" do
+ 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"
- expect(out).to match(%r{Path\: .*\/rails\-2\.3\.2})
+\tHomepage: http://example.com
+\tPath: #{default_bundle_path("gems", "rails-2.3.2")}"
end
- context "given a gem that is not installed" do
- it "prints missing gem error" do
- bundle "info foo"
- expect(out).to eq "Could not find gem 'foo'."
- 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 is missing, even if using a custom path without bundler there" do
+ bundle_config "path vendor/bundle"
+ bundle "install"
+ bundle "info bundler"
+ expect(out).to include("\tPath: #{root}")
+ expect(err).not_to match(/The gem bundler is missing/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 does not exist on disk, but specification is there" do
+ FileUtils.rm_r(default_bundle_path("gems", "rails-2.3.2"))
+
+ bundle "info rails --path"
+
+ expect(err).to include("The gem rails is missing.")
+ expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ bundle "info rail --path"
+ expect(err).to include("The gem rails is missing.")
+ expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ bundle "info rails"
+ expect(err).to include("The gem rails is missing.")
+ expect(err).to include(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", :if => (RUBY_VERSION >= "2.0") do
- bundle "info rdoc"
- expect(out).to include("* rdoc")
+ context "given a default gem shipped in ruby", :ruby_repo do
+ it "prints information about the default gem" do
+ bundle "info json"
+ expect(out).to include("* json")
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_repo1 do
+ build_repo2 do
build_gem "rails", "2.3.2" do |s|
s.executables = "rails"
s.summary = "Just another test gem"
@@ -48,11 +125,125 @@ RSpec.describe "bundle info" do
end
end
- context "given --path option" do
- it "prints the path to the gem" do
+ context "when gem has a reverse dependency on any version" do
+ it "prints the details" do
+ bundle "info myrack"
+
+ expect(out).to include("Reverse Dependencies: \n\t\tthin (1.0) depends on myrack (>= 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).to match(%r{.*\/rails\-2\.3\.2})
+
+ 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 "https://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 "https://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 "https://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 "https://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 "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-obama"
+ G
+
+ bundle "info rac"
+ expect(out).to match(/\A1 : myrack\n2 : myrack-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 "https://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 "https://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
index 6ab7e25cc3..989d6fa812 100644
--- a/spec/bundler/commands/init_spec.rb
+++ b/spec/bundler/commands/init_spec.rb
@@ -1,10 +1,33 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle init" do
it "generates a Gemfile" do
bundle :init
- expect(bundled_app("Gemfile")).to exist
+ 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
@@ -15,34 +38,65 @@ RSpec.describe "bundle init" do
end
it "does not change existing Gemfiles" do
- expect { bundle :init }.not_to change { File.read(bundled_app("Gemfile")) }
+ 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
- expect(out).to include("Gemfile already exists")
+
+ 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") }
+ let(:spec_file) { tmp("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_dependency 'myrack', '= 1.0.1'
s.add_development_dependency 'rspec', '1.2'
end
S
end
- bundle :init, :gemspec => spec_file
+ bundle :init, gemspec: spec_file
- gemfile = bundled_app("Gemfile").read
+ 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 "myrack", "= 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
@@ -58,9 +112,96 @@ RSpec.describe "bundle init" do
S
end
- bundle :init, :gemspec => spec_file
- expect(out).to include("There was an error while loading `test.gemspec`")
+ 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 "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
+ gemfile("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("test.gemspec") }
+
+ before do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.add_dependency 'myrack', '= 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 "myrack", "= 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
deleted file mode 100644
index dd0f1348cc..0000000000
--- a/spec/bundler/commands/inject_spec.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "bundle inject" do
- before :each do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- end
-
- context "without a lockfile" do
- it "locks with the injected gems" do
- expect(bundled_app("Gemfile.lock")).not_to exist
- bundle "inject 'rack-obama' '> 0'"
- expect(bundled_app("Gemfile.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("Gemfile.lock").read).not_to match(/rack-obama/)
- bundle "inject 'rack-obama' '> 0'"
- expect(bundled_app("Gemfile.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'"
- expect(out).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"
- expect(out).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://#{gem_repo1}"
- gemfile = bundled_app("Gemfile").read
- str = "gem \"foo\", \"> 0\", :source => \"file://#{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"
- bundle "config --local frozen 1"
- 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("Gemfile.lock").read).not_to match(/rack-obama/)
- bundle "inject 'rack-obama' '> 0'"
- expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/)
- end
-
- it "restores frozen afterwards" do
- bundle "inject 'rack-obama' '> 0'"
- config = YAML.load(bundled_app(".bundle/config").read)
- expect(config["BUNDLE_FROZEN"]).to eq("1")
- end
-
- it "doesn't allow Gemfile changes" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack-obama"
- G
- bundle "inject 'rack' '> 0'"
- expect(out).to match(/trying to install in deployment mode after changing/)
-
- expect(bundled_app("Gemfile.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
index 2d67a39f1e..3b24434dc7 100644
--- a/spec/bundler/commands/install_spec.rb
+++ b/spec/bundler/commands/install_spec.rb
@@ -1,50 +1,112 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
+ source "https://gem.repo1"
G
bundle :install
- expect(out).to match(/no dependencies/)
+ expect(err).to match(/no dependencies/)
end
it "does not make a lockfile if the install fails" do
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
raise StandardError, "FAIL"
G
- expect(err).to lack_errors
- expect(out).to match(/StandardError, "FAIL"/)
- expect(bundled_app("Gemfile.lock")).not_to exist
+ 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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "creates lockfile based on the lockfile method in Gemfile" do
+ install_gemfile <<-G
+ lockfile "OmgFile.lock"
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ bundle "install"
+
+ expect(bundled_app("OmgFile.lock")).to exist
+ end
+
+ it "creates lockfile using BUNDLE_LOCKFILE instead of lockfile method" do
+ ENV["BUNDLE_LOCKFILE"] = "ReallyOmgFile.lock"
+ install_gemfile <<-G
+ lockfile "OmgFile.lock"
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ expect(bundled_app("ReallyOmgFile.lock")).to exist
+ expect(bundled_app("OmgFile.lock")).not_to exist
+ ensure
+ ENV.delete("BUNDLE_LOCKFILE")
+ end
+
+ it "creates lockfile based on --lockfile option is given" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ bundle "install --gemfile OmgFile --lockfile ReallyOmgFile.lock"
+
+ expect(bundled_app("ReallyOmgFile.lock")).to exist
+ end
+
+ it "does not make a lockfile if lockfile false is used in Gemfile" do
+ install_gemfile <<-G
+ lockfile false
+ source "https://gem.repo1"
+ gem 'myrack'
G
- expect(bundled_app("Gemfile.lock")).to exist
+ expect(bundled_app_lock).not_to exist
end
it "does not create ./.bundle by default" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle :install # 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
+ it "will create a ./.bundle by default", bundler: "5" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expect(bundled_app(".bundle")).to exist
+ end
+
+ it "does not create ./.bundle by default when installing to system gems" do
+ install_gemfile <<-G, env: { "BUNDLE_PATH__SYSTEM" => "true" }
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expect(bundled_app(".bundle")).not_to exist
+ end
+
+ it "creates lockfiles based on the Gemfile name" do
gemfile bundled_app("OmgFile"), <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0"
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
G
bundle "install --gemfile OmgFile"
@@ -52,71 +114,126 @@ RSpec.describe "bundle install with gem sources" do
expect(bundled_app("OmgFile.lock")).to exist
end
+ it "doesn't create a lockfile if --no-lock option is given" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ bundle "install --gemfile OmgFile --no-lock"
+
+ expect(bundled_app("OmgFile.lock")).not_to exist
+ end
+
+ it "doesn't create a lockfile if --no-lock and --lockfile options are given" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "https://gem.repo1"
+ gem "myrack", "1.0"
+ G
+
+ bundle "install --gemfile OmgFile --no-lock --lockfile ReallyOmgFile.lock"
+
+ expect(bundled_app("OmgFile.lock")).not_to exist
+ expect(bundled_app("ReallyOmgFile.lock")).not_to exist
+ end
+
it "doesn't delete the lockfile if one already exists" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
- lockfile = File.read(bundled_app("Gemfile.lock"))
+ lockfile = File.read(bundled_app_lock)
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
raise StandardError, "FAIL"
G
- expect(File.read(bundled_app("Gemfile.lock"))).to eq(lockfile)
+ 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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- expect { run "1" }.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ expect { run "1" }.not_to change { File.mtime(bundled_app_lock) }
end
it "fetches gems" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ expect(default_bundle_path("gems/myrack-1.0.0")).to exist
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
+ it "auto-heals missing gems" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
G
- expect(default_bundle_path("gems/rack-1.0.0")).to exist
- expect(the_bundle).to include_gems("rack 1.0.0")
+ FileUtils.rm_r(default_bundle_path("gems/myrack-1.0.0"))
+
+ bundle "install --verbose"
+
+ expect(out).to include("Installing myrack 1.0.0")
+ expect(default_bundle_path("gems/myrack-1.0.0")).to exist
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
+ it "does not state that it's constantly reinstalling empty gems" do
+ build_repo4 do
+ build_gem "empty", "1.0.0", no_default: true
+ end
+
+ install_gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "empty"
+ G
+ gem_dir = default_bundle_path("gems/empty-1.0.0")
+ expect(gem_dir).to be_empty
+
+ bundle "install --verbose"
+ expect(out).not_to include("Installing empty")
end
it "fetches gems when multiple versions are specified" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack', "> 0.9", "< 1.0"
+ source "https://gem.repo1"
+ gem 'myrack', "> 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")
+ expect(default_bundle_path("gems/myrack-0.9.1")).to exist
+ expect(the_bundle).to include_gems("myrack 0.9.1")
end
it "fetches gems when multiple versions are specified take 2" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack', "< 1.0", "> 0.9"
+ source "https://gem.repo1"
+ gem 'myrack', "< 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")
+ expect(default_bundle_path("gems/myrack-0.9.1")).to exist
+ expect(the_bundle).to include_gems("myrack 0.9.1")
end
it "raises an appropriate error when gems are specified using symbols" do
- install_gemfile(<<-G)
- source "file://#{gem_repo1}"
- gem :rack
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+ gem :myrack
G
- expect(exitstatus).to eq(4) if exitstatus
+ expect(exitstatus).to eq(4)
end
it "pulls in dependencies" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
@@ -125,16 +242,22 @@ RSpec.describe "bundle install with gem sources" do
it "does the right version" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
- expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "with_development_dependency"
G
@@ -144,7 +267,7 @@ RSpec.describe "bundle install with gem sources" do
it "resolves correctly" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activemerchant"
gem "rails"
G
@@ -154,12 +277,12 @@ RSpec.describe "bundle install with gem sources" do
it "activates gem correctly according to the resolved gems" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activesupport", "2.3.5"
G
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activemerchant"
gem "rails"
G
@@ -168,7 +291,7 @@ RSpec.describe "bundle install with gem sources" do
end
it "does not reinstall any gem that is already available locally" do
- system_gems "activesupport-2.3.2"
+ system_gems "activesupport-2.3.2", path: default_bundle_path
build_repo2 do
build_gem "activesupport", "2.3.2" do |s|
@@ -177,7 +300,7 @@ RSpec.describe "bundle install with gem sources" do
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activerecord", "2.3.2"
G
@@ -185,170 +308,474 @@ RSpec.describe "bundle install with gem sources" do
end
it "works when the gemfile specifies gems that only exist in the system" do
- build_gem "foo", :to_system => true
+ build_gem "foo", to_bundle: true
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "foo"
G
- expect(the_bundle).to include_gems "rack 1.0.0", "foo 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0", "foo 1.0.0"
end
it "prioritizes local gems over remote gems" do
- build_gem "rack", "1.0.0", :to_system => true do |s|
- s.add_dependency "activesupport", "2.3.5"
- end
+ build_gem "myrack", "9.0.0", to_bundle: true
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ expect(the_bundle).to include_gems "myrack 9.0.0"
+ end
+
+ it "loads env plugins" do
+ plugin_msg = "hello from an env plugin!"
+ create_file "plugins/rubygems_plugin.rb", "puts '#{plugin_msg}'"
+ install_gemfile <<-G, env: { "RUBYLIB" => rubylib.unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) }
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expect(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
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
- G
+ simulate_platform "x86-darwin-100" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
- run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
- expect(out).to eq("1.0.0 #{Bundler.local_platform}")
+ expect(the_bundle).to include_gems("platform_specific 1.0 x86-darwin-100")
+ end
end
it "falls back on plain ruby" do
- simulate_platform "foo-bar-baz"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
- G
+ simulate_platform "foo-bar-baz" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
- run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
- expect(out).to eq("1.0.0 RUBY")
+ expect(the_bundle).to include_gems("platform_specific 1.0 ruby")
+ end
end
it "installs gems for java" do
- simulate_platform "java"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
- G
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
- run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
- expect(out).to eq("1.0.0 JAVA")
+ expect(the_bundle).to include_gems("platform_specific 1.0 java")
+ end
end
it "installs gems for windows" do
- simulate_platform mswin
+ simulate_platform "x86-mswin32" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
- G
+ expect(the_bundle).to include_gems("platform_specific 1.0 x86-mswin32")
+ end
+ end
- run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
- expect(out).to eq("1.0.0 MSWIN")
+ it "installs gems for aarch64-mingw-ucrt" do
+ simulate_platform "aarch64-mingw-ucrt" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
+ end
+
+ expect(out).to include("Installing platform_specific 1.0 (aarch64-mingw-ucrt)")
end
end
- describe "doing bundle install foo" do
- before do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ it "gives useful errors if no global sources are set, and gems not installed locally, with and without a lockfile" do
+ install_gemfile <<-G, raise_on_error: false
+ gem "myrack"
+ G
+
+ expect(err).to eq("Could not find gem 'myrack' in locally installed gems.")
+
+ lockfile <<~L
+ GEM
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install", raise_on_error: false
+
+ expect(err).to include(
+ "Because your Gemfile specifies no global remote source, your bundle is locked to " \
+ "myrack (1.0.0) from locally installed gems. However, myrack (1.0.0) is not installed. " \
+ "You'll need to either add a global remote source to your Gemfile or make sure myrack (1.0.0) " \
+ "is available locally before rerunning Bundler."
+ )
+ end
+
+ it "creates a Gemfile.lock on a blank Gemfile" do
+ install_gemfile <<-G
+ source "https://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 "https://gem.repo2"
+ gem "myrack"
+ gem "myrack"
+ G
+
+ expect(err).to include("Your Gemfile lists the gem myrack (>= 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 "https://gem.repo2"
+ gem "myrack", "1.0"
+ gem "myrack", "1.0"
+ G
+
+ expect(err).to include("Your Gemfile lists the gem myrack (= 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 "https://gem.repo2"
+ gem "myrack", :platform => :jruby
+ gem "myrack"
+ G
+
+ bundle "install"
+
+ expect(err).to include("Your Gemfile lists the gem myrack (>= 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
- it "works" do
- bundle "install --path vendor"
- expect(the_bundle).to include_gems "rack 1.0"
+ build_repo2 do
+ build_gem "my-private-gem"
end
- it "allows running bundle install --system without deleting foo" do
- bundle "install --path vendor"
- bundle "install --system"
- FileUtils.rm_rf(bundled_app("vendor"))
- expect(the_bundle).to include_gems "rack 1.0"
+ gemfile <<~G
+ source "https://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 "does not warn if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with compatible requirements" do
+ build_lib "my-gem", path: bundled_app do |s|
+ s.add_development_dependency "rubocop", "~> 1.36.0"
end
- it "allows running bundle install --system after deleting foo" do
- bundle "install --path vendor"
- FileUtils.rm_rf(bundled_app("vendor"))
- bundle "install --system"
- expect(the_bundle).to include_gems "rack 1.0"
+ build_repo4 do
+ build_gem "rubocop", "1.36.0"
+ build_gem "rubocop", "1.37.1"
end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec
+
+ gem "rubocop", group: :development
+ G
+
+ bundle :install
+
+ expect(err).to be_empty
+
+ expect(the_bundle).to include_gems("rubocop 1.36.0")
end
- it "finds gems in multiple sources" do
- build_repo2
- update_repo2
+ it "raises an error if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with incompatible requirements" do
+ build_lib "my-gem", path: bundled_app do |s|
+ s.add_development_dependency "rubocop", "~> 1.36.0"
+ end
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- source "file://#{gem_repo2}"
+ build_repo4 do
+ build_gem "rubocop", "1.36.0"
+ build_gem "rubocop", "1.37.1"
+ end
- gem "activesupport", "1.2.3"
- gem "rack", "1.2"
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec
+
+ gem "rubocop", "~> 1.37.0", group: :development
G
- expect(the_bundle).to include_gems "rack 1.2", "activesupport 1.2.3"
+ bundle :install, raise_on_error: false
+
+ expect(err).to include("The rubocop dependency has conflicting requirements in Gemfile (~> 1.37.0) and gemspec (~> 1.36.0)")
end
- it "gives a useful error if no sources are set" do
- install_gemfile <<-G
- gem "rack"
+ it "includes the gem without warning if two gemspecs add it with the same requirement" do
+ gem1 = tmp("my-gem-1")
+ gem2 = tmp("my-gem-2")
+
+ build_lib "my-gem", path: gem1 do |s|
+ s.add_development_dependency "rubocop", "~> 1.36.0"
+ end
+
+ build_lib "my-gem-2", path: gem2 do |s|
+ s.add_development_dependency "rubocop", "~> 1.36.0"
+ end
+
+ build_repo4 do
+ build_gem "rubocop", "1.36.0"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec path: "#{gem1}"
+ gemspec path: "#{gem2}"
G
bundle :install
- expect(out).to include("Your Gemfile has no gem server sources")
+
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems("rubocop 1.36.0")
end
- it "creates a Gemfile.lock on a blank Gemfile" do
- install_gemfile <<-G
+ it "includes the gem without warning if two gemspecs add it with compatible requirements" do
+ gem1 = tmp("my-gem-1")
+ gem2 = tmp("my-gem-2")
+
+ build_lib "my-gem", path: gem1 do |s|
+ s.add_development_dependency "rubocop", "~> 1.0"
+ end
+
+ build_lib "my-gem-2", path: gem2 do |s|
+ s.add_development_dependency "rubocop", "~> 1.36.0"
+ end
+
+ build_repo4 do
+ build_gem "rubocop", "1.36.0"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec path: "#{gem1}"
+ gemspec path: "#{gem2}"
G
- expect(File.exist?(bundled_app("Gemfile.lock"))).to eq(true)
+ bundle :install
+
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems("rubocop 1.36.0")
end
- it "gracefully handles error when rubygems server is unavailable" do
- install_gemfile <<-G, :artifice => nil
- source "file://#{gem_repo1}"
- source "http://localhost:9384"
+ it "errors out if two gemspecs add it with incompatible requirements" do
+ gem1 = tmp("my-gem-1")
+ gem2 = tmp("my-gem-2")
- gem 'foo'
+ build_lib "my-gem", path: gem1 do |s|
+ s.add_development_dependency "rubocop", "~> 2.0"
+ end
+
+ build_lib "my-gem-2", path: gem2 do |s|
+ s.add_development_dependency "rubocop", "~> 1.36.0"
+ end
+
+ build_repo4 do
+ build_gem "rubocop", "1.36.0"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec path: "#{gem1}"
+ gemspec path: "#{gem2}"
G
- bundle :install, :artifice => nil
- expect(out).to include("Could not fetch specs from http://localhost:9384/")
- expect(out).not_to include("file://")
+ bundle :install, raise_on_error: false
+
+ expect(err).to include("Two gemspec development dependencies have conflicting requirements on the same gem: rubocop (~> 1.36.0) and rubocop (~> 2.0). Bundler cannot continue.")
end
- it "fails gracefully when downloading an invalid specification from the full index", :rubygems => "2.5" 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"]]
+ it "errors out if a gem is specified in a gemspec and in the Gemfile" do
+ gem = tmp("my-gem-1")
+
+ build_lib "rubocop", path: gem do |s|
+ s.add_development_dependency "rubocop", "~> 1.0"
+ end
+
+ build_repo4 do
+ build_gem "rubocop"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "rubocop", :path => "#{gem}"
+ gemspec path: "#{gem}"
+ G
+
+ bundle :install, raise_on_error: false
+
+ expect(err).to include("There was an error parsing `Gemfile`: You cannot specify the same gem twice coming from different sources.")
+ expect(err).to include("You specified that rubocop (>= 0) should come from source at `#{gem}` and gemspec at `#{gem}`")
+ end
+
+ it "does not warn if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with same requirements, and different sources" do
+ build_lib "my-gem", path: bundled_app do |s|
+ s.add_development_dependency "activesupport"
+ end
+
+ build_repo4 do
+ build_gem "activesupport"
+ end
+
+ build_git "activesupport", "1.0", path: lib_path("activesupport")
+
+ install_gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec
+
+ gem "activesupport", :git => "#{lib_path("activesupport")}"
+ G
+
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}"
+
+ # if the Gemfile dependency is specified first
+ install_gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "activesupport", :git => "#{lib_path("activesupport")}"
+
+ gemspec
+ G
+
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}"
+ end
+
+ it "considers both dependencies for resolution if a gem is added once in Gemfile and also inside a local gemspec as a runtime dependency, with different requirements" do
+ build_lib "my-gem", path: bundled_app do |s|
+ s.add_dependency "rubocop", "~> 1.36.0"
+ end
+
+ build_repo4 do
+ build_gem "rubocop", "1.36.0"
+ build_gem "rubocop", "1.37.1"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gemspec
+
+ gem "rubocop"
+ G
+
+ bundle :install
+
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems("rubocop 1.36.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 "https://gem.repo2"
+ gem "myrack"
+ gem "myrack", "1.0"
+ G
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("You specified: myrack (>= 0) and myrack (= 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 "https://gem.repo2"
+ gem "myrack", "1.0"
+ gem "myrack", "1.1"
+ G
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("You specified: myrack (= 1.0) and myrack (= 1.1).")
+ end
+
+ it "gracefully handles error when rubygems server is unavailable" do
+ install_gemfile <<-G, artifice: nil, raise_on_error: false
+ source "https://gem.repo1"
+ source "http://0.0.0.0:9384" do
+ gem 'foo'
+ end
+ G
+
+ expect(err).to eq("Could not reach host 0.0.0.0:9384. Check your network connection and try again.")
+ expect(err).not_to include("file://")
+ end
+
+ it "fails gracefully when downloading an invalid specification from the full index" do
+ build_repo2(build_compact_index: false) do
+ build_gem "ajp-rails", "0.0.0", gemspec: false, skip_validation: true do |s|
+ invalid_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
+ instance_variable_set(:@dependencies, invalid_deps)
end
+
build_gem "ruby-ajp", "1.0.0"
end
- install_gemfile <<-G, :full_index => true
- source "file://#{gem_repo2}"
+ install_gemfile <<-G, full_index: true, raise_on_error: false
+ source "https://gem.repo2"
gem "ajp-rails", "0.0.0"
G
- expect(out).not_to match(/Error Report/i)
- expect(err).not_to match(/Error Report/i)
- expect(out).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue.").
- and include("Make sure that `gem install ajp-rails -v '0.0.0'` succeeds before bundling.")
+ expect(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
@@ -356,11 +783,10 @@ RSpec.describe "bundle install with gem sources" do
FileUtils.touch(bundled_app(".bundle/config"))
install_gemfile(<<-G)
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo'
G
- expect(exitstatus).to eq(0) if exitstatus
end
it "doesn't blow up when the global .bundle/config is empty" do
@@ -368,130 +794,713 @@ RSpec.describe "bundle install with gem sources" do
FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config")
install_gemfile(<<-G)
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo'
G
- expect(exitstatus).to eq(0) if exitstatus
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
- ::RUBY_VERSION = '1.8.7'
- ruby '~> 2.1'
+ install_gemfile <<-G, raise_on_error: false
+ ruby '~> 1.2'
+ source "https://gem.repo1"
G
- expect(out).to include("Your Ruby version is 1.8.7, but your Gemfile specified ~> 2.1")
+ 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_VERSION = '2.1.3'
- ::RUBY_PATCHLEVEL = 100
- ruby '~> 2.1.0'
+ ruby '~> #{Gem.ruby_version}'
+ source "https://gem.repo1"
G
end
it "writes current Ruby version to Gemfile.lock" do
- lockfile_should_be <<-L
+ checksums = checksums_section_when_enabled
+ expect(lockfile).to eq <<~L
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
-
+ #{checksums}
RUBY VERSION
- ruby 2.1.3p100
+ #{Bundler::RubyVersion.system}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
- it "updates Gemfile.lock with updated incompatible ruby version" do
+ it "updates Gemfile.lock with updated yet still compatible ruby version" do
install_gemfile <<-G
- ::RUBY_VERSION = '2.2.3'
- ::RUBY_PATCHLEVEL = 100
- ruby '~> 2.2.0'
+ ruby '~> #{current_ruby_minor}'
+ source "https://gem.repo1"
G
- lockfile_should_be <<-L
+ checksums = checksums_section_when_enabled
+
+ expect(lockfile).to eq <<~L
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
-
+ #{checksums}
RUBY VERSION
- ruby 2.2.3p100
+ #{Bundler::RubyVersion.system}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
+
+ it "does not crash when unlocking" do
+ gemfile <<-G
+ source "https://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
- before do
+ it "doesn't blow up when using the `gem` DSL" do
root_dir = tmp("foo[]bar")
FileUtils.mkdir_p(root_dir)
- in_app_root_custom(root_dir)
- end
- it "doesn't blow up" do
build_lib "foo"
gemfile = <<-G
+ source "https://gem.repo1"
gem 'foo', :path => "#{lib_path("foo-1.0")}"
G
- File.open("Gemfile", "w") do |file|
+ File.open("#{root_dir}/Gemfile", "w") do |file|
file.puts gemfile
end
- bundle :install
+ 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 "https://gem.repo1"
+ gemspec
+ G
+ File.open("#{root_dir}/Gemfile", "w") do |file|
+ file.puts gemfile
+ end
- expect(exitstatus).to eq(0) if exitstatus
+ bundle :install, dir: root_dir
end
end
describe "when requesting a quiet install via --quiet" do
- it "should be quiet" do
+ it "should be quiet if there are no warnings" do
+ bundle_config "force_ruby_platform true"
+
gemfile <<-G
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
- bundle :install, :quiet => true
- expect(out).to include("Could not find gem 'rack'")
- expect(out).to_not include("Your Gemfile has no gem server sources")
+ 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 "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 "https://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" do
+ describe "when bundle path does not have cd permission", :permissions do
+ let(:bundle_path) { bundled_app("vendor") }
+
before do
- FileUtils.mkdir_p(bundled_app("vendor"))
+ FileUtils.mkdir_p(bundle_path)
gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
end
it "should display a proper message to explain the problem" do
- FileUtils.chmod(0o500, bundled_app("vendor"))
+ FileUtils.chmod(0o500, bundle_path)
- bundle :install, :path => "vendor"
- expect(out).to include(bundled_app("vendor").to_s)
- expect(out).to include("grant write permissions")
+ bundle_config "path vendor"
+ bundle :install, raise_on_error: false
+ expect(err).to include(bundle_path.to_s)
+ expect(err).to include("grant executable permissions")
+ end
+ end
+
+ describe "when bundle gems path does not have cd permission", :permissions do
+ let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") }
+
+ before do
+ FileUtils.mkdir_p(gems_path)
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod("-x", gems_path)
+ bundle_config "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("myrack-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 there's an empty install folder (like with default gems) without cd permissions", :permissions do
+ let(:full_gem_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems/myrack-1.0.0") }
+
+ before do
+ FileUtils.mkdir_p(full_gem_path)
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod("-x", full_gem_path)
+ bundle_config "path vendor"
+
+ begin
+ bundle :install, raise_on_error: false
+ ensure
+ FileUtils.chmod("+x", full_gem_path)
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ expect(err).to include(
+ "There was an error while trying to write to `#{full_gem_path}`. " \
+ "It is likely that you need to grant write permissions for that path."
+ )
+ end
+ end
+
+ describe "when bundle bin dir does not have cd permission", :permissions do
+ let(:bin_dir) { bundled_app("vendor/#{Bundler.ruby_scope}/bin") }
+
+ before do
+ FileUtils.mkdir_p(bin_dir)
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod("-x", bin_dir)
+ bundle_config "path vendor"
+
+ begin
+ bundle :install, raise_on_error: false
+ ensure
+ FileUtils.chmod("+x", bin_dir)
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ expect(err).to include(
+ "There was an error while trying to write to `#{bin_dir}`. " \
+ "It is likely that you need to grant write permissions for that path."
+ )
+ end
+ end
+
+ describe "when bundle bin dir does not have write access", :permissions do
+ let(:bin_dir) { bundled_app("vendor/#{Bundler.ruby_scope}/bin") }
+
+ before do
+ FileUtils.mkdir_p(bin_dir)
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod("-w", bin_dir)
+ bundle_config "path vendor"
+
+ begin
+ bundle :install, raise_on_error: false
+ ensure
+ FileUtils.chmod("+w", bin_dir)
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ expect(err).to include(
+ "There was an error while trying to write to `#{bin_dir}`. " \
+ "It is likely that you need to grant write permissions for that 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 "https://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 "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 does not have cd permission", :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 "https://gem.repo4"
+ gem 'foo'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ bundle_config "path vendor"
+ bundle :install
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+
+ FileUtils.chmod("-x", foo_path)
+
+ begin
+ bundle "install --force", 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 gem home does not have the writable bit set, yet it's still writable", :permissions do
+ let(:gem_home) { bundled_app("vendor/#{Bundler.ruby_scope}") }
+
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0.0" do |s|
+ s.write "CHANGELOG.md", "foo"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem 'foo'
+ G
+ end
+
+ it "should still work" do
+ bundle_config "path vendor"
+ bundle :install
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+
+ FileUtils.chmod("-w", gem_home)
+
+ begin
+ bundle "install --force"
+ ensure
+ FileUtils.chmod("+w", gem_home)
+ end
+
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+ end
+ end
+
+ describe "when gems path is world writable (no sticky bit set)", :permissions do
+ let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") }
+
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0.0" do |s|
+ s.write "CHANGELOG.md", "foo"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem 'foo'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ bundle_config "path vendor"
+ bundle :install
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+
+ FileUtils.chmod(0o777, gems_path)
+
+ bundle "install --force", raise_on_error: false
+
+ expect(err).to include("Bundler cannot reinstall foo-1.0.0 because there's a previous installation of it at #{gems_path}/foo-1.0.0 that is unsafe to remove")
+ end
+ end
+
+ describe "when gems path is world writable (no sticky bit set), but previous install is just an empty dir (like it happens with default gems)", :permissions do
+ let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") }
+ let(:full_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 "https://gem.repo4"
+ gem 'foo'
+ G
+ end
+
+ it "does not try to remove the directory and thus don't abort with an error about unsafe directory removal" do
+ bundle_config "path vendor"
+
+ FileUtils.mkdir_p(gems_path)
+ FileUtils.chmod(0o777, gems_path)
+ Dir.mkdir(full_path)
+
+ bundle "install"
+ 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 "https://gem.repo1"
+ gem 'myrack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod(0o500, cache_path)
+
+ bundle_config "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
+
+ describe "when gemspecs are unreadable", :permissions do
+ let(:gemspec_path) { vendored_gems("specifications/myrack-1.0.0.gemspec") }
+
+ before do
+ gemfile <<~G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+ bundle_config "path vendor/bundle"
+ bundle :install
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+
+ FileUtils.chmod("-r", gemspec_path)
+ end
+
+ it "shows a good error" do
+ bundle :install, raise_on_error: false
+ expect(err).to include(gemspec_path.to_s)
+ expect(err).to include("grant read permissions")
+ end
+ end
+
+ describe "when using umask 002 and setgid bit", :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 "https://gem.repo4"
+ gem 'foo'
+ G
+
+ FileUtils.mkdir_p(gems_path)
+ FileUtils.chmod("g+s", gems_path)
+ end
+
+ it "should create the gem directory with proper permissions" do
+ with_umask(0o002) do
+ bundle_config "path vendor"
+ bundle :install
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+ # Linux's SysV-derived mkdir(2) propagates the set-group-ID bit
+ # from the parent directory to newly created subdirectories. BSD
+ # (including macOS) inherits the parent's group via mkdir(2) but
+ # does not copy the set-group-ID bit itself, so the expected
+ # mode differs by platform.
+ expected = RUBY_PLATFORM.include?("darwin") ? 0o0775 : 0o2775
+ expect(File.stat(foo_path).mode & 0o7777).to eq(expected)
+ end
+ end
+ end
+
+ describe "parallel make" do
+ before do
+ unless Gem::Installer.private_method_defined?(:build_jobs)
+ skip "This example is runnable when RubyGems::Installer implements `build_jobs`"
+ end
+
+ @old_makeflags = ENV["MAKEFLAGS"]
+ @gemspec = nil
+
+ extconf_code = <<~CODE
+ require "mkmf"
+ create_makefile("foo")
+ CODE
+
+ build_repo4 do
+ build_gem "mypsych", "4.0.6" do |s|
+ @gemspec = s
+ extension = "ext/mypsych/extconf.rb"
+ s.extensions = extension
+
+ s.write(extension, extconf_code)
+ end
+ end
+ end
+
+ after do
+ if @old_makeflags
+ ENV["MAKEFLAGS"] = @old_makeflags
+ else
+ ENV.delete("MAKEFLAGS")
+ end
+ end
+
+ it "doesn't pass down -j to make when MAKEFLAGS is set" do
+ ENV["MAKEFLAGS"] = "-j1"
+
+ install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" })
+ source "https://gem.repo4"
+ gem "mypsych"
+ G
+
+ gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out"))
+
+ expect(gem_make_out).not_to include("make -j8")
+ end
+
+ it "pass down the BUNDLE_JOBS to RubyGems when running the compilation of an extension" do
+ ENV.delete("MAKEFLAGS")
+
+ install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" })
+ source "https://gem.repo4"
+ gem "mypsych"
+ G
+
+ gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out"))
+
+ expect(gem_make_out).to include("make -j8")
+ end
+
+ it "uses nprocessors by default" do
+ ENV.delete("MAKEFLAGS")
+
+ install_gemfile(<<~G)
+ source "https://gem.repo4"
+ gem "mypsych"
+ G
+
+ gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out"))
+
+ expect(gem_make_out).to include("make -j#{Etc.nprocessors + 1}")
+ end
+ end
+
+ describe "when a native extension requires a transitive dependency at build time" do
+ before do
+ build_repo4 do
+ build_gem "alpha", "1.0.0" do |s|
+ extension = "ext/alpha/extconf.rb"
+ s.extensions = extension
+ s.write(extension, <<~CODE)
+ require "mkmf"
+ sleep 1
+ create_makefile("alpha")
+ CODE
+ s.write "lib/alpha.rb", "ALPHA = '1.0.0'"
+ end
+
+ build_gem "beta", "1.0.0" do |s|
+ s.add_dependency "alpha"
+ s.write "lib/beta.rb", "require 'alpha'\nBETA = '1.0.0'"
+ end
+
+ build_gem "gamma", "1.0.0" do |s|
+ s.add_dependency "beta"
+ extension = "ext/gamma/extconf.rb"
+ s.extensions = extension
+ s.write(extension, <<~EXTCONF)
+ require "beta"
+ require "mkmf"
+ create_makefile("gamma")
+ EXTCONF
+ end
+ end
+ end
+
+ it "installs successfully" do
+ install_gemfile <<~G
+ source "https://gem.repo4"
+ gem "gamma"
+ G
+
+ expect(the_bundle).to include_gems "alpha 1.0.0", "beta 1.0.0", "gamma 1.0.0"
+ end
+ end
+
+ describe "when configured path is UTF-8 and a file inside a gem package too" do
+ let(:app_path) do
+ path = tmp("♥")
+ FileUtils.mkdir_p(path)
+ path
+ end
+
+ let(:path) do
+ root.join("vendor/bundle")
+ end
+
+ before do
+ build_repo4 do
+ build_gem "mygem" do |s|
+ s.write "spec/fixtures/_posts/2016-04-01-错误.html"
+ end
+ end
+ end
+
+ it "works" do
+ bundle "config set path #{app_path}/vendor/bundle", dir: app_path
+
+ install_gemfile app_path.join("Gemfile"),<<~G, dir: app_path
+ source "https://gem.repo4"
+ gem "mygem", "1.0"
+ G
+ end
+ end
+
+ context "after installing with --standalone" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ bundle_config "path bundle"
+ bundle "install", standalone: true
+ end
+
+ it "includes the standalone path" do
+ bundle "binstubs myrack", standalone: true
+ standalone_line = File.read(bundled_app("bin/myrackup")).each_line.find {|line| line.include? "$:.unshift" }.strip
+ expect(standalone_line).to eq %($:.unshift File.expand_path "../bundle", __dir__)
end
end
@@ -499,15 +1508,608 @@ RSpec.describe "bundle install with gem sources" do
before do
gemfile <<-G
source 'https://rubygems.org/'
- gem 'bundler'
+ gem "."
G
end
- it "should display a helpful messag explaining how to fix it" do
- bundle :install, :env => { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }
- expect(exitstatus).to eq(17) if exitstatus
- expect(out).to eq("Please CGI escape your usernames and passwords before " \
+ 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 "when current platform not included in the lockfile" do
+ around do |example|
+ 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-linux"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "libv8"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ libv8 (8.4.255.0-x86_64-darwin-19)
+
+ PLATFORMS
+ x86_64-darwin-19
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform("x86_64-linux", &example)
+ end
+
+ it "adds the current platform to the lockfile" do
+ bundle "install --verbose"
+
+ expect(out).to include("re-resolving dependencies because your lockfile is missing the current platform")
+ expect(out).not_to include("you are adding a new platform to your lockfile")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ libv8 (8.4.255.0-x86_64-darwin-19)
+ libv8 (8.4.255.0-x86_64-linux)
+
+ PLATFORMS
+ x86_64-darwin-19
+ x86_64-linux
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "fails loudly if frozen mode set" do
+ bundle_config "deployment true"
+ 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 "racca", "1.5.2"
+
+ build_gem "nokogiri", "1.12.4" do |s|
+ s.platform = "x86_64-darwin"
+ s.add_dependency "racca", "~> 1.4"
+ end
+
+ build_gem "nokogiri", "1.12.4" do |s|
+ s.platform = "x86_64-linux"
+ s.add_dependency "racca", "~> 1.4"
+ end
+
+ build_gem "crass", "1.0.6"
+
+ build_gem "loofah", "2.12.0" do |s|
+ s.add_dependency "crass", "~> 1.0.2"
+ s.add_dependency "nokogiri", ">= 1.5.9"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ ruby "#{Gem.ruby_version}"
+
+ gem "loofah", "~> 2.12.0"
+ G
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "crass", "1.0.6"
+ c.checksum gem_repo4, "loofah", "2.12.0"
+ c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin"
+ c.checksum gem_repo4, "racca", "1.5.2"
+ end
+
+ 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)
+ racca (~> 1.4)
+ racca (1.5.2)
+
+ PLATFORMS
+ x86_64-darwin-20
+ x86_64-linux
+
+ DEPENDENCIES
+ loofah (~> 2.12.0)
+ #{checksums}
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile" do
+ bundle_config "path vendor/bundle"
+
+ simulate_platform "x86_64-linux" do
+ bundle "install", artifice: "compact_index"
+ end
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "crass", "1.0.6"
+ c.checksum gem_repo4, "loofah", "2.12.0"
+ c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin"
+ c.checksum gem_repo4, "racca", "1.5.2"
+ c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-linux"
+ 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)
+ racca (~> 1.4)
+ nokogiri (1.12.4-x86_64-linux)
+ racca (~> 1.4)
+ racca (1.5.2)
+
+ PLATFORMS
+ x86_64-darwin-20
+ x86_64-linux
+
+ DEPENDENCIES
+ loofah (~> 2.12.0)
+ #{checksums}
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when lockfile has incorrect dependencies" do
+ before do
+ build_repo2
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack_middleware"
+ G
+
+ system_gems "myrack_middleware-1.0", path: default_bundle_path
+
+ # we want to raise when the 1.0 line should be followed by " myrack (= 0.9.1)" but isn't
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "raises a clear error message when frozen" do
+ bundle_config "frozen true"
+ bundle "install", raise_on_error: false
+
+ expect(exitstatus).to eq(41)
+ expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
+ expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile")
+ end
+
+ it "updates the lockfile when not frozen" do
+ missing_dep = "myrack (0.9.1)"
+ expect(lockfile).not_to include(missing_dep)
+
+ bundle_config "frozen false"
+ bundle :install
+
+ expect(lockfile).to include(missing_dep)
+ expect(out).to include("now installed")
+ end
+ end
+
+ context "with --local flag" do
+ before do
+ system_gems "myrack-1.0.0", path: default_bundle_path
+ end
+
+ it "respects installed gems without fetching any remote sources" do
+ install_gemfile <<-G, local: true
+ source "https://gem.repo1"
+
+ source "https://not-existing-source" do
+ gem "myrack"
+ end
+ G
+
+ expect(last_command).to be_success
+ end
+ end
+
+ context "with only option" do
+ before do
+ bundle_config "only a:b"
+ end
+
+ it "installs only gems of the specified groups" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails"
+ gem "myrack", group: :a
+ gem "rake", group: :b
+ gem "yard", group: :c
+ G
+
+ expect(out).to include("Installing myrack")
+ expect(out).to include("Installing rake")
+ expect(out).not_to include("Installing yard")
+ end
+ end
+
+ context "with --prefer-local flag" do
+ context "and gems available locally" do
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0.1"
+ build_gem "foo", "1.0.0"
+ build_gem "bar", "1.0.0"
+
+ build_gem "a", "1.0.0" do |s|
+ s.add_dependency "foo", "~> 1.0.0"
+ end
+
+ build_gem "b", "1.0.0" do |s|
+ s.add_dependency "foo", "~> 1.0.1"
+ end
+ end
+
+ system_gems "foo-1.0.0", path: default_bundle_path, gem_repo: gem_repo4
+ end
+
+ it "fetches remote sources when not available locally" do
+ install_gemfile <<-G, "prefer-local": true, verbose: true
+ source "https://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
+
+ it "fetches remote sources when local version does not match requirements" do
+ install_gemfile <<-G, "prefer-local": true, verbose: true
+ source "https://gem.repo4"
+
+ gem "foo", "1.0.1"
+ gem "bar"
+ G
+
+ expect(out).to include("Fetching foo 1.0.1").and include("Installing foo 1.0.1").and include("Fetching bar 1.0.0").and include("Installing bar 1.0.0")
+ expect(last_command).to be_success
+ end
+
+ it "uses the locally available version for sub-dependencies when possible" do
+ install_gemfile <<-G, "prefer-local": true, verbose: true
+ source "https://gem.repo4"
+
+ gem "a"
+ G
+
+ expect(out).to include("Using foo 1.0.0").and include("Fetching a 1.0.0").and include("Installing a 1.0.0")
+ expect(last_command).to be_success
+ end
+
+ it "fetches remote sources for sub-dependencies when the locally available version does not satisfy the requirement" do
+ install_gemfile <<-G, "prefer-local": true, verbose: true
+ source "https://gem.repo4"
+
+ gem "b"
+ G
+
+ expect(out).to include("Fetching foo 1.0.1").and include("Installing foo 1.0.1").and include("Fetching b 1.0.0").and include("Installing b 1.0.0")
+ expect(last_command).to be_success
+ end
+ end
+
+ context "and no gems available locally" do
+ before do
+ build_repo4 do
+ build_gem "myreline", "0.3.8"
+ build_gem "debug", "0.2.1"
+
+ build_gem "debug", "1.10.0" do |s|
+ s.add_dependency "myreline"
+ end
+ end
+ end
+
+ it "resolves to the latest version if no gems are available locally" do
+ install_gemfile <<~G, "prefer-local": true, verbose: true
+ source "https://gem.repo4"
+
+ gem "debug"
+ G
+
+ expect(out).to include("Fetching debug 1.10.0").and include("Installing debug 1.10.0").and include("Fetching myreline 0.3.8").and include("Installing myreline 0.3.8")
+ expect(last_command).to be_success
+ end
+ 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 "https://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 "https://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
+
+ context "when a lockfile has unmet dependencies, and the Gemfile has no resolution" do
+ before do
+ build_repo4 do
+ build_gem "aaa", "0.2.0" do |s|
+ s.add_dependency "zzz", "< 0.2.0"
+ end
+
+ build_gem "zzz", "0.2.0"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "aaa"
+ gem "zzz"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ aaa (0.2.0)
+ zzz (< 0.2.0)
+ zzz (0.2.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ aaa!
+ zzz!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "does not install, but raises a resolution error" do
+ bundle "install", raise_on_error: false
+ expect(err).to include("Could not find compatible versions")
+ end
+ end
+
+ context "when --jobs option given" do
+ before do
+ install_gemfile "source 'https://gem.repo1'", jobs: 1
+ end
+
+ it "does not save the flag to config" do
+ expect(bundled_app(".bundle/config")).not_to exist
+ end
+ end
+
+ context "when bundler installation is corrupt" do
+ before do
+ system_gems "bundler-9.99.8"
+
+ replace_version_file("9.99.9", dir: system_gem_path("gems/bundler-9.99.8"))
+ end
+
+ it "shows a proper error" do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ 9.99.8
+ L
+
+ install_gemfile "source \"https://gem.repo1\"", env: { "BUNDLER_VERSION" => "9.99.8" }, raise_on_error: false
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+ expect(err).to include("The running version of Bundler (9.99.9) does not match the version of the specification installed for it (9.99.8)")
+ end
+ end
+
+ it "only installs executable files in bin" do
+ bundle_config "path vendor/bundle"
+
+ install_gemfile <<~G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expected_executables = [vendored_gems("bin/myrackup").to_s]
+ expected_executables << vendored_gems("bin/myrackup.bat").to_s if Gem.win_platform?
+ expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables)
+ end
+
+ it "prevents removing binstubs when BUNDLE_CLEAN is set" do
+ build_repo4 do
+ build_gem "kamal", "4.0.6" do |s|
+ s.executables = ["kamal"]
+ end
+ end
+
+ gemfile = <<~G
+ source "https://gem.repo4"
+ gem "kamal"
+ G
+
+ install_gemfile(gemfile, env: { "BUNDLE_CLEAN" => "true", "BUNDLE_PATH" => "vendor/bundle" })
+
+ expected_executables = [vendored_gems("bin/kamal").to_s]
+ expected_executables << vendored_gems("bin/kamal.bat").to_s if Gem.win_platform?
+ expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables)
+ end
+
+ it "preserves lockfile versions conservatively" do
+ build_repo4 do
+ build_gem "mypsych", "4.0.6" do |s|
+ s.add_dependency "mystringio"
+ end
+
+ build_gem "mypsych", "5.1.2" do |s|
+ s.add_dependency "mystringio"
+ end
+
+ build_gem "mystringio", "3.1.0"
+ build_gem "mystringio", "3.1.1"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ mypsych (4.0.6)
+ mystringio
+ mystringio (3.1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ mypsych (~> 4.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<~G
+ source "https://gem.repo4"
+ gem "mypsych", "~> 5.0"
+ G
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ mypsych (5.1.2)
+ mystringio
+ mystringio (3.1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ mypsych (~> 5.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
end
diff --git a/spec/bundler/commands/issue_spec.rb b/spec/bundler/commands/issue_spec.rb
index 056ef0f300..346cdedc42 100644
--- a/spec/bundler/commands/issue_spec.rb
+++ b/spec/bundler/commands/issue_spec.rb
@@ -1,10 +1,9 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle issue" do
it "exits with a message" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
diff --git a/spec/bundler/commands/licenses_spec.rb b/spec/bundler/commands/licenses_spec.rb
index 0ee1a46945..ebfad5ed4a 100644
--- a/spec/bundler/commands/licenses_spec.rb
+++ b/spec/bundler/commands/licenses_spec.rb
@@ -1,10 +1,15 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "rails"
gem "with_license"
G
@@ -13,19 +18,19 @@ RSpec.describe "bundle licenses" do
it "prints license information for all gems in the bundle" do
bundle "licenses"
- expect(out).to include("bundler: Unknown")
+ 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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "rails"
gem "with_license"
gem "foo"
G
- bundle "config auto_install 1"
+ bundle_config "auto_install 1"
bundle :licenses
expect(out).to include("Installing foo 1.0")
end
diff --git a/spec/bundler/commands/list_spec.rb b/spec/bundler/commands/list_spec.rb
new file mode 100644
index 0000000000..c890646a81
--- /dev/null
+++ b/spec/bundler/commands/list_spec.rb
@@ -0,0 +1,315 @@
+# frozen_string_literal: true
+
+require "json"
+
+RSpec.describe "bundle list" do
+ def find_gem_name(json:, name:)
+ parse_json(json)["gems"].detect {|h| h["name"] == name }
+ end
+
+ def parse_json(json)
+ JSON.parse(json)
+ end
+
+ context "in verbose mode" do
+ it "logs the actual flags passed to the command" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ G
+
+ bundle "list --verbose"
+
+ expect(out).to include("Running `bundle list --verbose`")
+ end
+ end
+
+ 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
+
+ context "with invalid format option" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ G
+ end
+
+ it "raises an error" do
+ bundle "list --format=nope", raise_on_error: false
+
+ expect(err).to eq "Unknown option`--format=nope`. Supported formats: `json`"
+ end
+ end
+
+ describe "with without-group option" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ 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(" * myrack (1.0.0)")
+ expect(out).to include(" * rails (2.3.2)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+
+ it "prints the gems not in the specified group with json" do
+ bundle "list --without-group test --format=json"
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem["version"]).to eq("1.0.0")
+ gem = find_gem_name(json: out, name: "rails")
+ expect(gem["version"]).to eq("2.3.2")
+ gem = find_gem_name(json: out, name: "rspec")
+ expect(gem).to be_nil
+ 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(" * myrack (1.0.0)")
+ expect(out).not_to include(" * rails (2.3.2)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+
+ it "prints the gems not in the specified groups with json" do
+ bundle "list --without-group test production --format=json"
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem["version"]).to eq("1.0.0")
+ gem = find_gem_name(json: out, name: "rails")
+ expect(gem).to be_nil
+ gem = find_gem_name(json: out, name: "rspec")
+ expect(gem).to be_nil
+ end
+ end
+ end
+
+ describe "with only-group option" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ 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(" * myrack (1.0.0)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+
+ it "prints the gems in the specified group with json" do
+ bundle "list --only-group default --format=json"
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem["version"]).to eq("1.0.0")
+ gem = find_gem_name(json: out, name: "rspec")
+ expect(gem).to be_nil
+ 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(" * myrack (1.0.0)")
+ expect(out).to include(" * rails (2.3.2)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+
+ it "prints the gems in the specified groups with json" do
+ bundle "list --only-group default production --format=json"
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem["version"]).to eq("1.0.0")
+ gem = find_gem_name(json: out, name: "rails")
+ expect(gem["version"]).to eq("2.3.2")
+ gem = find_gem_name(json: out, name: "rspec")
+ expect(gem).to be_nil
+ end
+ end
+ end
+
+ context "with name-only option" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ 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("myrack")
+ expect(out).to include("rspec")
+ end
+
+ it "prints only the name of the gems in the bundle with json" do
+ bundle "list --name-only --format=json"
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem.keys).to eq(["name"])
+ gem = find_gem_name(json: out, name: "rspec")
+ expect(gem.keys).to eq(["name"])
+ end
+ end
+
+ context "with paths option" do
+ before do
+ build_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "bar"
+ end
+
+ build_git "git_test", "1.0.0", path: lib_path("git_test")
+
+ build_lib("gemspec_test", path: tmp("gemspec_test")) do |s|
+ s.add_dependency "bar", "=1.0.0"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ gem "rails"
+ gem "git_test", :git => "#{lib_path("git_test")}"
+ gemspec :path => "#{tmp("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{.*\/myrack\-1\.2})
+ expect(out).to match(%r{.*\/git_test\-\w})
+ expect(out).to match(%r{.*\/gemspec_test})
+ end
+
+ it "prints the path of each gem in the bundle with json" do
+ bundle "list --paths --format=json"
+
+ gem = find_gem_name(json: out, name: "rails")
+ expect(gem["path"]).to match(%r{.*\/rails\-2\.3\.2})
+ expect(gem["git_version"]).to be_nil
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem["path"]).to match(%r{.*\/myrack\-1\.2})
+ expect(gem["git_version"]).to be_nil
+
+ gem = find_gem_name(json: out, name: "git_test")
+ expect(gem["path"]).to match(%r{.*\/git_test\-\w})
+ expect(gem["git_version"]).to be_truthy
+ expect(gem["git_version"].strip).to eq(gem["git_version"])
+
+ gem = find_gem_name(json: out, name: "gemspec_test")
+ expect(gem["path"]).to match(%r{.*\/gemspec_test})
+ expect(gem["git_version"]).to be_nil
+ end
+ end
+
+ context "when no gems are in the gemfile" do
+ before do
+ install_gemfile <<-G
+ source "https://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
+
+ it "prints empty json" do
+ bundle "list --format=json"
+ expect(parse_json(out)["gems"]).to eq([])
+ end
+ end
+
+ context "without options" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ gem "rspec", :group => [:test]
+ G
+ end
+
+ it "lists gems installed in the bundle" do
+ bundle "list"
+ expect(out).to include(" * myrack (1.0.0)")
+ end
+
+ it "lists gems installed in the bundle with json" do
+ bundle "list --format=json"
+
+ gem = find_gem_name(json: out, name: "myrack")
+ expect(gem["version"]).to eq("1.0.0")
+ end
+ end
+
+ context "when using the ls alias" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ 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
index 5c15b6a7f6..8ab3cc7e8d 100644
--- a/spec/bundler/commands/lock_spec.rb
+++ b/spec/bundler/commands/lock_spec.rb
@@ -1,28 +1,22 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{repo}"
- gem "rails"
- gem "with_license"
- gem "foo"
- G
+ let(:expected_lockfile) do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "actionmailer", "2.3.2"
+ c.checksum gem_repo4, "actionpack", "2.3.2"
+ c.checksum gem_repo4, "activerecord", "2.3.2"
+ c.checksum gem_repo4, "activeresource", "2.3.2"
+ c.checksum gem_repo4, "activesupport", "2.3.2"
+ c.checksum gem_repo4, "foo", "1.0"
+ c.checksum gem_repo4, "rails", "2.3.2"
+ c.checksum gem_repo4, "rake", rake_version
+ c.checksum gem_repo4, "weakling", "0.0.3"
+ end
- @lockfile = strip_lockfile <<-L
+ <<~L
GEM
- remote: file:#{repo}/
+ remote: https://gem.repo4/
specs:
actionmailer (2.3.2)
activesupport (= 2.3.2)
@@ -39,80 +33,654 @@ RSpec.describe "bundle lock" do
actionpack (= 2.3.2)
activerecord (= 2.3.2)
activeresource (= 2.3.2)
- rake (= 10.0.2)
- rake (10.0.2)
- with_license (1.0)
+ rake (= #{rake_version})
+ rake (#{rake_version})
+ weakling (0.0.3)
PLATFORMS
- #{local}
+ #{lockfile_platforms}
DEPENDENCIES
foo
rails
- with_license
+ weakling
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ let(:outdated_lockfile) do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "actionmailer", "2.3.1"
+ c.checksum gem_repo4, "actionpack", "2.3.1"
+ c.checksum gem_repo4, "activerecord", "2.3.1"
+ c.checksum gem_repo4, "activeresource", "2.3.1"
+ c.checksum gem_repo4, "activesupport", "2.3.1"
+ c.checksum gem_repo4, "foo", "1.0"
+ c.checksum gem_repo4, "rails", "2.3.1"
+ c.checksum gem_repo4, "rake", rake_version
+ c.checksum gem_repo4, "weakling", "0.0.3"
+ end
+
+ <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ actionmailer (2.3.1)
+ activesupport (= 2.3.1)
+ actionpack (2.3.1)
+ activesupport (= 2.3.1)
+ activerecord (2.3.1)
+ activesupport (= 2.3.1)
+ activeresource (2.3.1)
+ activesupport (= 2.3.1)
+ activesupport (2.3.1)
+ foo (1.0)
+ rails (2.3.1)
+ actionmailer (= 2.3.1)
+ actionpack (= 2.3.1)
+ activerecord (= 2.3.1)
+ activeresource (= 2.3.1)
+ rake (= #{rake_version})
+ rake (#{rake_version})
+ weakling (0.0.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ rails
+ weakling
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
+ let(:gemfile_with_rails_weakling_and_foo_from_repo4) do
+ build_repo4 do
+ build_gem "rake", "10.0.1"
+ build_gem "rake", rake_version
+
+ %w[2.3.1 2.3.2].each do |version|
+ build_gem "rails", version do |s|
+ s.executables = "rails"
+ s.add_dependency "rake", version == "2.3.1" ? "10.0.1" : rake_version
+ s.add_dependency "actionpack", version
+ s.add_dependency "activerecord", version
+ s.add_dependency "actionmailer", version
+ s.add_dependency "activeresource", version
+ end
+ build_gem "actionpack", version do |s|
+ s.add_dependency "activesupport", version
+ end
+ build_gem "activerecord", version do |s|
+ s.add_dependency "activesupport", version
+ end
+ build_gem "actionmailer", version do |s|
+ s.add_dependency "activesupport", version
+ end
+ build_gem "activeresource", version do |s|
+ s.add_dependency "activesupport", version
+ end
+ build_gem "activesupport", version
+ end
+
+ build_gem "weakling", "0.0.3"
+
+ build_gem "foo"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "rails"
+ gem "weakling"
+ gem "foo"
+ G
+ end
+
it "prints a lockfile when there is no existing lockfile with --print" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
bundle "lock --print"
- expect(out).to include(@lockfile)
+ expect(out).to eq(expected_lockfile.chomp)
end
it "prints a lockfile when there is an existing lockfile with --print" do
- lockfile @lockfile
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
+
+ bundle "lock --print"
+
+ expect(out).to eq(expected_lockfile.chomp)
+ end
+
+ it "prints a lockfile when there is an existing checksums lockfile with --print" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
bundle "lock --print"
- expect(out).to eq(@lockfile)
+ expect(out).to eq(expected_lockfile.chomp)
end
it "writes a lockfile when there is no existing lockfile" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
bundle "lock"
- expect(read_lockfile).to eq(@lockfile)
+ expect(read_lockfile).to eq(expected_lockfile)
+ end
+
+ it "prints a lockfile without fetching new checksums if the existing lockfile had no checksums" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
+
+ bundle "lock --print"
+
+ expect(out).to eq(expected_lockfile.chomp)
+ end
+
+ it "touches the lockfile when there is an existing lockfile that does not need changes" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
+
+ expect do
+ bundle "lock"
+ end.to change { bundled_app_lock.mtime }
+ end
+
+ it "does not touch lockfile with --print" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
+
+ expect do
+ bundle "lock --print"
+ end.not_to change { bundled_app_lock.mtime }
end
it "writes a lockfile when there is an outdated lockfile using --update" do
- lockfile @lockfile.gsub("2.3.2", "2.3.1")
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile outdated_lockfile
+
+ bundle "lock --update"
+
+ expect(read_lockfile).to eq(expected_lockfile)
+ end
+
+ it "prints an updated lockfile when there is an outdated lockfile using --print --update" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile outdated_lockfile
- bundle! "lock --update"
+ bundle "lock --print --update"
- expect(read_lockfile).to eq(@lockfile)
+ expect(out).to eq(expected_lockfile.rstrip)
+ end
+
+ it "emits info messages to stderr when updating an outdated lockfile using --print --update" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile outdated_lockfile
+
+ bundle "lock --print --update"
+
+ expect(err).to eq(<<~STDERR.rstrip)
+ Fetching gem metadata from https://gem.repo4/...
+ Resolving dependencies...
+ STDERR
+ end
+
+ it "writes a lockfile when there is an outdated lockfile and bundle is frozen" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile outdated_lockfile
+
+ bundle "lock --update", env: { "BUNDLE_FROZEN" => "true" }
+
+ expect(read_lockfile).to eq(expected_lockfile)
end
it "does not fetch remote specs when using the --local option" do
- bundle "lock --update --local"
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "lock --update --local", raise_on_error: false
+
+ expect(err).to match(/locally installed gems/)
+ end
+
+ it "does not fetch remote checksums with --local" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
+
+ bundle "lock --print --local"
+
+ expect(out).to eq(expected_lockfile.chomp)
+ end
- expect(out).to include("sources listed in your Gemfile")
+ it "works with --gemfile flag" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ gemfile "CustomGemfile", <<-G
+ source "https://gem.repo4"
+ gem "foo"
+ G
+ bundle "lock --gemfile CustomGemfile"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "foo", "1.0"
+ end
+
+ lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ 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
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
bundle "lock --lockfile=lock"
expect(out).to match(/Writing lockfile to.+lock/)
- expect(read_lockfile "lock").to eq(@lockfile)
+ expect(read_lockfile("lock")).to eq(expected_lockfile)
expect { read_lockfile }.to raise_error(Errno::ENOENT)
end
+ it "updates a specific gem and write to a custom location" do
+ build_repo4 do
+ build_gem "foo", %w[1.0.2 1.0.3]
+ build_gem "warning", %w[1.4.0 1.5.0]
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "foo"
+ gem "warning"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4
+ specs:
+ foo (1.0.2)
+ warning (1.4.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ uri
+ warning
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --update foo --lockfile=lock"
+
+ lockfile_content = read_lockfile("lock")
+ expect(lockfile_content).to include("foo (1.0.3)")
+ expect(lockfile_content).to include("warning (1.4.0)")
+ end
+
+ it "writes to custom location using --lockfile when a default lockfile is present" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "install"
+ bundle "lock --lockfile=lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "actionmailer", "2.3.2"
+ c.checksum gem_repo4, "actionpack", "2.3.2"
+ c.checksum gem_repo4, "activerecord", "2.3.2"
+ c.checksum gem_repo4, "activeresource", "2.3.2"
+ c.checksum gem_repo4, "activesupport", "2.3.2"
+ c.checksum gem_repo4, "foo", "1.0"
+ c.checksum gem_repo4, "rails", "2.3.2"
+ c.checksum gem_repo4, "rake", rake_version
+ c.checksum gem_repo4, "weakling", "0.0.3"
+ end
+
+ lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ 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 (= #{rake_version})
+ rake (#{rake_version})
+ weakling (0.0.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ rails
+ weakling
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ 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("10.0.2", "10.0.1")
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "actionmailer", "2.3.1"
+ c.checksum gem_repo4, "actionpack", "2.3.1"
+ c.checksum gem_repo4, "activerecord", "2.3.1"
+ c.checksum gem_repo4, "activeresource", "2.3.1"
+ c.checksum gem_repo4, "activesupport", "2.3.1"
+ c.checksum gem_repo4, "foo", "1.0"
+ c.checksum gem_repo4, "rails", "2.3.1"
+ c.checksum gem_repo4, "rake", "10.0.1"
+ c.checksum gem_repo4, "weakling", "0.0.3"
+ end
+
+ lockfile_with_outdated_rails_and_rake = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ actionmailer (2.3.1)
+ activesupport (= 2.3.1)
+ actionpack (2.3.1)
+ activesupport (= 2.3.1)
+ activerecord (2.3.1)
+ activesupport (= 2.3.1)
+ activeresource (2.3.1)
+ activesupport (= 2.3.1)
+ activesupport (2.3.1)
+ foo (1.0)
+ rails (2.3.1)
+ actionmailer (= 2.3.1)
+ actionpack (= 2.3.1)
+ activerecord (= 2.3.1)
+ activeresource (= 2.3.1)
+ rake (= 10.0.1)
+ rake (10.0.1)
+ weakling (0.0.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ rails
+ weakling
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile lockfile_with_outdated_rails_and_rake
bundle "lock --update rails rake"
- expect(read_lockfile).to eq(@lockfile)
+ expect(read_lockfile).to eq(expected_lockfile)
+ end
+
+ it "updates specific gems using --update, even if that requires unlocking other top level gems" do
+ build_repo4 do
+ build_gem "prism", "0.15.1"
+ build_gem "prism", "0.24.0"
+
+ build_gem "ruby-lsp", "0.12.0" do |s|
+ s.add_dependency "prism", "< 0.24.0"
+ end
+
+ build_gem "ruby-lsp", "0.16.1" do |s|
+ s.add_dependency "prism", ">= 0.24.0"
+ end
+
+ build_gem "tapioca", "0.11.10" do |s|
+ s.add_dependency "prism", "< 0.24.0"
+ end
+
+ build_gem "tapioca", "0.13.1" do |s|
+ s.add_dependency "prism", ">= 0.24.0"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "tapioca"
+ gem "ruby-lsp"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4
+ specs:
+ prism (0.15.1)
+ ruby-lsp (0.12.0)
+ prism (< 0.24.0)
+ tapioca (0.11.10)
+ prism (< 0.24.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ruby-lsp
+ tapioca
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --update tapioca --verbose"
+
+ expect(lockfile).to include("tapioca (0.13.1)")
+ end
+
+ it "updates specific gems using --update, even if that requires unlocking other top level gems, but only as few as possible" do
+ build_repo4 do
+ build_gem "prism", "0.15.1"
+ build_gem "prism", "0.24.0"
+
+ build_gem "ruby-lsp", "0.12.0" do |s|
+ s.add_dependency "prism", "< 0.24.0"
+ end
+
+ build_gem "ruby-lsp", "0.16.1" do |s|
+ s.add_dependency "prism", ">= 0.24.0"
+ end
+
+ build_gem "tapioca", "0.11.10" do |s|
+ s.add_dependency "prism", "< 0.24.0"
+ end
+
+ build_gem "tapioca", "0.13.1" do |s|
+ s.add_dependency "prism", ">= 0.24.0"
+ end
+
+ build_gem "other-prism-dependent", "1.0.0" do |s|
+ s.add_dependency "prism", ">= 0.15.1"
+ end
+
+ build_gem "other-prism-dependent", "1.1.0" do |s|
+ s.add_dependency "prism", ">= 0.15.1"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "tapioca"
+ gem "ruby-lsp"
+ gem "other-prism-dependent"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4
+ specs:
+ other-prism-dependent (1.0.0)
+ prism (>= 0.15.1)
+ prism (0.15.1)
+ ruby-lsp (0.12.0)
+ prism (< 0.24.0)
+ tapioca (0.11.10)
+ prism (< 0.24.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ruby-lsp
+ tapioca
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --update tapioca"
+
+ expect(lockfile).to include("tapioca (0.13.1)")
+ expect(lockfile).to include("other-prism-dependent (1.0.0)")
+ end
+
+ it "preserves unknown checksum algorithms" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile.gsub(/(sha256=[a-f0-9]+)$/, "constant=true,\\1,xyz=123")
+
+ previous_lockfile = read_lockfile
+
+ bundle "lock"
+
+ expect(read_lockfile).to eq(previous_lockfile)
+ end
+
+ it "does not unlock git sources when only uri shape changes" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ build_git("foo")
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ # Change uri format to end with "/" and reinstall
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "foo", :git => "#{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
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ ref = build_git("foo").ref_for("HEAD")
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "deadbeef"
+ G
+
+ lockfile <<~L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{ref}
+ branch: deadbeef
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: https://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 (#{rake_version})")
end
it "errors when updating a missing specific gems using --update" do
- lockfile @lockfile
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile expected_lockfile
- bundle "lock --update blahblah"
- expect(out).to eq("Could not find gem 'blahblah'.")
+ bundle "lock --update blahblah", raise_on_error: false
+ expect(err).to eq("Could not find gem 'blahblah'.")
- expect(read_lockfile).to eq(@lockfile)
+ expect(read_lockfile).to eq(expected_lockfile)
+ end
+
+ it "can lock without downloading gems" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "thin"
+ gem "myrack_middleware", :group => "test"
+ G
+ bundle_config "without test"
+ bundle_config "path vendor/bundle"
+ bundle "lock", verbose: true
+ 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
@@ -120,22 +688,25 @@ RSpec.describe "bundle lock" do
context "conservative updates" do
before do
build_repo4 do
- build_gem "foo", %w(1.4.3 1.4.4) do |s|
+ 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|
+ 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|
+ 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.0.1 1.1.0 2.0.0)
+ 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://#{gem_repo4}"
+ source "https://gem.repo4"
gem 'foo', '1.4.3'
gem 'bar', '2.0.3'
gem 'qux', '1.0.0'
@@ -144,66 +715,340 @@ RSpec.describe "bundle lock" do
# remove 1.4.3 requirement and bar altogether
# to setup update specs below
gemfile <<-G
- source "file://#{gem_repo4}"
+ source "https://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)
+ expect(the_bundle.locked_specs).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)
+ expect(the_bundle.locked_specs).to eq(%w[foo-1.5.0 bar-2.1.1 qux-1.1.0].sort)
+ end
+
+ it "shows proper error when Gemfile changes forbid patch upgrades, and --patch --strict is given" do
+ # force next minor via Gemfile
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem 'foo', '1.5.0'
+ gem 'qux'
+ G
+
+ bundle "lock --update foo --patch --strict", raise_on_error: false
+
+ expect(err).to include(
+ "foo is locked to 1.4.3, while Gemfile is requesting foo (= 1.5.0). " \
+ "--strict --patch was specified, but there are no patch level upgrades from 1.4.3 satisfying foo (= 1.5.0), so version solving has failed"
+ )
+ end
+
+ context "pre" do
+ it "defaults to major" do
+ bundle "lock --update --pre"
+
+ expect(the_bundle.locked_specs).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_specs).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_specs).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_specs).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort)
+ end
+ end
+ end
+
+ context "conservative updates when minor update adds a new dependency" do
+ before do
+ build_repo4 do
+ build_gem "sequel", "5.71.0"
+ build_gem "sequel", "5.72.0" do |s|
+ s.add_dependency "bigdecimal", ">= 0"
+ end
+ build_gem "bigdecimal", %w[1.4.4 99.1.4]
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+ gem 'sequel'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ sequel (5.71.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ sequel
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "adds the latest version of the new dependency" do
+ bundle "lock --minor --update sequel"
+
+ expect(the_bundle.locked_specs).to eq(%w[sequel-5.72.0 bigdecimal-99.1.4].sort)
+ 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://gem.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")
+
+ build_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 when there's no previous lockfile" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "lock --add-platform java x86-mingw32 --verbose"
+ expect(out).to include("Resolving dependencies because there's no lockfile")
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(the_bundle.locked_platforms).to match_array(default_platform_list("java", "x86-mingw32"))
+ end
+
+ it "supports adding new platforms when a previous lockfile exists" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "lock"
+ bundle "lock --add-platform java x86-mingw32 --verbose"
+ expect(out).to include("Found changes from the lockfile, re-resolving dependencies because you are adding a new platform to your lockfile")
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(the_bundle.locked_platforms).to match_array(default_platform_list("java", "x86-mingw32"))
+ end
+
+ it "supports adding new platforms, when most specific locked platform is not the current platform, and current resolve is not compatible with the target platform" do
+ simulate_platform "arm64-darwin-23" do
+ build_repo4 do
+ build_gem "foo" do |s|
+ s.platform = "arm64-darwin"
+ end
+
+ build_gem "foo" do |s|
+ s.platform = "java"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "foo"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ foo (1.0-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --add-platform java"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ foo (1.0-arm64-darwin)
+ foo (1.0-java)
+
+ PLATFORMS
+ arm64-darwin
+ java
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
end
- it "supports adding new platforms" do
- bundle! "lock --add-platform java x86-mingw32"
+ it "supports adding new platforms with force_ruby_platform = true" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-x86-64_linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
- lockfile = Bundler::LockfileParser.new(read_lockfile)
- expect(lockfile.platforms).to eq([java, local, mingw])
+ DEPENDENCIES
+ platform_specific
+ L
+
+ bundle_config "force_ruby_platform true"
+ bundle "lock --add-platform java x86-mingw32"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(the_bundle.locked_platforms).to contain_exactly(Gem::Platform::RUBY, "x86_64-linux", "java", "x86-mingw32")
end
it "supports adding the `ruby` platform" do
- bundle! "lock --add-platform ruby"
- lockfile = Bundler::LockfileParser.new(read_lockfile)
- expect(lockfile.platforms).to eq([local, "ruby"].uniq)
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "lock --add-platform ruby"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(the_bundle.locked_platforms).to match_array(default_platform_list("ruby"))
end
- it "warns when adding an unknown platform" do
- bundle "lock --add-platform foobarbaz"
- expect(out).to include("The platform `foobarbaz` is unknown to RubyGems and adding it will likely lead to resolution errors")
+ it "fails when adding an unknown platform" do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "lock --add-platform foobarbaz", raise_on_error: false
+ expect(err).to include("The platform `foobarbaz` is unknown to RubyGems and can't be added to the lockfile")
+ expect(last_command).to be_failure
end
it "allows removing platforms" do
- bundle! "lock --add-platform java x86-mingw32"
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ bundle "lock --add-platform java x86-mingw32"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(the_bundle.locked_platforms).to match_array(default_platform_list("java", "x86-mingw32"))
+
+ bundle "lock --remove-platform java"
+
+ expect(the_bundle.locked_platforms).to match_array(default_platform_list("x86-mingw32"))
+ 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
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.12.0"
+ c.checksum gem_repo4, "nokogiri", "1.12.0", "x86_64-darwin"
+ end
+
+ simulate_platform "x86_64-darwin-22" do
+ install_gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+ end
- lockfile = Bundler::LockfileParser.new(read_lockfile)
- expect(lockfile.platforms).to eq([java, local, mingw])
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.12.0)
+ nokogiri (1.12.0-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+ x86_64-darwin
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
- bundle! "lock --remove-platform java"
+ checksums.delete("nokogiri", Gem::Platform::RUBY)
- lockfile = Bundler::LockfileParser.new(read_lockfile)
- expect(lockfile.platforms).to eq([local, mingw])
+ simulate_platform "x86_64-darwin-22" do
+ bundle "lock --remove-platform ruby"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.12.0-x86_64-darwin)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
it "errors when removing all platforms" do
- bundle "lock --remove-platform #{local}"
- expect(out).to include("Removing all platforms from the bundle is not allowed")
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ 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/bundler/bundler/issues/4896
+ # 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 = mingw
+ s.platform = "x86-mingw32"
end
build_gem "gssapi", "0.1"
@@ -221,7 +1066,7 @@ RSpec.describe "bundle lock" do
# 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|
+ %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
@@ -229,17 +1074,24 @@ RSpec.describe "bundle lock" do
end
gemfile <<-G
- source "file:#{gem_repo4}"
+ source "https://gem.repo4"
gem "mixlib-shellout"
gem "gssapi"
G
- simulate_platform(mingw) { bundle! :lock }
+ simulate_platform("x86-mingw32") { bundle :lock }
- expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G))
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "ffi", "1.9.14", "x86-mingw32"
+ c.checksum gem_repo4, "gssapi", "1.2.0"
+ c.checksum gem_repo4, "mixlib-shellout", "2.2.6", "universal-mingw32"
+ c.checksum gem_repo4, "win32-process", "0.8.3"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo4}/
+ remote: https://gem.repo4/
specs:
ffi (1.9.14-x86-mingw32)
gssapi (1.2.0)
@@ -255,16 +1107,20 @@ RSpec.describe "bundle lock" do
DEPENDENCIES
gssapi
mixlib-shellout
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
- simulate_platform(rb) { bundle! :lock }
+ bundle_config "force_ruby_platform true"
+ bundle :lock
- expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G))
+ checksums.checksum gem_repo4, "ffi", "1.9.14"
+ checksums.checksum gem_repo4, "mixlib-shellout", "2.2.6"
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo4}/
+ remote: https://gem.repo4/
specs:
ffi (1.9.14)
ffi (1.9.14-x86-mingw32)
@@ -283,33 +1139,1739 @@ RSpec.describe "bundle lock" do
DEPENDENCIES
gssapi
mixlib-shellout
+ #{checksums}
+ 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 "https://gem.repo4"
+
+ gem "libv8"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: https://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}
+ #{Bundler::VERSION}
G
+
+ simulate_platform("x86_64-darwin-19") { bundle "lock --update" }
+
+ expect(out).to match(/Writing lockfile to.+Gemfile\.lock/)
end
- context "when an update is available" do
- let(:repo) { gem_repo2 }
+ 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 "https://gem.repo4"
+
+ gem "libv8"
+ G
+
+ simulate_platform("x86_64-darwin-19") { bundle "lock" }
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19"
+ c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20"
+ end
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://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-19
+ x86_64-darwin-20
+
+ DEPENDENCIES
+ libv8
+ #{checksums}
+ 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
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19"
+ c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "libv8"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: https://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
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ previous_lockfile = lockfile
+
+ %w[x86_64-darwin-19 x86_64-darwin-20].each do |platform|
+ simulate_platform(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-mingw-ucrt"
+ s.required_ruby_version = "< #{next_ruby_minor}.dev"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "raygun-apm"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ 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"
+ end
+
+ it "adds platform specific gems as necessary, even when adding the current platform" do
+ build_repo4 do
+ build_gem "nokogiri", "1.16.0"
+
+ build_gem "nokogiri", "1.16.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.16.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --add-platform x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.16.0)
+ nokogiri (1.16.0-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "refuses to add platforms incompatible with the lockfile" do
+ build_repo4 do
+ build_gem "sorbet-static", "0.5.11989" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "sorbet-static"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ sorbet-static (0.5.11989-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ sorbet-static
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --add-platform ruby", raise_on_error: false
+ end
+ nice_error = <<~E.strip
+ Could not find gems matching 'sorbet-static' valid for all resolution platforms (x86_64-linux, ruby) in rubygems repository https://gem.repo4/ or installed locally.
+
+ The source contains the following gems matching 'sorbet-static':
+ * sorbet-static-0.5.11989-x86_64-linux
+ E
+ expect(err).to include(nice_error)
+ 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
before do
- lockfile(@lockfile)
- build_repo2 do
+ gemfile_with_rails_weakling_and_foo_from_repo4
+
+ build_repo4 do
build_gem "foo", "2.0"
end
+
+ lockfile(expected_lockfile)
end
it "does not implicitly update" do
- bundle! "lock"
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "actionmailer", "2.3.2"
+ c.checksum gem_repo4, "actionpack", "2.3.2"
+ c.checksum gem_repo4, "activerecord", "2.3.2"
+ c.checksum gem_repo4, "activeresource", "2.3.2"
+ c.checksum gem_repo4, "activesupport", "2.3.2"
+ c.checksum gem_repo4, "foo", "1.0"
+ c.checksum gem_repo4, "rails", "2.3.2"
+ c.checksum gem_repo4, "rake", rake_version
+ c.checksum gem_repo4, "weakling", "0.0.3"
+ end
- expect(read_lockfile).to eq(@lockfile)
+ expected_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ 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 (= #{rake_version})
+ rake (#{rake_version})
+ weakling (0.0.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ rails
+ weakling
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(read_lockfile).to eq(expected_lockfile)
end
it "accounts for changes in the gemfile" do
gemfile gemfile.gsub('"foo"', '"foo", "2.0"')
- bundle! "lock"
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "actionmailer", "2.3.2"
+ c.checksum gem_repo4, "actionpack", "2.3.2"
+ c.checksum gem_repo4, "activerecord", "2.3.2"
+ c.checksum gem_repo4, "activeresource", "2.3.2"
+ c.checksum gem_repo4, "activesupport", "2.3.2"
+ c.checksum gem_repo4, "foo", "2.0"
+ c.checksum gem_repo4, "rails", "2.3.2"
+ c.checksum gem_repo4, "rake", rake_version
+ c.checksum gem_repo4, "weakling", "0.0.3"
+ end
+
+ expected_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ 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 (2.0)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= #{rake_version})
+ rake (#{rake_version})
+ weakling (0.0.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo (= 2.0)
+ rails
+ weakling
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(read_lockfile).to eq(expected_lockfile)
+ 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 "https://gem.repo4"
+
+ gem "debug"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "debug", "1.6.3"
+ c.checksum gem_repo4, "irb", "1.5.0"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ debug (1.6.3)
+ irb (>= 1.3.6)
+ irb (1.5.0)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ debug
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "arm64-darwin-22" do
+ bundle "lock"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ debug (1.6.3)
+ irb (>= 1.3.6)
+ irb (1.5.0)
+
+ PLATFORMS
+ arm64-darwin-22
+ x86_64-linux
+
+ DEPENDENCIES
+ debug
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when a system gem has incorrect dependencies, different from remote gems" do
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0.0" do |s|
+ s.add_dependency "bar"
+ end
+
+ build_gem "bar", "1.0.0"
+ end
+
+ system_gems "foo-1.0.0", gem_repo: gem_repo4, path: default_bundle_path
+
+ # simulate gemspec with wrong empty dependencies
+ foo_gemspec_path = default_bundle_path("specifications/foo-1.0.0.gemspec")
+ foo_gemspec = Gem::Specification.load(foo_gemspec_path.to_s)
+ foo_gemspec.dependencies.clear
+ File.write(foo_gemspec_path, foo_gemspec.to_ruby)
+ end
+
+ it "generates a lockfile using remote dependencies, and prints a warning" do
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "foo"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "foo", "1.0.0"
+ c.checksum gem_repo4, "bar", "1.0.0"
+ end
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --verbose"
+ end
+
+ expect(err).to eq("Local specification for foo-1.0.0 has different dependencies than the remote gem, ignoring it")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ bar (1.0.0)
+ foo (1.0.0)
+ bar
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ foo
+ #{checksums}
+ 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 "https://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 https://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 "https://gem.repo4"
+
+ gem "rails", ">= 7.0.2.3"
+ gem "activeadmin", "= 2.13.1"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ activeadmin (2.13.1)
+ ransack (= 3.1.0)
+ ransack (3.1.0)
+ activemodel (>= 6.0.4)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ activeadmin (= 2.13.1)
+ ransack (= 3.1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expected_error = <<~ERR.strip
+ Could not find compatible versions
+
+ Because rails >= 7.0.4 depends on activemodel = 7.0.4
+ and rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1,
+ rails >= 7.0.3.1 requires activemodel = 7.0.3.1 OR = 7.0.4.
+ (1) So, because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3
+ and every version of activemodel depends on activesupport = 6.0.4,
+ rails >= 7.0.2.3 requires activesupport = 6.0.4.
+
+ Because rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 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 activesupport = 7.0.2.3 OR = 7.0.3.1.
+ And because rails >= 7.0.4 depends on activesupport = 7.0.4,
+ rails >= 7.0.2.3 requires activesupport = 7.0.2.3 OR = 7.0.3.1 OR = 7.0.4.
+ And because rails >= 7.0.2.3 requires activesupport = 6.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
+
+ bundle "lock", raise_on_error: false
+ expect(err).to eq(expected_error)
+
+ lockfile lockfile.gsub(/PLATFORMS\n #{local_platform}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}")
+
+ bundle "lock", raise_on_error: false
+ expect(err).to eq(expected_error)
+ 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 "https://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 "https://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 "https://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
+
+ it "adds checksums to an existing lockfile, when re-resolving is necessary" do
+ 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 "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ # lockfile has a typo (nogokiri) in the dependencies section, so Bundler
+ # sees dependencies have changed, and re-resolves
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nogokiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --add-checksums"
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.2"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "adds checksums to an existing lockfile, when no re-resolve is necessary" do
+ 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 "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --add-checksums"
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.2"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "adds checksums when source is not specified" do
+ system_gems(%w[myrack-1.0.0], path: default_bundle_path)
+
+ gemfile <<-G
+ gem "myrack"
+ G
+
+ lockfile <<~L
+ GEM
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ myrack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --add-checksums"
+ end
+
+ # myrack is coming from gem_repo1
+ # but it's simulated to install in the system gems path
+ checksums = checksums_section do |c|
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "adds checksums to an existing lockfile, when gems are already installed" do
+ 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 "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "install"
+
+ bundle "lock --add-checksums"
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.2"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "generates checksums by default" do
+ build_repo4 do
+ build_gem "nokogiri", "1.14.2"
+ build_gem "nokogiri", "1.14.2" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ simulate_platform "x86_64-linux" do
+ install_gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.2"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "disables checksums if configured to do so" do
+ build_repo4 do
+ build_gem "nokogiri", "1.14.2"
+ build_gem "nokogiri", "1.14.2" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ bundle_config "lockfile_checksums false"
+
+ simulate_platform "x86_64-linux" do
+ install_gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "add checksums for gems installed on disk" do
+ build_repo4 do
+ build_gem "warning", "18.0.0"
+ end
+
+ bundle_config "lockfile_checksums false"
+
+ simulate_platform "x86_64-linux" do
+ install_gemfile(<<-G, artifice: "endpoint")
+ source "https://gem.repo4"
+
+ gem "warning"
+ G
+
+ bundle "config --delete lockfile_checksums"
+ bundle("lock --add-checksums", artifice: "endpoint")
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "warning", "18.0.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ warning (18.0.0)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ warning
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "doesn't add checksum for gems not installed on disk" do
+ lockfile(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ warning (18.0.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ warning
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile(<<~G)
+ source "https://gem.repo4"
+
+ gem "warning"
+ G
+
+ build_repo4 do
+ build_gem "warning", "18.0.0"
+ end
+
+ FileUtils.rm_rf("#{gem_repo4}/gems")
+
+ bundle("lock --add-checksums", artifice: "endpoint")
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "warning", "18.0.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ warning (18.0.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ warning
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ 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 "https://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 "https://gem.repo4"
+ gemspec
+ G
+ end
+
+ it "locks both ruby and platform specific specs" do
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo4, "nokogiri", "1.14.2"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock"
+ end
+
+ expect(lockfile).to eq <<~L
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ nokogiri
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ context "and a lockfile with platform specific gems only already exists" do
+ before do
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ lockfile <<~L
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ nokogiri
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "keeps platform specific gems" do
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo4, "nokogiri", "1.14.2"
+ c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux"
+ end
+
+ simulate_platform "x86_64-linux" do
+ bundle "install"
+ end
+
+ expect(lockfile).to eq <<~L
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ nokogiri
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ 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 "https://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: https://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
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "actionpack", "7.0.4.3"
+ c.no_checksum "activesupport", "7.0.4.3"
+ c.no_checksum "govuk_app_config", "4.13.0"
+ c.no_checksum "railties", "7.0.4.3"
+ end
+
+ simulate_platform "arm64-darwin-22" do
+ bundle "lock"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when lockfile has incorrectly indented platforms" do
+ before do
+ build_repo4 do
+ build_gem "ffi", "1.1.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "ffi", "1.1.0" do |s|
+ s.platform = "arm64-darwin"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "ffi"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ ffi (1.1.0-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin
+
+ DEPENDENCIES
+ ffi
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "does not remove any gems" do
+ simulate_platform "x86_64-linux" do
+ bundle "lock --update"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ ffi (1.1.0-arm64-darwin)
+ ffi (1.1.0-x86_64-linux)
+
+ PLATFORMS
+ arm64-darwin
+ x86_64-linux
+
+ DEPENDENCIES
+ ffi
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ describe "--normalize-platforms on linux" do
+ let(:normalized_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ irb (1.0.0)
+ irb (1.0.0-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ irb
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ before do
+ build_repo4 do
+ build_gem "irb", "1.0.0"
+
+ build_gem "irb", "1.0.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "irb"
+ G
+ end
+
+ context "when already normalized" do
+ before do
+ lockfile normalized_lockfile
+ end
+
+ it "is a noop" do
+ simulate_platform "x86_64-linux" do
+ bundle "lock --normalize-platforms"
+ end
+
+ expect(lockfile).to eq(normalized_lockfile)
+ end
+ end
+
+ context "when not already normalized" do
+ before do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ irb (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ irb
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "normalizes the list of platforms and native gems in the lockfile" do
+ simulate_platform "x86_64-linux" do
+ bundle "lock --normalize-platforms"
+ end
+
+ expect(lockfile).to eq(normalized_lockfile)
+ end
+ end
+ end
+
+ describe "--normalize-platforms on darwin" do
+ let(:normalized_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ irb (1.0.0)
+ irb (1.0.0-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin
+ ruby
+
+ DEPENDENCIES
+ irb
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ before do
+ build_repo4 do
+ build_gem "irb", "1.0.0"
+
+ build_gem "irb", "1.0.0" do |s|
+ s.platform = "arm64-darwin"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "irb"
+ G
+ end
+
+ context "when already normalized" do
+ before do
+ lockfile normalized_lockfile
+ end
+
+ it "is a noop" do
+ simulate_platform "arm64-darwin-23" do
+ bundle "lock --normalize-platforms"
+ end
+
+ expect(lockfile).to eq(normalized_lockfile)
+ end
+ end
+
+ context "when having only ruby" do
+ before do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ irb (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ irb
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "normalizes the list of platforms and native gems in the lockfile" do
+ simulate_platform "arm64-darwin-23" do
+ bundle "lock --normalize-platforms"
+ end
+
+ expect(lockfile).to eq(normalized_lockfile)
+ end
+ end
+
+ context "when having only the current platform with version" do
+ before do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ irb (1.0.0-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin-23
+
+ DEPENDENCIES
+ irb
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "normalizes the list of platforms by removing version" do
+ simulate_platform "arm64-darwin-23" do
+ bundle "lock --normalize-platforms"
+ end
+
+ expect(lockfile).to eq(normalized_lockfile)
+ end
+ end
+
+ context "when having other platforms with version" do
+ before do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ irb (1.0.0-arm64-darwin)
+
+ PLATFORMS
+ arm64-darwin-22
+
+ DEPENDENCIES
+ irb
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "normalizes the list of platforms by removing version" do
+ simulate_platform "arm64-darwin-23" do
+ bundle "lock --normalize-platforms"
+ end
+
+ expect(lockfile).to eq(normalized_lockfile)
+ end
+ end
+ end
+
+ describe "--normalize-platforms with gems without generic variant" do
+ let(:original_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ sorbet-static (1.0-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ sorbet-static
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ before do
+ build_repo4 do
+ build_gem "sorbet-static" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "sorbet-static"
+ G
+
+ lockfile original_lockfile
+ end
+
+ it "removes invalid platforms" do
+ simulate_platform "x86_64-linux" do
+ bundle "lock --normalize-platforms"
+ end
- expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)"))
+ expect(lockfile).to eq(original_lockfile.gsub(/^ ruby\n/m, ""))
end
end
end
diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb
index e9c19005eb..65fbad05aa 100644
--- a/spec/bundler/commands/newgem_spec.rb
+++ b/spec/bundler/commands/newgem_spec.rb
@@ -1,180 +1,454 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle gem" do
- def reset!
- super
- global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
+ 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/#{gem_name}.rb")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb")).to exist
+
+ expect(ignore_paths).to include("bin/")
+ expect(ignore_paths).to include("Gemfile")
end
- def remove_push_guard(gem_name)
- # Remove exception that prevents public pushes on older RubyGems versions
- if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
- path = "#{gem_name}/#{gem_name}.gemspec"
- content = File.read(path).sub(/raise "RubyGems 2\.0 or newer.*/, "")
- File.open(path, "w") {|f| f.write(content) }
- end
+ def bundle_exec_rubocop
+ prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec"))
+ bundle "config set path #{rubocop_gem_path}", dir: bundled_app(gem_name)
+ bundle "exec rubocop --debug --config .rubocop.yml", dir: bundled_app(gem_name)
end
- def execute_bundle_gem(gem_name, flag = "", to_remove_push_guard = true)
- bundle! "gem #{gem_name} #{flag}"
- remove_push_guard(gem_name) if to_remove_push_guard
- # reset gemspec cache for each test because of commit 3d4163a
- Bundler.clear_gemspec_cache
+ def bundle_exec_standardrb
+ prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec"))
+ bundle "config set path #{standard_gem_path}", dir: bundled_app(gem_name)
+ bundle "exec standardrb --debug", dir: bundled_app(gem_name)
end
- def gem_skeleton_assertions(gem_name)
- 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/test/gem.rb")).to exist
- expect(bundled_app("#{gem_name}/lib/test/gem/version.rb")).to exist
+ def ignore_paths
+ generated = bundled_app("#{gem_name}/#{gem_name}.gemspec").read
+ matched = generated.match(/^\s+f\.start_with\?\(\*%w\[(?<ignored>.*)\]\)$/)
+ matched[:ignored]&.split(" ")
end
- before do
- git_config_content = <<-EOF
- [user]
- name = "Bundler User"
- email = user@example.com
- [github]
- user = bundleuser
- EOF
- @git_config_location = ENV["GIT_CONFIG"]
- path = "#{File.expand_path(tmp, File.dirname(__FILE__))}/test_git_config.txt"
- File.open(path, "w") {|f| f.write(git_config_content) }
- ENV["GIT_CONFIG"] = path
+ def installed_go?
+ sys_exec("go version", raise_on_error: true)
+ true
+ rescue StandardError
+ false
end
- after do
- FileUtils.rm(ENV["GIT_CONFIG"]) if File.exist?(ENV["GIT_CONFIG"])
- ENV["GIT_CONFIG"] = @git_config_location
+ let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) }
+
+ let(:gem_name) { "mygem" }
+
+ before do
+ git("config --global user.name 'Bundler User'")
+ git("config --global user.email user@example.com")
+ git("config --global github.user bundleuser")
+
+ bundle_config_global "gem.mit false"
+ bundle_config_global "gem.test false"
+ bundle_config_global "gem.coc false"
+ bundle_config_global "gem.linter false"
+ bundle_config_global "gem.ci false"
+ bundle_config_global "gem.changelog false"
+ bundle_config_global "gem.bundle false"
end
- shared_examples_for "git config is present" do
- context "git config user.{name,email} present" do
- it "sets gemspec author to git user.name if available" do
- expect(generated_gem.gemspec.authors.first).to eq("Bundler User")
- end
+ describe "git repo initialization" do
+ it "generates a gem skeleton with a .git folder" do
+ bundle "gem #{gem_name}"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/.git")).to exist
+ end
- it "sets gemspec email to git user.email if available" do
- expect(generated_gem.gemspec.email.first).to eq("user@example.com")
- end
+ it "generates a gem skeleton with a .git folder when passing --git" do
+ bundle "gem #{gem_name} --git"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/.git")).to exist
end
- end
- shared_examples_for "git config is absent" do
- it "sets gemspec author to default message if git user.name is not set or empty" do
- expect(generated_gem.gemspec.authors.first).to eq("TODO: Write your name")
+ it "generates a gem skeleton without a .git folder when passing --no-git" do
+ bundle "gem #{gem_name} --no-git"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/.git")).not_to exist
end
- it "sets gemspec email to default message if git user.email is not set or empty" do
- expect(generated_gem.gemspec.email.first).to eq("TODO: Write your email address")
+ context "on a path with spaces" do
+ before do
+ Dir.mkdir(bundled_app("path with spaces"))
+ end
+
+ it "properly initializes git repo" do
+ skip "path with spaces needs special handling on Windows" if Gem.win_platform?
+
+ 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
- execute_bundle_gem(gem_name, "--mit")
+ bundle "gem #{gem_name} --mit"
end
it "generates a gem skeleton with MIT license" do
- gem_skeleton_assertions(gem_name)
- expect(bundled_app("test-gem/LICENSE.txt")).to exist
- skel = Bundler::GemHelper.new(bundled_app(gem_name).to_s)
- expect(skel.gemspec.license).to eq("MIT")
+ 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
- execute_bundle_gem(gem_name, "--no-mit")
+ bundle "gem #{gem_name} --no-mit"
end
it "generates a gem skeleton without MIT license" do
- gem_skeleton_assertions(gem_name)
- expect(bundled_app("test-gem/LICENSE.txt")).to_not exist
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/LICENSE.txt")).to_not exist
end
end
shared_examples_for "--coc flag" do
- before do
- execute_bundle_gem(gem_name, "--coc", false)
- end
it "generates a gem skeleton with MIT license" do
- gem_skeleton_assertions(gem_name)
- expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to exist
+ bundle "gem #{gem_name} --coc"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CODE_OF_CONDUCT.md")).to exist
end
- describe "README additions" do
- it "generates the README with a section for the Code of Conduct" do
- expect(bundled_app("test-gem/README.md").read).to include("## Code of Conduct")
- expect(bundled_app("test-gem/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md")
- 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
+ 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
- execute_bundle_gem(gem_name, "--no-coc", false)
+ bundle "gem #{gem_name} --no-coc"
end
it "generates a gem skeleton without Code of Conduct" do
- gem_skeleton_assertions(gem_name)
- expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to_not exist
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CODE_OF_CONDUCT.md")).to_not exist
end
- describe "README additions" do
- it "generates the README without a section for the Code of Conduct" do
- expect(bundled_app("test-gem/README.md").read).not_to include("## Code of Conduct")
- expect(bundled_app("test-gem/README.md").read).not_to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md")
- 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
- context "README.md" do
- let(:gem_name) { "test_gem" }
- let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+ shared_examples_for "--changelog flag" do
+ before do
+ bundle "gem #{gem_name} --changelog"
+ end
+ it "generates a gem skeleton with a CHANGELOG" 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" do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CHANGELOG.md")).to_not exist
+ end
+ end
+
+ shared_examples_for "--bundle flag" do
+ before do
+ bundle "gem #{gem_name} --bundle"
+ end
+ it "generates a gem skeleton with bundle install" do
+ gem_skeleton_assertions
+ expect(out).to include("Running bundle install in the new gem directory.")
+ end
+ end
+
+ shared_examples_for "--no-bundle flag" do
+ before do
+ bundle "gem #{gem_name} --no-bundle"
+ end
+ it "generates a gem skeleton without bundle install" do
+ gem_skeleton_assertions
+ expect(out).to_not include("Running bundle install in the new gem directory.")
+ 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("#{gem_name}/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_specific
+ expect(rubocop_dep.requirement).to eq(Gem::Requirement.new([">= 0"]))
+ end
+
+ it "generates a default .rubocop.yml" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+
+ it "includes .rubocop.yml into ignore list" do
+ expect(ignore_paths).to include(".rubocop.yml")
+ 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("#{gem_name}/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_specific
+ expect(standard_dep.requirement).to eq(Gem::Requirement.new([">= 0"]))
+ end
+
+ it "generates a default .standard.yml" do
+ expect(bundled_app("#{gem_name}/.standard.yml")).to exist
+ end
+
+ it "includes .standard.yml into ignore list" do
+ expect(ignore_paths).to include(".standard.yml")
+ end
+ end
+
+ shared_examples_for "--no-linter flag" do
+ define_negated_matcher :exclude, :include
+
+ before do
+ bundle "gem #{gem_name} --no-linter"
+ end
+
+ it "generates a gem skeleton without rubocop" do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/Rakefile")).to read_as(exclude("rubocop"))
+ expect(bundled_app("#{gem_name}/#{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 "does not add .rubocop.yml into ignore list" do
+ expect(ignore_paths).not_to include(".rubocop.yml")
+ end
+
+ it "doesn't generate a default .standard.yml" do
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+
+ it "does not add .standard.yml into ignore list" do
+ expect(ignore_paths).not_to include(".standard.yml")
+ end
+ end
+ it "has no rubocop offenses when using --linter=rubocop flag" 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" 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" 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" 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" 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" 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" 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=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" 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=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" 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=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" 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=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/#{gem_name}_spec.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/test/#{gem_name}.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to_not exist
+ end
+
+ it "does not add any test framework files into ignore list" do
+ expect(ignore_paths).not_to include("test/")
+ expect(ignore_paths).not_to include(".rspec")
+ expect(ignore_paths).not_to include("spec/")
+ end
+ end
+
+ context "README.md" do
context "git config github.user present" do
before do
- execute_bundle_gem(gem_name)
+ bundle "gem #{gem_name}"
end
it "contribute URL set to git username" do
- expect(bundled_app("test_gem/README.md").read).not_to include("[USERNAME]")
- expect(bundled_app("test_gem/README.md").read).to include("github.com/bundleuser")
+ 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 --unset github.user")
- reset!
- in_app_root
+ git("config --global --unset github.user")
bundle "gem #{gem_name}"
- remove_push_guard(gem_name)
end
it "contribute URL set to [USERNAME]" do
- expect(bundled_app("test_gem/README.md").read).to include("[USERNAME]")
- expect(bundled_app("test_gem/README.md").read).not_to include("github.com/bundleuser")
+ 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
+
+ describe "test task name on readme" do
+ shared_examples_for "test task name on readme" do |framework, task_name|
+ before do
+ bundle "gem #{gem_name} --test=#{framework}"
+ end
+
+ it "renders with correct name" do
+ expect(bundled_app("#{gem_name}/README.md").read).to include("Then, run `rake #{task_name}` to run the tests.")
+ end
end
+
+ it_behaves_like "test task name on readme", "test-unit", "test"
+ it_behaves_like "test task name on readme", "minitest", "test"
+ it_behaves_like "test task name on readme", "rspec", "spec"
end
end
it "creates a new git repository" do
- in_app_root
- bundle "gem test_gem"
- expect(bundled_app("test_gem/.git")).to exist
+ bundle "gem #{gem_name}"
+ expect(bundled_app("#{gem_name}/.git")).to exist
end
- context "when git is not avaiable" do
- let(:gem_name) { "test_gem" }
-
- # This spec cannot have `git` avaiable in the test env
+ context "when git is not available" do
+ # This spec cannot have `git` available in the test env
before do
- load_paths = [lib, spec]
- load_path_str = "-I#{load_paths.join(File::PATH_SEPARATOR)}"
-
- sys_exec "PATH=\"\" #{Gem.ruby} #{load_path_str} #{bindir.join("bundle")} gem #{gem_name}"
+ bundle "gem #{gem_name}", env: { "PATH" => "" }
end
it "creates the gem without the need for git" do
@@ -188,50 +462,32 @@ RSpec.describe "bundle gem" do
it "doesn't create a .gitignore file" do
expect(bundled_app("#{gem_name}/.gitignore")).to_not exist
end
+
+ it "does not add .gitignore into ignore list" do
+ expect(ignore_paths).not_to include(".gitignore")
+ end
end
it "generates a valid gemspec" do
- system_gems ["rake-10.0.2"]
-
- in_app_root
bundle "gem newgem --bin"
- process_file(bundled_app("newgem", "newgem.gemspec")) do |line|
- # Simulate replacing TODOs with real values
- case line
- when /spec\.metadata\['allowed_push_host'\]/, /spec\.homepage/
- line.gsub(/\=.*$/, "= 'http://example.org'")
- when /spec\.summary/
- line.gsub(/\=.*$/, "= %q{A short summary of my new gem.}")
- when /spec\.description/
- line.gsub(/\=.*$/, "= %q{A longer description of my new gem.}")
- # Remove exception that prevents public pushes on older RubyGems versions
- when /raise "RubyGems 2.0 or newer/
- line.gsub(/.*/, "") if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
- else
- line
- end
- end
+ prepare_gemspec(bundled_app("newgem", "newgem.gemspec"))
- Dir.chdir(bundled_app("newgem")) do
- bundle "exec rake build"
+ build_repo2 do
+ build_dummy_irb "9.9.9"
end
+ gems = ["rake-#{rake_version}", "irb-9.9.9"]
+ system_gems gems, path: system_gem_path, gem_repo: gem_repo2
+ bundle "exec rake build", dir: bundled_app("newgem")
- expect(exitstatus).to be_zero if exitstatus
- expect(out).not_to include("ERROR")
- expect(err).not_to include("ERROR")
+ expect(stdboth).not_to include("ERROR")
end
context "gem naming with relative paths" do
- before do
- reset!
- in_app_root
- end
-
it "resolves ." do
create_temporary_dir("tmp")
- bundle "gem ."
+ bundle "gem .", dir: bundled_app("tmp")
expect(bundled_app("tmp/lib/tmp.rb")).to exist
end
@@ -239,7 +495,7 @@ RSpec.describe "bundle gem" do
it "resolves .." do
create_temporary_dir("temp/empty_dir")
- bundle "gem .."
+ bundle "gem ..", dir: bundled_app("temp/empty_dir")
expect(bundled_app("temp/lib/temp.rb")).to exist
end
@@ -247,661 +503,1635 @@ RSpec.describe "bundle gem" do
it "resolves relative directory" do
create_temporary_dir("tmp/empty/tmp")
- bundle "gem ../../empty"
+ 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(dir)
- Dir.chdir(dir)
+ FileUtils.mkdir_p(bundled_app(dir))
end
end
- context "gem naming with underscore" do
- let(:gem_name) { "test_gem" }
+ 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
+ bundle_config_global "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
+ bundle_config_global "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
+
+ 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/#{gem_name}.rb")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb")).to exist
+ expect(bundled_app("#{gem_name}/sig/#{gem_name}.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
+
+ unless Gem.win_platform?
+ expect(bundled_app("#{gem_name}/bin/setup")).to be_executable
+ expect(bundled_app("#{gem_name}/bin/console")).to be_executable
+ end
+
+ expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!")
+ expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!")
+ end
+
+ it "includes bin/ into ignore list" do
+ bundle "gem #{gem_name}"
+
+ expect(ignore_paths).to include("bin/")
+ end
+
+ it "includes Gemfile into ignore list" do
+ bundle "gem #{gem_name}"
+ expect(ignore_paths).to include("Gemfile")
+ end
+
+ it "includes .gitignore into ignore list" do
+ bundle "gem #{gem_name}"
+
+ expect(ignore_paths).to include(".gitignore")
+ end
+
+ it "starts with version 0.1.0" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/lib/#{gem_name}/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/#{gem_name}.rbs").read).to match(/VERSION: String/)
+ end
+
+ context "git config user.{name,email} is set" do
before do
- execute_bundle_gem(gem_name)
+ bundle "gem #{gem_name}"
end
- let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+ it "sets gemspec author to git user.name if available" do
+ expect(generated_gemspec.authors.first).to eq("Bundler User")
+ end
- it "generates a gem skeleton" do
- expect(bundled_app("test_gem/test_gem.gemspec")).to exist
- expect(bundled_app("test_gem/Gemfile")).to exist
- expect(bundled_app("test_gem/Rakefile")).to exist
- expect(bundled_app("test_gem/lib/test_gem.rb")).to exist
- expect(bundled_app("test_gem/lib/test_gem/version.rb")).to exist
- expect(bundled_app("test_gem/.gitignore")).to exist
+ it "sets gemspec email to git user.email if available" do
+ expect(generated_gemspec.email.first).to eq("user@example.com")
+ end
+ end
- expect(bundled_app("test_gem/bin/setup")).to exist
- expect(bundled_app("test_gem/bin/console")).to exist
- expect(bundled_app("test_gem/bin/setup")).to be_executable
- expect(bundled_app("test_gem/bin/console")).to be_executable
+ context "git config user.{name,email} is not set" do
+ before do
+ git("config --global --unset user.name")
+ git("config --global --unset user.email")
+ bundle "gem #{gem_name}"
end
- it "starts with version 0.1.0" do
- expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/VERSION = "0.1.0"/)
+ 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 "does not nest constants" do
- expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/module TestGem/)
- expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(/module TestGem/)
+ 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_should_behave_like "git config is present"
+ it "sets gemspec metadata['allowed_push_host']" do
+ bundle "gem #{gem_name}"
- context "git config user.{name,email} is not set" do
- before do
- `git config --unset user.name`
- `git config --unset user.email`
- reset!
- in_app_root
- bundle "gem #{gem_name}"
- remove_push_guard(gem_name)
+ expect(generated_gemspec.metadata["allowed_push_host"]).
+ to match(/example\.com/)
+ end
+
+ it "includes a commented-out rubygems_mfa_required metadata hint" do
+ bundle "gem #{gem_name}"
+
+ gemspec_contents = bundled_app("#{gem_name}/#{gem_name}.gemspec").read
+
+ expect(gemspec_contents).to include('# spec.metadata["rubygems_mfa_required"] = "true"')
+ expect(gemspec_contents).to include("https://guides.rubygems.org/mfa-requirement-opt-in/")
+ 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 "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-#{rake_version}"]
+
+ rakefile = <<~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
+ unless Gem.win_platform?
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to be_executable
end
+ 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
+ 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/#{gem_name}_spec.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
+ end
+
+ it "includes .rspec and spec/ into ignore list" do
+ expect(ignore_paths).to include(".rspec")
+ expect(ignore_paths).to include("spec/")
+ end
+
+ it "depends on a non-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).not_to be_specific
+ expect(rspec_dep.requirement).to eq(Gem::Requirement.new([">= 0"]))
+ end
+ end
- it_should_behave_like "git config is absent"
+ context "init_gems_rb setting to true" do
+ before do
+ bundle_config "init_gems_rb true"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates gems.rb instead of Gemfile" do
+ expect(bundled_app("#{gem_name}/gems.rb")).to exist
+ expect(bundled_app("#{gem_name}/Gemfile")).to_not exist
+ end
+
+ it "includes gems.rb and gems.locked into ignore list" do
+ expect(ignore_paths).to include("gems.rb")
+ expect(ignore_paths).to include("gems.locked")
+ expect(ignore_paths).not_to include("Gemfile")
+ end
+ end
+
+ context "init_gems_rb setting to false" do
+ before do
+ bundle_config "init_gems_rb false"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates Gemfile instead of gems.rb" do
+ expect(bundled_app("#{gem_name}/gems.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/Gemfile")).to exist
+ end
+
+ it "includes Gemfile into ignore list" do
+ expect(ignore_paths).to include("Gemfile")
+ expect(ignore_paths).not_to include("gems.rb")
+ expect(ignore_paths).not_to include("gems.locked")
+ end
+ end
+
+ context "gem.test setting set to rspec" do
+ before do
+ bundle_config "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/#{gem_name}_spec.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
end
- it "sets gemspec metadata['allowed_push_host']", :rubygems => "2.0" do
- expect(generated_gem.gemspec.metadata["allowed_push_host"]).
- to match(/mygemserver\.com/)
+ it "includes .rspec and spec/ into ignore list" do
+ expect(ignore_paths).to include(".rspec")
+ expect(ignore_paths).to include("spec/")
end
+ end
- it "requires the version file" do
- expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(%r{require "test_gem/version"})
+ context "gem.test setting set to rspec and --test is set to minitest" do
+ before do
+ bundle_config "gem.test rspec"
+ bundle "gem #{gem_name} --test=minitest"
end
- it "runs rake without problems" do
- system_gems ["rake-10.0.2"]
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/test/test_#{gem_name}.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ end
- rakefile = strip_whitespace <<-RAKEFILE
- task :default do
- puts 'SUCCESS'
+ it "includes test/ into ignore list" do
+ expect(ignore_paths).to include("test/")
+ end
+ end
+
+ context "--test parameter set to minitest" do
+ before do
+ bundle "gem #{gem_name} --test=minitest"
+ end
+
+ it "depends on a non-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).not_to be_specific
+ expect(minitest_dep.requirement).to eq(Gem::Requirement.new([">= 0"]))
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/test/test_#{gem_name}.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ end
+
+ it "includes test/ into ignore list" do
+ expect(ignore_paths).to include("test/")
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = <<~RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "minitest/test_task"
+
+ Minitest::TestTask.create
+
+ task default: :test
+ RAKEFILE
+
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "gem.test setting set to minitest" do
+ before do
+ bundle_config "gem.test minitest"
+ bundle "gem #{gem_name}"
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = <<~RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "minitest/test_task"
+
+ Minitest::TestTask.create
+
+ 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 non-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).not_to be_specific
+ expect(test_unit_dep.requirement).to eq(Gem::Requirement.new([">= 0"]))
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/test/#{gem_name}_test.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ end
+
+ it "includes test/ into ignore list" do
+ expect(ignore_paths).to include("test/")
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = <<~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
- File.open(bundled_app("test_gem/Rakefile"), "w") do |file|
- file.puts rakefile
- end
- Dir.chdir(bundled_app(gem_name)) do
- sys_exec(rake)
- expect(out).to include("SUCCESS")
- end
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
end
+ end
- context "--exe parameter set" do
- before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --exe"
- end
+ context "--test parameter set to an invalid value" do
+ before do
+ bundle "gem #{gem_name} --test=foo", raise_on_error: false
+ end
- it "builds exe skeleton" do
- expect(bundled_app("test_gem/exe/test_gem")).to exist
- end
+ it "fails loudly" do
+ expect(last_command).to be_failure
+ expect(err).to match(/Expected '--test' to be one of .*; got foo/)
+ end
+ end
- it "requires 'test-gem'" do
- expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/)
- end
+ context "gem.test set to rspec and --test with no arguments" do
+ before do
+ bundle_config "gem.test rspec"
+ bundle "gem #{gem_name} --test"
end
- context "--bin parameter set" do
- before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --bin"
- end
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/.rspec")).to exist
+ expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
+ end
- it "builds exe skeleton" do
- expect(bundled_app("test_gem/exe/test_gem")).to exist
- end
+ it "includes .rspec and spec/ into ignore list" do
+ expect(ignore_paths).to include(".rspec")
+ expect(ignore_paths).to include("spec/")
+ end
- it "requires 'test-gem'" do
- expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/)
- end
+ it "hints that --test is already configured" do
+ expect(out).to match("rspec is already configured, ignoring --test flag.")
end
+ end
- context "no --test parameter" do
- before do
- reset!
- in_app_root
- bundle "gem #{gem_name}"
+ context "gem.test setting set to false and --test with no arguments", :readline do
+ before do
+ bundle_config "gem.test false"
+ bundle "gem #{gem_name} --test" do |input, _, _|
+ input.puts
end
+ end
- it "doesn't create any spec/test file" do
- expect(bundled_app("test_gem/.rspec")).to_not exist
- expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to_not exist
- expect(bundled_app("test_gem/spec/spec_helper.rb")).to_not exist
- expect(bundled_app("test_gem/test/test_test_gem.rb")).to_not exist
- expect(bundled_app("test_gem/test/minitest_helper.rb")).to_not exist
- end
+ it "asks to generate test files" do
+ expect(out).to match("Do you want to generate tests with your gem?")
end
- context "--test parameter set to rspec" do
- before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --test=rspec"
- 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 "builds spec skeleton" do
- expect(bundled_app("test_gem/.rspec")).to exist
- expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist
- expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist
- end
+ it_behaves_like "test framework is absent"
+ end
- it "depends on a specific version of rspec", :rubygems => ">= 1.8.1" do
- remove_push_guard(gem_name)
- rspec_dep = generated_gem.gemspec.development_dependencies.find {|d| d.name == "rspec" }
- expect(rspec_dep).to be_specific
+ context "gem.test setting not set and --test with no arguments", :readline do
+ before do
+ bundle_config_global "BUNDLE_GEM__TEST" => nil
+ bundle "gem #{gem_name} --test" do |input, _, _|
+ input.puts
end
+ end
- it "requires 'test-gem'" do
- expect(bundled_app("test_gem/spec/spec_helper.rb").read).to include(%(require "test_gem"))
- end
+ it "asks to generate test files" do
+ expect(out).to match("Do you want to generate tests with your gem?")
+ end
- it "creates a default test which fails" do
- expect(bundled_app("test_gem/spec/test_gem_spec.rb").read).to include("expect(false).to eq(true)")
- 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
- context "gem.test setting set to rspec" do
- before do
- reset!
- in_app_root
- bundle "config gem.test rspec"
- bundle "gem #{gem_name}"
+ it_behaves_like "test framework is absent"
+ end
+
+ context "gem.test setting set to a test framework and --no-test" do
+ before do
+ bundle_config "gem.test rspec"
+ bundle "gem #{gem_name} --no-test"
+ end
+
+ it_behaves_like "test framework is absent"
+ end
+
+ context "--ci with no argument" do
+ before do
+ bundle "gem #{gem_name}"
+ end
+
+ it "does not 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
+
+ it "does not add any CI config files into ignore list" do
+ expect(ignore_paths).not_to include(".github/")
+ expect(ignore_paths).not_to include(".gitlab-ci.yml")
+ expect(ignore_paths).not_to include(".circleci/")
+ end
+ end
+
+ context "--ci set to github" do
+ before do
+ bundle "gem #{gem_name} --ci=github"
+ end
+
+ it "generates a GitHub Actions config file" do
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist
+ end
+
+ it "includes .github/ into ignore list" do
+ expect(ignore_paths).to include(".github/")
+ end
+ end
+
+ context "--ci set to gitlab" do
+ before do
+ bundle "gem #{gem_name} --ci=gitlab"
+ end
+
+ it "generates a GitLab CI config file" do
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist
+ end
+
+ it "includes .gitlab-ci.yml into ignore list" do
+ expect(ignore_paths).to include(".gitlab-ci.yml")
+ end
+ end
+
+ context "--ci set to circle" do
+ before do
+ bundle "gem #{gem_name} --ci=circle"
+ end
+
+ it "generates a CircleCI config file" do
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist
+ end
+
+ it "includes .circleci/ into ignore list" do
+ expect(ignore_paths).to include(".circleci/")
+ end
+ end
+
+ context "--ci set to an invalid value" do
+ before do
+ bundle "gem #{gem_name} --ci=foo", raise_on_error: false
+ end
+
+ it "fails loudly" do
+ expect(last_command).to be_failure
+ expect(err).to match(/Expected '--ci' to be one of .*; got foo/)
+ 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 github" do
+ it "generates a GitHub Actions config file" do
+ bundle_config "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 "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 "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" do
+ before do
+ bundle_config "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", :readline do
+ before do
+ bundle_config "gem.ci false"
+ bundle "gem #{gem_name} --ci" do |input, _, _|
+ input.puts "github"
end
+ 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
- it "builds spec skeleton" do
- expect(bundled_app("test_gem/.rspec")).to exist
- expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist
- expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist
+ context "gem.ci setting not set and --ci with no arguments", :readline do
+ before do
+ bundle_config_global "BUNDLE_GEM__CI" => nil
+ bundle "gem #{gem_name} --ci" do |input, _, _|
+ input.puts "github"
end
end
- context "gem.test setting set to rspec and --test is set to minitest" do
- before do
- reset!
- in_app_root
- bundle "config gem.test rspec"
- bundle "gem #{gem_name} --test=minitest"
+ 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 "gem.ci setting set to a CI service and --no-ci" do
+ before do
+ bundle_config "gem.ci github"
+ bundle "gem #{gem_name} --no-ci"
+ end
+
+ it "does not 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 "--linter with no argument" do
+ before do
+ bundle "gem #{gem_name}"
+ end
+
+ it "does not generate any linter config" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+
+ it "does not add any linter config files into ignore list" do
+ expect(ignore_paths).not_to include(".rubocop.yml")
+ expect(ignore_paths).not_to include(".standard.yml")
+ end
+ end
+
+ context "--linter set to rubocop" do
+ before do
+ bundle "gem #{gem_name} --linter=rubocop"
+ end
+
+ it "generates a RuboCop config" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+
+ it "includes .rubocop.yml into ignore list" do
+ expect(ignore_paths).to include(".rubocop.yml")
+ expect(ignore_paths).not_to include(".standard.yml")
+ end
+ end
+
+ context "--linter set to standard" do
+ before do
+ bundle "gem #{gem_name} --linter=standard"
+ end
+
+ it "generates a Standard config" do
+ expect(bundled_app("#{gem_name}/.standard.yml")).to exist
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ end
+
+ it "includes .standard.yml into ignore list" do
+ expect(ignore_paths).to include(".standard.yml")
+ expect(ignore_paths).not_to include(".rubocop.yml")
+ end
+ end
+
+ context "--linter set to an invalid value" do
+ before do
+ bundle "gem #{gem_name} --linter=foo", raise_on_error: false
+ end
+
+ it "fails loudly" do
+ expect(last_command).to be_failure
+ expect(err).to match(/Expected '--linter' to be one of .*; got foo/)
+ end
+ end
+
+ context "gem.linter setting set to none" do
+ before do
+ bundle "gem #{gem_name}"
+ end
+
+ it "doesn't generate any linter config" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+
+ it "does not add any linter config files into ignore list" do
+ expect(ignore_paths).not_to include(".rubocop.yml")
+ expect(ignore_paths).not_to include(".standard.yml")
+ end
+ end
+
+ context "gem.linter setting set to rubocop" do
+ before do
+ bundle_config "gem.linter rubocop"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates a RuboCop config file" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+
+ it "includes .rubocop.yml into ignore list" do
+ expect(ignore_paths).to include(".rubocop.yml")
+ end
+ end
+
+ context "gem.linter setting set to standard" do
+ before do
+ bundle_config "gem.linter standard"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates a Standard config file" do
+ expect(bundled_app("#{gem_name}/.standard.yml")).to exist
+ end
+
+ it "includes .standard.yml into ignore list" do
+ expect(ignore_paths).to include(".standard.yml")
+ end
+ end
+
+ context "gem.linter set to rubocop and --linter with no arguments" do
+ before do
+ bundle_config "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 "includes .rubocop.yml into ignore list" do
+ expect(ignore_paths).to include(".rubocop.yml")
+ 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", :readline do
+ before do
+ bundle_config "gem.linter false"
+ bundle "gem #{gem_name} --linter" do |input, _, _|
+ input.puts "rubocop"
end
+ 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
- it "builds spec skeleton" do
- expect(bundled_app("test_gem/test/test_gem_test.rb")).to exist
- expect(bundled_app("test_gem/test/test_helper.rb")).to exist
+ context "gem.linter setting not set and --linter with no arguments", :readline do
+ before do
+ bundle_config_global "BUNDLE_GEM__LINTER" => nil
+ bundle "gem #{gem_name} --linter" do |input, _, _|
+ input.puts "rubocop"
end
end
- context "--test parameter set to minitest" do
+ 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 "gem.linter setting set to a linter and --no-linter" do
+ before do
+ bundle_config "gem.linter rubocop"
+ bundle "gem #{gem_name} --no-linter"
+ end
+
+ it "does not generate any linter config" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+
+ it "does not add any linter config files into ignore list" do
+ expect(ignore_paths).not_to include(".rubocop.yml")
+ expect(ignore_paths).not_to include(".standard.yml")
+ 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
+
+ shared_examples_for "paths that depend on gem name" do
+ it "generates entrypoint, version file and signatures file at the proper path, with the proper content" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"})
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/)
+
+ expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist
+ expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist
+ end
+
+ context "--exe parameter set" do
before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --test=minitest"
+ bundle "gem #{gem_name} --exe"
end
- it "depends on a specific version of minitest", :rubygems => ">= 1.8.1" do
- remove_push_guard(gem_name)
- rspec_dep = generated_gem.gemspec.development_dependencies.find {|d| d.name == "minitest" }
- expect(rspec_dep).to be_specific
+ it "builds an exe file that requires the proper entrypoint" do
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/)
end
+ end
- it "builds spec skeleton" do
- expect(bundled_app("test_gem/test/test_gem_test.rb")).to exist
- expect(bundled_app("test_gem/test/test_helper.rb")).to exist
+ context "--bin parameter set" do
+ before do
+ bundle "gem #{gem_name} --bin"
end
- it "requires 'test-gem'" do
- expect(bundled_app("test_gem/test/test_helper.rb").read).to include(%(require "test_gem"))
+ it "builds an exe file that requires the proper entrypoint" do
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/)
end
+ end
- it "requires 'minitest_helper'" do
- expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include(%(require "test_helper"))
+ context "--test parameter set to rspec" do
+ before do
+ bundle "gem #{gem_name} --test=rspec"
end
- it "creates a default test which fails" do
- expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include("assert false")
+ it "builds a spec helper that requires the proper entrypoint, and a default test in the proper path which fails" do
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb").read).to include(%(require "#{require_path}"))
+ expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist
+ 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 minitest" do
+ context "--test parameter set to minitest" do
before do
- reset!
- in_app_root
- bundle "config gem.test minitest"
- bundle "gem #{gem_name}"
+ bundle "gem #{gem_name} --test=minitest"
end
- it "creates a default rake task to run the test suite" do
- rakefile = strip_whitespace <<-RAKEFILE
- 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
+ it "builds a test helper that requires the proper entrypoint, and default test file in the proper path that defines the proper test class name, requires helper, and fails" do
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}"))
- expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile)
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name)
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper"))
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false")
end
end
- context "--test with no arguments" do
+ context "--test parameter set to test-unit" do
before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --test"
+ bundle "gem #{gem_name} --test=test-unit"
end
- it "defaults to rspec" do
- expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist
- expect(bundled_app("test_gem/test/minitest_helper.rb")).to_not exist
+ it "builds a test helper that requires the proper entrypoint, and default test file in the proper path which requires helper and fails" do
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}"))
+ expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper"))
+ expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include("assert_equal(\"expected\", \"actual\")")
end
+ end
+ end
- it "creates a .travis.yml file to test the library against the current Ruby version on Travis CI" do
- expect(bundled_app("test_gem/.travis.yml").read).to match(/- #{RUBY_VERSION}/)
- end
+ context "with mit option in bundle config settings set to true" do
+ before do
+ bundle_config_global "gem.mit true"
end
+ it_behaves_like "--mit flag"
+ it_behaves_like "--no-mit flag"
+ end
- context "--edit option" do
- it "opens the generated gemspec in the user's text editor" do
- reset!
- in_app_root
- output = bundle "gem #{gem_name} --edit=echo"
- gemspec_path = File.join(Dir.pwd, gem_name, "#{gem_name}.gemspec")
- expect(output).to include("echo \"#{gemspec_path}\"")
- end
+ context "with mit option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.mit false"
end
+ it_behaves_like "--mit flag"
+ it_behaves_like "--no-mit flag"
end
- context "testing --mit and --coc options against bundle config settings" do
- let(:gem_name) { "test-gem" }
+ context "with coc option in bundle config settings set to true" do
+ before do
+ bundle_config_global "gem.coc true"
+ end
+ it_behaves_like "--coc flag"
+ it_behaves_like "--no-coc flag"
+ end
- context "with mit option in bundle config settings set to true" do
- before do
- global_config "BUNDLE_GEM__MIT" => "true", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
- end
- after { reset! }
- it_behaves_like "--mit flag"
- it_behaves_like "--no-mit flag"
+ context "with coc option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.coc false"
end
+ it_behaves_like "--coc flag"
+ it_behaves_like "--no-coc flag"
+ end
- context "with mit option in bundle config settings set to false" do
- it_behaves_like "--mit flag"
- it_behaves_like "--no-mit flag"
+ context "with rubocop option in bundle config settings set to true" do
+ before do
+ bundle_config_global "gem.rubocop true"
end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--no-linter flag"
+ end
- context "with coc option in bundle config settings set to true" do
- before do
- global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "true"
- end
- after { reset! }
- it_behaves_like "--coc flag"
- it_behaves_like "--no-coc flag"
+ context "with rubocop option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.rubocop false"
end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--no-linter flag"
+ end
- context "with coc option in bundle config settings set to false" do
- it_behaves_like "--coc flag"
- it_behaves_like "--no-coc flag"
+ context "with linter option in bundle config settings set to rubocop" do
+ before do
+ bundle_config_global "gem.linter rubocop"
end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--no-linter flag"
end
- context "gem naming with dashed" do
- let(:gem_name) { "test-gem" }
+ context "with linter option in bundle config settings set to standard" do
+ before do
+ bundle_config_global "gem.linter standard"
+ end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--no-linter flag"
+ end
+ context "with linter option in bundle config settings set to false" do
before do
- execute_bundle_gem(gem_name)
+ bundle_config_global "gem.linter false"
end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--no-linter flag"
+ end
- let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+ context "with changelog option in bundle config settings set to true" do
+ before do
+ bundle_config_global "gem.changelog true"
+ end
+ it_behaves_like "--changelog flag"
+ it_behaves_like "--no-changelog flag"
+ end
- it "generates a gem skeleton" do
- expect(bundled_app("test-gem/test-gem.gemspec")).to exist
- expect(bundled_app("test-gem/Gemfile")).to exist
- expect(bundled_app("test-gem/Rakefile")).to exist
- expect(bundled_app("test-gem/lib/test/gem.rb")).to exist
- expect(bundled_app("test-gem/lib/test/gem/version.rb")).to exist
+ context "with changelog option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.changelog false"
+ end
+ it_behaves_like "--changelog flag"
+ it_behaves_like "--no-changelog flag"
+ end
+
+ context "with bundle option in bundle config settings set to true" do
+ before do
+ bundle_config_global "gem.bundle true"
end
+ it_behaves_like "--bundle flag"
+ it_behaves_like "--no-bundle flag"
- it "starts with version 0.1.0" do
- expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/VERSION = "0.1.0"/)
+ it "runs bundle install" do
+ bundle "gem #{gem_name}"
+ expect(out).to include("Running bundle install in the new gem directory.")
end
+ end
- it "nests constants so they work" do
- expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/module Test\n module Gem/)
- expect(bundled_app("test-gem/lib/test/gem.rb").read).to match(/module Test\n module Gem/)
+ context "with bundle option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.bundle false"
end
+ it_behaves_like "--bundle flag"
+ it_behaves_like "--no-bundle flag"
- it_should_behave_like "git config is present"
+ it "does not run bundle install" do
+ bundle "gem #{gem_name}"
+ expect(out).to_not include("Running bundle install in the new gem directory.")
+ end
+ end
- context "git config user.{name,email} is not set" do
+ context "without git config github.user set" do
+ before do
+ git("config --global --unset github.user")
+ end
+ context "with github-username option in bundle config settings set to some value" do
before do
- `git config --unset user.name`
- `git config --unset user.email`
- reset!
- in_app_root
- bundle "gem #{gem_name}"
- remove_push_guard(gem_name)
+ bundle_config_global "gem.github_username different_username"
end
-
- it_should_behave_like "git config is absent"
+ it_behaves_like "--github-username option", "gh_user"
end
- it "requires the version file" do
- expect(bundled_app("test-gem/lib/test/gem.rb").read).to match(%r{require "test/gem/version"})
+ it_behaves_like "github_username configuration"
+
+ context "with github-username option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.github_username false"
+ end
+ it_behaves_like "--github-username option", "gh_user"
end
- it "runs rake without problems" do
- system_gems ["rake-10.0.2"]
+ context "when changelog is enabled" do
+ it "sets gemspec changelog_uri, homepage, homepage_uri, source_code_uri to TODOs" do
+ bundle "gem #{gem_name} --changelog"
- rakefile = strip_whitespace <<-RAKEFILE
- task :default do
- puts 'SUCCESS'
- end
- RAKEFILE
- File.open(bundled_app("test-gem/Rakefile"), "w") do |file|
- file.puts rakefile
+ expect(generated_gemspec.metadata["changelog_uri"]).
+ to eq("TODO: Put your gem's CHANGELOG.md URL here.")
+ expect(generated_gemspec.homepage).to eq("TODO: Put your gem's website or public repo URL here.")
+ expect(generated_gemspec.metadata["homepage_uri"]).to eq("TODO: Put your gem's website or public repo URL here.")
+ expect(generated_gemspec.metadata["source_code_uri"]).to eq("TODO: Put your gem's public repo URL here.")
end
+ end
+
+ context "when changelog is not enabled" do
+ it "sets gemspec homepage, homepage_uri, source_code_uri to TODOs and changelog_uri to nil" do
+ bundle "gem #{gem_name}"
- Dir.chdir(bundled_app(gem_name)) do
- sys_exec(rake)
- expect(out).to include("SUCCESS")
+ expect(generated_gemspec.metadata["changelog_uri"]).to be_nil
+ expect(generated_gemspec.homepage).to eq("TODO: Put your gem's website or public repo URL here.")
+ expect(generated_gemspec.metadata["homepage_uri"]).to eq("TODO: Put your gem's website or public repo URL here.")
+ expect(generated_gemspec.metadata["source_code_uri"]).to eq("TODO: Put your gem's public repo URL here.")
end
end
+ end
- context "--bin parameter set" do
+ context "with git config github.user set" do
+ context "with github-username option in bundle config settings set to some value" do
before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --bin"
+ bundle_config_global "gem.github_username different_username"
end
+ it_behaves_like "--github-username option", "gh_user"
+ end
+
+ it_behaves_like "github_username configuration"
- it "builds bin skeleton" do
- expect(bundled_app("test-gem/exe/test-gem")).to exist
+ context "with github-username option in bundle config settings set to false" do
+ before do
+ bundle_config_global "gem.github_username false"
end
+ it_behaves_like "--github-username option", "gh_user"
+ end
+
+ context "when changelog is enabled" do
+ it "sets gemspec changelog_uri, homepage, homepage_uri, source_code_uri based on git username" do
+ bundle "gem #{gem_name} --changelog"
- it "requires 'test/gem'" do
- expect(bundled_app("test-gem/exe/test-gem").read).to match(%r{require "test/gem"})
+ expect(generated_gemspec.metadata["changelog_uri"]).
+ to eq("https://github.com/bundleuser/#{gem_name}/blob/main/CHANGELOG.md")
+ expect(generated_gemspec.homepage).to eq("https://github.com/bundleuser/#{gem_name}")
+ expect(generated_gemspec.metadata["homepage_uri"]).to eq("https://github.com/bundleuser/#{gem_name}")
+ expect(generated_gemspec.metadata["source_code_uri"]).to eq("https://github.com/bundleuser/#{gem_name}")
end
end
- context "no --test parameter" do
- before do
- reset!
- in_app_root
+ context "when changelog is not enabled" do
+ it "sets gemspec source_code_uri, homepage, homepage_uri but not changelog_uri" do
bundle "gem #{gem_name}"
- end
- it "doesn't create any spec/test file" do
- expect(bundled_app("test-gem/.rspec")).to_not exist
- expect(bundled_app("test-gem/spec/test/gem_spec.rb")).to_not exist
- expect(bundled_app("test-gem/spec/spec_helper.rb")).to_not exist
- expect(bundled_app("test-gem/test/test_test/gem.rb")).to_not exist
- expect(bundled_app("test-gem/test/minitest_helper.rb")).to_not exist
+ expect(generated_gemspec.metadata["changelog_uri"]).to be_nil
+ expect(generated_gemspec.homepage).to eq("https://github.com/bundleuser/#{gem_name}")
+ expect(generated_gemspec.metadata["homepage_uri"]).to eq("https://github.com/bundleuser/#{gem_name}")
+ expect(generated_gemspec.metadata["source_code_uri"]).to eq("https://github.com/bundleuser/#{gem_name}")
end
end
+ end
+
+ context "standard gem naming" do
+ let(:require_path) { gem_name }
+
+ let(:require_relative_path) { gem_name }
+
+ let(:minitest_test_file_path) { "test/test_#{gem_name}.rb" }
+
+ let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" }
+
+ include_examples "paths that depend on gem name"
+ end
+
+ context "gem naming with underscore" 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 "paths that depend on gem name"
+
+ context "--ext parameter set with C" do
+ let(:flags) { "--ext=c" }
- context "--test parameter set to rspec" do
before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --test=rspec"
+ bundle ["gem", gem_name, flags].compact.join(" ")
end
- it "builds spec skeleton" do
- expect(bundled_app("test-gem/.rspec")).to exist
- expect(bundled_app("test-gem/spec/test/gem_spec.rb")).to exist
- expect(bundled_app("test-gem/spec/spec_helper.rb")).to exist
+ 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 "requires 'test/gem'" do
- expect(bundled_app("test-gem/spec/spec_helper.rb").read).to include(%(require "test/gem"))
+ it "generates native extension loading code" do
+ expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb").read).to include(<<~RUBY)
+ require_relative "test_gem/version"
+ require "#{gem_name}/#{gem_name}"
+ RUBY
end
- it "creates a default test which fails" do
- expect(bundled_app("test-gem/spec/test/gem_spec.rb").read).to include("expect(false).to eq(true)")
+ 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}/#{gem_name}.gemspec").read).to_not include('spec.add_dependency "rb_sys"')
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to_not include('spec.required_rubygems_version = ">= ')
end
- it "creates a default rake task to run the specs" do
- rakefile = strip_whitespace <<-RAKEFILE
+ it "depends on compile task for build" do
+ rakefile = <<~RAKEFILE
+ # frozen_string_literal: true
+
require "bundler/gem_tasks"
- require "rspec/core/rake_task"
+ require "rake/extensiontask"
+
+ task build: :compile
- RSpec::Core::RakeTask.new(:spec)
+ GEMSPEC = Gem::Specification.load("#{gem_name}.gemspec")
- task :default => :spec
+ Rake::ExtensionTask.new("#{gem_name}", GEMSPEC) do |ext|
+ ext.lib_dir = "lib/#{gem_name}"
+ end
+
+ task default: %i[clobber compile]
RAKEFILE
- expect(bundled_app("test-gem/Rakefile").read).to eq(rakefile)
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
end
end
- context "--test parameter set to minitest" do
+ context "--ext parameter set with rust" do
+ let(:flags) { "--ext=rust" }
+
before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --test=minitest"
+ bundle ["gem", gem_name, flags].compact.join(" ")
end
- it "builds spec skeleton" do
- expect(bundled_app("test-gem/test/test/gem_test.rb")).to exist
- expect(bundled_app("test-gem/test/test_helper.rb")).to exist
+ it "is not deprecated" do
+ expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated."
end
- it "requires 'test/gem'" do
- expect(bundled_app("test-gem/test/test_helper.rb").read).to match(%r{require "test/gem"})
+ 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
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/build.rs")).to exist
end
- it "requires 'test_helper'" do
- expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/require "test_helper"/)
+ it "includes rake-compiler and rb_sys gems constraint" do
+ expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"')
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "rb_sys"')
end
- it "creates a default test which fails" do
- expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/assert false/)
- end
+ it "depends on compile task for build" do
+ rakefile = <<~RAKEFILE
+ # frozen_string_literal: true
- it "creates a default rake task to run the test suite" do
- rakefile = strip_whitespace <<-RAKEFILE
require "bundler/gem_tasks"
- require "rake/testtask"
+ require "rb_sys/extensiontask"
+
+ task build: :compile
- Rake::TestTask.new(:test) do |t|
- t.libs << "test"
- t.libs << "lib"
- t.test_files = FileList["test/**/*_test.rb"]
+ GEMSPEC = Gem::Specification.load("#{gem_name}.gemspec")
+
+ RbSys::ExtensionTask.new("#{gem_name}", GEMSPEC) do |ext|
+ ext.lib_dir = "lib/#{gem_name}"
end
- task :default => :test
+ task default: :compile
RAKEFILE
- expect(bundled_app("test-gem/Rakefile").read).to eq(rakefile)
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
end
- end
- context "--test with no arguments" do
- before do
- reset!
- in_app_root
- bundle "gem #{gem_name} --test"
+ it "configures the crate such that `cargo test` works", :ruby_repo, :mri_only do
+ env = setup_rust_env
+ gem_path = bundled_app(gem_name)
+ result = sys_exec("cargo test", env: env, dir: gem_path, timeout: 300)
+
+ expect(result).to include("1 passed")
end
- it "defaults to rspec" do
- expect(bundled_app("test-gem/spec/spec_helper.rb")).to exist
- expect(bundled_app("test-gem/test/minitest_helper.rb")).to_not exist
+ def setup_rust_env
+ skip "rust toolchain of mingw is broken" if RUBY_PLATFORM.match?("mingw")
+
+ env = {
+ "CARGO_HOME" => ENV.fetch("CARGO_HOME", File.join(ENV["HOME"], ".cargo")),
+ "RUSTUP_HOME" => ENV.fetch("RUSTUP_HOME", File.join(ENV["HOME"], ".rustup")),
+ "RUSTUP_TOOLCHAIN" => ENV.fetch("RUSTUP_TOOLCHAIN", "stable"),
+ }
+
+ system(env, "cargo", "-V", out: IO::NULL, err: [:child, :out])
+ skip "cargo not present" unless $?.success?
+ # Hermetic Cargo setup
+ RbConfig::CONFIG.each {|k, v| env["RBCONFIG_#{k}"] = v }
+ env
end
end
- context "--ext parameter set" do
+ context "--ext parameter set with go" do
+ let(:flags) { "--ext=go" }
+
before do
- reset!
- in_app_root
- bundle "gem test_gem --ext"
+ bundle ["gem", gem_name, flags].compact.join(" ")
+ end
+
+ after do
+ sys_exec("go clean -modcache", raise_on_error: true) if installed_go?
+ 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("test_gem/ext/test_gem/extconf.rb")).to exist
- expect(bundled_app("test_gem/ext/test_gem/test_gem.h")).to exist
- expect(bundled_app("test_gem/ext/test_gem/test_gem.c")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod")).to exist
end
- it "includes rake-compiler" do
- expect(bundled_app("test_gem/test_gem.gemspec").read).to include('spec.add_development_dependency "rake-compiler"')
+ it "includes extconf.rb in gem_name.gemspec" do
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(%(spec.extensions = ["ext/#{gem_name}/extconf.rb"]))
end
- it "depends on compile task for build" do
- rakefile = strip_whitespace <<-RAKEFILE
- require "bundler/gem_tasks"
- require "rake/extensiontask"
+ it "includes go_gem in gem_name.gemspec" do
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "go_gem", ">= 0.2"')
+ end
- task :build => :compile
+ it "includes go_gem extension in extconf.rb" do
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(<<~RUBY)
+ require "mkmf"
+ require "go_gem/mkmf"
+ RUBY
- Rake::ExtensionTask.new("test_gem") do |ext|
- ext.lib_dir = "lib/test_gem"
- end
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(%(create_go_makefile("#{gem_name}/#{gem_name}")))
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).not_to include("create_makefile")
+ end
- task :default => [:clobber, :compile, :spec]
- RAKEFILE
+ it "includes go_gem extension in gem_name.c" do
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c").read).to eq(<<~C)
+ #include "#{gem_name}.h"
+ #include "_cgo_export.h"
+ C
+ end
+
+ it "includes skeleton code in gem_name.go" do
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO)
+ /*
+ #include "#{gem_name}.h"
+
+ VALUE rb_#{gem_name}_sum(VALUE self, VALUE a, VALUE b);
+ */
+ import "C"
+ GO
+
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO)
+ //export rb_#{gem_name}_sum
+ func rb_#{gem_name}_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE {
+ GO
+
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO)
+ //export Init_#{gem_name}
+ func Init_#{gem_name}() {
+ GO
+ end
+
+ it "includes valid module name in go.mod" do
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/bundleuser/#{gem_name}")
+ end
+
+ it "includes go_gem extension in Rakefile" do
+ expect(bundled_app("#{gem_name}/Rakefile").read).to include(<<~RUBY)
+ require "go_gem/rake_task"
+
+ GoGem::RakeTask.new("#{gem_name}")
+ RUBY
+ end
+
+ context "with --no-ci" do
+ let(:flags) { "--ext=go --no-ci" }
- expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile)
+ it_behaves_like "CI config is absent"
end
+
+ context "--ci set to github" do
+ let(:flags) { "--ext=go --ci=github" }
+
+ it "generates .github/workflows/main.yml" do
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml").read).to include("go-version-file: ext/#{gem_name}/go.mod")
+ end
+ end
+
+ context "--ci set to circle" do
+ let(:flags) { "--ext=go --ci=circle" }
+
+ it "generates a .circleci/config.yml" do
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist
+
+ expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML.strip)
+ environment:
+ GO_VERSION:
+ YAML
+
+ expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML)
+ - run:
+ name: Install Go
+ command: |
+ wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
+ tar -C /usr/local -xzf /tmp/go.tar.gz
+ echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV"
+ YAML
+ end
+ end
+
+ context "--ci set to gitlab" do
+ let(:flags) { "--ext=go --ci=gitlab" }
+
+ it "generates a .gitlab-ci.yml" do
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist
+
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML)
+ - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
+ - tar -C /usr/local -xzf /tmp/go.tar.gz
+ - export PATH=/usr/local/go/bin:$PATH
+ YAML
+
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML.strip)
+ variables:
+ GO_VERSION:
+ YAML
+ end
+ end
+
+ context "without github.user" do
+ before do
+ # FIXME: GitHub Actions Windows Runner hang up here for some reason...
+ skip "Workaround for hung up" if Gem.win_platform?
+
+ git("config --global --unset github.user")
+ bundle ["gem", gem_name, flags].compact.join(" ")
+ end
+
+ it "includes valid module name in go.mod" do
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/username/#{gem_name}")
+ end
+ end
+ end
+ end
+
+ context "gem naming with dashed" 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 "paths that depend on gem name"
end
describe "uncommon gem names" do
it "can deal with two dashes" do
bundle "gem a--a"
- Bundler.clear_gemspec_cache
expect(bundled_app("a--a/a--a.gemspec")).to exist
end
it "fails gracefully with a ." do
- bundle "gem foo.gemspec"
- expect(out).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name")
+ 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 ^"
- expect(out).to end_with("Invalid gem name ^ -- `^` is an invalid constant name")
+ 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'"
- expect(out).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name")
+ 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"
- expect(out).to eq(<<-E.strip)
+ 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 GEM [OPTIONS]"
+Usage: "bundle gem NAME [OPTIONS]"
E
end
end
describe "#ensure_safe_gem_name" do
before do
- bundle "gem #{subject}"
- end
- after do
- Bundler.clear_gemspec_cache
+ bundle "gem #{subject}", raise_on_error: false
end
context "with an existing const name" do
subject { "gem" }
- it { expect(out).to include("Invalid gem name #{subject}") }
+ it { expect(err).to include("Invalid gem name #{subject}") }
end
context "with an existing hyphenated const name" do
subject { "gem-specification" }
- it { expect(out).to include("Invalid gem name #{subject}") }
+ it { expect(err).to include("Invalid gem name #{subject}") }
+ end
+
+ context "starting with a number" do
+ subject { "1gem" }
+ it { expect(err).to include("Invalid gem name #{subject}") }
+ end
+
+ context "including capital letter" do
+ subject { "CAPITAL" }
+ it "should warn but not error" do
+ expect(err).to include("Gem names with capital letters are not recommended")
+ expect(bundled_app("#{subject}/#{subject}.gemspec")).to exist
+ end
end
context "starting with an existing const name" do
subject { "gem-somenewconstantname" }
- it { expect(out).not_to include("Invalid gem name #{subject}") }
+ it { expect(err).not_to include("Invalid gem name #{subject}") }
end
context "ending with an existing const name" do
subject { "somenewconstantname-gem" }
- it { expect(out).not_to include("Invalid gem name #{subject}") }
+ it { expect(err).not_to include("Invalid gem name #{subject}") }
end
end
- context "on first run" do
- before do
- in_app_root
- end
-
+ context "on first run", :readline do
it "asks about test framework" do
- global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false"
+ bundle_config_global "BUNDLE_GEM__TEST" => nil
bundle "gem foobar" do |input, _, _|
input.puts "rspec"
end
expect(bundled_app("foobar/spec/spec_helper.rb")).to exist
- rakefile = strip_whitespace <<-RAKEFILE
+ rakefile = <<~RAKEFILE
+ # frozen_string_literal: true
+
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
- task :default => :spec
+ task default: :spec
RAKEFILE
expect(bundled_app("foobar/Rakefile").read).to eq(rakefile)
- expect(bundled_app("foobar/foobar.gemspec").read).to include('spec.add_development_dependency "rspec"')
+ expect(bundled_app("foobar/Gemfile").read).to include('gem "rspec"')
end
- it "asks about MIT license" do
- global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
+ it "asks about CI service" do
+ bundle_config_global "BUNDLE_GEM__CI" => nil
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "github"
+ end
- bundle :config
+ expect(bundled_app("foobar/.github/workflows/main.yml")).to exist
+ end
+
+ it "asks about MIT license just once" do
+ bundle_config_global "BUNDLE_GEM__MIT" => nil
+
+ bundle "config list"
bundle "gem foobar" do |input, _, _|
input.puts "yes"
end
expect(bundled_app("foobar/LICENSE.txt")).to exist
+ expect(out).to include("Using a MIT license means").once
end
- it "asks about CoC" do
- global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false"
+ it "asks about CoC just once" do
+ bundle_config_global "BUNDLE_GEM__COC" => nil
bundle "gem foobar" do |input, _, _|
input.puts "yes"
end
expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist
+ expect(out).to include("Codes of conduct can increase contributions to your project").once
+ end
+
+ it "asks about CHANGELOG just once" do
+ bundle_config_global "BUNDLE_GEM__CHANGELOG" => nil
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "yes"
+ end
+
+ expect(bundled_app("foobar/CHANGELOG.md")).to exist
+ expect(out).to include("A changelog is a file which contains").once
end
end
context "on conflicts with a previously created file" do
it "should fail gracefully" do
- in_app_root do
- FileUtils.touch("conflict-foobar")
- end
- output = bundle "gem conflict-foobar"
- expect(output).to include("Errno::ENOTDIR")
- expect(exitstatus).to eql(32) if exitstatus
+ 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" do
it "should succeed" do
- in_app_root do
- FileUtils.mkdir_p("conflict-foobar/Gemfile")
- end
- bundle! "gem conflict-foobar"
+ 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
diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb
index 6872e859d2..664dc58919 100644
--- a/spec/bundler/commands/open_spec.rb
+++ b/spec/bundler/commands/open_spec.rb
@@ -1,93 +1,175 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle open" do
- before :each do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
- G
- end
+ context "when opening a regular gem" do
+ before do
+ install_gemfile <<-G
+ source "https://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 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 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 "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 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" => "" }
- expect(out).to match(/could not find gem 'missing'/i)
- 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("master", 11)
+ 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://#{gem_repo1}"
- gem 'foo', :git => "#{lib_path("foo-1.0")}"
- G
+ install_gemfile <<-G
+ source "https://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
+ bundle "open foo", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("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" => "" }
- expect(out).to match(/did you mean rails\?/i)
- 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" }
+ 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
+ expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}")
+ end
- it "select the gem from many match gems" do
- env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
- bundle "open active", :env => env do |input, _, _|
- input.puts "2"
+ 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
- expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/)
- 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 "allows selecting exit from many match gems" do
- env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
- bundle! "open active", :env => env do |input, _, _|
- input.puts "0"
+ 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
- end
- it "performs an automatic bundle install" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
- gem "foo"
- G
+ 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
- bundle "config auto_install 1"
- bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
- expect(out).to include("Installing foo 1.0")
+ 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 include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2").join("CHANGELOG.md")}")
+ 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 include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2").join("lib", "activerecord", "version.rb")}")
+ 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 include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}")
+ 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 "https://gem.repo1"
+ gem "rails"
+ gem "foo"
+ G
+
+ bundle_config "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
- it "opens the editor with a clean env" do
- bundle "open", :env => { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
- expect(out).not_to include("BUNDLE_GEMFILE=")
+ 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 "https://gem.repo1"
+ 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
index c6b6c9f59e..28ed51d61e 100644
--- a/spec/bundler/commands/outdated_spec.rb
+++ b/spec/bundler/commands/outdated_spec.rb
@@ -1,64 +1,85 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle outdated" do
- before :each do
- build_repo2 do
- build_git "foo", :path => lib_path("foo")
- build_git "zebra", :path => lib_path("zebra")
- end
-
- install_gemfile <<-G
- source "file://#{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
-
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 "https://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")
+ update_git "foo", path: lib_path("foo")
+ update_git "zebra", path: lib_path("zebra")
end
- bundle "outdated"
+ bundle "outdated", raise_on_error: false
- expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
- expect(out).to include("weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)")
- expect(out).to include("foo (newest 1.0")
+ expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip
+ Gem Current Latest Requested Groups Release Date
+ 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
- # Gem names are one per-line, between "*" and their parenthesized version.
- gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact
- expect(gem_list).to eq(gem_list.sort)
+ 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 "https://gem.repo2"
+ gem "AAA", "1.0.0"
+ G
+
+ bundle "outdated", raise_on_error: false
+
+ expected_output = <<~TABLE
+ Gem Current Latest Requested Groups Release Date
+ 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")
+ update_git "foo", path: lib_path("foo")
end
- bundle "outdated"
+ bundle "outdated", raise_on_error: false
- expect(exitstatus).to_not be_zero if exitstatus
+ expect(exitstatus).to_not be_zero
end
it "returns success exit status if no outdated gems present" do
bundle "outdated"
-
- expect(exitstatus).to be_zero if exitstatus
end
it "adds gem group to dependency output when repo is updated" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
+
+ gem "terranova", '8'
group :development, :test do
gem 'activesupport', '2.3.5'
@@ -66,16 +87,79 @@ RSpec.describe "bundle outdated" do
G
update_repo2 { build_gem "activesupport", "3.0" }
+ update_repo2 { build_gem "terranova", "9" }
+
+ bundle "outdated", raise_on_error: false
- bundle "outdated --verbose"
- expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5) in groups \"development, test\"")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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 "https://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 "clean false"
+
+ update_repo2 { build_gem "activesupport", "3.0" }
+ update_repo2 { build_gem "terranova", "9" }
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "terranova", '9'
+ gem 'activesupport', '2.3.5'
+ G
+
+ gemfile <<-G
+ source "https://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 Release Date 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 --group option" do
- def test_group_option(group = nil, gems_list_size = 1)
+ 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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "weakling", "~> 0.0.1"
gem "terranova", '8'
@@ -84,80 +168,112 @@ RSpec.describe "bundle outdated" do
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}"
-
- # Gem names are one per-line, between "*" and their parenthesized version.
- gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact
- expect(gem_list).to eq(gem_list.sort)
- expect(gem_list.size).to eq gems_list_size
+ bundle "outdated --group #{group}", raise_on_error: false
end
- it "not outdated gems" do
- install_gemfile <<-G
- source "file://#{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
-
+ it "works when the bundle is up to date" do
bundle "outdated --group"
- expect(out).to include("Bundle up to date!")
+ expect(out).to end_with("Bundle up to date!")
+ end
+
+ it "works when only out of date gems are not in given group" do
+ update_repo2 do
+ build_gem "terranova", "9"
+ end
+ bundle "outdated --group development"
+ 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")
- expect(out).to include("===== Group default =====")
- expect(out).to include("terranova (")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ terranova 8 9 = 8 default
+ TABLE
- expect(out).not_to include("===== Group development, test =====")
- expect(out).not_to include("activesupport")
- expect(out).not_to include("duradura")
+ 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", 2)
+ test_group_option("development")
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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")
- expect(out).not_to include("===== Group default =====")
- expect(out).not_to include("terranova (")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ activesupport 2.3.5 3.0 = 2.3.5 development, test
+ duradura 7.0 8.0 = 7.0 development, test
+ TABLE
- expect(out).to include("===== Group development, test =====")
- expect(out).to include("activesupport")
- expect(out).to include("duradura")
+ expect(out).to end_with(expected_output)
end
end
- describe "with --groups option" do
- it "not outdated gems" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
+ 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")
- gem "weakling", "~> 0.0.1"
- gem "terranova", '8'
- group :development, :test do
- gem 'activesupport', '2.3.5'
- gem "duradura", '7.0'
+ 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 "https://gem.repo2"
+
+ gem "bar_dependant", '7.0'
G
- bundle "outdated --groups"
- expect(out).to include("Bundle up to date!")
+ update_repo2 do
+ build_gem "bar", %w[3.0.0]
+ end
end
- it "returns a sorted list of outdated gems by groups" do
+ 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 Release Date
+ 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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "weakling", "~> 0.0.1"
gem "terranova", '8'
@@ -166,44 +282,76 @@ RSpec.describe "bundle outdated" do
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"
- expect(out).to include("===== Group default =====")
- expect(out).to include("terranova (newest 9, installed 8, requested = 8)")
- expect(out).to include("===== Group development, test =====")
- expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
- expect(out).to include("duradura (newest 8.0, installed 7.0, requested = 7.0)")
+ bundle "outdated --groups", raise_on_error: false
- expect(out).not_to include("weakling (")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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
- # TODO: check gems order inside the group
+ 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 "https://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 "clean false"
+
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport", "2.3.4"
G
- bundle "outdated --local"
+ bundle "outdated --local", raise_on_error: false
- expect(out).to include("activesupport (newest 2.3.5, installed 2.3.4, requested = 2.3.4)")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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)
+ FileUtils.rm_r(gem_repo2)
bundle "outdated --local"
expect(out).not_to match(/Fetching (gem|version|dependency) metadata from/)
@@ -213,54 +361,163 @@ RSpec.describe "bundle outdated" do
shared_examples_for "a minimal output is desired" do
context "and gems are outdated" do
before do
- update_repo2 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 "https://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
+ it "outputs a sorted list of outdated gems with a more minimal format to stdout" 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
+
+ it "outputs progress to stderr" do
+ subject
+ expect(err).to include("Fetching gem metadata")
+ end
end
context "and no gems are outdated" do
- it "has empty output" do
+ before do
+ build_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport", "3.0"
+ G
+ end
+
+ it "does not output to stdout" do
+ subject
+ expect(out).to be_empty
+ end
+
+ it "outputs progress to stderr" do
subject
- expect(out).to eq("")
+ expect(err).to include("Fetching gem metadata")
end
end
end
describe "with --parseable option" do
- subject { bundle "outdated --parseable" }
+ 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" }
+ 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 "https://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")
+ 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 Release Date
+ foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default
+ TABLE
+
+ expect(out).to match(Regexp.new(expected_output))
+ end
+
+ it "does not require gems to be installed" do
+ build_repo4 do
+ build_gem "zeitwerk", "1.0.0"
+ build_gem "zeitwerk", "2.0.0"
end
- bundle "outdated foo"
- expect(out).not_to include("activesupport (newest")
- expect(out).to include("foo (newest 1.0")
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "zeitwerk"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ zeitwerk (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ zeitwerk
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "outdated zeitwerk", raise_on_error: false
+
+ expected_output = <<~TABLE.tr(".", "\.").strip
+ Gem Current Latest Requested Groups Release Date
+ zeitwerk 1.0.0 2.0.0 >= 0 default
+ TABLE
+
+ expect(out).to match(Regexp.new(expected_output))
+ expect(err).to be_empty
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 "https://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
@@ -268,7 +525,8 @@ RSpec.describe "bundle outdated" do
end
bundle "outdated"
- expect(out).not_to include("activesupport (3.0.0.beta > 2.3.5)")
+
+ expect(out).to end_with("Bundle up to date!")
end
end
@@ -278,8 +536,14 @@ RSpec.describe "bundle outdated" do
build_gem "activesupport", "3.0.0.beta"
end
- bundle "outdated --pre"
- expect(out).to include("activesupport (newest 3.0.0.beta, installed 2.3.5, requested = 2.3.5)")
+ bundle "outdated --pre", raise_on_error: false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ activesupport 2.3.5 3.0.0.beta = 2.3.5 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
end
end
@@ -291,195 +555,333 @@ RSpec.describe "bundle outdated" do
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport", "3.0.0.beta.1"
G
- bundle "outdated"
- expect(out).to include("(newest 3.0.0.beta.2, installed 3.0.0.beta.1, requested = 3.0.0.beta.1)")
+ bundle "outdated", raise_on_error: false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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 --strict option" do
+ 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 "https://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 --strict"
+ bundle :outdated, "filter-strict": true, raise_on_error: false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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 Release Date
+ weakling 0.0.3 0.0.5 ~> 0.0.1 default
+ TABLE
- expect(out).to_not include("activesupport (newest")
- expect(out).to include("(newest 0.0.5, installed 0.0.3, requested ~> 0.0.1)")
+ 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 "https://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://#{gem_repo2}"
- gem "rack_middleware", "1.0"
+ source "https://gem.repo2"
+ gem "myrack_middleware", "1.0"
G
- bundle "outdated --strict"
+ bundle :outdated, "filter-strict": true
- expect(out).to_not include("rack (1.2")
+ 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://#{gem_repo2}"
+ source "https://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 "activesupport", %w[2.4.0 3.0.0]
build_gem "weakling", "0.0.5"
end
- bundle "outdated --strict --filter-patch"
+ bundle :outdated, :"filter-strict" => true, "filter-patch" => true, :raise_on_error => false
- expect(out).to_not include("activesupport (newest")
- expect(out).to include("(newest 0.0.5, installed 0.0.3")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport", "~> 2.3"
gem "weakling", ">= 0.0.1"
G
update_repo2 do
- build_gem "activesupport", %w(2.3.9)
+ build_gem "activesupport", %w[2.3.9]
build_gem "weakling", "0.1.5"
end
- bundle "outdated --strict --filter-minor"
+ bundle :outdated, :"filter-strict" => true, "filter-minor" => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ weakling 0.0.3 0.1.5 >= 0.0.1 default
+ TABLE
- expect(out).to_not include("activesupport (newest")
- expect(out).to include("(newest 0.1.5, installed 0.0.3")
+ 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://#{gem_repo2}"
+ source "https://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 "activesupport", %w[2.4.0 2.5.0]
build_gem "weakling", "1.1.5"
end
- bundle "outdated --strict --filter-major"
+ bundle :outdated, :"filter-strict" => true, "filter-major" => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ weakling 0.0.3 1.1.5 >= 0.0.1 default
+ TABLE
- expect(out).to_not include("activesupport (newest")
- expect(out).to include("(newest 1.1.5, installed 0.0.3")
+ 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 "https://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"
- expect(out).to include("Could not find gem 'invalid_gem_name'.")
+ 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"
- expect(exitstatus).to_not be_zero if exitstatus
+ 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://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
gem "foo"
G
- bundle "config auto_install 1"
- bundle :outdated
+ bundle_config "auto_install 1"
+ bundle :outdated, raise_on_error: false
expect(out).to include("Installing foo 1.0")
end
- context "after bundle install --deployment" do
+ context "in deployment mode" do
before do
- install_gemfile <<-G, :deployment => true
- source "file://#{gem_repo2}"
+ build_repo2
- gem "rack"
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "myrack"
gem "foo"
G
+ bundle :lock
+ bundle_config "deployment true"
end
it "outputs a helpful message about being in deployment mode" do
update_repo2 { build_gem "activesupport", "3.0" }
- bundle "outdated"
- expect(exitstatus).to_not be_zero if exitstatus
- expect(out).to include("You are trying to check outdated gems in deployment mode.")
- expect(out).to include("Run `bundle outdated` elsewhere.")
- expect(out).to include("If this is a development machine, remove the ")
- expect(out).to include("Gemfile freeze\nby running `bundle install --no-deployment`.")
+ 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 "https://gem.repo2"
+
+ gem "myrack"
+ gem "foo"
+ G
+ bundle_config "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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "laduradura", '= 5.15.2'
G
end
it "reports that no updates are available" do
bundle "outdated"
- expect(out).to include("Bundle up to date!")
+ 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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
G
bundle "outdated"
- expect(out).to include("Bundle up to date!")
+ expect(out).to end_with("Bundle up to date!")
end
- it "reports that updates are available if the JRuby platform is used" do
- simulate_ruby_engine "jruby", "1.6.7" do
- simulate_platform "jruby" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
- G
+ it "reports that updates are available if the JRuby platform is used", :jruby_only do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
+ G
+
+ bundle "outdated", raise_on_error: false
- bundle "outdated"
- expect(out).to include("Outdated gems included in the bundle:")
- expect(out).to include("laduradura (newest 5.15.3, installed 5.15.2, requested = 5.15.2)")
- end
- end
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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
- expect(out).to include("Outdated gems included in the bundle:")
- expect(out).to include("activesupport (newest")
- expect(out).to_not include("ERROR REPORT TEMPLATE")
+
+ 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 "https://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"
@@ -491,21 +893,51 @@ RSpec.describe "bundle outdated" do
context "when on a new machine" do
before do
- simulate_new_machine
+ build_repo2 do
+ build_git "foo", path: lib_path("foo")
+ build_git "zebra", path: lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "https://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
+
+ pristine_system_gems
- update_git "foo", :path => lib_path("foo")
+ 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" }
+ 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 "https://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"
@@ -517,6 +949,21 @@ RSpec.describe "bundle outdated" do
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 "https://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"
@@ -529,15 +976,27 @@ RSpec.describe "bundle outdated" do
shared_examples_for "no version updates are detected" do
it "does not detect any version updates" do
subject
- expect(out).to include("updates to display.")
- expect(out).to_not include("ERROR REPORT TEMPLATE")
- expect(out).to_not include("activesupport (newest")
- expect(out).to_not include("weakling (newest")
+ 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 "https://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"
@@ -549,6 +1008,21 @@ RSpec.describe "bundle outdated" do
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 "https://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"
@@ -560,6 +1034,21 @@ RSpec.describe "bundle outdated" do
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 "https://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"
@@ -570,7 +1059,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-major option" do
- subject { bundle "outdated --filter-major" }
+ subject { bundle "outdated --filter-major", raise_on_error: false }
it_behaves_like "major version updates are detected"
it_behaves_like "minor version is ignored"
@@ -578,7 +1067,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-minor option" do
- subject { bundle "outdated --filter-minor" }
+ subject { bundle "outdated --filter-minor", raise_on_error: false }
it_behaves_like "minor version updates are detected"
it_behaves_like "major version is ignored"
@@ -586,7 +1075,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-patch option" do
- subject { bundle "outdated --filter-patch" }
+ subject { bundle "outdated --filter-patch", raise_on_error: false }
it_behaves_like "patch version updates are detected"
it_behaves_like "major version is ignored"
@@ -594,7 +1083,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-minor --filter-patch options" do
- subject { bundle "outdated --filter-minor --filter-patch" }
+ 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"
@@ -602,7 +1091,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-major --filter-minor options" do
- subject { bundle "outdated --filter-major --filter-minor" }
+ 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"
@@ -610,7 +1099,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-major --filter-patch options" do
- subject { bundle "outdated --filter-major --filter-patch" }
+ 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"
@@ -618,7 +1107,7 @@ RSpec.describe "bundle outdated" do
end
describe "with --filter-major --filter-minor --filter-patch options" do
- subject { bundle "outdated --filter-major --filter-minor --filter-patch" }
+ 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"
@@ -626,106 +1115,255 @@ RSpec.describe "bundle outdated" do
end
context "conservative updates" do
- context "without update-strict" 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
+ 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://#{gem_repo4}"
+ # establish a lockfile set to 1.0.0
+ install_gemfile <<-G
+ source "https://gem.repo4"
gem 'patch', '1.0.0'
gem 'minor', '1.0.0'
gem 'major', '1.0.0'
- G
+ G
- # remove 1.4.3 requirement and bar altogether
- # to setup update specs below
- gemfile <<-G
- source "file://#{gem_repo4}"
+ # remove all version requirements
+ gemfile <<-G
+ source "https://gem.repo4"
gem 'patch'
gem 'minor'
gem 'major'
- G
- end
+ G
+ end
- it "shows nothing when patching and filtering to minor" do
- bundle "outdated --patch --filter-minor"
+ it "shows nothing when patching and filtering to minor" do
+ bundle "outdated --patch --filter-minor"
- expect(out).to include("No minor updates to display.")
- expect(out).not_to include("patch (newest")
- expect(out).not_to include("minor (newest")
- expect(out).not_to include("major (newest")
- end
+ 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 Release Date
+ 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 Release Date
+ 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
- it "shows all gems when patching and filtering to patch" do
- bundle "outdated --patch --filter-patch"
+ expect(out).not_to include("patch (newest")
+ expect(out).to include("minor (newest")
+ expect(out).not_to include("major (newest")
+ end
+ end
- expect(out).to include("patch (newest 1.0.1")
- expect(out).to include("minor (newest 1.0.1")
- expect(out).to include("major (newest 1.0.1")
+ 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
- it "shows minor and major when updating to minor and filtering to patch and minor" do
- bundle "outdated --minor --filter-minor"
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "https://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 "https://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" }
- expect(out).not_to include("patch (newest")
- expect(out).to include("minor (newest 1.1.0")
- expect(out).to include("major (newest 1.1.0")
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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 Release Date 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
- it "shows minor when updating to major and filtering to minor with parseable" do
- bundle "outdated --major --filter-minor --parseable"
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem 'weakling', '0.2'
+ gem 'bar', '2.1'
+ G
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem 'weakling'
+ G
+
+ bundle "outdated --only-explicit", raise_on_error: false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ weakling 0.2 0.3 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
- expect(out).not_to include("patch (newest")
- expect(out).to include("minor (newest")
- expect(out).not_to include("major (newest")
+ 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: https://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 "https://gem.repo4"
+ gem "nokogiri"
+ G
end
- context "with update-strict" 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)
+ it "reports a single entry per gem" do
+ bundle "outdated", raise_on_error: false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Release Date
+ 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
- # establish a lockfile set to 1.4.3
- install_gemfile <<-G
- source "file://#{gem_repo4}"
- gem 'foo', '1.4.3'
- gem 'bar', '2.0.3'
- gem 'qux', '1.0.0'
- G
+ build_gem "mini_portile2", "2.5.3"
- # remove 1.4.3 requirement and bar altogether
- # to setup update specs below
- gemfile <<-G
- source "file://#{gem_repo4}"
- gem 'foo'
- gem 'qux'
- G
+ build_gem "net-ftp", "0.1.2"
end
- it "shows gems with update-strict updating to patch and filtering to patch" do
- bundle "outdated --patch --update-strict --filter-patch"
+ gemfile <<~G
+ source "https://gem.repo4"
- expect(out).to include("foo (newest 1.4.4")
- expect(out).to include("bar (newest 2.0.5")
- expect(out).not_to include("qux (newest")
- end
+ gem "mini_portile2"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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 Release Date
+ 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/package_spec.rb b/spec/bundler/commands/package_spec.rb
deleted file mode 100644
index 86c09db3ca..0000000000
--- a/spec/bundler/commands/package_spec.rb
+++ /dev/null
@@ -1,306 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "bundle package" do
- context "with --gemfile" do
- it "finds the gemfile" do
- gemfile bundled_app("NotGemfile"), <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
- G
-
- bundle "package --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://#{gem_repo1}"
- gem 'rack'
- gem 'bundler'
- D
-
- bundle "package --all"
-
- 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://#{gem_repo1}"
- gem 'rack'
- gemspec
- D
-
- bundle! "package --all"
-
- 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://#{gem_repo1}"
- gem 'rack'
- gemspec
- D
-
- bundle! "package --all"
-
- 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://#{gem_repo1}"
- gem 'rack'
- gemspec :name => 'mygem'
- gemspec :name => 'mygem_test'
- D
-
- bundle! "package --all"
-
- 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" do
- it "sets root directory for gems" do
- gemfile <<-D
- source "file://#{gem_repo1}"
- gem 'rack'
- D
-
- bundle "package --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://#{gem_repo1}"
- gem 'rack'
- D
-
- bundle "package --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://#{gem_repo1}"
- gem 'rack'
- D
-
- bundle "package --no-install"
- bundle "install"
-
- 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", :ruby => "2.1" do
- gemfile <<-D
- source "file://#{gem_repo1}"
- gem 'rack', :platforms => :ruby_19
- D
-
- bundle "package --all-platforms"
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
- end
- end
-
- context "with --frozen" do
- before do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- bundle "install"
- end
-
- subject { bundle "package --frozen" }
-
- it "tries to install with frozen" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "rack-obama"
- G
- subject
- expect(exitstatus).to eq(16) if exitstatus
- expect(out).to include("deployment mode")
- expect(out).to include("You have added to the Gemfile")
- expect(out).to include("* rack-obama")
- bundle "env"
- expect(out).to include("frozen")
- 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://#{gem_repo2}"
- gem "rack"
- G
-
- bundle :pack
- 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" do
- build_repo2
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack"
- G
-
- bundle :pack
- simulate_new_machine
- FileUtils.rm_rf gem_repo2
-
- bundle "install --deployment"
- expect(the_bundle).to include_gems "rack 1.0.0"
- end
-
- it "does not reinstall already-installed gems" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- bundle :pack
-
- 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 lack_errors
- 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://#{gem_repo1}"
- gem "platform_specific"
- G
- bundle :pack
- end
-
- simulate_new_machine
-
- simulate_platform "ruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
- G
- run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
- expect(out).to eq("1.0.0 RUBY")
- end
- end
-
- it "does not update the cache if --no-cache is passed" do
- gemfile <<-G
- source "file://#{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/platform_spec.rb b/spec/bundler/commands/platform_spec.rb
new file mode 100644
index 0000000000..9d7354c54f
--- /dev/null
+++ b/spec/bundler/commands/platform_spec.rb
@@ -0,0 +1,1260 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle platform" do
+ context "without flags" do
+ it "returns all the output" do
+ gemfile <<-G
+ source "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://gem.repo1"
+ ruby "< 1.8.7"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://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 "https://gem.repo1"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://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 "https://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 "https://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_ignore_patchlevel
+ expect(exitstatus).to eq(0)
+ expect(err).to eq("")
+ 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 "https://gem.repo1"
+ gem "myrack"
+
+ #{ruby_version_correct}
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "installs fine with any engine", :jruby_only do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "installs fine when the patchlevel matches" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+
+ #{engine_version_incorrect}
+ G
+
+ expect(bundled_app_lock).not_to exist
+ should_be_engine_version_incorrect
+ end
+
+ it "does install even when patchlevel doesn't match" do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{patchlevel_incorrect}
+ G
+
+ expect(bundled_app_lock).to exist
+ should_ignore_patchlevel
+ end
+ end
+
+ context "bundle check" do
+ it "checks fine when the ruby version matches" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :check, raise_on_error: false
+ should_be_engine_version_incorrect
+ end
+
+ it "checks fine even when patchlevel doesn't match" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :check
+ should_ignore_patchlevel
+ end
+ end
+
+ context "bundle update" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ G
+ end
+
+ it "updates successfully when the ruby version matches" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+
+ #{ruby_version_correct}
+ G
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", all: true
+ expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "updates fine with any engine", :jruby_only do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+
+ #{ruby_version_correct_engineless}
+ G
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", all: true
+ expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "fails when ruby version doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-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 "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-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 "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-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 "updates fine even when patchlevel doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+
+ #{patchlevel_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, all: true
+ should_ignore_patchlevel
+ expect(the_bundle).to include_gems "activesupport 3.0"
+ end
+ end
+
+ context "bundle info" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails"
+ G
+ end
+
+ it "prints path if ruby version is correct" do
+ install_gemfile <<-G
+ source "https://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 "https://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" do
+ gemfile <<-G
+ source "https://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" do
+ gemfile <<-G
+ source "https://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", jruby_only: true do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "show rails", raise_on_error: false
+ should_be_engine_version_incorrect
+ end
+
+ it "prints path even when patchlevel doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "show rails"
+ should_ignore_patchlevel
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+ end
+
+ context "bundle cache" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+
+ #{ruby_version_correct}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/myrack-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 "https://gem.repo1"
+ gem 'myrack'
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ end
+
+ it "fails if the ruby version doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+
+ #{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 "https://gem.repo1"
+ gem 'myrack'
+
+ #{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 "https://gem.repo1"
+ gem 'myrack'
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :cache, raise_on_error: false
+ should_be_engine_version_incorrect
+ end
+
+ it "copies the .gem file to vendor/cache even when patchlevel doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :cache
+ should_ignore_patchlevel
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "bundle pack" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+
+ #{ruby_version_correct}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/myrack-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 "https://gem.repo1"
+ gem 'myrack'
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ end
+
+ it "fails if the ruby version doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+
+ #{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 "https://gem.repo1"
+ gem 'myrack'
+
+ #{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 "https://gem.repo1"
+ gem 'myrack'
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :cache, raise_on_error: false
+ should_be_engine_version_incorrect
+ end
+
+ it "copies the .gem file to vendor/cache even when patchlevel doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :cache
+ should_ignore_patchlevel
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "bundle exec" do
+ before do
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ system_gems "myrack-1.0.0", "myrack-0.9.1", path: default_bundle_path
+ end
+
+ it "activates the correct gem when ruby version matches" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "exec myrackup"
+ expect(out).to include("0.9.1")
+ end
+
+ it "activates the correct gem when ruby version matches any engine", :jruby_only do
+ system_gems "myrack-1.0.0", "myrack-0.9.1", path: default_bundle_path
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "exec myrackup"
+ expect(out).to include("0.9.1")
+ end
+
+ it "fails when the ruby version doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "exec myrackup", raise_on_error: false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when the engine doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+
+ #{engine_incorrect}
+ G
+
+ bundle "exec myrackup", raise_on_error: false
+ should_be_engine_incorrect
+ end
+
+ it "fails when the engine version doesn't match", :jruby_only do
+ gemfile <<-G
+ gem "myrack", "0.9.1"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "exec myrackup", raise_on_error: false
+ should_be_engine_version_incorrect
+ end
+
+ it "activates the correct gem even when patchlevel doesn't match" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "exec myrackup"
+ should_ignore_patchlevel
+ expect(out).to include("1.0.0")
+ end
+ end
+
+ context "bundle console" do
+ before do
+ build_repo2 do
+ build_dummy_irb
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "irb"
+ gem "myrack"
+ gem "activesupport", :group => :test
+ gem "myrack_middleware", :group => :development
+ G
+ end
+
+ it "starts IRB with the default group loaded when ruby version matches", :readline do
+ gemfile gemfile + "\n\n#{ruby_version_correct}\n"
+
+ bundle "console" do |input, _, _|
+ input.puts("puts MYRACK")
+ 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 gemfile + "\n\n#{ruby_version_correct_engineless}\n"
+
+ bundle "console" do |input, _, _|
+ input.puts("puts MYRACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "fails when ruby version doesn't match" do
+ gemfile gemfile + "\n\n#{ruby_version_incorrect}\n"
+
+ bundle "console", raise_on_error: false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ gemfile gemfile + "\n\n#{engine_incorrect}\n"
+
+ bundle "console", raise_on_error: false
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match", :jruby_only do
+ gemfile gemfile + "\n\n#{engine_version_incorrect}\n"
+
+ bundle "console", raise_on_error: false
+ should_be_engine_version_incorrect
+ end
+
+ it "starts IRB with the default group loaded even when patchlevel doesn't match", :readline do
+ gemfile gemfile + "\n\n#{patchlevel_incorrect}\n"
+
+ bundle "console" do |input, _, _|
+ input.puts("puts MYRACK")
+ input.puts("exit")
+ end
+ should_ignore_patchlevel
+ expect(out).to include("0.9.1")
+ end
+ end
+
+ context "Bundler.setup" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "yard"
+ gem "myrack", :group => :test
+ G
+
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ it "makes a Gemfile.lock if setup succeeds" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "yard"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "yard"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "yard"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "yard"
+ gem "myrack"
+
+ #{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 "https://gem.repo1"
+ gem "yard"
+ gem "myrack"
+
+ #{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 "makes a Gemfile.lock even when patchlevel doesn't match" do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+ gem "yard"
+ gem "myrack"
+
+ #{patchlevel_incorrect}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ ruby "require 'bundler/setup'", env: { "BUNDLER_VERSION" => Bundler::VERSION }
+
+ should_ignore_patchlevel
+ expect(bundled_app_lock).to exist
+ end
+ end
+
+ context "bundle outdated" do
+ before do
+ build_repo2 do
+ build_git "foo", path: lib_path("foo")
+ end
+
+ install_gemfile <<-G
+ source "https://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 "https://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 Release Date
+ 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 "https://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 Release Date
+ 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 "https://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 "https://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 "https://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 "reports outdated gems even when patchlevel doesn't match" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", path: lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "outdated", raise_on_error: false
+ expect(err).not_to include("patchlevel")
+ expect(out).to include("activesupport")
+ expect(out).to include("foo")
+ 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..088fc29fe1
--- /dev/null
+++ b/spec/bundler/commands/post_bundle_message_spec.rb
@@ -0,0 +1,183 @@
+# frozen_string_literal: true
+
+RSpec.describe "post bundle message" do
+ before :each do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "activesupport", "2.3.5", :group => [:emo, :test]
+ group :test do
+ gem "rspec"
+ end
+ gem "myrack-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, 4 gems now installed." }
+
+ describe "when installing to system gems" do
+ before do
+ bundle_config "path.system true"
+ end
+
+ it "shows proper messages according to the configured groups" do
+ bundle :install
+ expect(out).to include(bundle_show_system_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 "without emo"
+ bundle :install
+ expect(out).to include(bundle_show_system_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 "without emo test"
+ bundle :install
+ expect(out).to include(bundle_show_system_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, 2 gems now installed.")
+
+ bundle_config "without emo obama test"
+ bundle :install
+ expect(out).to include(bundle_show_system_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, 1 gem now installed.")
+ end
+
+ describe "for second bundle install run" do
+ it "without any options" do
+ 2.times { bundle :install }
+ expect(out).to include(bundle_show_system_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
+ end
+ end
+
+ describe "with `path` configured" do
+ let(:bundle_path) { "./vendor" }
+
+ it "shows proper messages according to the configured groups" do
+ bundle_config "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 "path vendor"
+ bundle_config "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 "path vendor"
+ bundle_config "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 "path vendor"
+ bundle_config "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 "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 "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 "force_ruby_platform true"
+ end
+
+ it "should report a helpful error message" do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "not-a-gem", :group => :development
+ G
+ expect(err).to include <<~EOS.strip
+ Could not find gem 'not-a-gem' in rubygems repository https://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 "https://gem.repo1"
+ gem "myrack"
+ G
+ bundle :cache
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+ gem "myrack"
+ 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
+
+ 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 "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 "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 "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
index 3aca313e0f..5f80b9e534 100644
--- a/spec/bundler/commands/pristine_spec.rb
+++ b/spec/bundler/commands/pristine_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require "spec_helper"
-require "fileutils"
-RSpec.describe "bundle pristine", :ruby_repo do
+require "bundler/vendored_fileutils"
+
+RSpec.describe "bundle pristine" do
before :each do
- build_lib "baz", :path => bundled_app do |s|
+ build_lib "baz", path: bundled_app do |s|
s.version = "1.0.0"
s.add_development_dependency "baz-dev", "=1.0.0"
end
@@ -13,24 +13,28 @@ RSpec.describe "bundle pristine", :ruby_repo 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_lib "bar", :path => lib_path("bar")
+ 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://#{gem_repo2}"
+ install_gemfile <<-G
+ source "https://gem.repo2"
gem "weakling"
gem "very_simple_binary"
- gem "foo", :git => "#{lib_path("foo")}"
+ 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
+ context "when sourced from RubyGems" do
it "reverts using cached .gem file" do
- spec = Bundler.definition.specs["weakling"].first
+ spec = find_spec("weakling")
changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
FileUtils.touch(changes_txt)
@@ -40,46 +44,130 @@ RSpec.describe "bundle pristine", :ruby_repo do
expect(changes_txt).to_not be_file
end
- it "does not delete the bundler gem", :ruby_repo do
- system_gems :bundler
- bundle! "install"
- bundle! "pristine", :system_bundler => true
- bundle! "-v", :system_bundler => true
+ it "does not delete the bundler gem" do
+ bundle "install"
+ bundle "pristine"
+ bundle "-v"
+
expect(out).to end_with(Bundler::VERSION)
end
end
context "when sourced from git repo" do
it "reverts by resetting to current revision`" do
- spec = Bundler.definition.specs["foo"].first
+ 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 "#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 "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
+
+ it "doesn't run multiple git processes for the same repository" do
+ nested_gems = [
+ "actioncable",
+ "actionmailer",
+ "actionpack",
+ "actionview",
+ "activejob",
+ "activemodel",
+ "activerecord",
+ "activestorage",
+ "activesupport",
+ "railties",
+ ]
+
+ build_repo2 do
+ nested_gems.each do |gem|
+ build_lib gem, path: lib_path("rails/#{gem}")
+ end
+
+ build_git "rails", path: lib_path("rails") do |s|
+ nested_gems.each do |gem|
+ s.add_dependency gem
+ end
+ end
+ end
+
+ install_gemfile <<-G
+ source 'https://rubygems.org'
+
+ git "#{lib_path("rails")}" do
+ gem "rails"
+ gem "actioncable"
+ gem "actionmailer"
+ gem "actionpack"
+ gem "actionview"
+ gem "activejob"
+ gem "activemodel"
+ gem "activerecord"
+ gem "activestorage"
+ gem "activesupport"
+ gem "railties"
+ end
+ G
+
+ changed_files = []
+ diff = "#Pristine spec changes"
+
+ nested_gems.each do |gem|
+ spec = find_spec(gem)
+ changed_files << Pathname.new(spec.full_gem_path).join("lib/#{gem}.rb")
+ File.open(changed_files.last, "a") {|f| f.puts diff }
+ end
+
+ bundle "pristine"
+
+ changed_files.each do |changed_file|
+ expect(File.read(changed_file)).to_not include(diff)
+ end
+ end
end
context "when sourced from gemspec" do
it "displays warning and ignores changes when sourced from gemspec" do
- spec = Bundler.definition.specs["baz"].first
+ 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 "#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(out).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.")
+ 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 = Bundler.definition.specs["baz-dev"].first
- changed_file = Pathname.new(spec.full_gem_path).join("lib/baz-dev.rb")
+ 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" }
@@ -92,30 +180,96 @@ RSpec.describe "bundle pristine", :ruby_repo do
context "when sourced from path" do
it "displays warning and ignores changes when sourced from local path" do
- spec = Bundler.definition.specs["bar"].first
+ 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(out).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.")
+ 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) { Bundler.definition.specs["very_simple_binary"].first }
+ 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 build.very_simple_binary -- #{build_opt}" }
+ before { bundle_config "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 =.*#{Regexp.escape(c_ext_dir.to_s)}/)
+ expect(makefile_contents).to match(/LIBPATH =.*-L#{Regexp.escape(c_ext_dir.to_s)}/)
+ 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 "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"
+ 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}/)
+ expect(makefile_contents).to match(/libpath =.*#{Regexp.escape(c_ext_dir.to_s)}/)
+ expect(makefile_contents).to match(/LIBPATH =.*-L#{Regexp.escape(c_ext_dir.to_s)}/)
+ 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..8a2e6778ea
--- /dev/null
+++ b/spec/bundler/commands/remove_spec.rb
@@ -0,0 +1,736 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle remove" do
+ context "when no gems are specified" do
+ it "throws error" do
+ gemfile <<-G
+ source "https://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
+ myrack_dep = <<~L
+
+ DEPENDENCIES
+ myrack
+
+ L
+
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ G
+
+ bundle "install"
+
+ expect(lockfile).to include(myrack_dep)
+
+ bundle "remove myrack"
+
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ G
+ expect(lockfile).to_not include(myrack_dep)
+ end
+ 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 "https://gem.repo1"
+
+ gem "myrack"
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(the_bundle).to_not include_gems "myrack"
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ G
+ end
+
+ context "when gem is specified in multiple lines" do
+ it "shows success for removed gem" do
+ build_git "myrack"
+
+ gemfile <<-G
+ source 'https://gem.repo1'
+
+ gem 'git'
+ gem 'myrack',
+ git: "#{lib_path("myrack-1.0")}",
+ branch: 'main'
+ gem 'nokogiri'
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source 'https://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 "https://gem.repo1"
+ G
+
+ bundle "remove myrack", raise_on_error: false
+
+ expect(err).to include("`myrack` 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 "https://gem.repo1"
+
+ gem "myrack"
+ gem "rails"
+ G
+
+ bundle "remove myrack rails"
+
+ expect(out).to include("myrack was removed.")
+ expect(out).to include("rails was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://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 "https://gem.repo1"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+
+ bundle "remove rails myrack minitest", raise_on_error: false
+
+ expect(err).to include("`myrack` is not specified in #{bundled_app_gemfile} so it could not be removed.")
+ expect(gemfile).to eq <<~G
+ source "https://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 "https://gem.repo1"
+
+ gem "myrack", :group => [:dev]
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://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 "https://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 "https://gem.repo1"
+ G
+ end
+ end
+
+ context "when gem to be removed is outside block" do
+ it "does not modify group" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ group :test do
+ gem "coffee-script-source"
+ end
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://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 "https://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 "https://gem.repo1"
+ G
+ end
+ end
+
+ context "when the gem belongs to multiple groups" do
+ it "removes the groups" do
+ gemfile <<-G
+ source "https://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 "https://gem.repo1"
+ G
+ end
+ end
+
+ context "when the gem is present in multiple groups" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "https://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 "https://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 "https://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 "https://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 "https://gem.repo1"
+
+ group :test do
+ gem "myrack-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 "https://gem.repo1"
+
+ group :test do
+ gem "myrack-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 "https://gem.repo1"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ gem "myrack-test"
+ end
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+
+ group :test do
+ group :serioustest do
+ gem "myrack-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 "https://gem.repo1"
+ gem "myrack"; gem "rails"
+ G
+
+ bundle "remove rails", raise_on_error: false
+
+ expect(err).to include("Gems could not be removed. myrack (>= 0) would also have been removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ gem "myrack"; 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 "https://gem.repo1"
+ gem"myrack"
+ gem"rspec"
+ gem "rails"
+ gem "minitest"
+ G
+
+ bundle "remove rails myrack rspec minitest"
+
+ expect(out).to include("rails was removed.")
+ expect(out).to include("minitest was removed.")
+ expect(out).to include("myrack, rspec could not be removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ gem"myrack"
+ gem"rspec"
+ G
+ end
+ end
+ end
+
+ context "with sources" do
+ before do
+ build_repo3 do
+ build_gem "rspec"
+ end
+ end
+
+ it "removes gems and empty source blocks" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack"
+
+ source "https://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 "https://gem.repo1"
+
+ gem "myrack"
+ G
+ end
+ end
+
+ describe "with eval_gemfile" do
+ context "when gems are present in both gemfiles" do
+ it "removes the gems" do
+ gemfile "Gemfile-other", <<-G
+ gem "myrack"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+
+ gem "myrack"
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ end
+ end
+
+ context "when gems are present in other gemfile" do
+ it "removes the gems" do
+ gemfile "Gemfile-other", <<-G
+ gem "myrack"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle "remove myrack"
+
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"myrack\"")
+ expect(out).to include("myrack 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 "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle "remove myrack", raise_on_error: false
+
+ expect(err).to include("`myrack` 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
+ gemfile "Gemfile-other", <<-G
+ gem "rails"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ gem "myrack"
+ G
+
+ bundle "remove myrack", raise_on_error: false
+
+ expect(out).to include("myrack was removed.")
+ expect(err).to include("`myrack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems cannot be removed from other gemfile" do
+ it "shows error" do
+ gemfile "Gemfile-other", <<-G
+ gem "rails"; gem "myrack"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ gem "myrack"
+ G
+
+ bundle "remove myrack", raise_on_error: false
+
+ expect(out).to include("myrack was removed.")
+ expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems could not be removed from parent gemfile" do
+ it "shows error" do
+ gemfile "Gemfile-other", <<-G
+ gem "myrack"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "myrack"
+ G
+
+ bundle "remove myrack", 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 \"myrack\"")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "myrack"
+ 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
+ gemfile "Gemfile-other", <<-G
+ gem "myrack"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ eval_gemfile "Gemfile-other"
+ gem"myrack"
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"myrack\"")
+ end
+ end
+ end
+
+ context "with install_if" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ install_if(lambda { false }) do
+ gem "myrack"
+ end
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ G
+ end
+ end
+
+ context "with env" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ env "BUNDLER_TEST" do
+ gem "myrack"
+ end
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ G
+ end
+ end
+
+ context "with gemspec" do
+ it "should not remove the gem" do
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.write("foo.gemspec", "")
+ s.add_dependency "myrack"
+ end
+
+ install_gemfile(<<-G)
+ source "https://gem.repo1"
+ gemspec :path => '#{tmp("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 "https://gem.repo1"
+
+ # gem "myrack" might be used in the future
+ gem "myrack"
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+
+ # gem "myrack" 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 "https://gem.repo1"
+
+ gem "myrack" # this can be removed
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://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 "https://gem.repo1"
+ gem "puma" # implements interface provided by gem "myrack"
+
+ gem "myrack"
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to_not include("puma was removed.")
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ gem "puma" # implements interface provided by gem "myrack"
+ 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 "https://gem.repo1"
+ gem "puma" # implements interface provided by gem "myrack"
+
+ gem "myrack"
+ G
+
+ bundle "remove puma"
+
+ expect(out).to include("puma was removed.")
+ expect(out).to_not include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+
+ gem "myrack"
+ G
+ end
+ end
+ end
+
+ context "when gem definition has parentheses" do
+ it "removes the gem" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem("myrack")
+ gem("myrack", ">= 0")
+ gem("myrack", require: false)
+ G
+
+ bundle "remove myrack"
+
+ expect(out).to include("myrack was removed.")
+ expect(gemfile).to eq <<~G
+ source "https://gem.repo1"
+ G
+ end
+ end
+end
diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb
index 0391ddec52..d0d55ffbb9 100644
--- a/spec/bundler/commands/show_spec.rb
+++ b/spec/bundler/commands/show_spec.rb
@@ -1,29 +1,30 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle show" do
context "with a standard Gemfile" do
before :each do
+ build_repo2
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2"
gem "rails"
G
end
it "creates a Gemfile.lock if one did not exist" do
- FileUtils.rm("Gemfile.lock")
+ FileUtils.rm(bundled_app_lock)
bundle "show"
- expect(bundled_app("Gemfile.lock")).to exist
+ expect(bundled_app_lock).to exist
end
it "creates a Gemfile.lock when invoked with a gem name" do
- FileUtils.rm("Gemfile.lock")
+ FileUtils.rm(bundled_app_lock)
bundle "show rails"
- expect(bundled_app("Gemfile.lock")).to exist
+ expect(bundled_app_lock).to exist
end
it "prints path if gem exists in bundle" do
@@ -31,29 +32,34 @@ RSpec.describe "bundle show" do
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("#{system_gem_path}/gems/rails-2.3.2")
+ 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 specification is installed, but path does not exist on disk" do
+ FileUtils.rm_r(default_bundle_path("gems", "rails-2.3.2"))
bundle "show rails"
- expect(out).to match(/has been deleted/i)
- expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s)
+ expect(err).to match(/is missing/i)
+ expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s)
end
- it "prints the path to the running bundler", :ruby_repo do
+ 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"
- expect(out).to match(/could not find gem 'missing'/i)
+ 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-10.0.2").to_s)
+ expect(out).to include(default_bundle_path("gems", "rake-#{rake_version}").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.
@@ -64,10 +70,41 @@ RSpec.describe "bundle show" do
it "prints summary of gems" do
bundle "show --verbose"
- expect(out).to include("* actionmailer (2.3.2)")
- expect(out).to include("\tSummary: This is just a fake gem for testing")
- expect(out).to include("\tHomepage: No website available.")
- expect(out).to include("\tStatus: Up to date")
+ 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
+
+ it "includes up to date status in summary of gems" do
+ update_repo2 do
+ build_gem "rails", "3.0.0"
+ end
+
+ bundle "show --verbose"
+
+ expect(out).to include <<~MSG
+ * rails (2.3.2)
+ \tSummary: This is just a fake gem for testing
+ \tHomepage: http://example.com
+ \tStatus: Outdated - 2.3.2 < 3.0.0
+ MSG
+
+ # check lockfile is not accidentally updated
+ expect(lockfile).to include("actionmailer (2.3.2)")
end
end
@@ -83,11 +120,11 @@ RSpec.describe "bundle show" do
expect(the_bundle).to include_gems "foo 1.0"
bundle :show
- expect(out).to include("foo (1.0 #{@git.ref_for("master", 6)}")
+ expect(out).to include("foo (1.0 #{@git.ref_for("main", 6)}")
end
- it "prints out branch names other than master" do
- update_git "foo", :branch => "omg" do |s|
+ 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]
@@ -111,81 +148,70 @@ RSpec.describe "bundle show" do
expect(out).to include("foo (1.0 #{sha[0..6]})")
end
- it "handles when a version is a '-' prerelease", :rubygems => "2.1" do
- @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo"))
+ 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
+ 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")
- in_app_root_custom lib_path("foo")
- File.open("Gemfile", "w") {|f| f.puts "gemspec" }
- sys_exec "rm -rf .git && git init"
+ 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
- expect(err).to lack_errors
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo"
G
- bundle "config auto_install 1"
+ bundle_config "auto_install 1"
bundle :show
expect(out).to include("Installing foo 1.0")
end
- context "with an invalid regexp for gem name" do
- it "does not find the gem" do
+ context "with a valid regexp for gem name" do
+ it "presents alternatives", :readline do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-obama"
G
- invalid_regexp = "[]"
-
- bundle "show #{invalid_regexp}"
- expect(out).to include("Could not find gem '#{invalid_regexp}'.")
+ bundle "show rac"
+ expect(out).to match(/\A1 : myrack\n2 : myrack-obama\n0 : - exit -(\n>|\z)/)
end
end
- context "--outdated option" do
- # Regression test for https://github.com/bundler/bundler/issues/5375
- before do
- build_repo2
- end
-
- it "doesn't update gems to newer versions" do
- install_gemfile! <<-G
- source "file://#{gem_repo2}"
+ context "with an invalid regexp for gem name" do
+ it "does not find the gem" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
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"
+ invalid_regexp = "[]"
- bundle! "install"
- expect(the_bundle).to include_gem("rails 2.3.2")
+ bundle "show #{invalid_regexp}", raise_on_error: false
+ expect(err).to include("Could not find gem '#{invalid_regexp}'.")
end
end
end
+
+RSpec.describe "bundle show", bundler: "5" do
+ pending "shows a friendly error about the command removal"
+end
diff --git a/spec/bundler/commands/ssl_spec.rb b/spec/bundler/commands/ssl_spec.rb
new file mode 100644
index 0000000000..4220731b69
--- /dev/null
+++ b/spec/bundler/commands/ssl_spec.rb
@@ -0,0 +1,373 @@
+# frozen_string_literal: true
+
+require "bundler/cli"
+require "bundler/cli/doctor"
+require "bundler/cli/doctor/ssl"
+require_relative "../support/artifice/helpers/artifice"
+require "bundler/vendored_persistent.rb"
+
+RSpec.describe "bundle doctor ssl" do
+ before(:each) do
+ require_rack_test
+ require_relative "../support/artifice/helpers/endpoint"
+
+ @dummy_endpoint = Class.new(Endpoint) do
+ get "/" do
+ end
+ end
+
+ @previous_ui = Bundler.ui
+ Bundler.ui = Bundler::UI::Shell.new
+ Bundler.ui.level = "info"
+
+ @previous_client = Gem::Request::ConnectionPools.client
+ Artifice.activate_with(@dummy_endpoint)
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+ end
+
+ after(:each) do
+ Bundler.ui = @previous_ui
+ Artifice.deactivate
+ Gem::Request::ConnectionPools.client = @previous_client
+ end
+
+ context "when a diagnostic fails" do
+ it "prints the diagnostic when openssl can't be loaded" do
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ allow(subject).to receive(:require).with("openssl").and_raise(LoadError)
+
+ expected_err = <<~MSG
+ Oh no! Your Ruby doesn't have OpenSSL, so it can't connect to rubygems.org.
+ You'll need to recompile or reinstall Ruby with OpenSSL support and try again.
+ MSG
+
+ expect { subject.run }.to output("").to_stdout.and output(expected_err).to_stderr
+ end
+
+ it "fails due to certificate verification", :ruby_repo do
+ net_http = Class.new(Artifice::Net::HTTP) do
+ def connect
+ raise OpenSSL::SSL::SSLError, "certificate verify failed"
+ end
+ end
+
+ Artifice.replace_net_http(net_http)
+ Gem::Request::ConnectionPools.client = net_http
+ Gem::RemoteFetcher.fetcher.close_all
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ MSG
+
+ expected_err = <<~MSG
+ Bundler: failed (certificate verification)
+ RubyGems: failed (certificate verification)
+ Ruby net/http: failed
+
+ Unfortunately, this Ruby can't connect to rubygems.org.
+
+ Below affect only Ruby net/http connections:
+ SSL_CERT_FILE: exists #{OpenSSL::X509::DEFAULT_CERT_FILE}
+ SSL_CERT_DIR: exists #{OpenSSL::X509::DEFAULT_CERT_DIR}
+
+ Your Ruby can't connect to rubygems.org because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine rubygems.org servers.
+
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ end
+
+ it "fails due to a too old tls version" do
+ subject = Bundler::CLI::Doctor::SSL.new({})
+
+ net_http = Class.new(Artifice::Net::HTTP) do
+ def connect
+ raise OpenSSL::SSL::SSLError, "read server hello A"
+ end
+ end
+
+ Artifice.replace_net_http(net_http)
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+ Gem::RemoteFetcher.fetcher.close_all
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ MSG
+
+ expected_err = <<~MSG
+ Bundler: failed (SSL/TLS protocol version mismatch)
+ RubyGems: failed (SSL/TLS protocol version mismatch)
+ Ruby net/http: failed
+
+ Unfortunately, this Ruby can't connect to rubygems.org.
+
+ Your Ruby can't connect to rubygems.org because your version of OpenSSL is too old.
+ You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL.
+
+ MSG
+
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ end
+
+ it "fails due to unsupported tls 1.3 version" do
+ net_http = Class.new(Artifice::Net::HTTP) do
+ def connect
+ raise OpenSSL::SSL::SSLError, "read server hello A"
+ end
+ end
+
+ Artifice.replace_net_http(net_http)
+ Gem::Request::ConnectionPools.client = net_http
+ Gem::RemoteFetcher.fetcher.close_all
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ MSG
+
+ expected_err = <<~MSG
+ Bundler: failed (SSL/TLS protocol version mismatch)
+ RubyGems: failed (SSL/TLS protocol version mismatch)
+ Ruby net/http: failed
+
+ Unfortunately, this Ruby can't connect to rubygems.org.
+
+ Your Ruby can't connect to rubygems.org because TLS1_3 isn't supported yet.
+
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new("tls-version": "1.3")
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ end
+
+ it "fails due to a bundler and rubygems connection error" do
+ endpoint = Class.new(Endpoint) do
+ get "/" do
+ raise OpenSSL::SSL::SSLError, "read server hello A"
+ end
+ end
+
+ Artifice.activate_with(endpoint)
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ Ruby net/http: success
+
+ For some reason, your Ruby installation can connect to rubygems.org, but neither RubyGems nor Bundler can.
+ The most likely fix is to manually upgrade RubyGems by following the instructions at http://ruby.to/ssl-check-failed.
+ After you've done that, run `gem install bundler` to upgrade Bundler, and then run this script again to make sure everything worked. ❣
+
+ MSG
+
+ expected_err = <<~MSG
+ Bundler: failed (SSL/TLS protocol version mismatch)
+ RubyGems: failed (SSL/TLS protocol version mismatch)
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ end
+
+ it "fails due to a bundler connection error" do
+ endpoint = Class.new(Endpoint) do
+ get "/" do
+ if request.user_agent.include?("bundler")
+ raise OpenSSL::SSL::SSLError, "read server hello A"
+ end
+ end
+ end
+
+ Artifice.activate_with(endpoint)
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ RubyGems: success
+ Ruby net/http: success
+
+ Although your Ruby installation and RubyGems can both connect to rubygems.org, Bundler is having trouble.
+ The most likely way to fix this is to upgrade Bundler by running `gem install bundler`.
+ Run this script again after doing that to make sure everything is all set.
+ If you're still having trouble, check out the troubleshooting guide at http://ruby.to/ssl-check-failed.
+
+ MSG
+
+ expected_err = <<~MSG
+ Bundler: failed (SSL/TLS protocol version mismatch)
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ end
+
+ it "fails due to a RubyGems connection error" do
+ endpoint = Class.new(Endpoint) do
+ get "/" do
+ if request.user_agent.include?("Ruby, RubyGems")
+ raise OpenSSL::SSL::SSLError, "read server hello A"
+ end
+ end
+ end
+
+ Artifice.activate_with(endpoint)
+ Gem::Request::ConnectionPools.client = Gem::Net::HTTP
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ Bundler: success
+ Ruby net/http: success
+
+ It looks like Ruby and Bundler can connect to rubygems.org, but RubyGems itself cannot.
+ You can likely solve this by manually downloading and installing a RubyGems update.
+ Visit http://ruby.to/ssl-check-failed for instructions on how to manually upgrade RubyGems.
+
+ MSG
+
+ expected_err = <<~MSG
+ RubyGems: failed (SSL/TLS protocol version mismatch)
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ end
+ end
+
+ context "when no diagnostic fails" do
+ it "prints the SSL environment" do
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ Bundler: success
+ RubyGems: success
+ Ruby net/http: success
+
+ Hooray! This Ruby can connect to rubygems.org.
+ You are all set to use Bundler and RubyGems.
+
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ expect { subject.run }.to output(expected_out).to_stdout.and output("").to_stderr
+ end
+
+ it "uses the tls_version verify mode and host when given as option" do
+ net_http = Class.new(Artifice::Net::HTTP) do
+ class << self
+ attr_accessor :verify_mode, :min_version, :max_version
+ end
+
+ def connect
+ self.class.verify_mode = verify_mode
+ self.class.min_version = min_version
+ self.class.max_version = max_version
+
+ super
+ end
+ end
+
+ net_http.endpoint = @dummy_endpoint
+ Artifice.replace_net_http(net_http)
+ Gem::Request::ConnectionPools.client = net_http
+ Gem::RemoteFetcher.fetcher.close_all
+
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://example.org:
+ Bundler: success
+ RubyGems: success
+ Ruby net/http: success
+
+ Hooray! This Ruby can connect to example.org.
+ You are all set to use Bundler and RubyGems.
+
+ MSG
+
+ subject = Bundler::CLI::Doctor::SSL.new("tls-version": "1.3", "verify-mode": :none, host: "example.org")
+ expect { subject.run }.to output(expected_out).to_stdout.and output("").to_stderr
+ expect(net_http.verify_mode).to eq(0)
+ expect(net_http.min_version.to_s).to eq("TLS1_3")
+ expect(net_http.max_version.to_s).to eq("TLS1_3")
+ end
+
+ it "warns when TLS1.2 is not supported" do
+ expected_out = <<~MSG
+ Here's your OpenSSL environment:
+
+ OpenSSL: #{OpenSSL::VERSION}
+ Compiled with: #{OpenSSL::OPENSSL_VERSION}
+ Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
+
+ Trying connections to https://rubygems.org:
+ Bundler: success
+ RubyGems: success
+ Ruby net/http: success
+
+ Hooray! This Ruby can connect to rubygems.org.
+ You are all set to use Bundler and RubyGems.
+
+ MSG
+
+ expected_err = <<~MSG
+
+ WARNING: Although your Ruby can connect to rubygems.org today, your OpenSSL is very old!
+ WARNING: You will need to upgrade OpenSSL to use rubygems.org.
+
+ MSG
+
+ previous_version = OpenSSL::SSL::TLS1_2_VERSION
+ OpenSSL::SSL.send(:remove_const, :TLS1_2_VERSION)
+
+ subject = Bundler::CLI::Doctor::SSL.new({})
+ expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
+ ensure
+ OpenSSL::SSL.const_set(:TLS1_2_VERSION, previous_version)
+ end
+ end
+end
diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb
index 4992e428da..03a3786d80 100644
--- a/spec/bundler/commands/update_spec.rb
+++ b/spec/bundler/commands/update_spec.rb
@@ -1,41 +1,148 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle update" do
- before :each do
- build_repo2
+ describe "with no arguments" do
+ before do
+ build_repo2
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
- G
- end
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
- describe "with no arguments" do
it "updates the entire bundle" do
update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0"
end
it "doesn't delete the Gemfile.lock file if something goes wrong" do
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport"
- gem "rack-obama"
+ gem "myrack-obama"
exit!
G
- bundle "update"
- expect(bundled_app("Gemfile.lock")).to exist
+ bundle "update", raise_on_error: false
+ expect(bundled_app_lock).to exist
+ end
+ end
+
+ describe "with --verbose" do
+ before do
+ build_repo2
+
+ install_gemfile <<~G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+ end
+
+ it "logs the reason for re-resolving" do
+ bundle "update --verbose"
+ expect(out).not_to include("Found changes from the lockfile")
+ expect(out).to include("Re-resolving dependencies because bundler is unlocking")
+ end
+ end
+
+ describe "with --all" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "updates the entire bundle" do
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", all: true
+ expect(out).to include("Bundle updated!")
+ expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "doesn't delete the Gemfile.lock file if something goes wrong" do
+ install_gemfile "source 'https://gem.repo1'"
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-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 lockfiles based on the Gemfile name" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "https://gem.repo1"
+ gem "myrack", "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 "update_requires_all_flag true" }
+
+ it "errors when passed nothing" do
+ install_gemfile "source 'https://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 'https://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 'https://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 "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
it "hides UI messages" do
bundle "update --quiet"
expect(out).not_to include("Bundle updated!")
@@ -43,100 +150,622 @@ RSpec.describe "bundle update" do
end
describe "with a top level dependency" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
it "unlocks all child dependencies that are unrelated to other locked dependencies" do
update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ 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"
+ bundle "update myrack-obama"
+ expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 2.3.5"
end
end
describe "with an unknown dependency" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
it "should inform the user" do
- bundle "update halting-problem-solver"
- expect(out).to include "Could not find gem 'halting-problem-solver'"
+ 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 active-support"
- expect(out).to include "Did you mean activesupport?"
+ 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 "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
it "should update the child dependency" do
- update_repo2
- bundle "update rack"
- expect(the_bundle).to include_gems "rack 1.2"
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+ end
+
+ bundle "update myrack"
+ expect(the_bundle).to include_gems "myrack 1.2"
end
end
describe "when a possible resolve requires an older version of a locked gem" do
- context "and only_update_to_newer_versions is set" do
- before do
- bundle! "config only_update_to_newer_versions true"
+ 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
- it "does not go to an older version" 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"
+
+ install_gemfile <<-G
+ source "https://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")
+
+ build_repo4 do
+ build_gem "slim", "4.0.0" do |s|
+ s.add_dependency "tilt", [">= 2.0.6", "< 2.1"]
end
+ end
- install_gemfile! <<-G
- source "file:#{gem_repo4}"
- gem "a"
- G
+ 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
- expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
+ 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)
- update_repo4 do
- build_gem "b", "2.0" do |s|
- s.add_dependency "c", "< 2"
- end
+ 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 "https://gem.repo4"
+
+ gem "country_select"
+ gem "countries"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo4, "countries", "3.1.0")
+ c.checksum(gem_repo4, "country_select", "5.1.0")
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ countries (3.1.0)
+ country_select (5.1.0)
+ countries (~> 3.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ countries
+ country_select
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ previous_lockfile = lockfile
+
+ bundle "lock --update", env: { "DEBUG" => "1" }, verbose: true
+
+ 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 "https://gem.repo4"
+
+ gem "oauth2"
+ gem "quickbooks-ruby"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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 "https://gem.repo4"
+
+ gemspec
+
+ gem "rake"
+ gem "sneakers"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: .
+ specs:
+
+ GEM
+ remote: https://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 "downgrades indirect dependencies if required to fulfill an explicit upgrade request" do
+ build_repo4 do
+ build_gem "rbs", "3.6.1"
+ build_gem "rbs", "3.9.4"
+
+ build_gem "solargraph", "0.56.0" do |s|
+ s.add_dependency "rbs", "~> 3.3"
+ end
+
+ build_gem "solargraph", "0.56.2" do |s|
+ s.add_dependency "rbs", "~> 3.6.1"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem 'solargraph', '~> 0.56.0'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ rbs (3.9.4)
+ solargraph (0.56.0)
+ rbs (~> 3.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ solargraph (~> 0.56.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --update solargraph"
+
+ expect(lockfile).to include("solargraph (0.56.2)")
+ end
+
+ it "does not downgrade direct dependencies unnecessarily" do
+ build_repo4 do
+ build_gem "redis", "4.8.1"
+ build_gem "redis", "5.3.0"
+
+ build_gem "sidekiq", "6.5.5" do |s|
+ s.add_dependency "redis", ">= 4.5.0"
+ end
+
+ build_gem "sidekiq", "6.5.12" do |s|
+ s.add_dependency "redis", ">= 4.5.0", "< 5"
+ end
+
+ # one version of sidekiq above Gemfile's range is needed to make the
+ # resolver choose `redis` first and trying to upgrade it, reproducing
+ # the accidental sidekiq downgrade as a result
+ build_gem "sidekiq", "7.0.0 " do |s|
+ s.add_dependency "redis", ">= 4.2.0"
+ end
+
+ build_gem "sentry-sidekiq", "5.22.0" do |s|
+ s.add_dependency "sidekiq", ">= 3.0"
+ end
+
+ build_gem "sentry-sidekiq", "5.22.4" do |s|
+ s.add_dependency "sidekiq", ">= 3.0"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "redis"
+ gem "sidekiq", "~> 6.5"
+ gem "sentry-sidekiq"
+ G
+
+ original_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ redis (4.8.1)
+ sentry-sidekiq (5.22.0)
+ sidekiq (>= 3.0)
+ sidekiq (6.5.12)
+ redis (>= 4.5.0, < 5)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ redis
+ sentry-sidekiq
+ sidekiq (~> 6.5)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ bundle "lock --update sentry-sidekiq"
+
+ expect(lockfile).to eq(original_lockfile.sub("sentry-sidekiq (5.22.0)", "sentry-sidekiq (5.22.4)"))
+ 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 "https://gem.repo4"
+ gem "a"
+ G
+
+ expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
+
+ build_repo4 do
+ build_gem "b", "2.0" do |s|
+ s.add_dependency "c", "< 2"
+ end
+ end
- bundle! "update"
+ bundle "update", all: true, verbose: true
+ expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
+ end
- expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
+ 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 "https://gem.repo4"
+ gem "a"
+ gem "b"
+ G
+
+ expect(the_bundle).to include_gems("a 1.0", "b 2.0")
+
+ gemfile <<-G
+ source "https://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 "https://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 "https://gem.repo4"
+ gem "activesupport", "~> 6.0.0"
+ G
+
+ original_lockfile = lockfile
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "activesupport", "6.0.4.1"
+ c.checksum gem_repo4, "tzinfo", "1.2.9"
+ end
+
+ expected_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ activesupport (6.0.4.1)
+ tzinfo (~> 1.1)
+ tzinfo (1.2.9)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport (~> 6.0.0)
+ #{checksums}
+ 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
- it "doesn't hit repo2" do
- FileUtils.rm_rf(gem_repo2)
+ before do
+ build_repo2
- bundle "update --local"
- expect(out).not_to match(/Fetching source index/)
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "doesn't hit repo2" do
+ simulate_platform "x86-darwin-100" do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ activesupport (2.3.5)
+ platform_specific (1.0-x86-darwin-100)
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
+
+ PLATFORMS
+ x86-darwin-100
+
+ DEPENDENCIES
+ activesupport
+ platform_specific
+ myrack-obama
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ FileUtils.rm_r(gem_repo2)
+
+ bundle "update --local --all"
+ expect(out).not_to include("Fetching source index")
+ end
end
end
describe "with --group option" do
- it "should update only specifed group gems" do
+ before do
+ build_repo2
+ end
+
+ it "should update only specified group gems" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport", :group => :development
- gem "rack"
+ gem "myrack"
G
update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ 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"
+ expect(the_bundle).not_to include_gems "myrack 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 "https://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 :each do
- build_git "foo", :path => lib_path("activesupport")
+ before do
+ build_git "foo", path: lib_path("activesupport")
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport", :group => :development
gem "foo", :git => "#{lib_path("activesupport")}"
G
@@ -144,56 +773,111 @@ RSpec.describe "bundle update" do
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")
+ 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 "https://gem.repo1"
+ gem "activesupport", :group => :development
+ gem "myrack"
+ G
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ 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 "myrack 1.2"
+ end
+ end
end
describe "in a frozen bundle" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "activesupport"
+ gem "myrack-obama"
+ gem "platform_specific"
+ G
+ end
+
it "should fail loudly" do
- bundle "install --deployment"
- bundle "update"
+ bundle_config "deployment true"
+ bundle "update", all: true, raise_on_error: false
- expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m)
- expect(out).to match(/freeze \nby running `bundle install --no-deployment`./m)
- expect(exitstatus).not_to eq(0) if exitstatus
+ expect(last_command).to be_failure
+ expect(err).to eq <<~ERROR.strip
+ Bundler is unlocking, but the lockfile can't be updated because frozen mode is set
+
+ If this is a development machine, remove the Gemfile.lock freeze by running `bundle config set frozen false`.
+ ERROR
end
- it "should suggest different command when frozen is set globally" do
- bundler "config --global frozen 1"
- bundle "update"
- expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m)
- expect(out).to match(/freeze \nby running `bundle config --delete frozen`./m)
+ it "should fail loudly when frozen is set globally" do
+ bundle_config_global "frozen 1"
+ bundle "update", all: true, raise_on_error: false
+ expect(err).to eq <<~ERROR.strip
+ Bundler is unlocking, but the lockfile can't be updated because frozen mode is set
+
+ If this is a development machine, remove the Gemfile.lock freeze by running `bundle config set frozen false`.
+ ERROR
+ end
+
+ it "should fail loudly when deployment is set globally" do
+ bundle_config_global "deployment true"
+ bundle "update", all: true, raise_on_error: false
+ expect(err).to eq <<~ERROR.strip
+ Bundler is unlocking, but the lockfile can't be updated because frozen mode is set
+
+ If this is a development machine, remove the Gemfile.lock freeze by running `bundle config set frozen false`.
+ ERROR
+ 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 eq("Bundler is unlocking, but the lockfile can't be updated because frozen mode is set")
end
end
describe "with --source option" do
- it "should not update gems not included in the source that happen to have the same name" do
- pending("Allowed to fail to preserve backwards-compatibility")
+ 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://#{gem_repo2}"
+ source "https://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"
+ expect(the_bundle).not_to include_gem "activesupport 3.0"
end
- it "should update gems not included in the source that happen to have the same name" do
+ it "should not update gems not included in the source that happen to have the same name" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "activesupport"
G
update_repo2 { build_gem "activesupport", "3.0" }
bundle "update --source activesupport"
- expect(the_bundle).to include_gems "activesupport 3.0"
+ expect(the_bundle).not_to include_gems "activesupport 3.0"
end
end
@@ -207,7 +891,7 @@ RSpec.describe "bundle update" do
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "harry"
gem "fred"
G
@@ -222,8 +906,7 @@ RSpec.describe "bundle update" do
end
bundle "update --source harry"
- expect(the_bundle).to include_gems "harry 2.0"
- expect(the_bundle).to include_gems "fred 1.0"
+ expect(the_bundle).to include_gems "harry 1.0", "fred 1.0"
end
end
@@ -240,7 +923,7 @@ RSpec.describe "bundle update" do
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "harry"
gem "fred"
G
@@ -255,41 +938,233 @@ RSpec.describe "bundle update" do
end
bundle "update --source harry"
- expect(the_bundle).to include_gems "harry 2.0"
- expect(the_bundle).to include_gems "fred 1.0"
- expect(the_bundle).to include_gems "george 1.0"
+ 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 "https://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 "https://gem.repo4"
+ gem "bar"
+ gem "foo"
+ G
+
+ bundle "update", all: true
+ expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/)
+
+ build_repo4 do
+ build_gem "foo", "2.0"
+ end
+
+ bundle "update", all: true
+ expect(out.sub("Removing foo (1.0)\n", "")).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 "https://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 sources and caching enabled" do
+ before do
+ build_repo2 do
+ build_gem "myrack", "1.0.0"
+
+ build_gem "request_store", "1.0.0" do |s|
+ s.add_dependency "myrack", "1.0.0"
+ end
+ end
+
+ build_repo4 do
+ # set up repo with no gems
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ gem "request_store"
+
+ source "https://gem.repo4" do
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+ request_store (1.0.0)
+ myrack (= 1.0.0)
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ request_store
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "works" do
+ bundle :install
+ bundle :cache
+
+ update_repo2 do
+ build_gem "request_store", "1.1.0" do |s|
+ s.add_dependency "myrack", "1.0.0"
+ end
+ end
+
+ bundle "update request_store"
+
+ expect(out).to include("Bundle updated!")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+ request_store (1.1.0)
+ myrack (= 1.0.0)
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ request_store
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
end
end
RSpec.describe "bundle update in more complicated situations" do
- before :each do
+ before do
build_repo2
end
it "will eagerly unlock dependencies of a specified gem" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "thin"
- gem "rack-obama"
+ gem "myrack-obama"
G
update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
build_gem "thin", "2.0" do |s|
- s.add_dependency "rack"
+ s.add_dependency "myrack"
end
end
bundle "update thin"
- expect(the_bundle).to include_gems "thin 2.0", "rack 1.2", "rack-obama 1.0"
+ expect(the_bundle).to include_gems "thin 2.0", "myrack 1.2", "myrack-obama 1.0"
+ end
+
+ it "will warn when some explicitly updated gems are not updated" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "thin"
+ gem "myrack-obama"
+ G
+
+ update_repo2 do
+ build_gem("thin", "2.0") {|s| s.add_dependency "myrack" }
+ build_gem "myrack", "10.0"
+ end
+
+ bundle "update thin myrack-obama"
+ expect(stdboth).to include "Bundler attempted to update myrack-obama but its version stayed the same"
+ expect(the_bundle).to include_gems "thin 2.0", "myrack 10.0", "myrack-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 "https://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(stdboth).not_to include "attempted to update"
+ end
+
+ it "will not warn when changing gem sources but not versions" do
+ build_git "myrack"
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack", :git => '#{lib_path("myrack-1.0")}'
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "update myrack"
+
+ expect(stdboth).not_to include "attempted to update"
end
it "will update only from pinned source" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
- source "file://#{gem_repo1}" do
+ source "https://gem.repo1" do
gem "thin"
end
G
@@ -298,9 +1173,85 @@ RSpec.describe "bundle update in more complicated situations" do
build_gem "thin", "2.0"
end
- bundle "update"
+ bundle "update", artifice: "compact_index"
expect(the_bundle).to include_gems "thin 1.0"
end
+
+ context "when the lockfile is for a different platform" do
+ around do |example|
+ 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 "https://gem.repo4"
+ gem "a"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4
+ specs:
+ a (0.9-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ a
+ L
+
+ simulate_platform "x86_64-linux", &example
+ 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 "https://gem.repo4"
+ gem "a", platform: :jruby
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4
+ specs:
+ a (0.9-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ a
+ L
+ end
+
+ it "is not updated because it is not actually included in the bundle" do
+ simulate_platform "x86_64-linux" do
+ bundle "update a"
+ expect(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
end
RSpec.describe "bundle update without a Gemfile.lock" do
@@ -308,107 +1259,69 @@ RSpec.describe "bundle update without a Gemfile.lock" do
build_repo2
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
- gem "rack", "1.0"
+ gem "myrack", "1.0"
G
- bundle "update"
+ bundle "update", all: true
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
RSpec.describe "bundle update when a gem depends on a newer version of bundler" do
- before(:each) do
+ before do
build_repo2 do
build_gem "rails", "3.0.1" do |s|
- s.add_dependency "bundler", Bundler::VERSION.succ
+ s.add_dependency "bundler", "9.9.9"
end
+
+ build_gem "bundler", "9.9.9"
end
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "rails", "3.0.1"
G
end
- it "should not explode" do
- bundle "update"
- expect(err).to lack_errors
- end
-
- it "should explain that bundler conflicted" do
- bundle "update"
- expect(out).not_to match(/in snapshot/i)
- expect(out).to match(/current Bundler version/i)
- expect(out).to match(/perhaps you need to update bundler/i)
- end
-end
-
-RSpec.describe "bundle update" do
- it "shows the previous version of the gem when updated from rubygems source" do
- build_repo2
-
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- G
-
- bundle "update"
- expect(out).to include("Using activesupport 2.3.5")
-
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle "update"
- expect(out).to include("Installing activesupport 3.0 (was 2.3.5)")
- end
-
- it "shows error message when Gemfile.lock is not preset and gem is specified" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- G
-
- bundle "update nonexisting"
- expect(out).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.")
- expect(exitstatus).to eq(22) if exitstatus
+ it "should explain that bundler conflicted and how to resolve the conflict" do
+ bundle "update", all: true, raise_on_error: false
+ expect(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:9\.9\.9`/i)
end
end
RSpec.describe "bundle update --ruby" do
- before do
- install_gemfile <<-G
- ::RUBY_VERSION = '2.1.3'
- ::RUBY_PATCHLEVEL = 100
- ruby '~> 2.1.0'
- G
- bundle "update --ruby"
- end
-
context "when the Gemfile removes the ruby" do
before do
install_gemfile <<-G
- ::RUBY_VERSION = '2.1.4'
- ::RUBY_PATCHLEVEL = 222
+ ruby '~> #{Gem.ruby_version}'
+ source "https://gem.repo1"
+ G
+
+ gemfile <<-G
+ source "https://gem.repo1"
G
end
+
it "removes the Ruby from the Gemfile.lock" do
bundle "update --ruby"
- lockfile_should_be <<-L
+ expect(lockfile).to eq <<~L
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
-
+ #{checksums_section_when_enabled}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
end
@@ -416,27 +1329,33 @@ RSpec.describe "bundle update --ruby" do
context "when the Gemfile specified an updated Ruby version" do
before do
install_gemfile <<-G
- ::RUBY_VERSION = '2.1.4'
- ::RUBY_PATCHLEVEL = 222
- ruby '~> 2.1.0'
+ ruby '~> #{Gem.ruby_version}'
+ source "https://gem.repo1"
+ G
+
+ gemfile <<-G
+ ruby '~> #{current_ruby_minor}'
+ source "https://gem.repo1"
G
end
+
it "updates the Gemfile.lock with the latest version" do
bundle "update --ruby"
- lockfile_should_be <<-L
- GEM
- specs:
-
- PLATFORMS
- ruby
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
- DEPENDENCIES
+ PLATFORMS
+ #{lockfile_platforms}
- RUBY VERSION
- ruby 2.1.4p222
+ DEPENDENCIES
+ #{checksums_section_when_enabled}
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
- BUNDLED WITH
+ BUNDLED WITH
#{Bundler::VERSION}
L
end
@@ -445,45 +1364,435 @@ RSpec.describe "bundle update --ruby" do
context "when a different Ruby is being used than has been versioned" do
before do
install_gemfile <<-G
- ::RUBY_VERSION = '2.2.2'
- ::RUBY_PATCHLEVEL = 505
+ ruby '~> #{Gem.ruby_version}'
+ source "https://gem.repo1"
+ G
+
+ gemfile <<-G
ruby '~> 2.1.0'
+ source "https://gem.repo1"
G
end
it "shows a helpful error message" do
- bundle "update --ruby"
+ bundle "update --ruby", raise_on_error: false
- expect(out).to include("Your Ruby version is 2.2.2, but your Gemfile specified ~> 2.1.0")
+ 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
- install_gemfile <<-G
- ::RUBY_VERSION = '1.8.3'
- ::RUBY_PATCHLEVEL = 55
- ruby '~> 1.8.0'
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ CHECKSUMS
+
+ RUBY VERSION
+ ruby 2.1.4p222
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<-G
+ ruby '~> #{Gem.ruby_version}'
+ source "https://gem.repo1"
G
end
+
it "updates the Gemfile.lock with the latest version" do
bundle "update --ruby"
- lockfile_should_be <<-L
+ expect(lockfile).to eq <<~L
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
-
+ #{checksums_section_when_enabled}
RUBY VERSION
- ruby 1.8.3p55
+ #{Bundler::RubyVersion.system}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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 "bundler", "2.5.9"
+ build_gem "myrack", "1.0"
+ end
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo4, "myrack", "1.0")
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2')
+
+ bundle :update, bundler: true, verbose: true
+ expect(out).to include("Using bundler #{Bundler::VERSION}")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(the_bundle).to include_gem "myrack 1.0"
+ end
+
+ it "updates the bundler version in the lockfile without re-resolving if the highest version is already installed" do
+ build_repo4 do
+ build_gem "bundler", "2.3.9"
+ build_gem "myrack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9")
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo4, "myrack", "1.0")
+ end
+
+ bundle :update, bundler: true, verbose: true
+ expect(out).to include("Using bundler #{Bundler::VERSION}")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(the_bundle).to include_gem "myrack 1.0"
+ end
+
+ it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo do
+ bundle_config "path.system true"
+
+ pristine_system_gems "bundler-9.0.0"
+
+ build_repo4 do
+ build_gem "myrack", "1.0"
+
+ build_bundler "999.0.0"
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum(gem_repo4, "myrack", "1.0")
+ c.checksum(gem_repo4, "bundler", "999.0.0")
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ bundle :update, bundler: true, verbose: true
+
+ expect(out).to include("Updating bundler to 999.0.0")
+ expect(out).to include("Running `bundle update --bundler \"> 0.a\" --verbose` with bundler 999.0.0")
+ expect(out).not_to include("Installing Bundler 2.99.9 and restarting using that version.")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ 999.0.0
+ L
+
+ expect(the_bundle).to include_gems "bundler 999.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0"
+ end
+
+ it "does not claim to update to Bundler version to a wrong version when cached gems are present" do
+ pristine_system_gems "bundler-4.99.0"
+
+ build_repo4 do
+ build_gem "myrack", "3.0.9.1"
+
+ build_bundler "4.99.0"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (3.0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+
+ BUNDLED WITH
+ 2.99.0
+ L
+
+ bundle :cache, verbose: true
+
+ bundle :update, bundler: true, verbose: true
+
+ expect(out).not_to include("Updating bundler to")
+ end
+
+ it "does not update the bundler version in the lockfile if the latest version is not compatible with current ruby", :ruby_repo do
+ pristine_system_gems "bundler-9.9.9"
+
+ build_repo4 do
+ build_gem "myrack", "1.0"
+
+ build_bundler "9.9.9"
+ build_bundler "999.0.0" do |s|
+ s.required_ruby_version = "> #{Gem.ruby_version}"
+ end
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum(gem_repo4, "myrack", "1.0")
+ c.checksum(gem_repo4, "bundler", "9.9.9")
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ bundle :update, bundler: true, verbose: true
+
+ expect(out).to include("Using bundler 9.9.9")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ 9.9.9
+ L
+
+ expect(the_bundle).to include_gems "bundler 9.9.9"
+ expect(the_bundle).to include_gems "myrack 1.0"
+ end
+
+ it "errors if the explicit target version does not exist" do
+ pristine_system_gems "bundler-9.9.9"
+
+ build_repo4 do
+ build_gem "myrack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ bundle :update, bundler: "999.999.999", raise_on_error: false
+
+ expect(last_command).to be_failure
+ expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist")
+ end
+
+ it "errors if the explicit target version does not exist, even if auto switching is disabled" do
+ pristine_system_gems "bundler-9.9.9"
+
+ build_repo4 do
+ build_gem "myrack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ bundle :update, bundler: "999.999.999", raise_on_error: false, env: { "BUNDLER_VERSION" => "9.9.9" }
+
+ expect(last_command).to be_failure
+ expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist")
+ end
+
+ it "allows updating to development versions if already installed locally" do
+ system_gems "bundler-9.9.9"
+
+ build_repo4 do
+ build_gem "myrack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ system_gems "bundler-9.0.0.dev", path: local_gem_path
+ bundle :update, bundler: "9.0.0.dev", verbose: "true"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo4, "myrack", "1.0")
+ end
+ checksums.delete("bundler")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ 9.0.0.dev
L
+
+ expect(out).to include("Using bundler 9.0.0.dev")
+ end
+
+ it "does not touch the network if not necessary" do
+ system_gems "bundler-9.9.9"
+
+ build_repo4 do
+ build_gem "myrack", "1.0"
end
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+ system_gems "bundler-9.0.0", path: local_gem_path
+ bundle :update, bundler: "9.0.0", verbose: true
+
+ expect(out).not_to include("Fetching gem metadata from https://rubygems.org/")
+
+ # Only updates properly on modern RubyGems.
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo4, "myrack", "1.0")
+ c.checksum(local_gem_path, "bundler", "9.0.0", Gem::Platform::RUBY, "cache")
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ 9.0.0
+ L
+
+ expect(out).to include("Using bundler 9.0.0")
+ end
+
+ it "prints an error when trying to update bundler in frozen mode" do
+ system_gems "bundler-9.0.0"
+
+ gemfile <<~G
+ source "https://gem.repo2"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ 9.0.0
+ L
+
+ system_gems "bundler-9.9.9", path: local_gem_path
+
+ bundle "update --bundler=9.9.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
@@ -492,22 +1801,25 @@ 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|
+ 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|
+ 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|
+ 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.0.1 1.1.0 2.0.0)
+ 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://#{gem_repo4}"
+ source "https://gem.repo4"
gem 'foo', '1.4.3'
gem 'bar', '2.0.3'
gem 'qux', '1.0.0'
@@ -516,12 +1828,21 @@ RSpec.describe "bundle update conservative" do
# remove 1.4.3 requirement and bar altogether
# to setup update specs below
gemfile <<-G
- source "file://#{gem_repo4}"
+ source "https://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 "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"
@@ -530,7 +1851,7 @@ RSpec.describe "bundle update conservative" do
end
it "update all" do
- bundle "update --patch"
+ bundle "update --patch", all: true
expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1"
end
@@ -552,41 +1873,67 @@ RSpec.describe "bundle update conservative" do
end
it "minor preferred" do
- bundle "update --minor --strict"
+ 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|
+ 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 "isolated_dep", %w[2.0.1 2.0.2]
- build_gem "shared_owner_a", %w(3.0.1 3.0.2) do |s|
+ 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|
+ 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)
+ build_gem "shared_dep", %w[5.0.1 5.0.2]
end
gemfile <<-G
- source "file://#{gem_repo4}"
+ source "https://gem.repo4"
gem 'isolated_owner'
gem 'shared_owner_a'
gem 'shared_owner_b'
G
- lockfile <<-L
+ lockfile <<~L
GEM
- remote: file://#{gem_repo4}
+ remote: https://gem.repo4/
specs:
isolated_dep (2.0.1)
isolated_owner (1.0.1)
@@ -598,15 +1945,17 @@ RSpec.describe "bundle update conservative" do
shared_dep (~> 5.0)
PLATFORMS
- ruby
+ #{local_platform}
DEPENDENCIES
+ isolated_owner
shared_owner_a
shared_owner_b
- isolated_owner
+
+ CHECKSUMS
BUNDLED WITH
- 1.13.0
+ #{Bundler::VERSION}
L
end
@@ -625,12 +1974,55 @@ RSpec.describe "bundle update conservative" do
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.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ 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"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "isolated_dep", "2.0.1"
+ c.checksum gem_repo4, "isolated_owner", "1.0.2"
+ c.checksum gem_repo4, "shared_dep", "5.0.1"
+ c.checksum gem_repo4, "shared_owner_a", "3.0.2"
+ c.checksum gem_repo4, "shared_owner_b", "4.0.2"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
it "should match bundle install conservative update behavior when not eagerly unlocking" do
gemfile <<-G
- source "file://#{gem_repo4}"
+ source "https://gem.repo4"
gem 'isolated_owner', '1.0.2'
gem 'shared_owner_a', '3.0.2'
@@ -639,19 +2031,65 @@ RSpec.describe "bundle update conservative" do
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"
+ 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
+ end
+
+ context "when Gemfile dependencies have changed" do
+ before do
+ build_repo4 do
+ build_gem "nokogiri", "1.16.4" do |s|
+ s.platform = "arm64-darwin"
+ end
+
+ build_gem "nokogiri", "1.16.4" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "prism", "0.25.0"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+ gem "nokogiri", ">=1.16.4"
+ gem "prism", ">=0.25.0"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.16.4-arm64-darwin)
+ nokogiri (1.16.4-x86_64-linux)
+
+ PLATFORMS
+ arm64-darwin
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri (>= 1.16.4)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "still works" do
+ simulate_platform "arm64-darwin-23" do
+ bundle "update"
+ end
end
end
context "error handling" do
before do
- gemfile ""
+ gemfile "source 'https://gem.repo1'"
end
it "raises if too many flags are provided" do
- bundle "update --patch --minor"
+ bundle "update --patch --minor", all: true, raise_on_error: false
- expect(out).to eq "Provide only one of the following options: minor, patch"
+ 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..4320ad0611
--- /dev/null
+++ b/spec/bundler/commands/version_spec.rb
@@ -0,0 +1,65 @@
+# 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,}/
+ else
+ COMMIT_HASH = /[a-fA-F0-9]{7,}/
+ end
+
+ context "with -v" do
+ it "outputs the version and virtual version if set" do
+ bundle "-v"
+ expect(out).to eq(Bundler::VERSION.to_s)
+
+ bundle_config "simulate_version 5"
+ bundle "-v"
+ expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)")
+ end
+ end
+
+ context "with --version" do
+ it "outputs the version and virtual version if set" do
+ bundle "--version"
+ expect(out).to eq(Bundler::VERSION.to_s)
+
+ bundle_config "simulate_version 5"
+ bundle "--version"
+ expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)")
+ end
+ end
+
+ context "with version" do
+ context "when released", :ruby_repo do
+ before do
+ system_gems "bundler-4.9.9", released: true
+ end
+
+ it "outputs the version, virtual version if set, and build metadata" do
+ bundle "version"
+ expect(out).to match(/\A4\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/)
+
+ bundle_config "simulate_version 5"
+ bundle "version"
+ expect(out).to match(/\A4\.9\.9 \(simulating Bundler 5\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/)
+ end
+ end
+
+ context "when not released" do
+ before do
+ system_gems "bundler-4.9.9", released: false
+ end
+
+ it "outputs the version, virtual version if set, and build metadata" do
+ bundle "version"
+ expect(out).to match(/\A4\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/)
+
+ bundle_config "simulate_version 5"
+ bundle "version"
+ expect(out).to match(/\A4\.9\.9 \(simulating Bundler 5\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb
deleted file mode 100644
index 77112aace4..0000000000
--- a/spec/bundler/commands/viz_spec.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "bundle viz", :ruby => "1.9.3", :if => Bundler.which("dot") do
- let(:ruby_graphviz) do
- graphviz_glob = base_system_gems.join("cache/ruby-graphviz*")
- Pathname.glob(graphviz_glob).first
- end
-
- before do
- system_gems ruby_graphviz
- end
-
- it "graphs gems from the Gemfile" do
- install_gemfile <<-G
- source "file://#{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://#{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 ruby_graphviz, "graphviz-999", :gem_repo => gem_repo4
- end
-
- it "loads the correct ruby-graphviz gem" do
- install_gemfile <<-G
- source "file://#{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://#{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://#{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
index 1bca055c9f..c7ab7c3d7e 100644
--- a/spec/bundler/install/allow_offline_install_spec.rb
+++ b/spec/bundler/install/allow_offline_install_spec.rb
@@ -1,59 +1,58 @@
# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "bundle install with :allow_offline_install" do
- before do
- bundle "config allow_offline_install true"
- end
+RSpec.describe "bundle install allows offline install" do
context "with no cached data locally" do
it "still installs" do
- install_gemfile! <<-G, :artifice => "compact_index"
+ install_gemfile <<-G, artifice: "compact_index"
source "http://testgemserver.local"
- gem "rack-obama"
+ gem "myrack-obama"
G
- expect(the_bundle).to include_gem("rack 1.0")
+ expect(the_bundle).to include_gem("myrack 1.0")
end
it "still fails when the network is down" do
- install_gemfile <<-G, :artifice => "fail"
+ install_gemfile <<-G, artifice: "fail", raise_on_error: false
source "http://testgemserver.local"
- gem "rack-obama"
+ gem "myrack-obama"
G
- expect(out).to include("Could not reach host testgemserver.local.")
+ 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"]
+ system_gems ["myrack-1.0.0"], path: default_bundle_path
- install_gemfile! <<-G, :artifice => "compact_index"
+ bundle_config "clean false"
+ install_gemfile <<-G, artifice: "compact_index"
source "http://testgemserver.local"
- gem "rack-obama"
- gem "rack", "< 1.0"
+ gem "myrack-obama"
+ gem "myrack", "< 1.0"
G
- expect(the_bundle).to include_gems("rack-obama 1.0", "rack 0.9.1")
+ expect(the_bundle).to include_gems("myrack-obama 1.0", "myrack 0.9.1")
gemfile <<-G
source "http://testgemserver.local"
- gem "rack-obama"
+ gem "myrack-obama"
G
- bundle! :update, :artifice => "fail"
- expect(out).to include("Using the cached data for the new index because of a network error")
+ bundle :update, artifice: "fail", all: true
+ expect(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")
+ expect(the_bundle).to include_gems("myrack-obama 1.0", "myrack 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)
+ f.puts <<~RUBY
#!/usr/bin/env ruby
- if %w(fetch --force --quiet --tags refs/heads/*:refs/heads/*).-(ARGV).empty? || %w(clone --bare --no-hardlinks --quiet).-(ARGV).empty?
+ fetch_args = %w(fetch --force --quiet --no-tags)
+ clone_args = %w(clone --bare --no-hardlinks --quiet)
+
+ if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && File.exist?(ARGV[ARGV.index("--") + 1])
warn "git remote ops have been disabled"
exit 1
end
@@ -70,22 +69,26 @@ RSpec.describe "bundle install with :allow_offline_install" do
end
it "will install from a cached git repo" do
- git = build_git "a", "1.0.0", :path => lib_path("a")
- update_git("a", :path => git.path, :branch => "new_branch")
- install_gemfile! <<-G
+ 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 "https://gem.repo1"
gem "a", :git => #{git.path.to_s.dump}
G
- break_git_remote_ops! { bundle! :update }
- expect(out).to include("Using cached git data because of network errors")
+ 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
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "a", :git => #{git.path.to_s.dump}, :branch => "new_branch"
G
end
- expect(out).to include("Using cached git data because of network errors")
+ expect(err).to include("Using cached git data because of network errors")
expect(the_bundle).to be_locked
end
end
diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb
index a1a9ab167d..c2eccb3ef2 100644
--- a/spec/bundler/install/binstubs_spec.rb
+++ b/spec/bundler/install/binstubs_spec.rb
@@ -1,23 +1,19 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install" do
describe "when system_bindir is set" do
- # On OS X, Gem.bindir defaults to /usr/bin, so system_bindir is useful if
- # you want to avoid sudo installs for system gems with OS X's default ruby
it "overrides Gem.bindir" do
- expect(Pathname.new("/usr/bin")).not_to be_writable unless Process.euid == 0
+ expect(Pathname.new("/usr/bin")).not_to be_writable
gemfile <<-G
- require 'rubygems'
def Gem.bindir; "/usr/bin"; end
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- config "BUNDLE_SYSTEM_BINDIR" => system_gem_path("altbin").to_s
+ bundle_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
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ expect(system_gem_path("altbin/myrackup")).to exist
end
end
@@ -25,26 +21,29 @@ RSpec.describe "bundle install" do
before do
build_repo2 do
build_gem "fake", "14" do |s|
- s.executables = "rackup"
+ s.executables = "myrackup"
end
end
- install_gemfile <<-G, :binstubs => true
- source "file://#{gem_repo2}"
+ install_gemfile <<-G
+ source "https://gem.repo2"
gem "fake"
- gem "rack"
+ gem "myrack"
G
end
- it "prints a deprecation notice" do
- bundle "config major_deprecations true"
- gembin("rackup")
- expect(out).to include("Bundler is using a binstub that was created for a different gem.")
- end
+ it "warns about the situation" do
+ bundle "exec myrackup"
- it "loads the correct spec's executable" do
- gembin("rackup")
- expect(out).to eq("1.2")
+ expect(last_command.stderr).to include(
+ "The `myrackup` executable in the `fake` gem is being loaded, but it's also present in other gems (myrack).\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 `myrackup` executable in the `myrack` 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
index c1ce57e60e..86c22dad55 100644
--- a/spec/bundler/install/bundler_spec.rb
+++ b/spec/bundler/install/bundler_spec.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: true
-require "spec_helper"
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"
+ s.add_dependency "bundler", ">= 0.9.0"
end
build_gem "bundler", "0.9.1"
build_gem "bundler", Bundler::VERSION
@@ -15,57 +14,101 @@ RSpec.describe "bundle install" do
it "are forced to the current bundler version" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "rails", "3.0"
G
expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
end
- it "are not added if not already present" do
+ it "are forced to the current bundler version even if not already present" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- expect(the_bundle).not_to include_gems "bundler #{Bundler::VERSION}"
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
end
- it "causes a conflict if explicitly requesting a different version" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
+ it "causes a conflict if explicitly requesting a different version of bundler" do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
+ gem "rails", "3.0"
+ gem "bundler", "0.9.1"
+ G
+
+ nice_error = <<~E.strip
+ 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 "https://gem.repo2"
+ gem "rails", "3.0"
+ gem "bundler", "~> 0.8"
+ G
+
+ nice_error = <<~E.strip
+ Could not find compatible versions
+
+ Because rails >= 3.0 depends on bundler >= 0.9.0
+ and the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler >= 0.9.0, < 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 "https://gem.repo2"
gem "rails", "3.0"
gem "bundler", "0.9.2"
G
- nice_error = <<-E.strip.gsub(/^ {8}/, "")
- Fetching source index from file:#{gem_repo2}/
- Resolving dependencies...
- Bundler could not find compatible versions for gem "bundler":
- In Gemfile:
- bundler (= 0.9.2)
+ nice_error = <<~E.strip
+ Could not find compatible versions
- Current Bundler version:
- bundler (#{Bundler::VERSION})
- This Gemfile requires a different version of Bundler.
- Perhaps you need to update Bundler by running `gem install bundler`?
+ 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.
- Could not find gem 'bundler (= 0.9.2)' in any of the sources
+ Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.
E
- expect(out).to eq(nice_error)
+ 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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "multiple_versioned_deps"
G
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "multiple_versioned_deps"
- gem "rack"
+ gem "myrack"
G
expect(the_bundle).to include_gems "multiple_versioned_deps 1.0.0"
@@ -73,7 +116,7 @@ RSpec.describe "bundle install" do
it "includes bundler in the bundle when it's a child dependency" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "rails", "3.0"
G
@@ -83,8 +126,8 @@ RSpec.describe "bundle install" do
it "allows gem 'bundler' when Bundler is not in the Gemfile or its dependencies" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack"
+ source "https://gem.repo2"
+ gem "myrack"
G
run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError => e; puts e.backtrace; end"
@@ -92,53 +135,131 @@ RSpec.describe "bundle install" do
end
it "causes a conflict if child dependencies conflict" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
+ bundle_config "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 "https://gem.repo2"
gem "activemerchant"
- gem "rails_fail"
+ gem "rails_pinned_to_old_activesupport"
G
- nice_error = <<-E.strip.gsub(/^ {8}/, "")
- Fetching source index from file:#{gem_repo2}/
- Resolving dependencies...
- Bundler could not find compatible versions for gem "activesupport":
- In Gemfile:
- activemerchant was resolved to 1.0, which depends on
- activesupport (>= 2.0.0)
+ nice_error = <<~E.strip
+ Could not find compatible versions
- rails_fail was resolved to 1.0, which depends on
- activesupport (= 1.2.3)
+ 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(out).to include(nice_error)
+ expect(err).to include(nice_error)
end
it "causes a conflict if a child dependency conflicts with the Gemfile" do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rails_fail"
+ bundle_config "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 "https://gem.repo2"
+ gem "rails_pinned_to_old_activesupport"
gem "activesupport", "2.3.5"
G
- nice_error = <<-E.strip.gsub(/^ {8}/, "")
- Fetching source index from file:#{gem_repo2}/
- Resolving dependencies...
- Bundler could not find compatible versions for gem "activesupport":
- In Gemfile:
- activesupport (= 2.3.5)
+ nice_error = <<~E.strip
+ Could not find compatible versions
- rails_fail was resolved to 1.0, which depends on
- activesupport (= 1.2.3)
+ 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(out).to include(nice_error)
+ 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 "https://gem.repo2"
+ gem 'rails', "2.3.2"
+ G
+
+ install_gemfile <<-G
+ source "https://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 "path.system true"
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem 'rails', "7.0.4"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ gem 'rails', "7.0.3"
+ G
+
+ install_gemfile <<-G
+ source "https://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" do
+ it "can install dependencies with newer bundler version with system gems" do
+ bundle_config "path.system true"
+
+ system_gems "bundler-99999999.99.1"
+
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "rails", "3.0"
G
- simulate_bundler_version "10.0.0"
+ 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 "path .bundle"
+
+ system_gems "bundler-99999999.99.1"
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "rails", "3.0"
+ G
bundle "check"
expect(out).to include("The Gemfile's dependencies are satisfied")
diff --git a/spec/bundler/install/cooldown_spec.rb b/spec/bundler/install/cooldown_spec.rb
new file mode 100644
index 0000000000..bad7b7cf34
--- /dev/null
+++ b/spec/bundler/install/cooldown_spec.rb
@@ -0,0 +1,433 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with the cooldown setting" do
+ before do
+ build_repo2
+ end
+
+ context "Gemfile DSL" do
+ it "accepts `source ..., cooldown: N` without error" do
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo2", cooldown: 5
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
+ it "accepts `cooldown: 0` to disable cooldown for a source" do
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo2", cooldown: 0
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+ end
+
+ context "CLI flag" do
+ before do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+ end
+
+ it "accepts --cooldown N on install" do
+ bundle "install --cooldown 7", artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
+ it "accepts --cooldown 0 as an escape hatch" do
+ bundle "install --cooldown 0", artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
+ it "rejects a negative --cooldown value" do
+ bundle "install --cooldown=-7", artifice: "compact_index", raise_on_error: false
+
+ expect(err).to match(/non-negative integer/)
+ end
+ end
+
+ context "configuration" do
+ it "reads BUNDLE_COOLDOWN as an integer" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle "install", env: { "BUNDLE_COOLDOWN" => "7" }, artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
+ it "reads `bundle config set cooldown N`" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+
+ bundle "config set cooldown 7"
+ bundle "install", artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+ end
+
+ context "end-to-end with v2 compact index" do
+ before do
+ now = Time.now.utc
+ build_repo3 do
+ build_gem "ripe_gem", "1.0.0" do |s|
+ s.date = now - (30 * 86_400)
+ end
+ build_gem "ripe_gem", "2.0.0" do |s|
+ s.date = now - (1 * 86_400)
+ end
+
+ # parent only resolves with the in-cooldown child 2.0.0
+ build_gem "child", "1.0.0" do |s|
+ s.date = now - (30 * 86_400)
+ end
+ build_gem "child", "2.0.0" do |s|
+ s.date = now - (1 * 86_400)
+ end
+ build_gem "parent", "1.0.0" do |s|
+ s.add_dependency "child", ">= 2.0.0"
+ s.date = now - (30 * 86_400)
+ end
+
+ # a cooldown-eligible version exists above the in-cooldown locked one
+ build_gem "upgradable", "2.0.0" do |s|
+ s.date = now - (1 * 86_400)
+ end
+ build_gem "upgradable", "3.0.0" do |s|
+ s.date = now - (30 * 86_400)
+ end
+ end
+ end
+
+ it "excludes versions within the cooldown window" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ bundle "install --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 1.0.0")
+ end
+
+ it "selects the latest version when --cooldown 0 is passed" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ bundle "install --cooldown 0", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 2.0.0")
+ end
+
+ it "applies cooldown declared per-source in the Gemfile" do
+ gemfile <<-G
+ source "https://gem.repo3", cooldown: 7
+ gem "ripe_gem"
+ G
+
+ bundle "install", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 1.0.0")
+ end
+
+ it "is overridden by CLI --cooldown when Gemfile sets a different per-source value" do
+ gemfile <<-G
+ source "https://gem.repo3", cooldown: 0
+ gem "ripe_gem"
+ G
+
+ bundle "install --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 1.0.0")
+ end
+
+ it "bypasses cooldown when bundle install uses an existing lockfile" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (2.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 2.0.0")
+ end
+
+ it "annotates in-cooldown versions in bundle outdated table output" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem", "1.0.0"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem (= 1.0.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "outdated --cooldown 7", artifice: "compact_index_cooldown", raise_on_error: false
+
+ expect(out).to match(/ripe_gem.*\(cooldown \d+d\)/)
+ end
+
+ it "annotates in-cooldown versions in bundle outdated --parseable output" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem", "1.0.0"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem (= 1.0.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "outdated --cooldown 7 --parseable", artifice: "compact_index_cooldown", raise_on_error: false
+
+ expect(out).to match(/ripe_gem.*in cooldown for \d+ more day/)
+ end
+
+ it "excludes a locally-installed version that is still within the cooldown window" do
+ system_gems "ripe_gem-2.0.0", gem_repo: gem_repo3
+
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ bundle "install --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 1.0.0")
+ end
+
+ it "selects a locally-installed in-cooldown version when --cooldown 0 bypasses the filter" do
+ system_gems "ripe_gem-2.0.0", gem_repo: gem_repo3
+
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ bundle "install --cooldown 0", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 2.0.0")
+ end
+
+ it "surfaces a cooldown hint when bundle update filters every candidate" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update ripe_gem --cooldown 99999", artifice: "compact_index_cooldown", raise_on_error: false
+
+ expect(err).to match(/excluded by the cooldown setting/)
+ expect(err).to match(/--cooldown 0/)
+ end
+
+ it "keeps an in-cooldown locked version on bundle update --all instead of failing" do
+ # Lockfile written before cooldown was enabled pins the now-in-cooldown
+ # latest version. A full update must not downgrade below it, and cooldown
+ # must not filter it out, otherwise resolution becomes impossible (#9598).
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (2.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update --all --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 2.0.0")
+ end
+
+ it "does not fail bundle outdated when the locked version is in cooldown" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (2.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "outdated --cooldown 7", artifice: "compact_index_cooldown", raise_on_error: false
+
+ # exit 0 means no outdated gems and, crucially, no resolution failure (exit 7)
+ expect(exitstatus).to eq(0)
+ end
+
+ it "still applies cooldown and downgrades a gem that is updated explicitly" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "ripe_gem"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ ripe_gem (2.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ripe_gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update ripe_gem --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("ripe_gem 1.0.0")
+ end
+
+ it "keeps an in-cooldown transitive dependency on bundle update --all" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "parent"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ child (2.0.0)
+ parent (1.0.0)
+ child (>= 2.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ parent
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update --all --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("parent 1.0.0", "child 2.0.0")
+ end
+
+ it "still upgrades to a cooldown-eligible version above the locked one" do
+ gemfile <<-G
+ source "https://gem.repo3"
+ gem "upgradable"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ upgradable (2.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ upgradable
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update --all --cooldown 7", artifice: "compact_index_cooldown"
+
+ expect(the_bundle).to include_gems("upgradable 3.0.0")
+ end
+ end
+end
diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb
index b66135c6b0..a3b4a87ecf 100644
--- a/spec/bundler/install/deploy_spec.rb
+++ b/spec/bundler/install/deploy_spec.rb
@@ -1,109 +1,139 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "install with --deployment or --frozen" do
+RSpec.describe "install in deployment or frozen mode" do
before do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
- it "fails without a lockfile and says that --deployment requires a lock" do
- bundle "install --deployment"
- expect(out).to include("The --deployment flag requires a Gemfile.lock")
+ it "fails without a lockfile and says that deployment requires a lock" do
+ bundle_config "deployment true"
+ bundle "install", raise_on_error: false
+ expect(err).to include("The deployment setting requires a lockfile")
end
- it "fails without a lockfile and says that --frozen requires a lock" do
- bundle "install --frozen"
- expect(out).to include("The --frozen flag requires a Gemfile.lock")
- end
-
- it "disallows --deployment --system" do
- bundle "install --deployment --system"
- expect(out).to include("You have specified both --deployment")
- expect(out).to include("Please choose only one option")
- expect(exitstatus).to eq(15) if exitstatus
- end
-
- it "disallows --deployment --path --system" do
- bundle "install --deployment --path . --system"
- expect(out).to include("You have specified both --path")
- expect(out).to include("as well as --system")
- expect(out).to include("Please choose only one option")
- expect(exitstatus).to eq(15) if exitstatus
- end
-
- it "works after you try to deploy without a lock" do
- bundle "install --deployment"
- bundle :install
- expect(exitstatus).to eq(0) if exitstatus
- expect(the_bundle).to include_gems "rack 1.0"
+ it "fails without a lockfile and says that frozen requires a lock" do
+ bundle_config "frozen true"
+ bundle "install", raise_on_error: false
+ expect(err).to include("The frozen setting requires a lockfile")
end
it "still works if you are not in the app directory and specify --gemfile" do
bundle "install"
- Dir.chdir tmp
- simulate_new_machine
- bundle "install --gemfile #{tmp}/bundled_app/Gemfile --deployment"
- Dir.chdir bundled_app
- expect(the_bundle).to include_gems "rack 1.0"
+ pristine_system_gems
+ bundle_config "deployment true"
+ bundle_config "path vendor/bundle"
+ bundle "install --gemfile #{tmp}/bundled_app/Gemfile", dir: tmp
+ expect(the_bundle).to include_gems "myrack 1.0"
end
it "works if you exclude a group with a git gem" do
build_git "foo"
gemfile <<-G
+ source "https://gem.repo1"
group :test do
gem "foo", :git => "#{lib_path("foo-1.0")}"
end
G
bundle :install
- bundle "install --deployment --without test"
- expect(exitstatus).to eq(0) if exitstatus
+ bundle_config "deployment true"
+ bundle_config "without test"
+ bundle :install
end
- it "works when you bundle exec bundle", :ruby_repo do
+ it "works when you bundle exec bundle" do
+ skip "doesn't find bundle" if Gem.win_platform?
+
+ bundle :install
+ bundle_config "deployment true"
bundle :install
- bundle "install --deployment"
- bundle "exec bundle check"
- expect(exitstatus).to eq(0) if exitstatus
+ 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")
+ build_lib "foo", path: lib_path("nested/foo")
+ build_lib "bar", path: lib_path("nested/bar")
gemfile <<-G
+ source "https://gem.repo1"
gem "foo", "1.0", :path => "#{lib_path("nested")}"
gem "bar", :path => "#{lib_path("nested")}"
G
- bundle! :install
- bundle! "install --deployment"
+ bundle :install
+ bundle_config "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 "https://gem.repo1"
+ gem "foo", :path => "#{lib_path("nested/foo")}"
+ gem "foo", :path => "#{lib_path("nested/foo")}"
+ G
+
+ bundle :install
+ bundle_config "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)
+ install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true)
source "http://user:pass@localgemserver.test/"
- gem "rack-obama", ">= 1.0"
+ gem "myrack-obama", ">= 1.0"
G
- bundle "install --deployment", :artifice => "endpoint_strict_basic_authentication"
-
- expect(exitstatus).to eq(0) if exitstatus
+ bundle_config "deployment true"
+ bundle :install, artifice: "endpoint_strict_basic_authentication"
end
it "works with sources given by a block" do
install_gemfile <<-G
- source "file://#{gem_repo1}" do
- gem "rack"
+ source "https://gem.repo1"
+ source "https://gem.repo1" do
+ gem "myrack"
end
G
- bundle "install --deployment"
+ bundle_config "deployment true"
+ bundle :install
- expect(exitstatus).to eq(0) if exitstatus
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 1.0"
+ end
+
+ context "when replacing a host with the same host with credentials" do
+ before do
+ bundle_config "path vendor/bundle"
+ bundle "install"
+ gemfile <<-G
+ source "http://user_name:password@localgemserver.test/"
+ gem "myrack"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ myrack
+ G
+
+ bundle_config "deployment true"
+ end
+
+ it "allows the replace" do
+ bundle :install
+
+ expect(out).to match(/Bundle complete!/)
+ end
end
describe "with an existing lockfile" do
@@ -111,189 +141,351 @@ RSpec.describe "install with --deployment or --frozen" do
bundle "install"
end
- it "works with the --deployment flag if you didn't change anything" do
- bundle "install --deployment"
- expect(exitstatus).to eq(0) if exitstatus
+ it "installs gems by default to vendor/bundle" do
+ bundle_config "deployment true"
+ expect do
+ bundle "install"
+ end.not_to change { bundled_app_lock.mtime }
+ expect(out).to include("vendor/bundle")
+ end
+
+ it "installs gems to custom path if specified" do
+ bundle_config "path vendor/bundle2"
+ bundle_config "deployment true"
+ bundle "install"
+ expect(out).to include("vendor/bundle2")
end
- it "works with the --frozen flag if you didn't change anything" do
- bundle "install --frozen"
- expect(exitstatus).to eq(0) if exitstatus
+ it "installs gems to custom path if specified, even when configured through ENV" do
+ bundle_config "deployment true"
+ bundle "install", env: { "BUNDLE_PATH" => "vendor/bundle2" }
+ expect(out).to include("vendor/bundle2")
end
- it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do
+ it "works with the `frozen` setting" do
+ bundle_config "frozen true"
+ expect do
+ bundle "install"
+ end.not_to change { bundled_app_lock.mtime }
+ end
+
+ it "works with BUNDLE_FROZEN if you didn't change anything" do
+ expect do
+ bundle :install, env: { "BUNDLE_FROZEN" => "true" }
+ end.not_to change { bundled_app_lock.mtime }
+ end
+
+ it "explodes with the `deployment` setting if you make a change and don't check in the lockfile" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-obama"
G
- bundle "install --deployment"
- expect(out).to include("deployment mode")
- expect(out).to include("You have added to the Gemfile")
- expect(out).to include("* rack-obama")
- expect(out).not_to include("You have deleted from the Gemfile")
- expect(out).not_to include("You have changed in the Gemfile")
+ bundle_config "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("* myrack-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 "https://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 "path .bundle"
+ bundle_config "without development"
+ bundle_config "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 "https://gem.repo2"
+
+ source "https://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 lockfile is missing \"bar\", but 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 "https://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 "path .bundle"
+ bundle_config "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://#{gem_repo1}"
- gem "rack"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-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("* myrack-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 "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-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("* myrack-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("deployment mode")
- expect(out).to include("You have added to the Gemfile")
- expect(out).to include("* rack-obama")
- expect(out).not_to include("You have deleted from the Gemfile")
- expect(out).not_to include("You have changed in the Gemfile")
+ 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://#{gem_repo1}"
- gem "rack"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-obama"
G
ENV["BUNDLE_FROZEN"] = "false"
+ ENV["BUNDLE_DEPLOYMENT"] = "false"
bundle "install"
- expect(out).not_to include("deployment mode")
+ 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")
+ expect(out).not_to include("* myrack-obama")
end
- it "explodes with the --frozen flag if you make a change and don't check in the lockfile" do
+ it "explodes if you replace a gem and don't check in the lockfile" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "rack-obama", "1.1"
+ source "https://gem.repo1"
+ gem "activesupport"
G
- bundle "install --frozen"
- expect(out).to include("deployment mode")
- expect(out).to include("You have added to the Gemfile")
- expect(out).to include("* rack-obama (= 1.1)")
- expect(out).not_to include("You have deleted from the Gemfile")
- expect(out).not_to include("You have changed in the Gemfile")
+ bundle_config "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* myrack")
+ expect(err).not_to include("You have changed in the Gemfile")
end
it "explodes if you remove a gem and don't check in the lockfile" do
+ gemfile 'source "https://gem.repo1"'
+
+ bundle_config "deployment true"
+ bundle :install, raise_on_error: false
+ expect(err).to include("Some dependencies were deleted")
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have deleted from the Gemfile:\n* myrack")
+ expect(err).not_to include("You have changed in the Gemfile")
+ end
+
+ it "explodes if you add a source" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "activesupport"
+ source "https://gem.repo1"
+ gem "myrack", :git => "git://hubz.com"
G
- bundle "install --deployment"
- expect(out).to include("deployment mode")
- expect(out).to include("You have added to the Gemfile:\n* activesupport\n\n")
- expect(out).to include("You have deleted from the Gemfile:\n* rack")
- expect(out).not_to include("You have changed in the Gemfile")
+ bundle_config "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* myrack from `no specified source` to `git://hubz.com`")
end
- it "explodes if you add a source" do
+ it "explodes if you change a source from git to the default" do
+ build_git "myrack"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-1.0")}"
+ G
+
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "git://hubz.com"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "install --deployment"
- expect(out).to include("deployment mode")
- expect(out).to include("You have added to the Gemfile:\n* source: git://hubz.com (at master)")
- expect(out).not_to include("You have changed in the Gemfile")
+ bundle_config "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* myrack from `#{lib_path("myrack-1.0")}` to `no specified source`")
end
- it "explodes if you unpin a source" do
- build_git "rack"
+ it "explodes if you change a source from git to the default, in presence of other git sources" do
+ build_lib "foo", path: lib_path("myrack/foo")
+ build_git "myrack", path: lib_path("myrack")
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-1.0")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack")}"
+ gem "foo", :git => "#{lib_path("myrack")}"
G
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "foo", :git => "#{lib_path("myrack")}"
G
- bundle "install --deployment"
- expect(out).to include("deployment mode")
- expect(out).to include("You have deleted from the Gemfile:\n* source: #{lib_path("rack-1.0")} (at master@#{revision_for(lib_path("rack-1.0"))[0..6]}")
- expect(out).not_to include("You have added to the Gemfile")
- expect(out).not_to include("You have changed in the Gemfile")
+ bundle_config "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* myrack from `#{lib_path("myrack")}` 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 "explodes if you unpin a source, leaving it pinned somewhere else" do
- build_lib "foo", :path => lib_path("rack/foo")
- build_git "rack", :path => lib_path("rack")
+ it "explodes if you change a source from path to git" do
+ build_git "myrack", path: lib_path("myrack")
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack")}"
- gem "foo", :git => "#{lib_path("rack")}"
+ source "https://gem.repo1"
+ gem "myrack", :path => "#{lib_path("myrack")}"
G
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "foo", :git => "#{lib_path("rack")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "https:/my-git-repo-for-myrack"
G
- bundle "install --deployment"
- expect(out).to include("deployment mode")
- expect(out).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `#{lib_path("rack")} (at master@#{revision_for(lib_path("rack"))[0..6]})`")
- expect(out).not_to include("You have added to the Gemfile")
- expect(out).not_to include("You have deleted from the Gemfile")
+ bundle_config "frozen true"
+ bundle :install, raise_on_error: false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have changed in the Gemfile:\n* myrack from `#{lib_path("myrack")}` to `https:/my-git-repo-for-myrack`")
+ 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 "install --deployment"
+ bundle :lock
+
+ bundle_config "deployment true"
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0.0"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
+ gem "myrack-obama"
G
- expect(the_bundle).not_to include_gems "rack 1.0.0"
- expect(err).to include strip_whitespace(<<-E).strip
-The dependencies in your gemfile changed
+ run "require 'myrack'", raise_on_error: false
+ expect(err).to include <<~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 added to the Gemfile:
+ * myrack (= 1.0.0)
+ * myrack-obama
-You have deleted from the Gemfile:
-* rack
+ You have deleted from the Gemfile:
+ * myrack
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
+ build_lib "foo", path: lib_path("foo")
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo")}"
G
- bundle! :install
+ bundle :install
expect(the_bundle).to include_gems "foo 1.0"
- bundle! "package --all"
+
+ bundle :cache
expect(bundled_app("vendor/cache/foo")).to be_directory
- bundle! "install --local"
- expect(out).to include("Using foo 1.0 from source at")
- expect(out).to include("vendor/cache/foo")
+ bundle "install --local"
+ expect(out).to include("Updating files in vendor/cache")
- simulate_new_machine
- bundle! "install --deployment --verbose"
- expect(out).not_to include("You are trying to install in deployment mode after changing your Gemfile")
+ pristine_system_gems
+ bundle_config "deployment true"
+ bundle "install --verbose"
+ expect(out).not_to include("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("Using foo 1.0 from source at")
expect(out).to include("vendor/cache/foo")
expect(the_bundle).to include_gems "foo 1.0"
end
diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb
index 738b2cf1bd..32ca455439 100644
--- a/spec/bundler/install/failure_spec.rb
+++ b/spec/bundler/install/failure_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install" do
context "installing a gem fails" do
- it "prints out why that gem was being installed" 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"
@@ -15,13 +14,13 @@ RSpec.describe "bundle install" do
end
end
- install_gemfile <<-G
- source "file:#{gem_repo2}"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
gem "rails"
G
- expect(out).to end_with(<<-M.strip)
+ 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.
-Make sure that `gem install activesupport -v '2.3.2'` succeeds before bundling.
In Gemfile:
rails was resolved to 2.3.2, which depends on
@@ -29,5 +28,58 @@ In Gemfile:
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 "https://gem.repo4"
+ gem "a"
+ G
+
+ expect(default_bundle_path("cache", "a-1.0.gem")).not_to exist
+ end
+ end
+ end
+
+ context "when lockfile dependencies don't match the gemspec" do
+ before do
+ build_repo4 do
+ build_gem "myrack", "1.0.0" do |s|
+ s.add_dependency "myrack-test", "~> 1.0"
+ end
+
+ build_gem "myrack-test", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "myrack"
+ G
+
+ # First install to generate lockfile
+ bundle :install
+
+ # Manually edit lockfile to have incorrect dependencies
+ lockfile_content = File.read(bundled_app_lock)
+ # Remove the myrack-test dependency from myrack
+ lockfile_content.gsub!(/^ myrack \(1\.0\.0\)\n myrack-test \(~> 1\.0\)\n/, " myrack (1.0.0)\n")
+ File.write(bundled_app_lock, lockfile_content)
+ end
+
+ it "reports the mismatch with detailed information" do
+ bundle :install, raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" }
+
+ expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack-1.0.0")
+ expect(err).to include("myrack-test: gemspec specifies ~> 1.0, not in lockfile")
+ expect(err).to include("Please run `bundle install` to regenerate the lockfile.")
+ end
end
end
diff --git a/spec/bundler/install/force_spec.rb b/spec/bundler/install/force_spec.rb
index dc4956a7ae..e0f6fb6364 100644
--- a/spec/bundler/install/force_spec.rb
+++ b/spec/bundler/install/force_spec.rb
@@ -1,36 +1,31 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install" do
- describe "with --force" do
- before :each do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- end
+ before :each do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
+ shared_examples_for "an option to force reinstalling gems" do
it "re-installs installed gems" do
- rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb")
+ myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb")
- bundle "install"
- rack_lib.open("w") {|f| f.write("blah blah blah") }
- bundle "install --force"
+ bundle :install
+ myrack_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle :install, flag => true
- expect(exitstatus).to eq(0) if exitstatus
- expect(out).to include "Using bundler"
- 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"
+ expect(out).to include "Installing myrack 1.0.0"
+ expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "works on first bundle install" do
- bundle "install --force"
+ bundle :install, flag => true
- expect(exitstatus).to eq(0) if exitstatus
- expect(out).to include "Using bundler"
- expect(out).to include "Installing rack 1.0.0"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(out).to include "Installing myrack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
context "with a git gem" do
@@ -38,6 +33,7 @@ RSpec.describe "bundle install" do
before do
gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
end
@@ -45,23 +41,31 @@ RSpec.describe "bundle install" do
it "re-installs installed gems" do
foo_lib = default_bundle_path("bundler/gems/foo-1.0-#{ref}/lib/foo.rb")
- bundle! "install"
+ bundle :install
foo_lib.open("w") {|f| f.write("blah blah blah") }
- bundle! "install --force"
+ bundle :install, flag => true
- expect(out).to include "Using bundler"
- expect(out).to include "Using foo 1.0 from #{lib_path("foo-1.0")} (at master@#{ref[0, 7]})"
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 --force"
+ bundle :install, flag => true
- expect(out).to include "Using bundler"
- expect(out).to include "Using foo 1.0 from #{lib_path("foo-1.0")} (at master@#{ref[0, 7]})"
expect(the_bundle).to include_gems "foo 1.0"
end
end
end
+
+ describe "with --force" do
+ it_behaves_like "an option to force reinstalling gems" do
+ let(:flag) { "force" }
+ end
+ end
+
+ describe "with --redownload" do
+ it_behaves_like "an option to force reinstalling gems" do
+ let(:flag) { "redownload" }
+ end
+ end
end
diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb
index f02223d34d..3afa4f5daa 100644
--- a/spec/bundler/install/gemfile/eval_gemfile_spec.rb
+++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install with gemfile that uses eval_gemfile" do
before do
- build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s|
+ build_lib("gunks", path: bundled_app("gems/gunks")) do |s|
s.name = "gunks"
s.version = "0.0.1"
end
@@ -11,43 +10,83 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do
context "eval-ed Gemfile points to an internal gemspec" do
before do
- create_file "Gemfile-other", <<-G
+ gemfile "Gemfile-other", <<-G
+ source "https://gem.repo1"
gemspec :path => 'gems/gunks'
G
end
it "installs the gemspec specified gem" do
install_gemfile <<-G
+ source "https://gem.repo1"
eval_gemfile 'Gemfile-other'
G
expect(out).to include("Resolving dependencies")
- expect(out).to include("Using gunks 0.0.1 from source at `gems/gunks`")
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
+
+ gemfile bundled_app("gems/Gemfile"), <<-G
+ source "https://gem.repo2"
+
+ gemspec :path => "\#{__dir__}/gunks"
+
+ source "https://gem.repo2" do
+ gem "zip-zip"
+ end
+ G
+ end
+
+ it "installs and finds gems correctly" do
+ install_gemfile <<-G
+ source "https://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 => "gems/a")
- create_file "nested/Gemfile-nested", <<-G
+ build_lib("a", path: bundled_app("gems/a"))
+ gemfile bundled_app("nested/Gemfile-nested"), <<-G
+ source "https://gem.repo1"
gem "a", :path => "../gems/a"
G
gemfile <<-G
+ source "https://gem.repo1"
eval_gemfile "nested/Gemfile-nested"
G
end
it "installs the path gem" do
- bundle! :install
+ 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" do
- bundle! :install
- bundle! "install --deployment"
+ it "bundles with deployment mode configured" do
+ bundle :install
+ bundle_config "deployment true"
+ bundle :install
end
end
@@ -56,12 +95,28 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do
it "installs the gemspec specified gem" do
install_gemfile <<-G
+ source "https://gem.repo1"
eval_gemfile 'other/Gemfile-other'
gemspec :path => 'gems/gunks'
G
expect(out).to include("Resolving dependencies")
- expect(out).to include("Using gunks 0.0.1 from source at `gems/gunks`")
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
+ gemfile "other/Gemfile-other", "gem 'myrack'"
+ gemfile "other/Gemfile", "eval_gemfile 'Gemfile-other'"
+ gemfile "Gemfile-alt", <<-G
+ source "https://gem.repo1"
+ eval_gemfile "other/Gemfile"
+ G
+ install_gemfile "eval_gemfile File.expand_path('Gemfile-alt')"
+
+ expect(the_bundle).to include_gem "myrack 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..bcc1f36823
--- /dev/null
+++ b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb
@@ -0,0 +1,136 @@
+# 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")
+
+ build_gem("platform_specific") do |s|
+ s.platform = 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")
+
+ build_gem("platform_specific_forced") do |s|
+ s.platform = Bundler.local_platform
+ end
+ end
+ end
+
+ it "pulls the pure ruby variant of the given gem" do
+ install_gemfile <<-G
+ source "https://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 ruby"
+ expect(the_bundle).to include_gems "platform_specific 1.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 "https://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 ruby"
+ expect(the_bundle).to include_gems "platform_specific 1.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_dependency "platform_specific" }
+
+ build_gem("platform_specific")
+
+ build_gem("platform_specific") do |s|
+ s.platform = Bundler.local_platform
+ end
+ end
+ end
+
+ it "still pulls the ruby variant" do
+ install_gemfile <<-G
+ source "https://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 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_dependency "platform_specific"
+ end
+
+ build_gem("depends_on_platform_specific") do |s|
+ s.add_dependency "platform_specific"
+ s.platform = Bundler.local_platform
+ end
+
+ build_gem("platform_specific")
+
+ build_gem("platform_specific") do |s|
+ s.platform = Bundler.local_platform
+ end
+ end
+ end
+
+ it "ignores ruby variants for the transitive dependencies" do
+ install_gemfile <<-G, env: { "DEBUG_RESOLVER" => "true" }
+ source "https://gem.repo4"
+
+ gem "depends_on_platform_specific", :force_ruby_platform => true
+ G
+
+ expect(the_bundle).to include_gems "depends_on_platform_specific 1.0 ruby"
+ expect(the_bundle).to include_gems "platform_specific 1.0 #{Bundler.local_platform}"
+ end
+
+ it "reinstalls the ruby variant when a platform specific variant is already installed, the lockile has only ruby platform, and :force_ruby_platform is used in the Gemfile" do
+ skip "Can't simulate platform reliably on JRuby, installing a platform specific gem fails to activate io-wait because only the -java version is present, and we're simulating a different platform" if RUBY_ENGINE == "jruby"
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4
+ specs:
+ platform_specific (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86-darwin-100" do
+ system_gems "platform_specific-1.0-x86-darwin-100", path: default_bundle_path
+
+ install_gemfile <<-G, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, artifice: "compact_index"
+ source "https://gem.repo4"
+
+ gem "platform_specific", :force_ruby_platform => true
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 ruby"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb
index 1ea613c9d2..e51fc9247d 100644
--- a/spec/bundler/install/gemfile/gemspec_spec.rb
+++ b/spec/bundler/install/gemfile/gemspec_spec.rb
@@ -1,211 +1,216 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install from an existing gemspec" do
before(:each) do
- build_gem "bar", :to_system => true
- build_gem "bar-dev", :to_system => true
+ build_repo2 do
+ build_gem "bar"
+ build_gem "bar-dev"
+ end
end
it "should install runtime and development dependencies" do
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ build_lib("foo", path: tmp("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://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}'
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("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
+ 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|
+ build_lib("foo", path: tmp("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")
+ FileUtils.mv tmp("foo", "foo.gemspec"), tmp("foo", ".gemspec")
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}'
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("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
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development
end
it "should handle a list of requirements" do
- build_gem "baz", "1.0", :to_system => true
- build_gem "baz", "1.1", :to_system => true
+ update_repo2 do
+ build_gem "baz", "1.0"
+ build_gem "baz", "1.1"
+ end
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ build_lib("foo", path: tmp("foo")) do |s|
s.write("Gemfile", "source :rubygems\ngemspec")
s.add_dependency "baz", ">= 1.0", "< 1.1"
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}'
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("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)
+ build_lib("foo", path: tmp("foo"), gemspec: false)
- error = install_gemfile(<<-G)
- source "file://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}'
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("foo")}'
G
- expect(error).to match(/There are no gemspecs at #{tmp.join('foo')}/)
+ expect(err).to match(/There are no gemspecs at #{tmp("foo")}/)
end
it "should raise if there are too many gemspecs available" do
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ build_lib("foo", path: tmp("foo")) do |s|
s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby)
end
- error = install_gemfile(<<-G)
- source "file://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}'
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("foo")}'
G
- expect(error).to match(/There are multiple gemspecs at #{tmp.join('foo')}/)
+ expect(err).to match(/There are multiple gemspecs at #{tmp("foo")}/)
end
it "should pick a specific gemspec" do
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ build_lib("foo", path: tmp("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://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("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
+ 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|
+ build_lib("foo", path: tmp("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://#{gem_repo2}"
- gemspec :path => '#{tmp.join("foo")}', :name => 'foo', :development_group => :dev
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("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
+ 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://#{gem_repo1}'\ngemspec")
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.write("Gemfile", "source 'https://gem.repo1'\ngemspec")
s.add_dependency "actionpack", "=2.3.2"
- s.add_development_dependency "rake", "=10.0.2"
+ s.add_development_dependency "rake", rake_version
end
- Dir.chdir(tmp.join("foo")) do
- bundle "install"
- # 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).
- output = bundle("install --deployment")
- 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(/install in deployment mode after changing/)
- end
+ bundle "install", dir: tmp("foo"), artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ # 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 "deployment true"
+ output = bundle("install", dir: tmp("foo"), artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s })
+ 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"
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.add_dependency "myrack"
end
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gemspec :path => '#{tmp.join("foo")}'
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gemspec :path => '#{tmp("foo")}'
G
- bundle! "install", :verbose => true
- expect(out).to include("Found no changes, using resolution from the lockfile")
+ 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
+ simulate_platform "java" do
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.add_dependency "myrack"
+ s.add_development_dependency "thin"
+ end
- build_lib("foo", :path => tmp.join("foo")) do |s|
- s.add_dependency "rack"
- s.add_development_dependency "thin"
- end
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gemspec :path => '#{tmp("foo")}'
+ G
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gemspec :path => '#{tmp.join("foo")}'
- G
+ bundle "install", verbose: true
- bundle! "install", :verbose => true
- expect(out).to include("Found no changes, using resolution from the lockfile")
+ message = "Found no changes, using resolution from the lockfile"
+ expect(out.scan(message).size).to eq(1)
+ end
end
- it "should match a lockfile on non-ruby platforms with a transitive platform dependency" do
- simulate_platform java
- simulate_ruby_engine "jruby"
-
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby_only do
+ build_lib("foo", path: tmp("foo")) do |s|
s.add_dependency "platform_specific"
end
- install_gem "platform_specific-1.0-java"
+ system_gems "platform_specific-1.0-java", path: default_bundle_path
- install_gemfile! <<-G
- gemspec :path => '#{tmp.join("foo")}'
+ install_gemfile <<-G
+ gemspec :path => '#{tmp("foo")}'
G
- bundle! "update --bundler", :verbose => true
- expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA"
+ 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")}'"
+ build_lib("foo", path: tmp("foo"))
+ File.open(tmp("foo/foo.gemspec"), "w") do |s|
+ s.write "raise 'ahh' unless Dir.pwd == '#{tmp("foo")}'"
end
- install_gemfile <<-G
- gemspec :path => '#{tmp.join("foo")}'
+ install_gemfile <<-G, raise_on_error: false
+ gemspec :path => '#{tmp("foo")}'
G
- expect(@err).not_to match(/ahh/)
+ expect(stdboth).not_to include("ahh")
end
it "allows the gemspec to activate other gems" do
- # see https://github.com/bundler/bundler/issues/5409
+ 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)
+ system_gems %w[myrack-1.0.0 myrack-0.9.1 myrack-obama-1.0]
- build_lib("foo", :path => bundled_app)
+ 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' }"
+ f.write "#{gemspec.strip}.tap { gem 'myrack-obama'; require 'myrack/obama' }"
end
- install_gemfile! <<-G
+ install_gemfile <<-G
+ source "https://gem.repo1"
gemspec
G
@@ -213,69 +218,104 @@ RSpec.describe "bundle install from an existing gemspec" do
end
it "allows conflicts" do
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ build_lib("foo", path: tmp("foo")) do |s|
s.version = "1.0.0"
s.add_dependency "bar", "= 1.0.0"
end
- build_gem "deps", :to_system => true do |s|
+ build_gem "deps", to_bundle: true do |s|
s.add_dependency "foo", "= 0.0.1"
end
- build_gem "foo", "0.0.1", :to_system => true
+ build_gem "foo", "0.0.1", to_bundle: true
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "deps"
- gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gems "foo 1.0.0"
end
- it "does not break Gem.finish_resolve with conflicts", :rubygems => ">= 2" do
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ it "does not break Gem.finish_resolve with conflicts" do
+ build_lib("foo", path: tmp("foo")) do |s|
s.version = "1.0.0"
s.add_dependency "bar", "= 1.0.0"
end
- build_repo2 do
+ 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://#{gem_repo2}"
+ install_gemfile <<-G
+ source "https://gem.repo2"
gem "deps"
- gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ gemspec :path => '#{tmp("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 "does not make Gem.try_activate warn when local gem has extensions" do
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_c_extension
+ end
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("foo")}'
G
expect(the_bundle).to include_gems "foo 1.0.0"
- run! "Gem.finish_resolve; puts 'WIN'"
+ run "Gem.try_activate('irb/lc/es/error.rb'); puts 'WIN'"
expect(out).to eq("WIN")
+ expect(err).to be_empty
+ end
+
+ it "handles downgrades" do
+ build_lib "omg", "2.0", path: lib_path("omg")
+
+ install_gemfile <<-G
+ source "https://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|
+ build_lib "cocoapods", path: bundled_app do |s|
s.add_dependency "activesupport", ">= 1"
end
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G
+ source "https://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|
+ build_lib "cocoapods", path: bundled_app do |s|
s.add_dependency "activesupport", ">= 1.0.1"
end
- bundle "install --deployment"
+ bundle_config "deployment true"
+ bundle :install, raise_on_error: false
- expect(out).to include("changed")
+ expect(err).to include("changed")
end
end
end
@@ -283,119 +323,93 @@ RSpec.describe "bundle install from an existing gemspec" do
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"
+ build_lib "source_conflict", path: bundled_app do |s|
+ s.add_dependency "myrack_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
+ build_lib "myrack_middleware", "1.0", path: bundled_app("myrack_middleware") do |s|
+ s.add_dependency "myrack", "1.0" # anything other than 0.9.1
end
end
it "should install the child gemspec's deps" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gemspec
G
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
context "with a lockfile and some missing dependencies" do
let(:source_uri) { "http://localgemserver.test" }
- context "previously bundled for Ruby" do
- let(:platform) { "ruby" }
- let(:explicit_platform) { false }
-
- before do
- build_lib("foo", :path => tmp.join("foo")) do |s|
- s.add_dependency "rack", "=1.0.0"
- end
-
- if explicit_platform
- create_file(
- tmp.join("foo", "foo-#{platform}.gemspec"),
- build_spec("foo", "1.0", platform) do
- dep "rack", "=1.0.0"
- @spec.authors = "authors"
- @spec.summary = "summary"
- end.first.to_ruby
- )
- 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
+ before do
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.add_dependency "myrack", "=1.0.0"
end
- context "using JRuby with explicit platform" do
- let(:platform) { "java" }
- let(:explicit_platform) { true }
+ gemfile <<-G
+ source "#{source_uri}"
+ gemspec :path => "../foo"
+ G
- it "should install" do
- simulate_ruby_engine "jruby" do
- simulate_platform "java" 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
- end
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
end
- context "using JRuby" do
- let(:platform) { "java" }
+ lockfile <<-L
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+ myrack (= 1.0.0)
+
+ GEM
+ remote: #{source_uri}
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
- it "should install" do
- simulate_ruby_engine "jruby" do
- simulate_platform "java" 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
- end
+ context "using JRuby with explicit platform", :jruby_only do
+ before do
+ create_file(
+ tmp("foo", "foo-java.gemspec"),
+ build_spec("foo", "1.0", "java") do
+ dep "myrack", "=1.0.0"
+ @spec.authors = "authors"
+ @spec.summary = "summary"
+ end.first.to_ruby
+ )
end
- context "using Windows" do
- it "should install" do
- simulate_windows 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" do
+ results = bundle "install", artifice: "endpoint"
+ expect(results).to include("Installing myrack 1.0.0")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
- context "bundled for ruby and jruby" do
+ it "should install", :jruby do
+ results = bundle "install", artifice: "endpoint"
+ expect(results).to include("Installing myrack 1.0.0")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ context "bundled for multiple platforms" do
let(:platform_specific_type) { :runtime }
let(:dependency) { "platform_specific" }
before do
@@ -405,36 +419,49 @@ RSpec.describe "bundle install from an existing gemspec" do
end
end
- build_lib "foo", :path => "." do |s|
- if platform_specific_type == :runtime
+ build_lib "foo", path: bundled_app do |s|
+ case platform_specific_type
+ when :runtime
s.add_runtime_dependency dependency
- elsif platform_specific_type == :development
+ when :development
s.add_development_dependency dependency
else
- raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
+ raise ArgumentError, "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
end
end
- %w(ruby jruby).each do |platform|
- simulate_platform(platform) do
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gemspec
- G
- end
- end
+ gemfile <<-G
+ source "https://gem.repo2"
+ gemspec
+ G
+
+ bundle_config "force_ruby_platform true"
+ bundle "install"
+
+ simulate_new_machine
+ simulate_platform("jruby") { bundle "install" }
+ expect(lockfile).to include("platform_specific (1.0-java)")
+ simulate_platform("x64-mingw-ucrt") { bundle "install" }
end
context "on ruby" do
before do
- simulate_platform("ruby")
+ bundle_config "force_ruby_platform true"
bundle :install
end
context "as a runtime dependency" do
- it "keeps java 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)
+ it "keeps all platform dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo2, "platform_specific", "1.0"
+ c.checksum gem_repo2, "platform_specific", "1.0", "java"
+ c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt"
+ end
+
+ expect(lockfile).to eq <<~L
PATH
remote: .
specs:
@@ -442,20 +469,22 @@ RSpec.describe "bundle install from an existing gemspec" do
platform_specific
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
platform_specific (1.0)
platform_specific (1.0-java)
+ platform_specific (1.0-x64-mingw-ucrt)
PLATFORMS
java
ruby
+ x64-mingw-ucrt
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
end
@@ -463,30 +492,40 @@ RSpec.describe "bundle install from an existing gemspec" do
context "as a development dependency" do
let(:platform_specific_type) { :development }
- it "keeps java 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)
+ it "keeps all platform dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo2, "platform_specific", "1.0"
+ c.checksum gem_repo2, "platform_specific", "1.0", "java"
+ c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt"
+ end
+
+ expect(lockfile).to eq <<~L
PATH
remote: .
specs:
foo (1.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
platform_specific (1.0)
platform_specific (1.0-java)
+ platform_specific (1.0-x64-mingw-ucrt)
PLATFORMS
java
ruby
+ x64-mingw-ucrt
DEPENDENCIES
foo!
platform_specific
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
end
@@ -495,32 +534,43 @@ RSpec.describe "bundle install from an existing gemspec" do
let(:platform_specific_type) { :development }
let(:dependency) { "indirect_platform_specific" }
- it "keeps java 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)
+ 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"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo2, "indirect_platform_specific", "1.0"
+ c.checksum gem_repo2, "platform_specific", "1.0"
+ c.checksum gem_repo2, "platform_specific", "1.0", "java"
+ c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt"
+ end
+
+ expect(lockfile).to eq <<~L
PATH
remote: .
specs:
foo (1.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
indirect_platform_specific (1.0)
platform_specific
platform_specific (1.0)
platform_specific (1.0-java)
+ platform_specific (1.0-x64-mingw-ucrt)
PLATFORMS
java
ruby
+ x64-mingw-ucrt
DEPENDENCIES
foo!
indirect_platform_specific
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
end
@@ -530,34 +580,158 @@ RSpec.describe "bundle install from an existing gemspec" do
context "with multiple platforms" do
before do
- build_lib("foo", :path => tmp.join("foo")) do |s|
+ build_lib("foo", path: tmp("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
+ s.add_development_dependency "myrack"
+ s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "myrack", "1.0.0" }.first.to_ruby
end
end
it "installs the ruby platform gemspec" do
- simulate_platform "ruby"
+ bundle_config "force_ruby_platform true"
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
- expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0"
+ expect(the_bundle).to include_gems "foo 1.0.0", "myrack 1.0.0"
end
- it "installs the ruby platform gemspec and skips dev deps with --without development" do
- simulate_platform "ruby"
+ it "installs the ruby platform gemspec and skips dev deps with `without development` configured" do
+ bundle_config "force_ruby_platform true"
- install_gemfile! <<-G, :without => "development"
- source "file://#{gem_repo1}"
- gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ bundle_config "without development"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gemspec :path => '#{tmp("foo")}', :name => 'foo'
G
expect(the_bundle).to include_gem "foo 1.0.0"
- expect(the_bundle).not_to include_gem "rack"
+ expect(the_bundle).not_to include_gem "myrack"
+ end
+ end
+
+ context "with multiple platforms and resolving for more specific platforms" do
+ before do
+ build_lib("chef", path: tmp("chef")) do |s|
+ s.version = "17.1.17"
+ s.write "chef-universal-mingw-ucrt.gemspec", build_spec("chef", "17.1.17", "universal-mingw-ucrt") {|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-mingw-ucrt"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gemspec :path => "../chef"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "chef", "17.1.17"
+ c.no_checksum "chef", "17.1.17", "universal-mingw-ucrt"
+ c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw-ucrt"
+ end
+
+ initial_lockfile = <<~L
+ PATH
+ remote: ../chef
+ specs:
+ chef (17.1.17)
+ chef (17.1.17-universal-mingw-ucrt)
+ win32-api (~> 1.5.3)
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ win32-api (1.5.3-universal-mingw-ucrt)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby", "x64-mingw-ucrt", "x86-mingw32")}
+
+ DEPENDENCIES
+ chef!
+ #{checksums}
+ 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("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 "https://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
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "activeadmin", "2.9.0"
+ c.checksum gem_repo4, "jruby-openssl", "0.10.7", "java"
+ c.checksum gem_repo4, "railties", "6.1.4"
+ end
+
+ expect(lockfile).to eq <<~L
+ PATH
+ remote: ../activeadmin
+ specs:
+ activeadmin (2.9.0)
+ railties (>= 5.2, < 6.2)
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ jruby-openssl (0.10.7-java)
+ railties (6.1.4)
+
+ PLATFORMS
+ #{lockfile_platforms("java")}
+
+ DEPENDENCIES
+ activeadmin!
+ jruby-openssl
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemspec = tmp("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
index 5868c76570..b2a82caf01 100644
--- a/spec/bundler/install/gemfile/git_spec.rb
+++ b/spec/bundler/install/gemfile/git_spec.rb
@@ -1,22 +1,26 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install with git sources" do
- describe "when floating on master" do
- before :each do
- build_git "foo" do |s|
- s.executables = "foobar"
- end
-
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ describe "when floating on main" do
+ let(:base_gemfile) do
+ <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
G
end
+ let(:install_base_gemfile) do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile base_gemfile
+ end
+
it "fetches gems" do
+ install_base_gemfile
expect(the_bundle).to include_gems("foo 1.0")
run <<-RUBY
@@ -27,11 +31,69 @@ RSpec.describe "bundle install with git sources" do
expect(out).to eq("WIN")
end
+ it "does not (yet?) enforce CHECKSUMS" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+
+ bundle_config "lockfile_checksums true"
+ gemfile base_gemfile
+
+ lockfile <<~L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{revision}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ CHECKSUMS
+ foo (1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle_config "frozen true"
+
+ bundle "install"
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
it "caches the git repo" do
- expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"].size).to eq(1)
+ install_base_gemfile
+ expect(Dir["#{default_cache_path}/git/foo-1.0-*"]).to have_attributes size: 1
+ end
+
+ it "does not write to cache on bundler/setup" do
+ install_base_gemfile
+ FileUtils.rm_r(default_cache_path)
+ ruby "require 'bundler/setup'"
+ expect(default_cache_path).not_to exist
+ end
+
+ it "caches the git repo globally and properly uses the cached repo on the next invocation" do
+ install_base_gemfile
+ pristine_system_gems
+ bundle_config "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
+ install_base_gemfile
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
@@ -41,35 +103,35 @@ RSpec.describe "bundle install with git sources" do
bundle "update foo"
- sha = git.ref_for("master", 11)
- spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec").to_s
- ruby_code = Gem::Specification.load(spec_file).to_ruby
+ sha = git.ref_for("main", 11)
+ spec_file = default_bundle_path("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
+ install_base_gemfile
update_git "foo"
- in_app_root2 do
- install_gemfile bundled_app2("Gemfile"), <<-G
- git "#{lib_path("foo-1.0")}" do
- gem 'foo'
- end
- G
- end
+ install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
+ source "https://gem.repo1"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
- in_app_root do
- run <<-RUBY
- require 'foo'
- puts "fail" if defined?(FOO_PREV_REF)
- RUBY
+ run <<-RUBY
+ require 'foo'
+ puts "fail" if defined?(FOO_PREV_REF)
+ RUBY
- expect(out).to be_empty
- end
+ expect(out).to be_empty
end
it "sets up git gem executables on the path" do
+ install_base_gemfile
bundle "exec foobar"
expect(out).to eq("1.0")
end
@@ -77,32 +139,30 @@ RSpec.describe "bundle install with git sources" do
it "complains if pinned specs don't exist in the git repo" do
build_git "foo"
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}"
G
- expect(out).to include("Source contains 'foo' at: 1.0 ruby")
+ 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" do
- simulate_platform "java"
-
+ 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
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
platforms :jruby do
gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}"
end
G
- expect(out).to include("Source contains 'only_java' at: 1.0 java")
+ 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" do
- simulate_platform "java"
-
+ 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
@@ -112,42 +172,45 @@ RSpec.describe "bundle install with git sources" do
s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec")
end
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
platforms :jruby do
gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}"
end
G
- expect(out).to include("Source contains 'only_java' at: 1.0 java, 1.1 java")
+ 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 "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ install_base_gemfile
+
FileUtils.mv bundled_app, tmp("bundled_app.bck")
- Dir.chdir tmp("bundled_app.bck")
- expect(the_bundle).to include_gems "foo 1.0"
+ 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 "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ install_base_gemfile
+
FileUtils.mv bundled_app, tmp("bundled_app.bck")
- update_git "foo", "1.1", :path => lib_path("foo-1.0")
+ update_git "foo", "1.1", path: lib_path("foo-1.0")
- Dir.chdir tmp("bundled_app.bck")
gemfile tmp("bundled_app.bck/Gemfile"), <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
- gem "rack", "1.0"
+ gem "myrack", "1.0"
G
- bundle "update foo"
+ bundle "update foo", dir: tmp("bundled_app.bck")
- expect(the_bundle).to include_gems "foo 1.1", "rack 1.0"
+ expect(the_bundle).to include_gems "foo 1.1", "myrack 1.0", dir: tmp("bundled_app.bck")
end
end
@@ -155,8 +218,8 @@ RSpec.describe "bundle install with git sources" do
before do
build_git "foo"
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
git "#{lib_path("foo-1.0")}" do
# this page left intentionally blank
@@ -166,7 +229,7 @@ RSpec.describe "bundle install with git sources" do
it "does not explode" do
bundle "install"
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
@@ -179,10 +242,12 @@ RSpec.describe "bundle install with git sources" do
it "works" do
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do
gem "foo"
end
G
+ expect(err).to be_empty
run <<-RUBY
require 'foo'
@@ -194,11 +259,12 @@ RSpec.describe "bundle install with git sources" do
it "works when the revision is a symbol" do
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do
gem "foo"
end
G
- expect(err).to lack_errors
+ expect(err).to be_empty
run <<-RUBY
require 'foo'
@@ -207,17 +273,141 @@ RSpec.describe "bundle install with git sources" do
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 "https://gem.repo1"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ install_gemfile <<-G
+ source "https://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: @remote.path
+ update_git "foo", push: "main"
+
+ install_gemfile <<-G
+ source "https://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 "https://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
+
+ git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", 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 "https://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 "https://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
+
+ git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", 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 "https://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
+ git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0"))
+
+ bundle_config "global_gem_cache true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ # ensure we also git fetch after cloning
+ bundle :update, all: true
+
+ git("ls-remote .", 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 }
- before(:each) do
- update_git("foo", :path => repo, :branch => branch)
- end
it "works" do
+ update_git("foo", path: repo, branch: branch)
+
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{repo}", :branch => #{branch.dump} do
gem "foo"
end
@@ -229,7 +419,12 @@ RSpec.describe "bundle install with git sources" do
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 "https://gem.repo1"
git "#{repo}", :branch => #{branch.dump} do
gem "foo"
end
@@ -242,7 +437,12 @@ RSpec.describe "bundle install with git sources" do
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 "https://gem.repo1"
git "#{repo}", :branch => #{branch.dump} do
gem "foo"
end
@@ -256,12 +456,12 @@ RSpec.describe "bundle install with git sources" do
describe "when specifying a tag" do
let(:tag) { "tag" }
let(:repo) { build_git("foo").path }
- before(:each) do
- update_git("foo", :path => repo, :tag => tag)
- end
it "works" do
+ update_git("foo", path: repo, tag: tag)
+
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{repo}", :tag => #{tag.dump} do
gem "foo"
end
@@ -273,7 +473,12 @@ RSpec.describe "bundle install with git sources" do
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 "https://gem.repo1"
git "#{repo}", :tag => #{tag.dump} do
gem "foo"
end
@@ -286,7 +491,12 @@ RSpec.describe "bundle install with git sources" do
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 "https://gem.repo1"
git "#{repo}", :tag => #{tag.dump} do
gem "foo"
end
@@ -299,186 +509,216 @@ RSpec.describe "bundle install with git sources" do
describe "when specifying local override" do
it "uses the local repository instead of checking a new one out" do
- # We don't generate it because we actually don't need it
- # build_git "rack", "0.8"
-
- build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
- s.write "lib/rack.rb", "puts :LOCAL"
+ build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
+ s.write "lib/myrack.rb", "puts :LOCAL"
end
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
bundle :install
- expect(out).to match(/at #{lib_path('local-rack')}/)
- run "require 'rack'"
+ run "require 'myrack'"
expect(out).to eq("LOCAL")
end
it "chooses the local repository on runtime" do
- build_git "rack", "0.8"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
- update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
- s.write "lib/rack.rb", "puts :LOCAL"
+ update_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
+ s.write "lib/myrack.rb", "puts :LOCAL"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
- run "require 'rack'"
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ run "require 'myrack'"
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"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
- 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"
+ update_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
+ s.write "myrack.gemspec", build_spec("myrack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby
+ s.write "lib/myrack.rb", "puts :LOCAL"
end
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle! %(config local.rack #{lib_path("local-rack")})
- bundle! :install
- run! "require 'rack'"
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ bundle :install
+ run "require 'myrack'"
expect(out).to eq("LOCAL")
end
it "updates specs on runtime" do
system_gems "nokogiri-1.4.2"
- build_git "rack", "0.8"
+ build_git "myrack", "0.8"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- lockfile0 = File.read(bundled_app("Gemfile.lock"))
+ 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|
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
+ update_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
s.add_dependency "nokogiri", "1.4.2"
end
- bundle %(config local.rack #{lib_path("local-rack")})
- run "require 'rack'"
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ run "require 'myrack'"
- lockfile1 = File.read(bundled_app("Gemfile.lock"))
+ lockfile1 = File.read(bundled_app_lock)
expect(lockfile1).not_to eq(lockfile0)
end
it "updates ref on install" do
- build_git "rack", "0.8"
+ build_git "myrack", "0.8"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- lockfile0 = File.read(bundled_app("Gemfile.lock"))
+ 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")
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
+ update_git "myrack", "0.8", path: lib_path("local-myrack")
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
bundle :install
- lockfile1 = File.read(bundled_app("Gemfile.lock"))
+ lockfile1 = File.read(bundled_app_lock)
expect(lockfile1).not_to eq(lockfile0)
end
- it "explodes if given path does not exist on install" do
- build_git "rack", "0.8"
+ it "explodes and gives correct solution if given path does not exist on install" do
+ build_git "myrack", "0.8"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ bundle :install, raise_on_error: false
+ expect(err).to match(/Cannot use local override for myrack-0.8 because #{Regexp.escape(lib_path("local-myrack").to_s)} does not exist/)
+
+ solution = "config unset local.myrack"
+ expect(err).to match(/Run `bundle #{solution}` to remove the local override/)
+
+ bundle solution
bundle :install
- expect(out).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/)
+
+ expect(err).to be_empty
end
- it "explodes 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"))
+ it "explodes and gives correct solution if branch is not given on install" do
+ build_git "myrack", "0.8"
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ bundle :install, raise_on_error: false
+ expect(err).to match(/Cannot use local override for myrack-0.8 at #{Regexp.escape(lib_path("local-myrack").to_s)} because :branch is not specified in Gemfile/)
+
+ solution = "config unset local.myrack"
+ expect(err).to match(/Specify a branch or run `bundle #{solution}` to remove the local override/)
+
+ bundle solution
bundle :install
- expect(out).to match(/cannot use local override/i)
+
+ 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"))
+ build_git "myrack", "0.8"
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}"
G
- bundle %(config local.rack #{lib_path("local-rack")})
- bundle %(config disable_local_branch_check true)
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ 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"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
- update_git "rack", "0.8", :path => lib_path("local-rack"), :branch => "another" do |s|
- s.write "lib/rack.rb", "puts :LOCAL"
+ update_git "myrack", "0.8", path: lib_path("local-myrack"), branch: "another" do |s|
+ s.write "lib/myrack.rb", "puts :LOCAL"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
- bundle :install
- expect(out).to match(/is using branch another but Gemfile specifies master/)
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ 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 "myrack", "0.8"
+
+ build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
+ s.write "lib/myrack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ 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 "myrack", "0.8"
- build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
- s.write "lib/rack.rb", "puts :LOCAL"
+ build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
+ s.write "lib/myrack.rb", "puts :LOCAL"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ bundle %(config set disable_local_revision_check true)
bundle :install
- expect(out).to match(/The Gemfile lock is pointing to revision \w+/)
+ expect(out).to match(/Bundle complete!/)
end
end
@@ -500,75 +740,76 @@ RSpec.describe "bundle install with git sources" do
# end
it "installs from git even if a newer gem is available elsewhere" do
- build_git "rack", "0.8"
+ build_git "myrack", "0.8"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}"
G
- expect(the_bundle).to include_gems "rack 0.8"
+ expect(the_bundle).to include_gems "myrack 0.8"
end
it "installs dependencies from git even if a newer gem is available elsewhere" do
- system_gems "rack-1.0.0"
+ system_gems "myrack-1.0.0"
- build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s|
- s.write "lib/rack.rb", "puts 'WIN OVERRIDE'"
+ build_lib "myrack", "1.0", path: lib_path("nested/bar") do |s|
+ s.write "lib/myrack.rb", "puts 'WIN OVERRIDE'"
end
- build_git "foo", :path => lib_path("nested") do |s|
- s.add_dependency "rack", "= 1.0"
+ build_git "foo", path: lib_path("nested") do |s|
+ s.add_dependency "myrack", "= 1.0"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("nested")}"
G
- run "require 'rack'"
+ run "require 'myrack'"
expect(out).to eq("WIN OVERRIDE")
end
it "correctly unlocks when changing to a git source" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
- build_git "rack", :path => lib_path("rack")
+ build_git "myrack", path: lib_path("myrack")
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0.0", :git => "#{lib_path("rack")}"
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0", :git => "#{lib_path("myrack")}"
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "correctly unlocks when changing to a git source without versions" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- build_git "rack", "1.2", :path => lib_path("rack")
+ build_git "myrack", "1.2", path: lib_path("myrack")
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack")}"
G
- expect(the_bundle).to include_gems "rack 1.2"
+ expect(the_bundle).to include_gems "myrack 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")
+ build_lib "omg", path: lib_path("hi2u/omg")
+ build_lib "hi2u", path: lib_path("hi2u")
install_gemfile <<-G
+ source "https://gem.repo1"
path "#{lib_path("hi2u")}" do
gem "omg"
gem "hi2u"
@@ -585,6 +826,7 @@ RSpec.describe "bundle install with git sources" do
update_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}"
G
@@ -602,7 +844,7 @@ RSpec.describe "bundle install with git sources" do
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
gem "rails", "2.3.2"
G
@@ -612,10 +854,10 @@ RSpec.describe "bundle install with git sources" do
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|
+ 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 # For 1.9
+ $:.unshift Dir.pwd
require 'lib/version'
Gem::Specification.new do |s|
s.name = 'bar'
@@ -627,12 +869,12 @@ RSpec.describe "bundle install with git sources" do
G
end
- build_git "foo", :path => lib_path("foo") do |s|
+ build_git "foo", path: lib_path("foo") do |s|
s.write "bin/foo", ""
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "bar", :git => "#{lib_path("foo")}"
gem "rails", "2.3.2"
G
@@ -641,14 +883,41 @@ RSpec.describe "bundle install with git sources" do
expect(the_bundle).to include_gems "rails 2.3.2"
end
+ it "runs the gemspec in the context of its parent directory, when using local overrides" do
+ build_git "foo", path: lib_path("foo"), gemspec: false do |s|
+ s.write lib_path("foo/lib/foo/version.rb"), %(FOO_VERSION = '1.0')
+ s.write "foo.gemspec", <<-G
+ $:.unshift Dir.pwd
+ require 'lib/foo/version'
+ Gem::Specification.new do |s|
+ s.name = 'foo'
+ s.author = 'no one'
+ s.version = FOO_VERSION
+ s.summary = 'Foo'
+ s.files = Dir["lib/**/*.rb"]
+ end
+ G
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => "https://github.com/gems/foo", branch: "main"
+ G
+
+ bundle %(config set local.foo #{lib_path("foo")})
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ 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|
+ 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 "https://gem.repo1"
gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
G
@@ -656,10 +925,10 @@ RSpec.describe "bundle install with git sources" do
end
it "fakes the gem out if there is no gemspec" do
- build_git "foo", :gemspec => false
+ build_git "foo", gemspec: false
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
gem "rails", "2.3.2"
G
@@ -670,20 +939,22 @@ RSpec.describe "bundle install with git sources" do
it "catches git errors and spits out useful output" do
gemfile <<-G
+ source "https://gem.repo1"
gem "foo", "1.0", :git => "omgomg"
G
- bundle :install
+ bundle :install, raise_on_error: false
- expect(out).to include("Git error:")
+ 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")
+ build_git "foo", path: lib_path("foo space-1.0")
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo space-1.0")}"
G
@@ -694,6 +965,7 @@ RSpec.describe "bundle install with git sources" do
build_git "forced", "1.0"
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("forced-1.0")}" do
gem 'forced'
end
@@ -704,48 +976,50 @@ RSpec.describe "bundle install with git sources" do
s.write "lib/forced.rb", "FORCED = '1.1'"
end
- bundle "update"
+ bundle "update", all: true
expect(the_bundle).to include_gems "forced 1.1"
- Dir.chdir(lib_path("forced-1.0")) do
- `git reset --hard HEAD^`
- end
+ git("reset --hard HEAD^", lib_path("forced-1.0"))
- bundle "update"
+ 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
- Dir.chdir(lib_path("has_submodule-1.0")) do
- sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
- `git commit -m "submodulator"`
- end
+ git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
+ git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
git "#{lib_path("has_submodule-1.0")}" do
gem "has_submodule"
end
G
- expect(out).to match(/could not find gem 'submodule/i)
+ expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository https://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
- Dir.chdir(lib_path("has_submodule-1.0")) do
- sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
- `git commit -m "submodulator"`
- end
+ git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
+ git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("has_submodule-1.0")}", :submodules => true do
gem "has_submodule"
end
@@ -754,10 +1028,33 @@ RSpec.describe "bundle install with git sources" do
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"
+
+ git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
+ git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")
+
+ install_gemfile <<-G
+ source "https://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 "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem "foo"
end
@@ -767,6 +1064,7 @@ RSpec.describe "bundle install with git sources" do
update_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do
gem "foo"
end
@@ -780,14 +1078,15 @@ RSpec.describe "bundle install with git sources" do
expect(out).to eq("WIN")
end
- it "does not to a remote fetch if the revision is cached locally" do
+ it "does not do a remote fetch if the revision is cached locally" do
build_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- FileUtils.rm_rf(lib_path("foo-1.0"))
+ FileUtils.rm_r(lib_path("foo-1.0"))
bundle "install"
expect(out).not_to match(/updating/i)
@@ -797,62 +1096,64 @@ RSpec.describe "bundle install with git sources" do
build_git "foo"
gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
bundle "install"
bundle "install"
- expect(exitstatus).to eq(0) if exitstatus
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
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- expect(exitstatus).to_not eq(0) if exitstatus
- expect(out).to include("Bundler could not install a gem because it " \
+ expect(last_command).to be_failure
+ 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_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")
+ build_git "foo", path: lib_path("nested")
+ build_git "bar", path: lib_path("nested")
- gemfile <<-G
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("nested")}"
gem "bar", :git => "#{lib_path("nested")}"
G
- bundle "install"
- expect(File.read(bundled_app("Gemfile.lock")).scan("GIT").size).to eq(1)
+ 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|
+ 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|
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "bar", :path => "#{lib_path("bar")}"
G
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "bar", :git => "#{lib_path("bar")}"
G
@@ -861,24 +1162,67 @@ RSpec.describe "bundle install with git sources" do
it "doesn't explode when switching Gem to Git source" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack-obama"
- gem "rack", "1.0.0"
+ source "https://gem.repo1"
+ gem "myrack-obama"
+ gem "myrack", "1.0.0"
G
- build_git "rack", "1.0" do |s|
+ build_git "myrack", "1.0" do |s|
s.write "lib/new_file.rb", "puts 'USING GIT'"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack-obama"
- gem "rack", "1.0.0", :git => "#{lib_path("rack-1.0")}"
+ source "https://gem.repo1"
+ gem "myrack-obama"
+ gem "myrack", "1.0.0", :git => "#{lib_path("myrack-1.0")}"
G
run "require 'new_file'"
expect(out).to eq("USING GIT")
end
+
+ it "doesn't explode when removing an explicit exact version from a git gem with dependencies" do
+ build_lib "activesupport", "7.1.4", path: lib_path("rails/activesupport")
+ build_git "rails", "7.1.4", path: lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 7.1.4"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails", "7.1.4", :git => "#{lib_path("rails")}"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails", :git => "#{lib_path("rails")}"
+ G
+
+ expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4"
+ end
+
+ it "doesn't explode when adding an explicit ref to a git gem with dependencies" do
+ lib_root = lib_path("rails")
+
+ build_lib "activesupport", "7.1.4", path: lib_root.join("activesupport")
+ build_git "rails", "7.1.4", path: lib_root do |s|
+ s.add_dependency "activesupport", "= 7.1.4"
+ end
+
+ old_revision = revision_for(lib_root)
+ update_git "rails", "7.1.4", path: lib_root
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails", "7.1.4", :git => "#{lib_root}"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails", :git => "#{lib_root}", :ref => "#{old_revision}"
+ G
+
+ expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4"
+ end
end
describe "bundle install after the remote has been updated" do
@@ -886,17 +1230,16 @@ RSpec.describe "bundle install with git sources" do
build_git "valim"
install_gemfile <<-G
- gem "valim", :git => "file://#{lib_path("valim-1.0")}"
+ source "https://gem.repo1"
+ gem "valim", :git => "#{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"))
- lockfile = File.read(bundled_app("Gemfile.lock"))
- File.open(bundled_app("Gemfile.lock"), "w") do |file|
- file.puts lockfile.gsub(/revision: #{old_revision}/, "revision: #{new_revision}")
- end
+ old_lockfile = File.read(bundled_app_lock)
+ lockfile(bundled_app_lock, old_lockfile.gsub(/revision: #{old_revision}/, "revision: #{new_revision}"))
bundle "install"
@@ -913,32 +1256,43 @@ RSpec.describe "bundle install with git sources" do
revision = revision_for(lib_path("foo-1.0"))
install_gemfile <<-G
- gem "foo", :git => "file://#{lib_path("foo-1.0")}", :ref => "#{revision}"
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{revision}"
G
- bundle "install"
expect(out).to_not match(/Revision.*does not exist/)
- install_gemfile <<-G
- gem "foo", :git => "file://#{lib_path("foo-1.0")}", :ref => "deadbeef"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "deadbeef"
G
- bundle "install"
- expect(out).to include("Revision deadbeef does not exist in the repository")
+ 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 "https://gem.repo1"
+ gem "foo", :git => "#{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 --deployment with git sources" do
+ describe "bundle install with deployment mode configured and git sources" do
it "works" do
- build_git "valim", :path => lib_path("valim")
+ build_git "valim", path: lib_path("valim")
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "valim", "= 1.0", :git => "#{lib_path("valim")}"
G
- simulate_new_machine
+ pristine_system_gems
- bundle "install --deployment"
- expect(exitstatus).to eq(0) if exitstatus
+ bundle_config "deployment true"
+ bundle :install
end
end
@@ -946,12 +1300,12 @@ RSpec.describe "bundle install with git sources" do
it "runs pre-install hooks" do
build_git "foo"
gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
File.open(lib_path("install_hooks.rb"), "w") do |h|
h.write <<-H
- require 'rubygems'
Gem.pre_install_hooks << lambda do |inst|
STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
end
@@ -959,19 +1313,19 @@ RSpec.describe "bundle install with git sources" do
end
bundle :install,
- :requires => [lib_path("install_hooks.rb")]
- expect(err).to eq_err("Ran pre-install hook: foo-1.0")
+ 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 "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
File.open(lib_path("install_hooks.rb"), "w") do |h|
h.write <<-H
- require 'rubygems'
Gem.post_install_hooks << lambda do |inst|
STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
end
@@ -979,39 +1333,38 @@ RSpec.describe "bundle install with git sources" do
end
bundle :install,
- :requires => [lib_path("install_hooks.rb")]
- expect(err).to eq_err("Ran post-install hook: foo-1.0")
+ 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 "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
File.open(lib_path("install_hooks.rb"), "w") do |h|
h.write <<-H
- require 'rubygems'
Gem.pre_install_hooks << lambda do |inst|
false
end
H
end
- bundle :install,
- :requires => [lib_path("install_hooks.rb")]
- expect(out).to include("failed for foo-1.0")
+ 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", :ruby_repo 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", __FILE__)
+ path = File.expand_path("lib", __dir__)
FileUtils.mkdir_p(path)
File.open("\#{path}/foo.rb", "w") do |f|
f.puts "FOO = 'YES'"
@@ -1021,7 +1374,7 @@ RSpec.describe "bundle install with git sources" do
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
@@ -1031,14 +1384,14 @@ RSpec.describe "bundle install with git sources" do
R
expect(out).to eq("YES")
- run! <<-R
+ run <<-R
puts $:.grep(/ext/)
R
- expect(out).to eq(Pathname.glob(system_gem_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s)
+ 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", :ruby_repo do
- git_reader = build_git "foo", :no_default => true do |s|
+ 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"
@@ -1048,21 +1401,20 @@ RSpec.describe "bundle install with git sources" do
end
2.times do |i|
- Dir.chdir(git_reader.path) do
- File.open("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
- `git commit -m 'commit for iteration #{i}' ext/foo.c`
+ File.open(git_reader.path.join("ext/foo.c"), "w") do |file|
+ file.write <<-C
+ #include "ruby.h"
+ VALUE foo(VALUE self) { return INT2FIX(#{i}); }
+ void Init_foo() { rb_define_global_function("foo", &foo, 0); }
+ C
end
- git_sha = git_reader.ref_for("HEAD")
+ git("commit -m \"commit for iteration #{i}\" ext/foo.c", git_reader.path)
+
+ git_commit_sha = git_reader.ref_for("HEAD")
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_sha}"
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}"
G
run <<-R
@@ -1085,12 +1437,12 @@ RSpec.describe "bundle install with git sources" do
RUBY
end
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- expect(out).to end_with(<<-M.strip)
+ expect(err).to end_with(<<-M.strip)
An error occurred while installing foo (1.0), and Bundler cannot continue.
In Gemfile:
@@ -1099,13 +1451,13 @@ In Gemfile:
expect(out).not_to include("gem install foo")
end
- it "does not reinstall the extension", :ruby_repo, :rubygems => ">= 2.3.0" do
+ 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", __FILE__)
+ 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|
@@ -1116,11 +1468,11 @@ In Gemfile:
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- run! <<-R
+ run <<-R
require 'foo'
puts FOO
R
@@ -1129,16 +1481,114 @@ In Gemfile:
expect(installed_time).to match(/\A\d+\.\d+\z/)
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- run! <<-R
+ 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 "https://gem.repo1"
+ gem "myrack", "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 "https://gem.repo1"
+ gem "myrack", "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 "https://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 "https://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
@@ -1151,54 +1601,91 @@ In Gemfile:
ENV["GIT_WORK_TREE"] = "bar"
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
git "#{lib_path("xxxxxx-1.0")}" do
gem 'xxxxxx'
end
G
- expect(exitstatus).to eq(0) if exitstatus
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" do
+ it "prints a better error message when installing" do
+ gemfile <<-G
+ source "https://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 "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
G
with_path_as("") do
- bundle "update"
+ bundle "update", all: true, raise_on_error: false
end
- expect(out).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")
+ 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
+ it "doesn't need git in the new machine if an installed git gem is copied to another machine" do
build_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
G
- bundle "package --all"
- simulate_new_machine
+ bundle_config_global "path vendor/bundle"
+ bundle :install
+ pristine_system_gems
- bundle "install", :env => { "PATH" => "" }
+ bundle "install", env: { "PATH" => "" }
expect(out).to_not include("You need to install git to be able to use gems from git repositories.")
- expect(exitstatus).to be_zero if exitstatus
end
end
- describe "when the git source is overriden with a local git repo" do
+ describe "when the git source is overridden with a local git repo" do
before do
- bundle "config --global local.foo #{lib_path("foo")}"
+ bundle_config_global "local.foo #{lib_path("foo")}"
end
describe "and git output is colorized" do
@@ -1209,10 +1696,11 @@ In Gemfile:
end
it "installs successfully" do
- build_git "foo", "1.0", :path => lib_path("foo")
+ build_git "foo", "1.0", path: lib_path("foo")
gemfile <<-G
- gem "foo", :git => "#{lib_path("foo")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo")}", :branch => "main"
G
bundle :install
@@ -1226,15 +1714,14 @@ In Gemfile:
let(:credentials) { "user1:password1" }
it "does not display the password" do
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
git "https://#{credentials}@github.com/company/private-repo" do
gem "foo"
end
G
- bundle :install
- expect(out).to_not include("password1")
- expect(err).to_not include("password1")
+ expect(stdboth).to_not include("password1")
expect(out).to include("Fetching https://user1@github.com/company/private-repo")
end
end
@@ -1243,15 +1730,14 @@ In Gemfile:
let(:credentials) { "oauth_token" }
it "displays the oauth scheme but not the oauth token" do
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do
gem "foo"
end
G
- bundle :install
- expect(out).to_not include("oauth_token")
- expect(err).to_not include("oauth_token")
+ expect(stdboth).to_not include("oauth_token")
expect(out).to include("Fetching https://x-oauth-basic@github.com/company/private-repo")
end
end
diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb
index a3a5eeefdf..4013b112ec 100644
--- a/spec/bundler/install/gemfile/groups_spec.rb
+++ b/spec/bundler/install/gemfile/groups_spec.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install with groups" do
describe "installing with no options" do
before :each do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
group :emo do
gem "activesupport", "2.3.5"
end
@@ -15,7 +14,7 @@ RSpec.describe "bundle install with groups" do
end
it "installs gems in the default group" do
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "installs gems in a group block into that group" do
@@ -26,7 +25,7 @@ RSpec.describe "bundle install with groups" do
puts ACTIVESUPPORT
R
- expect(err).to eq_err("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- activesupport/)
end
it "installs gems with inline :groups into those groups" do
@@ -37,11 +36,11 @@ RSpec.describe "bundle install with groups" do
puts THIN
R
- expect(err).to eq_err("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- thin/)
end
it "sets up everything if Bundler.setup is used with no groups" do
- output = run("require 'rack'; puts RACK")
+ output = run("require 'myrack'; puts MYRACK")
expect(output).to eq("1.0.0")
output = run("require 'activesupport'; puts ACTIVESUPPORT")
@@ -58,7 +57,7 @@ RSpec.describe "bundle install with groups" do
puts THIN
RUBY
- expect(err).to eq_err("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- thin/)
end
it "sets up old groups when they have previously been removed" do
@@ -71,12 +70,12 @@ RSpec.describe "bundle install with groups" do
end
end
- describe "installing --without" do
+ describe "without option" do
describe "with gems assigned to a single group" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
group :emo do
gem "activesupport", "2.3.5"
end
@@ -87,54 +86,58 @@ RSpec.describe "bundle install with groups" do
end
it "installs gems in the default group" do
- bundle :install, :without => "emo"
- expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ bundle_config "without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0", groups: [:default]
end
- it "does not install gems from the excluded group" do
- bundle :install, :without => "emo"
- expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default]
+ it "respects global `without` configuration, but does not save it locally" do
+ bundle_config_global "without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 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 "does not install gems from the previously excluded group" do
- bundle :install, :without => "emo"
- expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ it "allows running application where groups where configured by a different user" do
+ bundle_config "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 "without emo"
bundle :install
- expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default]
end
it "does not say it installed gems from the excluded group" do
- bundle :install, :without => "emo"
+ bundle_config "without emo"
+ bundle :install
expect(out).not_to include("activesupport")
end
it "allows Bundler.setup for specific groups" do
- bundle :install, :without => "emo"
- run("require 'rack'; puts RACK", :default)
+ bundle_config "without emo"
+ bundle :install
+ run("require 'myrack'; puts MYRACK", :default)
expect(out).to eq("1.0.0")
end
it "does not effect the resolve" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activesupport"
group :emo do
gem "rails", "2.3.2"
end
G
- bundle :install, :without => "emo"
- expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default]
- end
-
- it "still works on a different machine and excludes gems" do
- bundle :install, :without => "emo"
-
- simulate_new_machine
- bundle :install, :without => "emo"
-
- 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]
+ bundle_config "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
@@ -143,86 +146,52 @@ RSpec.describe "bundle install with groups" do
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]
+ expect(the_bundle).to include_gems "myrack 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" 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" 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 "does install gems from the optional group when requested" do
- bundle :install, :with => "debugging"
- expect(the_bundle).to include_gems "thin 1.0"
- end
-
- it "does install gems from the previously requested group" do
- bundle :install, :with => "debugging"
- expect(the_bundle).to include_gems "thin 1.0"
+ it "installs gems from the optional group when requested" do
+ bundle_config "with debugging"
bundle :install
expect(the_bundle).to include_gems "thin 1.0"
end
- it "does install gems from the optional groups requested with BUNDLE_WITH" do
+ 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" do
- bundle :install, :with => "debugging"
- bundle 'install --with ""'
- expect(the_bundle).not_to include_gems "thin 1.0"
- end
-
- it "does remove groups from without when passed at with" do
- bundle :install, :without => "emo"
- bundle :install, :with => "emo"
- expect(the_bundle).to include_gems "activesupport 2.3.5"
- end
+ it "allows the BUNDLE_WITH setting to override BUNDLE_WITHOUT" do
+ ENV["BUNDLE_WITH"] = "debugging"
- it "does remove groups from with when passed at without" do
- bundle :install, :with => "debugging"
- bundle :install, :without => "debugging"
- expect(the_bundle).not_to include_gems "thin 1.0"
- end
+ bundle :install
+ expect(the_bundle).to include_gem "thin 1.0"
- it "errors out when passing a group to with and without" do
- bundle :install, :with => "emo debugging", :without => "emo"
- expect(out).to include("The offending groups are: emo")
- end
+ ENV["BUNDLE_WITHOUT"] = "debugging"
+ expect(the_bundle).to include_gem "thin 1.0"
- it "can add and remove a group at the same time" 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"
+ bundle :install
+ expect(the_bundle).to include_gem "thin 1.0"
end
- it "does have no effect when listing a not optional group in with" do
- bundle :install, :with => "emo"
+ it "has no effect when listing a not optional group in with" do
+ bundle_config "with emo"
+ bundle :install
expect(the_bundle).to include_gems "activesupport 2.3.5"
end
- it "does have no effect when listing an optional group in without" do
- bundle :install, :without => "debugging"
+ it "has no effect when listing an optional group in without" do
+ bundle_config "without debugging"
+ bundle :install
expect(the_bundle).not_to include_gems "thin 1.0"
end
end
@@ -230,8 +199,8 @@ RSpec.describe "bundle install with groups" do
describe "with gems assigned to multiple groups" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
group :emo, :lolercoaster do
gem "activesupport", "2.3.5"
end
@@ -239,20 +208,22 @@ RSpec.describe "bundle install with groups" do
end
it "installs gems in the default group" do
- bundle :install, :without => "emo lolercoaster"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle_config "without emo lolercoaster"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "installs the gem if any of its groups are installed" do
- bundle "install --without emo"
- expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ bundle_config "without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
group :emo do
gem "activesupport", "2.3.5"
@@ -264,23 +235,21 @@ RSpec.describe "bundle install with groups" do
G
end
- it "installs the gem w/ option --without emo" do
- bundle "install --without emo"
+ it "installs the gem unless all groups are excluded" do
+ bundle_config "without emo"
+ bundle :install
expect(the_bundle).to include_gems "activesupport 2.3.5"
- end
- it "installs the gem w/ option --without lolercoaster" do
- bundle "install --without lolercoaster"
+ bundle_config "without lolercoaster"
+ bundle :install
expect(the_bundle).to include_gems "activesupport 2.3.5"
- end
- it "does not install the gem w/ option --without emo lolercoaster" do
- bundle "install --without emo lolercoaster"
+ bundle_config "without emo lolercoaster"
+ bundle :install
expect(the_bundle).not_to include_gems "activesupport 2.3.5"
- end
- it "does not install the gem w/ option --without 'emo lolercoaster'" do
- bundle "install --without 'emo lolercoaster'"
+ bundle "config set --local without 'emo lolercoaster'"
+ bundle :install
expect(the_bundle).not_to include_gems "activesupport 2.3.5"
end
end
@@ -289,8 +258,8 @@ RSpec.describe "bundle install with groups" do
describe "nesting groups" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
group :emo do
group :lolercoaster do
gem "activesupport", "2.3.5"
@@ -300,13 +269,15 @@ RSpec.describe "bundle install with groups" do
end
it "installs gems in the default group" do
- bundle :install, :without => "emo lolercoaster"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle_config "without emo lolercoaster"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "installs the gem if any of its groups are installed" do
- bundle "install --without emo"
- expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ bundle_config "without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5"
end
end
end
@@ -314,8 +285,8 @@ RSpec.describe "bundle install with groups" do
describe "when loading only the default group" do
it "should not load all groups" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "activesupport", :groups => :development
G
@@ -323,7 +294,7 @@ RSpec.describe "bundle install with groups" do
require "bundler"
Bundler.setup :default
Bundler.require :default
- puts RACK
+ puts MYRACK
begin
require "activesupport"
rescue LoadError
@@ -336,36 +307,39 @@ RSpec.describe "bundle install with groups" do
end
end
- describe "when locked and installed with --without" do
+ describe "when locked and installed with `without` setting" do
before(:each) do
build_repo2
- system_gems "rack-0.9.1" do
- install_gemfile <<-G, :without => :rack
- source "file://#{gem_repo2}"
- gem "rack"
- group :rack do
- gem "rack_middleware"
- end
- G
- end
+ system_gems "myrack-0.9.1"
+
+ bundle_config "without myrack"
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+
+ group :myrack do
+ gem "myrack_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"
+ it "uses versions from excluded gems in a machine without the without configuration" do
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+ expect(the_bundle).not_to include_gems "myrack_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"
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+ expect(the_bundle).to include_gems "myrack_middleware 1.0"
end
it "does not hit the remote a second time" do
- FileUtils.rm_rf gem_repo2
- bundle "install --without rack"
- expect(err).to lack_errors
+ FileUtils.rm_r gem_repo2
+ bundle_config "without myrack"
+ bundle :install, verbose: true
+ expect(stdboth).not_to match(/fetching/i)
end
end
end
diff --git a/spec/bundler/install/gemfile/install_if.rb b/spec/bundler/install/gemfile/install_if.rb
deleted file mode 100644
index b1717ad583..0000000000
--- a/spec/bundler/install/gemfile/install_if.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-describe "bundle install with install_if conditionals" do
- it "follows the install_if DSL" do
- install_gemfile <<-G
- source "file://#{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")
-
- lockfile_should_be <<-L
- GEM
- remote: file:#{gem_repo1}/
- specs:
- activesupport (2.3.5)
- foo (1.0)
- rack (1.0.0)
- thin (1.0)
- rack
-
- PLATFORMS
- ruby
-
- DEPENDENCIES
- activesupport (= 2.3.5)
- foo
- rack
- thin
-
- BUNDLED WITH
- #{Bundler::VERSION}
- L
- 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..05a6d15129
--- /dev/null
+++ b/spec/bundler/install/gemfile/install_if_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with install_if conditionals" do
+ it "follows the install_if DSL" do
+ install_gemfile <<-G
+ source "https://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 "myrack"
+ G
+
+ expect(the_bundle).to include_gems("myrack 1.0", "activesupport 2.3.5")
+ expect(the_bundle).not_to include_gems("thin")
+ expect(the_bundle).not_to include_gems("foo")
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "activesupport", "2.3.5"
+ c.checksum gem_repo1, "foo", "1.0"
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ c.checksum gem_repo1, "thin", "1.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ activesupport (2.3.5)
+ foo (1.0)
+ myrack (1.0.0)
+ thin (1.0)
+ myrack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport (= 2.3.5)
+ foo
+ myrack
+ thin
+ #{checksums}
+ 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..19bd7074b2
--- /dev/null
+++ b/spec/bundler/install/gemfile/lockfile_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with a lockfile present" do
+ let(:gf) { <<-G }
+ source "https://gem.repo1"
+
+ gem "myrack", "1.0.0"
+ G
+
+ it "touches the lockfile on install even when nothing has changed" do
+ install_gemfile(gf)
+ expect { bundle :install }.to change { bundled_app_lock.mtime }
+ 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 "plugins false"
+ end
+
+ it "does not evaluate the gemfile twice when the gem is already installed" do
+ install_gemfile(gf)
+ bundle :install
+
+ with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "myrack 1.0.0" }
+
+ expect(bundled_app("evals").read.lines.to_a.size).to eq(2)
+ end
+
+ it "does not evaluate the gemfile twice when the gem is not installed" do
+ gemfile(gf)
+ bundle :install
+
+ with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "myrack 1.0.0" }
+
+ expect(bundled_app("evals").read.lines.to_a.size).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/override_spec.rb b/spec/bundler/install/gemfile/override_spec.rb
new file mode 100644
index 0000000000..02b0e7d772
--- /dev/null
+++ b/spec/bundler/install/gemfile/override_spec.rb
@@ -0,0 +1,401 @@
+# frozen_string_literal: true
+
+RSpec.describe "override DSL" do
+ context "with a version: string operation" do
+ it "replaces a direct dependency requirement with the override version spec" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: "= 0.9.1"
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+ end
+
+ it "replaces a transitive dependency requirement" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: "= 1.0.0"
+ gem "myrack_middleware"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack_middleware 1.0"
+ end
+
+ it "replaces the requirement even when the Gemfile pins a different version" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: "= 0.9.1"
+ gem "myrack", "= 1.0.0"
+ G
+
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+ end
+
+ it "applies the override against an existing lockfile" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: "= 0.9.1"
+ gem "myrack"
+ G
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+ end
+
+ it "pins a prerelease version that the Gemfile dependency would otherwise filter out" do
+ build_repo2 do
+ build_gem "has_prerelease", "1.0"
+ build_gem "has_prerelease", "1.1.pre"
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ override "has_prerelease", version: "= 1.1.pre"
+ gem "has_prerelease"
+ G
+
+ expect(the_bundle).to include_gems "has_prerelease 1.1.pre"
+ end
+ end
+
+ context "with a version: :ignore_upper operation" do
+ it "strips a < upper bound on a direct dependency" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: :ignore_upper
+ gem "myrack", "< 1.0"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "folds ~> into >= so newer versions become reachable" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: :ignore_upper
+ gem "myrack", "~> 0.9.1"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+ end
+
+ context "with a version: nil operation" do
+ it "drops a direct dependency's pin entirely" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: nil
+ gem "myrack", "= 0.9.1"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "drops a transitive dependency's pin entirely" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: nil
+ gem "myrack_middleware"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack_middleware 1.0"
+ end
+
+ it "applies a transitive-only override against an existing lockfile" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack_middleware"
+ G
+
+ expect(the_bundle).to include_gems "myrack 0.9.1", "myrack_middleware 1.0"
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: "= 1.0.0"
+ gem "myrack_middleware"
+ G
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack_middleware 1.0"
+ end
+ end
+
+ context "lockfile contents" do
+ it "does not record the override directive in Gemfile.lock" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ override "myrack", version: "= 0.9.1"
+ gem "myrack"
+ G
+
+ expect(lockfile).not_to match(/override/i)
+ end
+ end
+
+ context "with a required_ruby_version: operation" do
+ it "lets the resolver pick a gem whose required_ruby_version excludes the current Ruby with :ignore_upper" do
+ build_repo2 do
+ build_gem "needs_old_ruby", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_ruby", required_ruby_version: :ignore_upper
+ gem "needs_old_ruby"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("needs_old_ruby (1.0)")
+ end
+
+ it "lets the resolver pick the gem with required_ruby_version: nil" do
+ build_repo2 do
+ build_gem "needs_old_ruby", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_ruby", required_ruby_version: nil
+ gem "needs_old_ruby"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("needs_old_ruby (1.0)")
+ end
+
+ it "applies to a transitive dependency's required_ruby_version" do
+ build_repo2 do
+ build_gem "needs_old_ruby", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ build_gem "wraps_old", "1.0" do |s|
+ s.add_dependency "needs_old_ruby"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_ruby", required_ruby_version: :ignore_upper
+ gem "wraps_old"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("needs_old_ruby (1.0)")
+ expect(lockfile).to include("wraps_old (1.0)")
+ end
+
+ it "re-resolves a direct dep when a metadata override is added against an existing lockfile" do
+ build_repo2 do
+ build_gem "selectable", "1.0"
+ build_gem "selectable", "2.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "selectable"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("selectable (1.0)")
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override "selectable", required_ruby_version: :ignore_upper
+ gem "selectable"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("selectable (2.0)")
+ end
+ end
+
+ context "with a required_rubygems_version: operation" do
+ it "lets the resolver pick a gem whose required_rubygems_version excludes the current RubyGems with :ignore_upper" do
+ build_repo2 do
+ build_gem "needs_old_rubygems", "1.0" do |s|
+ s.required_rubygems_version = "< #{Gem.rubygems_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_rubygems", required_rubygems_version: :ignore_upper
+ gem "needs_old_rubygems"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("needs_old_rubygems (1.0)")
+ end
+ end
+
+ context "with an :all target" do
+ it "applies required_ruby_version: :ignore_upper to every gem" do
+ build_repo2 do
+ build_gem "needs_old_ruby_a", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ build_gem "needs_old_ruby_b", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override :all, required_ruby_version: :ignore_upper
+ gem "needs_old_ruby_a"
+ gem "needs_old_ruby_b"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("needs_old_ruby_a (1.0)")
+ expect(lockfile).to include("needs_old_ruby_b (1.0)")
+ end
+
+ it "is overridden by a per-gem override on the same field" do
+ build_repo2 do
+ build_gem "permissive", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ build_gem "still_blocked", "1.0" do |s|
+ s.required_ruby_version = "= #{Gem.ruby_version}.999"
+ end
+ end
+
+ # :all says ignore_upper (would unblock both), but per-gem on
+ # still_blocked nails it to a hard requirement that still fails.
+ gemfile <<-G
+ source "https://gem.repo2"
+ override :all, required_ruby_version: :ignore_upper
+ override "still_blocked", required_ruby_version: "= #{Gem.ruby_version}.999"
+ gem "permissive"
+ gem "still_blocked"
+ G
+
+ bundle :lock, raise_on_error: false
+ expect(err).to include("still_blocked")
+ end
+
+ it "preserves locked versions when an :all metadata override is added without bundle update" do
+ build_repo2 do
+ build_gem "selectable", "1.0"
+ build_gem "selectable", "2.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "selectable"
+ G
+
+ bundle :lock
+ expect(lockfile).to include("selectable (1.0)")
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override :all, required_ruby_version: :ignore_upper
+ gem "selectable"
+ G
+
+ # :all override alone does not pre-unlock locked specs; narrow change
+ # should not trigger unrelated lockfile churn.
+ bundle :lock
+ expect(lockfile).to include("selectable (1.0)")
+
+ # bundle update opts the user into re-resolution under the override.
+ bundle "update selectable"
+ expect(lockfile).to include("selectable (2.0)")
+ end
+ end
+
+ context "diagnostic on resolve failure" do
+ it "lists active overrides with their Gemfile location" do
+ build_repo2 do
+ build_gem "needs_old_ruby", "1.0" do |s|
+ s.required_ruby_version = "= #{Gem.ruby_version}.999"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_ruby", required_ruby_version: "= #{Gem.ruby_version}.999"
+ gem "needs_old_ruby"
+ G
+
+ bundle :lock, raise_on_error: false
+ expect(err).to include("Bundler applied the following overrides")
+ expect(err).to include("override \"needs_old_ruby\", required_ruby_version:")
+ expect(err).to match(/declared at Gemfile:\d+/)
+ end
+ end
+
+ context "install-time compatibility" do
+ it "installs a gem whose required_ruby_version excludes the current Ruby when an override removes the constraint" do
+ build_repo2 do
+ build_gem "needs_old_ruby", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_ruby", required_ruby_version: nil
+ gem "needs_old_ruby"
+ G
+
+ expect(the_bundle).to include_gems "needs_old_ruby 1.0"
+ end
+
+ it "installs a gem whose required_rubygems_version excludes the current RubyGems when an override removes it" do
+ build_repo2 do
+ build_gem "needs_old_rubygems", "1.0" do |s|
+ s.required_rubygems_version = "< #{Gem.rubygems_version}"
+ end
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ override "needs_old_rubygems", required_rubygems_version: nil
+ gem "needs_old_rubygems"
+ G
+
+ expect(the_bundle).to include_gems "needs_old_rubygems 1.0"
+ end
+
+ it "installs every gem when :all required_ruby_version override is in effect" do
+ build_repo2 do
+ build_gem "needs_old_ruby_a", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ build_gem "needs_old_ruby_b", "1.0" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ override :all, required_ruby_version: :ignore_upper
+ gem "needs_old_ruby_a"
+ gem "needs_old_ruby_b"
+ G
+
+ expect(the_bundle).to include_gems "needs_old_ruby_a 1.0", "needs_old_ruby_b 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb
index a1c41aebbb..b069488531 100644
--- a/spec/bundler/install/gemfile/path_spec.rb
+++ b/spec/bundler/install/gemfile/path_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install with explicit source paths" do
it "fetches gems" do
build_lib "foo"
install_gemfile <<-G
- path "#{lib_path("foo-1.0")}"
- gem 'foo'
+ path "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
G
expect(the_bundle).to include_gems("foo 1.0")
@@ -26,7 +26,7 @@ RSpec.describe "bundle install with explicit source paths" do
it "supports relative paths" do
build_lib "foo"
- relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new(Dir.pwd))
+ relative_path = lib_path("foo-1.0").relative_path_from(bundled_app)
install_gemfile <<-G
gem 'foo', :path => "#{relative_path}"
@@ -48,71 +48,116 @@ RSpec.describe "bundle install with explicit source paths" do
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
+ install_gemfile <<-G, raise_on_error: false
gem 'foo', :path => "~#{username}/#{relative_path}"
G
- expect(out).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.")
- expect(out).to match("user #{username} doesn't exist")
+ 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")
+ build_lib "foo", path: bundled_app("foo-1.0")
install_gemfile <<-G
gem 'foo', :path => "./foo-1.0"
G
- bundled_app("subdir").mkpath
- Dir.chdir(bundled_app("subdir")) do
- expect(the_bundle).to include_gems("foo 1.0")
+ 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 "https://gem.repo1"
+ gemspec
+ gem "aaa", :path => "./aaa"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "aaa", "1.0"
+ c.no_checksum "demo", "1.0"
end
+
+ lockfile = <<~L
+ PATH
+ remote: .
+ specs:
+ demo (1.0)
+
+ PATH
+ remote: aaa
+ specs:
+ aaa (1.0)
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ aaa!
+ demo!
+ #{checksums}
+ 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")
+ build_lib "foo", path: bundled_app("foo-1.0")
install_gemfile <<-G
- gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__)
+ gem 'foo', :path => File.expand_path("foo-1.0", __dir__)
G
- bundle "install --frozen"
- expect(exitstatus).to eq(0) if exitstatus
+ bundle_config "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"
+ system_gems "myrack-1.0.0"
- build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s|
- s.write "lib/rack.rb", "puts 'WIN OVERRIDE'"
+ build_lib "myrack", "1.0", path: lib_path("nested/bar") do |s|
+ s.write "lib/myrack.rb", "puts 'WIN OVERRIDE'"
end
- build_lib "foo", :path => lib_path("nested") do |s|
- s.add_dependency "rack", "= 1.0"
+ build_lib "foo", path: lib_path("nested") do |s|
+ s.add_dependency "myrack", "= 1.0"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
gem "foo", :path => "#{lib_path("nested")}"
G
- run "require 'rack'"
+ run "require 'myrack'"
expect(out).to eq("WIN OVERRIDE")
end
it "works" do
- build_gem "foo", "1.0.0", :to_system => true do |s|
+ 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|
+ 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")
+ build_lib "foo", "1.0.0", path: lib_path("omg/foo")
install_gemfile <<-G
gem "omg", :path => "#{lib_path("omg")}"
@@ -121,8 +166,88 @@ RSpec.describe "bundle install with explicit source paths" do
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 "https://gem.repo1"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (0.0.0.dev)
+
+ GEM
+ remote: https://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 "https://gem.repo1"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (0.0.0.SNAPSHOT)
+
+ GEM
+ remote: https://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
+ 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|
+ 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'
@@ -142,7 +267,7 @@ RSpec.describe "bundle install with explicit source paths" do
expect(the_bundle).to include_gems "premailer 1.0.0"
end
- it "warns on invalid specs", :rubygems => "1.7" do
+ it "warns on invalid specs" do
build_lib "foo"
gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
@@ -154,106 +279,152 @@ RSpec.describe "bundle install with explicit source paths" do
G
end
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
gem "foo", :path => "#{lib_path("foo-1.0")}"
G
- expect(out).to_not include("ERROR REPORT")
- expect(out).to_not include("Your Gemfile has no gem server sources.")
- expect(out).to match(/is not valid. Please fix this gemspec./)
- expect(out).to match(/The validation error was 'missing value for attribute version'/)
- expect(out).to match(/You have one or more invalid gemspecs that need to be fixed/)
+ 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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack", "1.0"
end
- gemfile = <<-G
- source "file://#{gem_repo1}"
+ gemfile lib_path("foo/Gemfile"), <<-G
+ source "https://gem.repo1"
gemspec
G
- File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts gemfile }
+ 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 "myrack 1.0", dir: lib_path("foo")
+ end
- Dir.chdir(lib_path("foo")) do
- bundle "install"
- expect(the_bundle).to include_gems "foo 1.0"
- expect(the_bundle).to include_gems "rack 1.0"
+ 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 "https://gem.repo4"
+ gemspec
+ G
+
+ lockfile_path = lib_path("foo/Gemfile.lock")
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "0.1.0"
+ c.checksum gem_repo4, "graphql", "2.0.15"
+ end
+
+ original_lockfile = <<~L
+ PATH
+ remote: .
+ specs:
+ foo (0.1.0)
+ graphql (~> 2.0)
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ graphql (2.0.15)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ 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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack", "1.0"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://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"
+ expect(the_bundle).to include_gems "myrack 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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack", ">= 1.0"
end
- Dir.chdir lib_path("foo")
-
- install_gemfile lib_path("foo/Gemfile"), <<-G
- source "file://#{gem_repo1}"
+ install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo")
+ source "https://gem.repo1"
gemspec
G
- build_gem "rack", "1.0.1", :to_system => true
+ build_gem "myrack", "1.0.1", to_system: true
- bundle "install"
+ bundle "install", dir: lib_path("foo")
- expect(the_bundle).to include_gems "foo 1.0"
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo")
+ expect(the_bundle).to include_gems "myrack 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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack", ">= 1.0"
s.add_development_dependency "activesupport"
end
- Dir.chdir lib_path("foo")
-
- install_gemfile lib_path("foo/Gemfile"), <<-G
- source "file://#{gem_repo1}"
+ install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo")
+ source "https://gem.repo1"
gemspec
G
- build_gem "rack", "1.0.1", :to_system => true
+ build_gem "myrack", "1.0.1", to_system: true
- bundle "install"
+ bundle "install", dir: lib_path("foo")
- expect(the_bundle).to include_gems "foo 1.0"
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo")
+ expect(the_bundle).to include_gems "myrack 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|
+ 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
+ install_gemfile <<-G, raise_on_error: false
gemspec :path => "#{lib_path("foo")}"
G
- expect(exitstatus).to eq(15) if exitstatus
- expect(out).to match(/There are multiple gemspecs/)
+ 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|
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
s.write "bar.gemspec"
end
@@ -269,10 +440,12 @@ RSpec.describe "bundle install with explicit source paths" do
s.executables = "foobar"
end
- install_gemfile <<-G
- path "#{lib_path("foo-1.0")}"
- gem 'foo'
+ install_gemfile <<-G, verbose: true
+ 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"
@@ -281,13 +454,13 @@ RSpec.describe "bundle install with explicit source paths" do
it "handles directories in bin/" do
build_lib "foo"
- lib_path("foo-1.0").join("foo.gemspec").rmtree
+ FileUtils.rm_rf lib_path("foo-1.0").join("foo.gemspec")
lib_path("foo-1.0").join("bin/performance").mkpath
install_gemfile <<-G
gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}"
G
- expect(err).to lack_errors
+ expect(err).to be_empty
end
it "removes the .gem file after installing" do
@@ -317,9 +490,9 @@ RSpec.describe "bundle install with explicit source paths" do
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|
+ 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
@@ -332,7 +505,7 @@ RSpec.describe "bundle install with explicit source paths" do
end
it "works when the path does not have a gemspec" do
- build_lib "foo", :gemspec => false
+ build_lib "foo", gemspec: false
gemfile <<-G
gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}"
@@ -344,54 +517,50 @@ RSpec.describe "bundle install with explicit source paths" do
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
+ lockfile <<~L
+ PATH
+ remote: vendor/bar
+ specs:
L
- in_app_root { FileUtils.mkdir_p("vendor/bar") }
+ FileUtils.mkdir_p(bundled_app("vendor/bar"))
install_gemfile <<-G
gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard"
G
- expect(exitstatus).to eq(0) if exitstatus
end
context "existing lockfile" do
it "rubygems gems don't re-resolve without changes" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack-obama', '1.0'
+ source "https://gem.repo1"
+ gem 'myrack-obama', '1.0'
gem 'net-ssh', '1.0'
G
- bundle :check, :env => { "DEBUG" => 1 }
+ 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"
+ expect(the_bundle).to include_gems "myrack-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|
+ build_lib "myrack-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|
+ build_lib "net-ssh", "1.0", path: lib_path("omg") do |s|
s.add_dependency "yard"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack-obama', :path => "#{lib_path("omg")}"
+ source "https://gem.repo1"
+ gem 'myrack-obama', :path => "#{lib_path("omg")}"
gem 'net-ssh', :path => "#{lib_path("omg")}"
G
- bundle :check, :env => { "DEBUG" => 1 }
+ 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"
+ expect(the_bundle).to include_gems "myrack-obama 1.0", "net-ssh 1.0"
end
end
@@ -410,10 +579,10 @@ RSpec.describe "bundle install with explicit source paths" do
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|
+ 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")
+ build_lib "bar", "1.0", path: lib_path("foo/bar")
install_gemfile <<-G
gem "foo", :path => "#{lib_path("foo")}"
@@ -421,7 +590,7 @@ RSpec.describe "bundle install with explicit source paths" do
end
it "unlocks all gems when the top level gem is updated" do
- build_lib "foo", "2.0", :path => lib_path("foo") do |s|
+ build_lib "foo", "2.0", path: lib_path("foo") do |s|
s.add_dependency "bar"
end
@@ -431,7 +600,7 @@ RSpec.describe "bundle install with explicit source paths" do
end
it "unlocks all gems when a child dependency gem is updated" do
- build_lib "bar", "2.0", :path => lib_path("foo/bar")
+ build_lib "bar", "2.0", path: lib_path("foo/bar")
bundle "install"
@@ -441,42 +610,280 @@ RSpec.describe "bundle install with explicit source paths" do
describe "when dependencies in the path are updated" do
before :each do
- build_lib "foo", "1.0", :path => lib_path("foo")
+ build_lib "foo", "1.0", path: lib_path("foo")
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://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"
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack"
end
bundle "install"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 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 "myrack", "0.9.1"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo1, "myrack", "0.9.1"
+ end
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ myrack (= 0.9.1)
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack"
+ end
+
+ bundle "install"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ myrack
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "myrack 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 "myrack", "0.9.1"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.checksum gem_repo1, "myrack", "0.9.1"
+ end
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ myrack (= 0.9.1)
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ build_lib "foo", "1.0", path: lib_path("foo") do |s|
+ s.add_dependency "myrack"
+ s.add_dependency "rake", rake_version
+ end
+
+ bundle "install"
+
+ checksums.checksum gem_repo1, "rake", rake_version
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ myrack
+ rake (= #{rake_version})
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+ rake (#{rake_version})
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "myrack 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 "myrack", "0.9.1"
+ end
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ checksums.checksum gem_repo1, "myrack", "0.9.1"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ myrack (= 0.9.1)
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ foo!
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+
+ context "when platform specific version locked, and having less dependencies that the generic version that's actually installed" do
+ before do
+ build_repo4 do
+ build_gem "racc", "1.8.1"
+ build_gem "mini_portile2", "2.8.2"
+ end
+
+ build_lib "nokogiri", "1.18.9", path: lib_path("nokogiri") do |s|
+ s.add_dependency "mini_portile2", "~> 2.8.2"
+ s.add_dependency "racc", "~> 1.4"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri", path: "#{lib_path("nokogiri")}"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("nokogiri")}
+ specs:
+ nokogiri (1.18.9)
+ mini_portile2 (~> 2.8.2)
+ racc (~> 1.4)
+ nokogiri (1.18.9-arm64-darwin)
+ racc (~> 1.4)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ racc (1.8.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ nokogiri!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "works" do
+ bundle "install"
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|
+ 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|
+ 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://#{gem_repo1}"
gem "bar", :git => "#{lib_path("bar")}"
G
install_gemfile <<-G
- source "file://#{gem_repo1}"
gem "bar", :path => "#{lib_path("bar")}"
G
@@ -484,23 +891,23 @@ RSpec.describe "bundle install with explicit source paths" do
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_system => true do |s|
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "bar"
path "#{lib_path("foo")}" do
gem "foo"
end
G
- build_lib "bar", "1.0", :path => lib_path("foo/bar")
+ build_lib "bar", "1.0", path: lib_path("foo/bar")
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
path "#{lib_path("foo")}" do
gem "foo"
gem "bar"
@@ -513,21 +920,17 @@ RSpec.describe "bundle install with explicit source paths" do
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 = <<-G
+ 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'
+ gem 'myrack'
G
- File.open(lib_path("private_lib/Gemfile"), "w") {|f| f.puts gemfile }
-
- Dir.chdir(lib_path("private_lib")) do
- bundle :install, :env => { "DEBUG" => 1 }, :artifice => "endpoint"
- 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"
- expect(the_bundle).to include_gems "rack 1.0"
- end
+ 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=myrack$})
+ 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 "myrack 1.0", dir: lib_path("private_lib")
end
end
@@ -540,7 +943,6 @@ RSpec.describe "bundle install with explicit source paths" do
File.open(lib_path("install_hooks.rb"), "w") do |h|
h.write <<-H
- require 'rubygems'
Gem.pre_install_hooks << lambda do |inst|
STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
end
@@ -548,8 +950,8 @@ RSpec.describe "bundle install with explicit source paths" do
end
bundle :install,
- :requires => [lib_path("install_hooks.rb")]
- expect(err).to eq_err("Ran pre-install hook: foo-1.0")
+ 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
@@ -560,7 +962,6 @@ RSpec.describe "bundle install with explicit source paths" do
File.open(lib_path("install_hooks.rb"), "w") do |h|
h.write <<-H
- require 'rubygems'
Gem.post_install_hooks << lambda do |inst|
STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
end
@@ -568,8 +969,8 @@ RSpec.describe "bundle install with explicit source paths" do
end
bundle :install,
- :requires => [lib_path("install_hooks.rb")]
- expect(err).to eq_err("Ran post-install hook: foo-1.0")
+ 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
@@ -580,16 +981,37 @@ RSpec.describe "bundle install with explicit source paths" do
File.open(lib_path("install_hooks.rb"), "w") do |h|
h.write <<-H
- require 'rubygems'
Gem.pre_install_hooks << lambda do |inst|
false
end
H
end
- bundle :install,
- :requires => [lib_path("install_hooks.rb")]
- expect(out).to include("failed for foo-1.0")
+ 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
+ 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
index c6eaec7ca6..e12933ebcf 100644
--- a/spec/bundler/install/gemfile/platform_spec.rb
+++ b/spec/bundler/install/gemfile/platform_spec.rb
@@ -1,34 +1,33 @@
# frozen_string_literal: true
-require "spec_helper"
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:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (0.9.1)
+ myrack (0.9.1)
PLATFORMS
#{not_local}
DEPENDENCIES
- rack
+ myrack
G
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).to include_gems "myrack 0.9.1"
end
it "pulls in the correct platform specific gem" do
lockfile <<-G
GEM
- remote: file:#{gem_repo1}
+ remote: https://gem.repo1
specs:
platform_specific (1.0)
platform_specific (1.0-java)
@@ -41,100 +40,395 @@ RSpec.describe "bundle install across platforms" do
platform_specific
G
- simulate_platform "java"
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 java"
+ end
+ 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: https://gem.repo1
+ specs:
+ platform_specific (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+ G
+
+ bundle_config "frozen true"
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "platform_specific"
G
- expect(the_bundle).to include_gems "platform_specific 1.0 JAVA"
+ expect(the_bundle).to include_gems "platform_specific 1.0 ruby"
+ expect(err).to be_empty
end
- it "works with gems that have different dependencies" do
- simulate_platform "java"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ context "on universal Rubies" do
+ before do
+ build_repo4 do
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "ruby"
+ end
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "arm64-darwin"
+ end
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "x86_64-darwin"
+ end
+ end
+ end
- gem "nokogiri"
- G
+ it "pulls in the correct architecture gem" do
+ lockfile <<-G
+ GEM
+ remote: https://gem.repo4
+ specs:
+ darwin_single_arch (1.0)
+ darwin_single_arch (1.0-arm64-darwin)
+ darwin_single_arch (1.0-x86_64-darwin)
- expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
+ PLATFORMS
+ ruby
- simulate_new_machine
+ DEPENDENCIES
+ darwin_single_arch
+ G
- simulate_platform "ruby"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ simulate_platform "universal-darwin-21" do
+ simulate_ruby_platform "universal.x86_64-darwin21" do
+ install_gemfile <<-G
+ source "https://gem.repo4"
- gem "nokogiri"
- G
+ gem "darwin_single_arch"
+ G
- expect(the_bundle).to include_gems "nokogiri 1.4.2"
- expect(the_bundle).not_to include_gems "weakling"
+ expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin"
+ end
+ end
+ end
+
+ it "pulls in the correct architecture gem on arm64e macOS Ruby" do
+ lockfile <<-G
+ GEM
+ remote: https://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" do
+ simulate_ruby_platform "universal.arm64e-darwin21" do
+ install_gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "darwin_single_arch"
+ G
+
+ expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin"
+ end
+ end
+ end
end
- it "works the other way with gems that have different dependencies" do
- simulate_platform "ruby"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ it "works with gems that have different dependencies" do
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
- gem "nokogiri"
- G
+ gem "nokogiri"
+ G
- simulate_platform "java"
- bundle "install"
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 java", "weakling 0.0.3"
+
+ pristine_system_gems
+ bundle_config "force_ruby_platform true"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ expect(the_bundle).not_to include_gems "weakling"
- expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
+ simulate_new_machine
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 java", "weakling 0.0.3"
+ end
end
- it "works with gems that have extra platform-specific runtime dependencies" do
- simulate_platform x64_mac
+ 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_dependency "ffi" }
+ build_gem "pry", "0.11.3" do |s|
+ s.platform = "java"
+ s.add_dependency "coderay", "~> 1.1.0"
+ s.add_dependency "method_source", "~> 0.9.0"
+ s.add_dependency "spoon", "~> 0.0"
+ end
+ build_gem "pry", "0.11.3" do |s|
+ s.add_dependency "coderay", "~> 1.1.0"
+ s.add_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" do
+ install_gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "empyrean", "0.1.0"
+ gem "pry"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "coderay", "1.1.2"
+ c.checksum gem_repo4, "empyrean", "0.1.0"
+ c.checksum gem_repo4, "ffi", "1.9.23", "java"
+ c.checksum gem_repo4, "method_source", "0.9.0"
+ c.checksum gem_repo4, "pry", "0.11.3", "java"
+ c.checksum gem_repo4, "spoon", "0.0.6"
+ end
- update_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"
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --add-platform ruby"
+
+ checksums.checksum gem_repo4, "pry", "0.11.3"
+
+ good_lockfile = <<~L
+ GEM
+ remote: https://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
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(lockfile).to eq good_lockfile
+
+ bad_lockfile = <<~L
+ GEM
+ remote: https://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
+ #{checksums}
+ 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
- build_gem "CFPropertyList"
end
+ end
- install_gemfile! <<-G
- source "file://#{gem_repo2}"
+ it "works with gems with platform-specific dependency having different requirements order" do
+ simulate_platform "x86_64-darwin-15" do
+ update_repo2 do
+ build_gem "fspath", "3"
+ build_gem "image_optim_pack", "1.2.3" do |s|
+ s.add_dependency "fspath", ">= 2.1", "< 4"
+ end
+ build_gem "image_optim_pack", "1.2.3" do |s|
+ s.platform = "universal-darwin"
+ s.add_dependency "fspath", "< 4", ">= 2.1"
+ end
+ end
- gem "facter"
- G
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "image_optim_pack"
+ G
- expect(out).to include "Unable to use the platform-specific (universal-darwin) version of facter (2.4.6) " \
- "because it has different dependencies from the ruby version. " \
- "To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again."
+ expect(err).not_to include "Unable to use the platform-specific"
- expect(the_bundle).to include_gem "facter 2.4.6"
- expect(the_bundle).not_to include_gem "CFPropertyList"
+ expect(the_bundle).to include_gem "image_optim_pack 1.2.3 universal-darwin"
+ end
end
it "fetches gems again after changing the version of Ruby" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
G
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle :install
- new_version = Gem::ConfigMap[:ruby_version] == "1.8" ? "1.9.1" : "1.8"
- FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, new_version))
+ FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, "1.8"))
- bundle "install --path vendor/bundle"
- expect(vendored_gems("gems/rack-1.0.0")).to exist
+ bundle :install
+ expect(vendored_gems("gems/myrack-1.0.0")).to exist
+ end
+
+ it "keeps existing platforms when installing with force_ruby_platform" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "platform_specific", "1.0"
+ c.checksum gem_repo1, "platform_specific", "1.0", "java"
+ end
+
+ lockfile <<-G
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ platform_specific
+ #{checksums}
+ G
+
+ bundle_config "force_ruby_platform true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "platform_specific"
+ G
+
+ checksums.checksum gem_repo1, "platform_specific", "1.0"
+
+ expect(the_bundle).to include_gem "platform_specific 1.0 ruby"
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+ #{checksums}
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
platforms :#{local_tag} do
gem "nokogiri"
@@ -146,20 +440,63 @@ RSpec.describe "bundle install with platform conditionals" do
it "does not install gems tagged w/ another platforms" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
platforms :#{not_local_tag} do
gem "nokogiri"
end
G
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 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 "https://gem.repo4"
+
+ gem "activesupport"
+
+ platforms :#{not_local_tag} do
+ gem "tzinfo", "~> 1.2"
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "nokogiri", :platforms => :#{local_tag}
G
expect(the_bundle).to include_gems "nokogiri 1.4.2"
@@ -167,17 +504,17 @@ RSpec.describe "bundle install with platform conditionals" do
it "does not install gems tagged w/ another platforms inline" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "nokogiri", :platforms => :#{not_local_tag}
G
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "nokogiri", :platform => :#{local_tag}
G
expect(the_bundle).to include_gems "nokogiri 1.4.2"
@@ -185,7 +522,7 @@ RSpec.describe "bundle install with platform conditionals" do
it "doesn't install gems tagged w/ another platform inline" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "nokogiri", :platform => :#{not_local_tag}
G
expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
@@ -195,21 +532,20 @@ RSpec.describe "bundle install with platform conditionals" do
build_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
platform :#{not_local_tag} do
gem "foo", :git => "#{lib_path("foo-1.0")}"
end
G
- bundle :show
- expect(exitstatus).to eq(0) if exitstatus
+ bundle :list
end
it "does not attempt to install gems from :rbx when using --local" do
- simulate_platform "ruby"
- simulate_ruby_engine "ruby"
+ bundle_config "force_ruby_platform true"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "some_gem", :platform => :rbx
G
@@ -218,48 +554,85 @@ RSpec.describe "bundle install with platform conditionals" do
end
it "does not attempt to install gems from other rubies when using --local" do
- simulate_platform "ruby"
- simulate_ruby_engine "ruby"
- other_ruby_version_tag = RUBY_VERSION =~ /^1\.8/ ? :ruby_19 : :ruby_18
-
+ bundle_config "force_ruby_platform true"
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "some_gem", platform: :#{other_ruby_version_tag}
+ source "https://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 "prints a helpful warning when a dependency is unused on any platform" do
- simulate_platform "ruby"
- simulate_ruby_engine "ruby"
+ it "does not print a warning when a dependency is unused on a platform different from the current one" do
+ bundle_config "force_ruby_platform true"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby]
+ gem "myrack", :platform => [:windows, :jruby]
G
- bundle! "install"
+ bundle "install"
+
+ expect(err).to be_empty
- expect(out).to include <<-O.strip
-The dependency #{Gem::Dependency.new("rack", ">= 0")} will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
- O
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ myrack
+ #{checksums_section_when_enabled}
+ 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 "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 "https://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 mswin
+ simulate_platform "x86-mswin32" do
+ 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
+ gemfile <<-G
+ # Try to install gem with nil arch
+ source "http://localgemserver.test/"
+ gem "rcov"
+ G
- bundle :install, :artifice => "windows"
- expect(the_bundle).to include_gems "rcov 1.0.0"
+ 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
end
diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb
index b9d9683758..d937abd714 100644
--- a/spec/bundler/install/gemfile/ruby_spec.rb
+++ b/spec/bundler/install/gemfile/ruby_spec.rb
@@ -1,109 +1,157 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "ruby requirement" do
def locked_ruby_version
- Bundler::RubyVersion.from_string(Bundler::LockfileParser.new(lockfile).ruby_version)
+ Bundler::RubyVersion.from_string(Bundler::LockfileParser.new(File.read(bundled_app_lock)).ruby_version)
end
- # As discovered by https://github.com/bundler/bundler/issues/4147, there is
+ # 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://#{gem_repo1}"
- ruby "#{RUBY_VERSION}"
- gem "rack"
+ source "https://gem.repo1"
+ ruby "#{Gem.ruby_version}"
+ gem "myrack"
G
install_gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "#{RUBY_VERSION}"
- gem "rack"
- gem "rack-obama"
+ source "https://gem.repo1"
+ ruby "#{Gem.ruby_version}"
+ gem "myrack"
+ gem "myrack-obama"
G
- expect(exitstatus).to eq(0) if exitstatus
- expect(the_bundle).to include_gems "rack-obama 1.0"
+ expect(the_bundle).to include_gems "myrack-obama 1.0"
end
it "allows removing the ruby version requirement" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "~> #{RUBY_VERSION}"
- gem "rack"
+ source "https://gem.repo1"
+ ruby "~> #{Gem.ruby_version}"
+ gem "myrack"
G
expect(lockfile).to include("RUBY VERSION")
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo1}"
- ruby ">= 1.0.0"
- gem "rack"
+ source "https://gem.repo1"
+ ruby ">= #{current_ruby_minor}"
+ gem "myrack"
G
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
- simulate_ruby_version "5100"
-
install_gemfile <<-G
- source "file://#{gem_repo1}"
- ruby ">= 1.0.1"
- gem "rack"
+ source "https://gem.repo1"
+ ruby ">= #{Gem.ruby_version}"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo1}"
+ source "https://gem.repo1"
ruby ">= 1.0.0"
- gem "rack"
+ gem "myrack"
G
- expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+
+ RUBY VERSION
+ ruby 2.1.4
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
- simulate_ruby_version "5100"
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
install_gemfile <<-G
- source "file://#{gem_repo1}"
- ruby ">= 5000.0"
- gem "rack"
+ source "https://gem.repo1"
+ ruby ">= #{current_ruby_minor}"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 1.0.0"
- expect(locked_ruby_version.versions).to eq(["5100"])
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo1}"
- ruby "#{RUBY_VERSION}\\n \t\\n"
- gem "rack"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ ruby "#{Gem.ruby_version}\\n \t\\n"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "fails gracefully with malformed requirements" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
ruby ">= 0", "-.\\0"
- gem "rack"
+ gem "myrack"
G
- expect(out).to include("There was an error parsing") # i.e. DSL error, not error template
+ expect(err).to include("There was an error parsing") # i.e. DSL error, not error template
+ end
+
+ it "allows picking up ruby version from a file" do
+ create_file ".ruby-version", Gem.ruby_version.to_s
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ ruby file: ".ruby-version"
+ gem "myrack"
+ G
+
+ expect(lockfile).to include("RUBY VERSION")
+ end
+
+ it "reads the ruby version file from the right folder when nested Gemfiles are involved" do
+ create_file ".ruby-version", Gem.ruby_version.to_s
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ ruby file: ".ruby-version"
+ gem "myrack"
+ G
+
+ nested_dir = bundled_app(".ruby-lsp")
+
+ FileUtils.mkdir nested_dir
+
+ gemfile ".ruby-lsp/Gemfile", <<-G
+ eval_gemfile(File.expand_path("../Gemfile", __dir__))
+ G
+
+ bundle "install", dir: nested_dir
+
+ expect(bundled_app(".ruby-lsp/Gemfile.lock").read).to include("RUBY VERSION")
end
end
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb
index c5375b4abf..654d638e1f 100644
--- a/spec/bundler/install/gemfile/sources_spec.rb
+++ b/spec/bundler/install/gemfile/sources_spec.rb
@@ -1,323 +1,515 @@
# frozen_string_literal: true
-require "spec_helper"
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
+ # it contains myrack-obama 1.0.0 and myrack 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 source affinity" do
+ context "with sources given by a block" do
+ before do
+ # Oh no! Someone evil is trying to hijack myrack :(
+ # need this to be broken to check for correct source ordering
+ build_repo3 do
+ build_gem "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
+ end
- context "with multiple toplevel sources" do
- let(:repo3_rack_version) { "1.0.0" }
+ build_gem "myrack-obama" do |s|
+ s.add_dependency "myrack"
+ end
+ end
- before do
gemfile <<-G
- source "file://#{gem_repo3}"
- source "file://#{gem_repo1}"
- gem "rack-obama"
- gem "rack"
+ source "https://gem.repo3"
+ source "https://gem.repo1" do
+ gem "thin" # comes first to test name sorting
+ gem "myrack"
+ end
+ gem "myrack-obama" # should come from repo3!
G
- bundle "config major_deprecations true"
end
- it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first" do
- bundle :install
-
- expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.")
- expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
- expect(out).to include("Installed from: file:#{gem_repo1}")
- expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
+ 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("myrack-obama 1.0.0")
+ expect(the_bundle).to include_gems("myrack 1.0.0", source: "remote1")
end
- it "errors when disable_multisource is set" do
- bundle "config disable_multisource true"
- bundle :install
- expect(out).to include("Each source after the first must include a block")
- expect(exitstatus).to eq(4) if exitstatus
+ it "can cache and deploy" do
+ bundle :cache, artifice: "compact_index"
+
+ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/myrack-obama-1.0.gem")).to exist
+
+ bundle_config "deployment true"
+ bundle :install, artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0")
end
end
- context "when different versions of the same gem are in multiple sources" do
- let(:repo3_rack_version) { "1.2" }
-
+ context "with sources set by an option" do
before do
- gemfile <<-G
- source "file://#{gem_repo3}"
- source "file://#{gem_repo1}"
- gem "rack-obama"
- gem "rack", "1.0.0" # force it to install the working version in repo1
+ # Oh no! Someone evil is trying to hijack myrack :(
+ # need this to be broken to check for correct source ordering
+ build_repo3 do
+ build_gem "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
+ end
+
+ build_gem "myrack-obama" do |s|
+ s.add_dependency "myrack"
+ end
+ end
+
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo3"
+ gem "myrack-obama" # should come from repo3!
+ gem "myrack", :source => "https://gem.repo1"
G
- bundle "config major_deprecations true"
end
- it "warns about ambiguous gems, but installs anyway" do
- bundle :install
-
- expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.")
- expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
- expect(out).to include("Installed from: file:#{gem_repo1}")
- expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
+ it "installs the gems without any warning" do
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0")
end
end
- end
- context "with source affinity" do
- context "with sources given by a block" do
+ context "when a pinned gem has an indirect dependency in the pinned source" 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'"
+ build_repo3 do
+ build_gem "depends_on_myrack", "1.0.1" do |s|
+ s.add_dependency "myrack"
end
end
+ # we need a working myrack gem in repo3
+ update_repo gem_repo3 do
+ build_gem "myrack", "1.0.0"
+ end
+
gemfile <<-G
- source "file://#{gem_repo3}"
- source "file://#{gem_repo1}" do
- gem "thin" # comes first to test name sorting
- gem "rack"
+ source "https://gem.repo2"
+ source "https://gem.repo3" do
+ gem "depends_on_myrack"
end
- gem "rack-obama" # shoud come from repo3!
G
end
- it "installs the gems without any warning" do
- bundle :install
- expect(out).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")
+ 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_myrack 1.0.1", "myrack 1.0.0", source: "remote3")
+ end
end
- it "can cache and deploy" do
- bundle :package
+ 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 "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
+ end
+ end
+ end
+
+ it "installs from the same source without any warning" do
+ bundle :install, 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
+ expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3")
- bundle "install --deployment"
+ # In https://github.com/bundler/bundler/issues/3585 this failed
+ # when there is already a lockfile, and the gems are missing, so try again
+ system_gems []
+ bundle :install, artifice: "compact_index"
- expect(exitstatus).to eq(0) if exitstatus
- expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
+ expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3")
+ end
end
end
- context "with sources set by an option" do
+ context "when a pinned gem has an indirect dependency in a different source" 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'"
+ # In these tests, we need a working myrack gem in repo2 and not repo3
+
+ build_repo3 do
+ build_gem "depends_on_myrack", "1.0.1" do |s|
+ s.add_dependency "myrack"
end
end
+ build_repo gem_repo2 do
+ build_gem "myrack", "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_myrack"
+ 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_myrack 1.0.1", "myrack 1.0.0")
+ end
+ end
+ end
+
+ context "when a top-level gem can only be found in an scoped source" do
+ before do
+ build_repo2
+
+ build_repo3 do
+ build_gem "private_gem_1", "1.0.0"
+ build_gem "private_gem_2", "1.0.0"
+ end
+
gemfile <<-G
- source "file://#{gem_repo3}"
- gem "rack-obama" # should come from repo3!
- gem "rack", :source => "file://#{gem_repo1}"
+ source "https://gem.repo2"
+
+ gem "private_gem_1"
+
+ source "https://gem.repo3" do
+ gem "private_gem_2"
+ end
G
end
- it "installs the gems without any warning" do
- bundle :install
- expect(out).not_to include("Warning")
- expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
+ 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 "with an indirect dependency" do
+ context "when a top-level gem has an indirect dependency" do
before do
- build_repo gem_repo3 do
- build_gem "depends_on_rack", "1.0.1" do |s|
- s.add_dependency "rack"
+ build_repo gem_repo2 do
+ build_gem "depends_on_myrack", "1.0.1" do |s|
+ s.add_dependency "myrack"
end
end
+
+ build_repo3 do
+ build_gem "unrelated_gem", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "depends_on_myrack"
+
+ source "https://gem.repo3" do
+ gem "unrelated_gem"
+ end
+ G
end
- context "when the indirect dependency is in the pinned source" do
+ context "and the dependency is only in the top-level source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "myrack", "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_myrack 1.0.1", "myrack 1.0.0", "unrelated_gem 1.0.0")
+ expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 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
- # we need a working rack gem in repo3
update_repo gem_repo3 do
- build_gem "rack", "1.0.0"
+ build_gem "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
+ end
end
+ end
- gemfile <<-G
- source "file://#{gem_repo2}"
- source "file://#{gem_repo3}" do
- gem "depends_on_rack"
+ 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_myrack depends on myrack >= 0
+ and myrack >= 0 could not be found in rubygems repository https://gem.repo2/ or installed locally,
+ depends_on_myrack cannot be used.
+ So, because Gemfile depends on depends_on_myrack >= 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 "myrack", "1.0.0"
+ end
+
+ update_repo gem_repo3 do
+ build_gem "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
end
- G
+ end
end
- context "and not in any other sources" do
- before do
- build_repo(gem_repo2) {}
+ 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 'myrack'; puts MYRACK")).to eq("1.0.0")
+ expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", "unrelated_gem 1.0.0")
+ expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 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_repo3 do
+ build_gem "depends_on_depends_on_myrack", "1.0.1" do |s|
+ s.add_dependency "depends_on_myrack"
end
- it "installs from the same source without any warning" do
- bundle :install
- expect(out).not_to include("Warning")
- expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ build_gem "depends_on_myrack", "1.0.1" do |s|
+ s.add_dependency "myrack"
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
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ source "https://gem.repo3" do
+ gem "depends_on_depends_on_myrack"
end
+ G
+ end
- it "installs from the same source without any warning" do
- bundle :install
- expect(out).not_to include("Warning")
- expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ context "and the dependency is only in the top-level source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "myrack", "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_myrack 1.0.1", "depends_on_myrack 1.0.1", "myrack 1.0.0")
+ expect(the_bundle).to include_gems("myrack 1.0.0", source: "remote2")
+ expect(the_bundle).to include_gems("depends_on_depends_on_myrack 1.0.1", "depends_on_myrack 1.0.1", source: "remote3")
+ end
end
- context "when the indirect dependency is in a different source" do
+ context "and the dependency is only in a pinned source" do
before do
- # In these tests, we need a working rack gem in repo2 and not repo3
- build_repo gem_repo2 do
- build_gem "rack", "1.0.0"
+ build_repo2
+
+ update_repo gem_repo3 do
+ build_gem "myrack", "1.0.0"
end
end
- context "and not in any other sources" do
- before do
- gemfile <<-G
- source "file://#{gem_repo2}"
- source "file://#{gem_repo3}" do
- gem "depends_on_rack"
- end
- G
+ it "installs the dependency from the pinned source" do
+ bundle :install, artifice: "compact_index"
+ expect(the_bundle).to include_gems("depends_on_depends_on_myrack 1.0.1", "depends_on_myrack 1.0.1", "myrack 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 "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
+ end
end
- it "installs from the other source without any warning" do
- bundle :install
- expect(out).not_to include("Warning")
- expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ update_repo gem_repo3 do
+ build_gem "myrack", "1.0.0"
end
end
- context "and in yet another source" do
- before do
- gemfile <<-G
- source "file://#{gem_repo1}"
- source "file://#{gem_repo2}"
- source "file://#{gem_repo3}" do
- gem "depends_on_rack"
- end
- G
- 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_myrack 1.0.1", "depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3")
+ end
+ 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"
- it "installs from the other source and warns about ambiguous gems" do
- bundle "config major_deprecations true"
- bundle :install
- expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.")
- expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
- expect(out).to include("Installed from: file:#{gem_repo2}")
- expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ build_gem "webpacker", "5.2.1" do |s|
+ s.add_dependency "activesupport", ">= 5.2"
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.repo2"
+
+ gemspec :path => "#{lib_path("rails")}"
+
+ gem "webpacker", "~> 5.0"
+ G
+ end
- gemfile <<-G
- source "file://#{gem_repo3}" # contains depends_on_rack
- source "file://#{gem_repo2}" # contains broken rack
+ 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
- gem "depends_on_rack" # installed from gem_repo3
- gem "rack", :source => "file://#{gem_repo1}"
- G
+ context "when a pinned gem has an indirect dependency with more than one level of indirection in the default source " do
+ before do
+ build_repo3 do
+ build_gem "handsoap", "0.2.5.5" do |s|
+ s.add_dependency "nokogiri", ">= 1.2.3"
end
+ end
- it "installs the dependency from the pinned source without warning" do
- bundle :install
+ update_repo gem_repo2 do
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.add_dependency "racca", "~> 1.4"
+ end
- expect(out).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")
+ build_gem "racca", "1.5.2"
+ end
- # 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
+ gemfile <<-G
+ source "https://gem.repo2"
- expect(out).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 "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
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo3, "handsoap", "0.2.5.5"
+ c.checksum gem_repo2, "nokogiri", "1.11.1"
+ c.checksum gem_repo2, "racca", "1.5.2"
end
+
+ 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
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ handsoap!
+ nokogiri
+ #{checksums}
+ 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_repo3 do
build_gem "not_in_repo1", "1.0.0"
end
- gemfile <<-G
- source "file://#{gem_repo3}"
- gem "not_in_repo1", :source => "file://#{gem_repo1}"
+ 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
- bundle :install
- expect(out).to include("Could not find gem 'not_in_repo1'")
+ 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"
+ system_gems "myrack-0.9.1", "myrack-1.0.0", path: default_bundle_path
lockfile <<-L
GEM
- remote: file:#{gem_repo1}
- remote: file:#{gem_repo3}
+ remote: https://gem.repo1
specs:
- rack (0.9.1)
+
+ GEM
+ remote: https://gem.repo3
+ specs:
+ myrack (0.9.1)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- rack!
+ myrack!
L
gemfile <<-G
- source "file://#{gem_repo1}"
- source "file://#{gem_repo3}" do
- gem 'rack'
+ source "https://gem.repo1"
+ source "https://gem.repo3" do
+ gem 'myrack'
end
G
end
- # Reproduction of https://github.com/bundler/bundler/issues/3298
+ # 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")
+ expect(the_bundle).to include_gems("myrack 0.9.1")
end
end
@@ -326,113 +518,96 @@ RSpec.describe "bundle install with gems on multiple sources" do
build_lib "foo"
gemfile <<-G
- gem "rack", :source => "file://#{gem_repo1}"
+ source "https://gem.repo1"
+ gem "myrack", :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
+ bundle :install, artifice: "compact_index"
bundle %(exec ruby -e 'puts "OK"')
expect(out).to include("OK")
- expect(exitstatus).to eq(0) if exitstatus
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"
+ system_gems "myrack-0.9.1"
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack" # shoud come from repo1!
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo1"
+ gem "myrack" # should come from repo1!
G
end
it "installs the gems without any warning" do
- bundle :install
- expect(out).not_to include("Warning")
- expect(the_bundle).to include_gems("rack 1.0.0")
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("myrack 1.0.0")
end
end
context "when a single source contains multiple locked gems" do
before do
- # 1. With these gems,
+ # With these gems,
build_repo4 do
build_gem "foo", "0.1"
build_gem "bar", "0.1"
end
- # 2. Installing this gemfile will produce...
+ # Installing this gemfile...
gemfile <<-G
- source 'file://#{gem_repo1}'
- gem 'rack'
- gem 'foo', '~> 0.1', :source => 'file://#{gem_repo4}'
- gem 'bar', '~> 0.1', :source => 'file://#{gem_repo4}'
+ source 'https://gem.repo1'
+ gem 'myrack'
+ gem 'foo', '~> 0.1', :source => 'https://gem.repo4'
+ gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
G
- # 3. this lockfile.
- lockfile <<-L
- GEM
- remote: file:/Users/andre/src/bundler/bundler/tmp/gems/remote1/
- remote: file:/Users/andre/src/bundler/bundler/tmp/gems/remote4/
- specs:
- bar (0.1)
- foo (0.1)
- rack (1.0.0)
-
- PLATFORMS
- ruby
-
- DEPENDENCIES
- bar (~> 0.1)!
- foo (~> 0.1)!
- rack
- L
-
- bundle "install --path ../gems/system"
+ bundle_config "path ../gems/system"
+ bundle :install, artifice: "compact_index"
- # 4. Then we add some new versions...
- update_repo4 do
+ # And then we add some new versions...
+ build_repo4 do
build_gem "foo", "0.2"
build_gem "bar", "0.3"
end
end
it "allows them to be unlocked separately" do
- # 5. and install this gemfile, updating only foo.
- install_gemfile <<-G
- source 'file://#{gem_repo1}'
- gem 'rack'
- gem 'foo', '~> 0.2', :source => 'file://#{gem_repo4}'
- gem 'bar', '~> 0.1', :source => 'file://#{gem_repo4}'
+ # And install this gemfile, updating only foo.
+ install_gemfile <<-G, artifice: "compact_index"
+ source 'https://gem.repo1'
+ gem 'myrack'
+ gem 'foo', '~> 0.2', :source => 'https://gem.repo4'
+ gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
G
- # 6. Which should update foo to 0.2, but not the (locked) bar 0.1
- expect(the_bundle).to include_gems("foo 0.2")
- expect(the_bundle).to include_gems("bar 0.1")
+ # 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_repo3
+ build_repo3 do
+ build_gem "myrack"
+ end
+
build_lib "path1"
build_lib "path2"
build_git "git1"
build_git "git2"
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo1"
gem "rails"
- source "file://#{gem_repo3}" do
- gem "rack"
+ source "https://gem.repo3" do
+ gem "myrack"
end
gem "path1", :path => "#{lib_path("path1-1.0")}"
@@ -443,7 +618,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
end
it "does not re-resolve" do
- bundle :install, :verbose => true
+ 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
@@ -452,67 +627,687 @@ RSpec.describe "bundle install with gems on multiple sources" do
context "when a gem is installed to system gems" do
before do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
context "and the gemfile changes" do
it "is still able to find that gem from remote sources" do
- source_uri = "file://#{gem_repo1}"
- second_uri = "file://#{gem_repo4}"
-
build_repo4 do
- build_gem "rack", "2.0.1.1.forked"
+ build_gem "myrack", "2.0.1.1.forked"
build_gem "thor", "0.19.1.1.forked"
end
# When this gemfile is installed...
- gemfile <<-G
- source "#{source_uri}"
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo1"
- source "#{second_uri}" do
- gem "rack", "2.0.1.1.forked"
+ source "https://gem.repo4" do
+ gem "myrack", "2.0.1.1.forked"
gem "thor"
end
- gem "rack-obama"
+ gem "myrack-obama"
G
- # It creates this lockfile.
- lockfile <<-L
- GEM
- remote: #{source_uri}/
- remote: #{second_uri}/
- specs:
- rack (2.0.1.1.forked)
- rack-obama (1.0)
- rack
- thor (0.19.1.1.forked)
-
- PLATFORMS
- ruby
-
- DEPENDENCIES
- rack (= 2.0.1.1.forked)!
- rack-obama
- thor!
- L
-
# Then we change the Gemfile by adding a version to thor
gemfile <<-G
- source "#{source_uri}"
+ source "https://gem.repo1"
- source "#{second_uri}" do
- gem "rack", "2.0.1.1.forked"
+ source "https://gem.repo4" do
+ gem "myrack", "2.0.1.1.forked"
gem "thor", "0.19.1.1.forked"
end
- gem "rack-obama"
+ gem "myrack-obama"
G
- # But we should still be able to find rack 2.0.1.1.forked and install it
- bundle! :install
+ # But we should still be able to find myrack 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 "myrack"
+ G
+
+ build_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "bar"
+ end
+
+ build_lib("gemspec_test", path: tmp("gemspec_test")) do |s|
+ s.add_dependency "bar", "=1.0.0"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://gem.repo2"
+ gem "myrack"
+ gemspec :path => "#{tmp("gemspec_test")}"
+ G
+ end
+
+ it "conservatively installs the existing locked version" do
+ expect(the_bundle).to include_gems("myrack 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("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("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
+
+ ruby <<~R, raise_on_error: false
+ require 'bundler/setup'
+ R
+
+ 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" do
+ it "raises, suggesting a source block" do
+ build_repo4 do
+ build_gem "depends_on_myrack" do |s|
+ s.add_dependency "myrack"
+ end
+ build_gem "myrack"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index_extra_api", raise_on_error: false
+ source "https://global.source"
+
+ source "https://scoped.source/extra" do
+ gem "depends_on_myrack"
+ end
+
+ source "https://scoped.source" do
+ gem "thin"
+ end
+ G
+ expect(last_command).to be_failure
+ expect(err).to eq <<~EOS.strip
+ The gem 'myrack' was found in multiple relevant sources.
+ * rubygems repository https://scoped.source/
+ * rubygems repository https://scoped.source/extra/
+ 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 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://gem.repo4"
+
+ gem "ruport", "= 1.7.0.3", :source => "https://gem.repo4/extra"
+ G
+ end
+
+ it "handles that fine" do
+ bundle "install", artifice: "compact_index_extra"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "pdf-writer", "1.1.8"
+ c.checksum gem_repo2, "ruport", "1.7.0.3"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ pdf-writer (1.1.8)
+
+ GEM
+ remote: https://gem.repo4/extra/
+ specs:
+ ruport (1.7.0.3)
+ pdf-writer (= 1.1.8)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ruport (= 1.7.0.3)!
+ #{checksums}
+ 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://gem.repo4"
+
+ gem "ruport", "= 1.7.0.3", :source => "https://gem.repo4/extra"
+ G
+ end
+
+ it "handles that fine" do
+ bundle "install", artifice: "compact_index_extra"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "pdf-writer", "1.1.8"
+ c.checksum gem_repo2, "ruport", "1.7.0.3"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ pdf-writer (1.1.8)
+
+ GEM
+ remote: https://gem.repo4/extra/
+ specs:
+ ruport (1.7.0.3)
+ pdf-writer (= 1.1.8)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ruport (= 1.7.0.3)!
+ #{checksums}
+ 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://gem.repo4"
+
+ gem "pdf-writer", "= 1.1.8"
+ G
+ end
+
+ it "handles that fine" do
+ bundle "install --verbose", artifice: "endpoint"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "pdf-writer", "1.1.8"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ pdf-writer (1.1.8)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ pdf-writer (= 1.1.8)
+ #{checksums}
+ 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
+
+ context "when a gem has versions in two sources, but only the locked one has updates" do
+ let(:original_lockfile) do
+ <<~L
+ GEM
+ remote: https://main.source/
+ specs:
+ activesupport (1.0)
+ bigdecimal
+ bigdecimal (1.0.0)
+
+ GEM
+ remote: https://main.source/extra/
+ specs:
+ foo (1.0)
+ bigdecimal
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ before do
+ build_repo3 do
+ build_gem "activesupport" do |s|
+ s.add_dependency "bigdecimal"
+ end
+
+ build_gem "bigdecimal", "1.0.0"
+ build_gem "bigdecimal", "3.3.1"
+ end
+
+ build_repo4 do
+ build_gem "foo" do |s|
+ s.add_dependency "bigdecimal"
+ end
+
+ build_gem "bigdecimal", "1.0.0"
+ end
+
+ gemfile <<~G
+ source "https://main.source"
+
+ gem "activesupport"
+
+ source "https://main.source/extra" do
+ gem "foo"
+ end
+ G
+
+ lockfile original_lockfile
+ end
+
+ it "properly upgrades the lockfile when updating that specific gem" do
+ bundle "update bigdecimal --conservative", artifice: "compact_index_extra_api", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo3.to_s }
+
+ expect(lockfile).to eq original_lockfile.gsub("bigdecimal (1.0.0)", "bigdecimal (3.3.1)")
+ end
+ end
+
+ context "when switching a gem with components from rubygems to git source" do
+ before do
+ build_repo2 do
+ build_gem "rails", "7.0.0" do |s|
+ s.add_dependency "actionpack", "7.0.0"
+ s.add_dependency "activerecord", "7.0.0"
+ end
+ build_gem "actionpack", "7.0.0"
+ build_gem "activerecord", "7.0.0"
+ # propshaft also depends on actionpack, creating the conflict
+ build_gem "propshaft", "1.0.0" do |s|
+ s.add_dependency "actionpack", ">= 7.0.0"
+ end
+ end
+
+ build_git "rails", "7.0.0", path: lib_path("rails") do |s|
+ s.add_dependency "actionpack", "7.0.0"
+ s.add_dependency "activerecord", "7.0.0"
+ end
+
+ build_git "actionpack", "7.0.0", path: lib_path("rails")
+ build_git "activerecord", "7.0.0", path: lib_path("rails")
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "rails", "7.0.0"
+ gem "propshaft"
+ G
+ end
+
+ it "moves component gems to the git source in the lockfile" do
+ expect(lockfile).to include("remote: https://gem.repo2")
+ expect(lockfile).to include("rails (7.0.0)")
+ expect(lockfile).to include("actionpack (7.0.0)")
+ expect(lockfile).to include("activerecord (7.0.0)")
+ expect(lockfile).to include("propshaft (1.0.0)")
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "rails", git: "#{lib_path("rails")}"
+ gem "propshaft"
+ G
+
+ bundle "install"
+
+ expect(lockfile).to include("remote: #{lib_path("rails")}")
+ expect(lockfile).to include("rails (7.0.0)")
+ expect(lockfile).to include("actionpack (7.0.0)")
+ expect(lockfile).to include("activerecord (7.0.0)")
+
+ # Component gems should NOT remain in the GEM section
+ # Extract just the GEM section by splitting on GIT first, then GEM
+ gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0]
+ expect(gem_section).not_to include("actionpack (7.0.0)")
+ expect(gem_section).not_to include("activerecord (7.0.0)")
+ end
+ end
+
+ context "when switching a gem with components from rubygems to path source" do
+ before do
+ build_repo2 do
+ build_gem "rails", "7.0.0" do |s|
+ s.add_dependency "actionpack", "7.0.0"
+ s.add_dependency "activerecord", "7.0.0"
+ end
+ build_gem "actionpack", "7.0.0"
+ build_gem "activerecord", "7.0.0"
+ # propshaft also depends on actionpack, creating the conflict
+ build_gem "propshaft", "1.0.0" do |s|
+ s.add_dependency "actionpack", ">= 7.0.0"
+ end
+ end
+
+ build_lib "rails", "7.0.0", path: lib_path("rails") do |s|
+ s.add_dependency "actionpack", "7.0.0"
+ s.add_dependency "activerecord", "7.0.0"
+ end
+
+ build_lib "actionpack", "7.0.0", path: lib_path("rails")
+ build_lib "activerecord", "7.0.0", path: lib_path("rails")
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "rails", "7.0.0"
+ gem "propshaft"
+ G
+ end
+
+ it "moves component gems to the path source in the lockfile" do
+ expect(lockfile).to include("remote: https://gem.repo2")
+ expect(lockfile).to include("rails (7.0.0)")
+ expect(lockfile).to include("actionpack (7.0.0)")
+ expect(lockfile).to include("activerecord (7.0.0)")
+ expect(lockfile).to include("propshaft (1.0.0)")
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "rails", path: "#{lib_path("rails")}"
+ gem "propshaft"
+ G
+
+ bundle "install"
+
+ expect(lockfile).to include("remote: #{lib_path("rails")}")
+ expect(lockfile).to include("rails (7.0.0)")
+ expect(lockfile).to include("actionpack (7.0.0)")
+ expect(lockfile).to include("activerecord (7.0.0)")
+
+ # Component gems should NOT remain in the GEM section
+ # Extract just the GEM section by splitting appropriately
+ gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0]
+ expect(gem_section).not_to include("actionpack (7.0.0)")
+ expect(gem_section).not_to include("activerecord (7.0.0)")
+ end
+ end
+
+ context "when a scoped rubygems source is missing a transitive dependency" do
+ before do
+ build_repo2 do
+ build_gem "fallback_dep", "1.0.0"
+ build_gem "foo", "1.0.0"
+ end
+
+ build_repo3 do
+ build_gem "private_parent", "1.0.0" do |s|
+ s.add_dependency "fallback_dep"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "foo"
+
+ source "https://gem.repo3" do
+ gem "private_parent", "1.0.0"
+ end
+ G
+
+ bundle :install, artifice: "compact_index"
+ end
+
+ it "falls back to the default rubygems source for that dependency" do
+ build_repo2 do
+ build_gem "foo", "2.0.0"
+ end
+
+ system_gems []
+
+ bundle "update foo", artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("private_parent 1.0.0", "fallback_dep 1.0.0", "foo 2.0.0")
+ expect(the_bundle).to include_gems("private_parent 1.0.0", source: "remote3")
+ expect(the_bundle).to include_gems("fallback_dep 1.0.0", source: "remote2")
+ end
+ end
+
+ context "when a path gem has a transitive dependency that does not exist in the path source" do
+ before do
+ build_repo2 do
+ build_gem "missing_dep", "1.0.0"
+ build_gem "foo", "1.0.0"
+ end
+
+ build_lib "parent_gem", "1.0.0", path: lib_path("parent_gem") do |s|
+ s.add_dependency "missing_dep"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "foo"
+
+ gem "parent_gem", path: "#{lib_path("parent_gem")}"
+ G
+
+ bundle :install, artifice: "compact_index"
+ end
+
+ it "falls back to the default rubygems source for that dependency when updating" do
+ build_repo2 do
+ build_gem "foo", "2.0.0"
+ end
+
+ system_gems []
+
+ bundle "update foo", artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("parent_gem 1.0.0", "missing_dep 1.0.0", "foo 2.0.0")
+ expect(the_bundle).to include_gems("parent_gem 1.0.0", source: "path@#{lib_path("parent_gem")}")
+ expect(the_bundle).to include_gems("missing_dep 1.0.0", source: "remote2")
+ end
+ end
+
+ context "when a git gem has a transitive dependency that does not exist in the git source" do
+ before do
+ build_repo2 do
+ build_gem "missing_dep", "1.0.0"
+ build_gem "foo", "1.0.0"
+ end
+
+ build_git "parent_gem", "1.0.0", path: lib_path("parent_gem") do |s|
+ s.add_dependency "missing_dep"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "foo"
+
+ gem "parent_gem", git: "#{lib_path("parent_gem")}"
+ G
+
+ bundle :install, artifice: "compact_index"
+ end
+
+ it "falls back to the default rubygems source for that dependency when updating" do
+ build_repo2 do
+ build_gem "foo", "2.0.0"
+ end
+
+ system_gems []
+
+ bundle "update foo", artifice: "compact_index"
+
+ expect(the_bundle).to include_gems("parent_gem 1.0.0", "missing_dep 1.0.0", "foo 2.0.0")
+ expect(the_bundle).to include_gems("parent_gem 1.0.0", source: "git@#{lib_path("parent_gem")}")
+ expect(the_bundle).to include_gems("missing_dep 1.0.0", source: "remote2")
end
end
end
diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb
index cc6c82c0ff..97b1d233bf 100644
--- a/spec/bundler/install/gemfile/specific_platform_spec.rb
+++ b/spec/bundler/install/gemfile/specific_platform_spec.rb
@@ -1,114 +1,1972 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "bundle install with specific_platform enabled" do
- before do
- bundle "config specific_platform true"
-
- 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 = "x86-mingw32" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-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" }
+RSpec.describe "bundle install with specific platforms" do
+ let(:google_protobuf) { <<-G }
+ source "https://gem.repo2"
+ gem "google-protobuf"
+ G
- 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 = "x86-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") {|s| s.platform = "x86-mingw32" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.5")
+ 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_platforms).to include("universal-darwin")
+ 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 include(
+ "google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin"
+ )
+ end
+ end
- build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86_64-linux" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-mingw32" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-linux" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x64-mingw32" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.4")
-
- build_gem("google-protobuf", "3.0.0.alpha.5.0.3")
- build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86_64-linux" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-mingw32" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-linux" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x64-mingw32" }
- build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "universal-darwin" }
+ it "still installs the platform specific variant when locked only to ruby, and the platform specific variant has different dependencies" do
+ simulate_platform "x86_64-darwin-15" do
+ build_repo4 do
+ build_gem("sass-embedded", "1.72.0") do |s|
+ s.add_dependency "rake"
+ end
- build_gem("google-protobuf", "3.0.0.alpha.4.0")
- build_gem("google-protobuf", "3.0.0.alpha.3.1.pre")
- build_gem("google-protobuf", "3.0.0.alpha.3")
- build_gem("google-protobuf", "3.0.0.alpha.2.0")
- build_gem("google-protobuf", "3.0.0.alpha.1.1")
- build_gem("google-protobuf", "3.0.0.alpha.1.0")
+ build_gem("sass-embedded", "1.72.0") do |s|
+ s.platform = "x86_64-darwin-15"
+ end
- build_gem("facter", "2.4.6")
- build_gem("facter", "2.4.6") do |s|
- s.platform = "universal-darwin"
- s.add_runtime_dependency "CFPropertyList"
+ build_gem "rake"
end
- build_gem("CFPropertyList")
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "sass-embedded"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ rake (1.0)
+ sass-embedded (1.72.0)
+ rake
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ sass-embedded
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose"
+ expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version")
+ expect(out).to include("Installing sass-embedded 1.72.0 (x86_64-darwin-15)")
+
+ expect(the_bundle).to include_gem("sass-embedded 1.72.0 x86_64-darwin-15")
end
end
- let(:google_protobuf) { <<-G }
- source "file:#{gem_repo2}"
- gem "google-protobuf"
- G
+ 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
+
+ # Consistent location to install and look for gems
+ bundle_config "path vendor/bundle"
+
+ install_gemfile(google_protobuf)
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ google-protobuf (3.0.0.alpha.5.0.5.1)
- context "when on a darwin machine" do
- before { simulate_platform "x86_64-darwin-15" }
+ PLATFORMS
+ ruby
- it "locks to both the specific darwin platform and ruby" do
- install_gemfile!(google_protobuf)
- expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")])
+ DEPENDENCIES
+ google-protobuf
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ # force strict usage of the lockfile by setting frozen mode
+ bundle_config "frozen true"
+
+ # 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")
- 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
+
+ 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
+
+ # Consistent location to install and look for gems
+ bundle_config "path vendor/bundle"
+
+ gemfile google_protobuf
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.4.0"
+ end
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ google-protobuf (3.0.0.alpha.4.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ google-protobuf
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update"
+ expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version")
+
+ checksums.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.5.0.5.1"
+
+ # 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: https://gem.repo2/
+ specs:
+ google-protobuf (3.0.0.alpha.5.0.5.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ google-protobuf
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when running on a legacy lockfile locked only to ruby" do
+ # Exercises the legacy lockfile path (use_exact_resolved_specifications? = false)
+ # because most_specific_locked_platform is ruby, matching the generic platform.
+ # Key insight: when target (arm64-darwin-22) != platform (ruby), the code tries
+ # both platforms before falling back, preserving lockfile integrity.
+
+ 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 "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://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"
+ expect(the_bundle).to include_gem("nokogiri 1.3.10")
+ expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin")
+ end
+
+ it "still installs the generic ruby variant if necessary, even in frozen mode" do
+ bundle "install", env: { "BUNDLE_FROZEN" => "true" }
+ expect(the_bundle).to include_gem("nokogiri 1.3.10")
+ expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin")
+ end
+ end
+
+ context "when platform-specific gem has incompatible required_ruby_version" do
+ # Key insight: candidate_platforms tries [target, platform, ruby] in order.
+ # Ruby platform is last since it requires compilation, but works when
+ # precompiled gems are incompatible with the current Ruby version.
+ #
+ # Note: This fix requires the lockfile to include both ruby and platform-
+ # specific variants (typical after `bundle lock --add-platform`). If the
+ # lockfile only has platform-specific gems, frozen mode cannot help because
+ # Bundler.setup would still expect the locked (incompatible) gem.
+
+ # Exercises the exact spec path (use_exact_resolved_specifications? = true)
+ # because lockfile has platform-specific entry as most_specific_locked_platform
+ it "falls back to ruby platform in frozen mode when lockfile includes both variants" do
+ build_repo4 do
+ build_gem "nokogiri", "1.18.10"
+ build_gem "nokogiri", "1.18.10" do |s|
+ s.platform = "x86_64-linux"
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ # Lockfile has both ruby and platform-specific gem (typical after `bundle lock --add-platform`)
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.18.10)
+ nokogiri (1.18.10-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "install", env: { "BUNDLE_FROZEN" => "true" }
+ expect(the_bundle).to include_gem("nokogiri 1.18.10")
+ expect(the_bundle).not_to include_gem("nokogiri 1.18.10 x86_64-linux")
+ end
+ 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_dependency "libv8"
+ end
+ end
+
+ # Consistent location to install and look for gems
+ bundle_config "path vendor/bundle"
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "libv8"
+ G
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ libv8 (8.4.255.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
- it "caches both the universal-darwin and ruby gems when --all-platforms is passed" do
+ bundle "install --verbose"
+ expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version")
+ expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)")
+
+ bundle "add mini_racer --verbose"
+ 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://gem.repo4"
+ gem "grpc"
+ G
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ grpc (1.50.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ grpc
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose", artifice: "compact_index_precompiled_before"
+ expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version")
+ 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! "package --all-platforms"
- expect([cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1"), cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")]).
- to all(exist)
+ 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 "cache_all_platforms true"
+ bundle "cache"
+ expect(cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")).to exist
- it "uses the platform-specific gem with extra dependencies" do
- install_gemfile! <<-G
- source "file:#{gem_repo2}"
+ 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 "https://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
+ #{lockfile_platforms("java")}
+
+ DEPENDENCIES
+ pg_array_parser!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ 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 "https://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("ruby"), pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_platforms).to include("universal-darwin")
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",
- "facter-2.4.6-universal-darwin"])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to include("CFPropertyList-1.0",
+ "facter-2.4.6-universal-darwin")
end
+ end
- context "when adding a platform via lock --add_platform" do
- it "adds the foreign platform" do
- install_gemfile!(google_protobuf)
- bundle! "lock --add-platform=#{x64_mingw}"
+ 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
- expect(the_bundle.locked_gems.platforms).to eq([rb, x64_mingw, 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
+ 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-mingw-ucrt"
+
+ expect(the_bundle.locked_platforms).to include("x64-mingw-ucrt", "universal-darwin")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to include(*%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
- ))
+ google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw-ucrt
+ ])
end
+ end
- it "falls back on plain ruby when that version doesnt have a platform-specific gem" do
- install_gemfile!(google_protobuf)
- bundle! "lock --add-platform=#{java}"
+ 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, rb, 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
- ))
+ expect(the_bundle.locked_platforms).to include("java", "universal-darwin")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to include(
+ "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, in absence of a lockfile, 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 "https://gem.repo2"
+
+ gem "sorbet-static", "0.5.6403"
+ G
+
+ bundle "install --verbose"
+ end
+
+ it "installs sorbet-static, which does not provide a pure ruby variant, in presence of a lockfile, 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 "https://gem.repo2"
+
+ gem "sorbet-static", "0.5.6403"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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 "https://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 https://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 "shows a platform mismatch hint when the current platform is not in the lockfile's platforms" do
+ build_repo4 do
+ build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux-musl" }
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "sorbet-static", "0.5.6433"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ sorbet-static (0.5.6433-x86_64-linux-musl)
+
+ PLATFORMS
+ x86_64-linux-musl
+
+ DEPENDENCIES
+ sorbet-static (= 0.5.6433)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-linux" do
+ bundle "install", raise_on_error: false
+ end
+
+ expect(err).to include("Your current platform (x86_64-linux) is not included in the lockfile's platforms (x86_64-linux-musl)")
+ expect(err).to include("bundle lock --add-platform x86_64-linux")
+ 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 "https://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 https://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 "https://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 https://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_dependency "sorbet", "= 0.5.10160"
+ s.add_dependency "sorbet-runtime", "= 0.5.10160"
+ end
+
+ build_gem("sorbet", "0.5.10160") do |s|
+ s.add_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 "https://gem.repo4"
+
+ gem "sorbet-static-and-runtime"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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
+
+ bundle "update"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "sorbet", "0.5.10160"
+ c.checksum gem_repo4, "sorbet-runtime", "0.5.10160"
+ c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local
+ c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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
+ #{local_platform}
+
+ DEPENDENCIES
+ sorbet-static-and-runtime
+ #{checksums}
+ 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 "https://gem.repo4"
+
+ gem "nokogiri"
+ gem "sorbet-static"
+ G
+ end
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.13.0", "x86_64-darwin"
+ c.checksum gem_repo4, "sorbet-static", "0.5.10601", "x86_64-darwin"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://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-static
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-darwin-22" do
+ bundle "update --conservative nokogiri"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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
+ #{checksums}
+ 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_dependency "sorbet", "= 0.5.10160"
+ s.add_dependency "sorbet-runtime", "= 0.5.10160"
+ end
+
+ build_gem("sorbet", "0.5.10160") do |s|
+ s.add_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 "https://gem.repo4"
+
+ gem "sorbet-static-and-runtime"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "sorbet", "0.5.10160"
+ c.checksum gem_repo4, "sorbet-runtime", "0.5.10160"
+ c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local
+ c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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
+ #{local_platform}
+
+ DEPENDENCIES
+ sorbet-static-and-runtime
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile when adding a gem that introduces dependencies with no ruby platform variants transitively" do
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem "nokogiri", "1.18.2"
+
+ build_gem "nokogiri", "1.18.2" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem("sorbet", "0.5.11835") do |s|
+ s.add_dependency "sorbet-static", "= 0.5.11835"
+ end
+
+ build_gem "sorbet-static", "0.5.11835" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ gem "sorbet"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.18.2)
+ nokogiri (1.18.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.18.2", "x86_64-linux"
+ c.checksum gem_repo4, "sorbet", "0.5.11835"
+ c.checksum gem_repo4, "sorbet-static", "0.5.11835", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.18.2)
+ nokogiri (1.18.2-x86_64-linux)
+ sorbet (0.5.11835)
+ sorbet-static (= 0.5.11835)
+ sorbet-static (0.5.11835-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ sorbet
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "automatically fixes the lockfile if multiple platforms locked, but no valid versions of direct dependencies for all of them" do
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "arm-linux"
+ end
+
+ build_gem "sorbet-static", "0.5.10696" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ gem "sorbet-static"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0-arm-linux)
+ nokogiri (1.14.0-x86_64-linux)
+ sorbet-static (0.5.10696-x86_64-linux)
+
+ PLATFORMS
+ aarch64-linux
+ arm-linux
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ sorbet-static
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux"
+ c.checksum gem_repo4, "sorbet-static", "0.5.10696", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0-x86_64-linux)
+ sorbet-static (0.5.10696-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ sorbet-static
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ 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 "https://gem.repo4"
+
+ gem "sorbet-static", "= 0.5.10549"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-20"
+ c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21"
+ end
+
+ # Make sure the lockfile is missing sorbet-static-0.5.10549-universal-darwin-21
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ sorbet-static (0.5.10549-universal-darwin-20)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ sorbet-static (= 0.5.10549)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "automatically fixes the lockfile if locked only to ruby, and some locked specs don't meet locked dependencies" do
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem("ibandit", "0.7.0") do |s|
+ s.add_dependency "i18n", "~> 0.7.0"
+ end
+
+ build_gem("i18n", "0.7.0.beta1")
+ build_gem("i18n", "0.7.0")
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "ibandit", "~> 0.7.0"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ i18n (0.7.0.beta1)
+ ibandit (0.7.0)
+ i18n (~> 0.7.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ ibandit (~> 0.7.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --update i18n"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ i18n (0.7.0)
+ ibandit (0.7.0)
+ i18n (~> 0.7.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ ibandit (~> 0.7.0)
+
+ 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 "https://gem.repo4"
+
+ gem "nokogiri"
+
+ gem "tzinfo", "~> 1.2", platform: :#{not_local_tag}
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.13.8"
+ c.checksum gem_repo4, "nokogiri", "1.13.8", Gem::Platform.local
+ end
+
+ original_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.13.8)
+ nokogiri (1.13.8-#{Gem::Platform.local})
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ nokogiri
+ tzinfo (~> 1.2)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ bundle "lock --update"
+
+ expect(lockfile).to eq(original_lockfile)
+ end
+
+ it "does not remove ruby if gems for other platforms, and not present in the lockfile, exist in the Gemfile, and the lockfile only has ruby" do
+ build_repo4 do
+ build_gem "nokogiri", "1.13.8"
+ build_gem "nokogiri", "1.13.8" do |s|
+ s.platform = "arm64-darwin"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+
+ gem "tzinfo", "~> 1.2", platforms: %i[windows jruby]
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.13.8"
+ end
+
+ original_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.13.8)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ nokogiri
+ tzinfo (~> 1.2)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ simulate_platform "arm64-darwin-23" do
+ bundle "lock --update"
+ end
+
+ 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 "myrack", "3.0.7"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "concurrent-ruby"
+ gem "myrack"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "concurrent-ruby", "1.2.2"
+ c.checksum gem_repo4, "myrack", "3.0.7"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ concurrent-ruby (1.2.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ concurrent-ruby
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ concurrent-ruby (1.2.2)
+ myrack (3.0.7)
+
+ PLATFORMS
+ #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])}
+
+ DEPENDENCIES
+ concurrent-ruby
+ myrack
+ #{checksums}
+ 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
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ 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: https://gem.repo2/
+ 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 adds the ruby variant to 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 "https://gem.repo4"
+
+ gem "nokogiri", "1.14.0"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri (= 1.14.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.0"
+ c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux"
+ end
+
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0)
+ nokogiri (1.14.0-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri (= 1.14.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "automatically fixes the lockfile when only ruby platform locked, and adding a dependency with subdependencies not valid for ruby" do
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem("sorbet", "0.5.10160") do |s|
+ s.add_dependency "sorbet-static", "= 0.5.10160"
+ end
+
+ build_gem("sorbet-static", "0.5.10160") do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "sorbet"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ sorbet (0.5.10160)
+ sorbet-static (= 0.5.10160)
+ sorbet-static (0.5.10160-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ sorbet
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "locks specific platforms automatically" do
+ 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"
+ end
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "arm-linux"
+ end
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "x64-mingw-ucrt"
+ end
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "java"
+ end
+
+ build_gem "sorbet-static", "0.5.10696" do |s|
+ s.platform = "x86_64-linux"
+ end
+ build_gem "sorbet-static", "0.5.10696" do |s|
+ s.platform = "universal-darwin-22"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.14.0"
+ c.checksum gem_repo4, "nokogiri", "1.14.0", "arm-linux"
+ c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux"
+ end
+
+ # locks all compatible platforms, excluding Java and Windows
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0)
+ nokogiri (1.14.0-arm-linux)
+ nokogiri (1.14.0-x86_64-linux)
+
+ PLATFORMS
+ arm-linux
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ gem "sorbet-static"
+ G
+
+ FileUtils.rm bundled_app_lock
+
+ bundle "lock"
+
+ checksums.delete "nokogiri", "arm-linux"
+ checksums.checksum gem_repo4, "sorbet-static", "0.5.10696", "universal-darwin-22"
+ checksums.checksum gem_repo4, "sorbet-static", "0.5.10696", "x86_64-linux"
+
+ # locks only platforms compatible with all gems in the bundle
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0)
+ nokogiri (1.14.0-x86_64-linux)
+ sorbet-static (0.5.10696-universal-darwin-22)
+ sorbet-static (0.5.10696-x86_64-linux)
+
+ PLATFORMS
+ universal-darwin-22
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ sorbet-static
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "does not fail when a platform variant is incompatible with the current ruby and another equivalent platform specific variant is part of the resolution" do
+ build_repo4 do
+ build_gem "nokogiri", "1.15.5"
+
+ build_gem "nokogiri", "1.15.5" do |s|
+ s.platform = "x86_64-linux"
+ s.required_ruby_version = "< #{current_ruby_minor}.dev"
+ end
+
+ build_gem "sass-embedded", "1.69.5"
+
+ build_gem "sass-embedded", "1.69.5" do |s|
+ s.platform = "x86_64-linux-gnu"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ gem "sass-embedded"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.15.5"
+ c.checksum gem_repo4, "sass-embedded", "1.69.5"
+ c.checksum gem_repo4, "sass-embedded", "1.69.5", "x86_64-linux-gnu"
+ end
+
+ simulate_platform "x86_64-linux" do
+ bundle "install --verbose"
+
+ # locks all compatible platforms, excluding Java and Windows
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.15.5)
+ sass-embedded (1.69.5)
+ sass-embedded (1.69.5-x86_64-linux-gnu)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ sass-embedded
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "does not add ruby platform gem if it brings extra dependencies not resolved originally" do
+ build_repo4 do
+ build_gem "nokogiri", "1.15.5" do |s|
+ s.add_dependency "mini_portile2", "~> 2.8.2"
+ end
+
+ build_gem "nokogiri", "1.15.5" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "nokogiri", "1.15.5", "x86_64-linux"
+ end
+
+ simulate_platform "x86_64-linux" do
+ bundle "install --verbose"
+
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.15.5-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform|
+ describe "on host platform #{host_platform}" do
+ it "adds current musl platform" do
+ build_repo4 do
+ build_gem "rcee_precompiled", "0.5.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "rcee_precompiled", "0.5.0" do |s|
+ s.platform = "x86_64-linux-musl"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "rcee_precompiled", "0.5.0"
+ G
+
+ simulate_platform host_platform do
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux"
+ c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux-musl"
+ end
+
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ rcee_precompiled (0.5.0-x86_64-linux)
+ rcee_precompiled (0.5.0-x86_64-linux-musl)
+
+ PLATFORMS
+ x86_64-linux
+ x86_64-linux-musl
+
+ DEPENDENCIES
+ rcee_precompiled (= 0.5.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+ end
+ end
+
+ it "adds current musl platform, when there are also gnu variants" do
+ build_repo4 do
+ build_gem "rcee_precompiled", "0.5.0" do |s|
+ s.platform = "x86_64-linux-gnu"
+ end
+
+ build_gem "rcee_precompiled", "0.5.0" do |s|
+ s.platform = "x86_64-linux-musl"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "rcee_precompiled", "0.5.0"
+ G
+
+ simulate_platform "x86_64-linux-musl" do
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux-gnu"
+ c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux-musl"
+ end
+
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ rcee_precompiled (0.5.0-x86_64-linux-gnu)
+ rcee_precompiled (0.5.0-x86_64-linux-musl)
+
+ PLATFORMS
+ x86_64-linux-gnu
+ x86_64-linux-musl
+
+ DEPENDENCIES
+ rcee_precompiled (= 0.5.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "does not add current platform if there's an equivalent less specific platform among the ones resolved" do
+ build_repo4 do
+ build_gem "rcee_precompiled", "0.5.0" do |s|
+ s.platform = "universal-darwin"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "rcee_precompiled", "0.5.0"
+ G
+
+ simulate_platform "x86_64-darwin-15" do
+ bundle "lock"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "universal-darwin"
+ end
+
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ rcee_precompiled (0.5.0-universal-darwin)
+
+ PLATFORMS
+ universal-darwin
+
+ DEPENDENCIES
+ rcee_precompiled (= 0.5.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "does not re-resolve when a specific platform, but less specific than the current platform, is locked" do
+ build_repo4 do
+ build_gem "nokogiri"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.0)
+
+ PLATFORMS
+ arm64-darwin
+
+ DEPENDENCIES
+ nokogiri!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "arm64-darwin-23" do
+ bundle "install --verbose"
+
+ expect(out).to include("Found no changes, using resolution from the lockfile")
+ end
+ end
+
+ it "does not remove generic platform gems locked for a specific platform from lockfile when unlocking an unrelated gem" do
+ build_repo4 do
+ build_gem "ffi"
+
+ build_gem "ffi" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "nokogiri"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "ffi"
+ gem "nokogiri"
+ G
+
+ original_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ ffi (1.0)
+ nokogiri (1.0)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ ffi
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --update nokogiri"
+
+ expect(lockfile).to eq(original_lockfile)
+ end
+ end
+
+ it "does not remove generic platform gems locked for a specific platform from lockfile when unlocking an unrelated gem, and variants for other platform also locked" do
+ build_repo4 do
+ build_gem "ffi"
+
+ build_gem "ffi" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "ffi" do |s|
+ s.platform = "java"
+ end
+
+ build_gem "nokogiri"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "ffi"
+ gem "nokogiri"
+ G
+
+ original_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ ffi (1.0)
+ ffi (1.0-java)
+ nokogiri (1.0)
+
+ PLATFORMS
+ java
+ x86_64-linux
+
+ DEPENDENCIES
+ ffi
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --update nokogiri"
+
+ expect(lockfile).to eq(original_lockfile)
+ end
+ end
+
+ it "does not remove platform specific gems from lockfile when using a ruby version that does not match their ruby requirements, since they may be useful in other rubies" do
+ build_repo4 do
+ build_gem("google-protobuf", "3.25.5")
+ build_gem("google-protobuf", "3.25.5") do |s|
+ s.required_ruby_version = "< #{current_ruby_minor}.dev"
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "google-protobuf", "~> 3.0"
+ G
+
+ original_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ google-protobuf (3.25.5)
+ google-protobuf (3.25.5-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ google-protobuf (~> 3.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ simulate_platform "x86_64-linux" do
+ bundle "lock --update"
+ end
+
+ expect(lockfile).to eq(original_lockfile)
+ 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-mingw-ucrt" }
+ 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-mingw-ucrt" }
+ 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_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
diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb
index bc49053081..83875a3d0e 100644
--- a/spec/bundler/install/gemfile_spec.rb
+++ b/spec/bundler/install/gemfile_spec.rb
@@ -1,98 +1,192 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install" do
context "with duplicated gems" do
it "will display a warning" do
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo1"
+
gem 'rails', '~> 4.0.0'
gem 'rails', '~> 4.0.0'
G
- expect(out).to include("more than once")
+ 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://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
+ 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 "myrack 1.0.0"
+ end
+
+ it "respects lockfile and BUNDLE_LOCKFILE" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ lockfile "ReallyNotGemfile.lock"
+ source "https://gem.repo1"
+ gem 'myrack'
G
- bundle :install, :gemfile => bundled_app("NotGemfile")
+ bundle :install, gemfile: bundled_app("NotGemfile")
ENV["BUNDLE_GEMFILE"] = "NotGemfile"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "respects BUNDLE_LOCKFILE during bundle install" do
+ ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock"
+
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle :install, gemfile: bundled_app("NotGemfile")
+ expect(bundled_app("ReallyNotGemfile.lock")).to exist
+
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
context "with gemfile set via config" do
before do
gemfile bundled_app("NotGemfile"), <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
- bundle "config --local gemfile #{bundled_app("NotGemfile")}"
+ bundle_config "gemfile #{bundled_app("NotGemfile")}"
end
it "uses the gemfile to install" do
bundle "install"
- bundle "show"
+ bundle "list"
- expect(out).to include("rack (1.0.0)")
+ expect(out).to include("myrack (1.0.0)")
end
it "uses the gemfile while in a subdirectory" do
bundled_app("subdir").mkpath
- Dir.chdir(bundled_app("subdir")) do
- bundle "install"
- bundle "show"
+ bundle "install", dir: bundled_app("subdir")
+ bundle "list", dir: bundled_app("subdir")
- expect(out).to include("rack (1.0.0)")
- end
+ expect(out).to include("myrack (1.0.0)")
end
end
- context "with deprecated features" do
- before :each do
- in_app_root
+ it "reports that lib is an invalid option" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack", :lib => "myrack"
+ G
+
+ bundle :install, raise_on_error: false
+ expect(err).to match(/You passed :lib as an option for gem 'myrack', but it is invalid/)
+ end
+
+ it "reports that type is an invalid option" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack", :type => "development"
+ G
+
+ bundle :install, raise_on_error: false
+ expect(err).to match(/You passed :type as an option for gem 'myrack', but it is invalid/)
+ end
+
+ it "reports that gemfile is an invalid option" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "myrack", :gemfile => "foo"
+ G
+
+ bundle :install, raise_on_error: false
+ expect(err).to match(/You passed :gemfile as an option for gem 'myrack', but it is invalid/)
+ end
+
+ context "when an internal error happens" do
+ let(:bundler_bug) do
+ create_file("bundler_bug.rb", <<~RUBY)
+ require "bundler"
+
+ module Bundler
+ class Dsl
+ def source(source, *args, &blk)
+ nil.name
+ end
+ end
+ end
+ RUBY
+
+ bundled_app("bundler_bug.rb").to_s
end
- it "reports that lib is an invalid option" do
- gemfile <<-G
- gem "rack", :lib => "rack"
- G
+ it "shows culprit file and line" do
+ skip "ruby-core test setup has always \"lib\" in $LOAD_PATH so `require \"bundler\"` always activates the local version rather than using RubyGems gem activation stuff, causing conflicts" if ruby_core?
- bundle :install
- expect(out).to match(/You passed :lib as an option for gem 'rack', but it is invalid/)
+ install_gemfile "source 'https://gem.repo1'", requires: [bundler_bug], artifice: nil, raise_on_error: false
+ expect(err).to include("bundler_bug.rb:6")
end
end
- context "with engine specified in symbol" do
+ context "with engine specified in symbol", :jruby_only do
it "does not raise any error parsing Gemfile" do
- simulate_ruby_version "2.3.0" do
- simulate_ruby_engine "jruby", "9.1.2.0" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0"
- G
-
- expect(out).to match(/Bundle complete!/)
- end
- end
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ ruby "#{RUBY_VERSION}", :engine => :jruby, :engine_version => "#{RUBY_ENGINE_VERSION}"
+ G
+
+ expect(out).to match(/Bundle complete!/)
end
it "installation succeeds" do
- simulate_ruby_version "2.3.0" do
- simulate_ruby_engine "jruby", "9.1.2.0" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0"
- gem "rack"
- G
-
- expect(the_bundle).to include_gems "rack 1.0.0"
- end
- end
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ ruby "#{RUBY_VERSION}", :engine => :jruby, :engine_version => "#{RUBY_ENGINE_VERSION}"
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems "myrack 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 "https://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 "https://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
index e9e671105a..9db73b84b5 100644
--- a/spec/bundler/install/gems/compact_index_spec.rb
+++ b/spec/bundler/install/gems/compact_index_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "compact index api" do
let(:source_hostname) { "localgemserver.test" }
@@ -8,12 +7,27 @@ RSpec.describe "compact index api" do
it "should use the API" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "has a debug mode" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "myrack"
+ G
+
+ bundle :install, artifice: "compact_index", env: { "DEBUG_COMPACT_INDEX" => "true" }
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(err).to include("[Bundler::CompactIndexClient] available?")
+ expect(err).to include("[Bundler::CompactIndexClient] fetching versions")
+ expect(err).to include("[Bundler::CompactIndexClient] info(myrack)")
+ expect(err).to include("[Bundler::CompactIndexClient] fetching info/myrack")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "should URI encode gem names" do
@@ -22,8 +36,8 @@ RSpec.describe "compact index api" do
gem " sinatra"
G
- bundle :install, :artifice => "compact_index"
- expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.")
+ 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
@@ -32,7 +46,7 @@ RSpec.describe "compact index api" do
gem "rails"
G
- bundle! :install, :artifice => "compact_index"
+ 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",
@@ -45,23 +59,23 @@ RSpec.describe "compact index api" do
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")
+ build_repo4(build_compact_index: false) do
+ build_gem "myrack", "1.0" do |s|
+ s.add_dependency("Myrack", "0.1")
end
- build_gem "Rack", "0.1"
+ build_gem "Myrack", "0.1"
end
- install_gemfile! <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4 }
+ 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"
+ gem "myrack", "1.0"
+ gem "Myrack", "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")
+ run "Bundler.require; puts Gem.loaded_specs.values_at('myrack', 'Myrack').map(&:full_name)"
+ expect(out).to eq("myrack-1.0\nMyrack-0.1")
end
it "should handle multiple gem dependencies on the same gem" do
@@ -70,20 +84,21 @@ RSpec.describe "compact index api" do
gem "net-sftp"
G
- bundle! :install, :artifice => "compact_index"
+ 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" do
+ it "should use the endpoint when using deployment mode" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index"
+ bundle :install, artifice: "compact_index"
- bundle "install --deployment", :artifice => "compact_index"
+ bundle_config "deployment true"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "handles git dependencies that are in rubygems" do
@@ -94,17 +109,17 @@ RSpec.describe "compact index api" do
gemfile <<-G
source "#{source_uri}"
- git "file:///#{lib_path("foo-1.0")}" do
+ git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
G
- bundle! :install, :artifice => "compact_index"
+ 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" do
+ 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"
@@ -112,95 +127,102 @@ RSpec.describe "compact index api" do
gemfile <<-G
source "#{source_uri}"
- gem 'foo', :git => "file:///#{lib_path("foo-1.0")}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
G
- bundle! :install, :artifice => "compact_index"
+ bundle :install, artifice: "compact_index"
- bundle "install --deployment", :artifice => "compact_index"
+ bundle_config "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" do
+ 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:///#{lib_path("foo-1.0")}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
G
- bundle "install", :artifice => "compact_index"
- bundle "install --deployment", :artifice => "compact_index"
+ bundle "install", artifice: "compact_index"
+ bundle_config "deployment true"
+ bundle :install, artifice: "compact_index"
- expect(exitstatus).to eq(0) if exitstatus
expect(the_bundle).to include_gems("foo 1.0")
end
- it "falls back when the API errors out" do
- simulate_platform mswin
-
+ it "falls back when the API URL returns 403 Forbidden" do
gemfile <<-G
source "#{source_uri}"
- gem "rcov"
+ gem "myrack"
G
- bundle! :install, :artifice => "windows"
- expect(out).to include("Fetching source index from #{source_uri}")
- expect(the_bundle).to include_gems "rcov 1.0.0"
+ bundle :install, verbose: true, artifice: "compact_index_forbidden"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "falls back when the API URL returns 403 Forbidden" do
+ it "falls back when the versions endpoint has a checksum mismatch" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :verbose => true, :artifice => "compact_index_forbidden"
+ bundle :install, verbose: true, artifice: "compact_index_checksum_mismatch"
expect(out).to include("Fetching gem metadata from #{source_uri}")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(out).to include("The checksum of /versions does not match the checksum provided by the server!")
+ expect(out).to include("Calculated checksums #{{ "sha-256" => "8KfZiM/fszVkqhP/m5s9lvE6M9xKu4I1bU4Izddp5Ms=" }.inspect} did not match expected #{{ "sha-256" => "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=" }.inspect}")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "falls back when the versions endpoint has a checksum mismatch" do
+ it "shows proper path when permission errors happen", :permissions do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
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"
+ versions = compact_index_cache_path.join(
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions"
+ )
+ versions.dirname.mkpath
+ versions.write("created_at")
+ 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"] = nil
+ ENV["HOME"] = tmp("missing_home").to_s
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "handles host redirects" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index_host_redirect"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "compact_index_host_redirect"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "handles host redirects without Net::HTTP::Persistent" do
+ it "handles host redirects without Gem::Net::HTTP::Persistent" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
FileUtils.mkdir_p lib_path
@@ -216,91 +238,132 @@ The checksum of /versions does not match the checksum provided by the server! So
H
end
- bundle! :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")]
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "times out when Bundler::Fetcher redirects too much" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "compact_index_redirects"
- expect(out).to match(/Too many redirects/)
+ 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"
+ gem "myrack"
G
- bundle "install --full-index", :artifice => "compact_index"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "should use the modern index for update" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle "update --full-index", :artifice => "compact_index"
+ 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"
+ expect(the_bundle).to include_gems "myrack 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[myrack-1.0.0 thin-1.0 net_a-1.0], gem_repo: gem_repo2
+ bundle_config "path.system true"
+ ENV["BUNDLER_SPEC_ALL_REQUESTS"] = <<~EOS.strip
+ #{source_uri}/versions
+ #{source_uri}/info/myrack
+ EOS
+
+ install_gemfile <<-G, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ source "#{source_uri}"
+ gem "myrack"
+ G
+
+ expect(stdboth).not_to include "Double checking"
+ end
+
it "fetches again when more dependencies are found in subsequent sources" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
- gemfile <<-G
+ install_gemfile <<-G, artifice: "compact_index_extra", verbose: true
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle! :install, :artifice => "compact_index_extra"
- expect(the_bundle).to include_gems "back_deps 1.0"
+ 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"
+ gem "myrack", "1.0.0"
G
- bundle! :install, :artifice => "compact_index_extra_api"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "compact_index_extra_api"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
build_repo4 do
- build_gem "rack", "1.2" do |s|
- s.executables = "rackup"
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
end
end
gemfile <<-G
source "#{source_uri}" do; end
source "#{source_uri}/extra"
- gem "rack", "1.2"
+ gem "myrack", "1.2"
G
- bundle! :install, :artifice => "compact_index_extra_api"
- expect(the_bundle).to include_gems "rack 1.2"
+ bundle :install, artifice: "compact_index_extra_api"
+ expect(the_bundle).to include_gems "myrack 1.2"
end
- it "considers all possible versions of dependencies from all api gem sources" 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.
+ it "resolves indirect dependencies to the most scoped source that includes them" 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 tries to
+ # use the version in the most scoped source, even if not compatible, and
+ # gives a resolution error
build_repo4 do
build_gem "activesupport", "1.2.0"
build_gem "somegem", "1.0.0" do |s|
@@ -310,14 +373,14 @@ The checksum of /versions does not match the checksum provided by the server! So
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem 'somegem', '1.0.0'
+ source "#{source_uri}/extra" do
+ gem 'somegem', '1.0.0'
+ end
G
- bundle! :install, :artifice => "compact_index_extra_api"
+ bundle :install, artifice: "compact_index_extra_api", raise_on_error: false
- expect(the_bundle).to include_gems "somegem 1.0.0"
- expect(the_bundle).to include_gems "activesupport 1.2.3"
+ expect(err).to include("Could not find compatible versions")
end
it "prints API output properly with back deps" do
@@ -325,45 +388,62 @@ The checksum of /versions does not match the checksum provided by the server! So
build_gem "back_deps" do |s|
s.add_dependency "foo"
end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle! :install, :artifice => "compact_index_extra"
+ 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 if the index of gems is large when doing back deps" do
+ 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"
- # need to hit the limit
- 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i|
- build_gem "gem#{i}"
- end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
- gemfile <<-G
+ install_gemfile <<-G, artifice: "compact_index_extra_missing"
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle! :install, :artifice => "compact_index_extra_missing"
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_r Dir[gem_repo4("gems/foo-*.gem")]
+ end
+
+ install_gemfile <<-G, artifice: "compact_index_extra_api_missing"
+ 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}"
@@ -371,113 +451,74 @@ The checksum of /versions does not match the checksum provided by the server! So
gem 'foo'
G
- bundle! :install, :artifice => "compact_index_api_missing"
+ 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" do
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle! :install, :artifice => "compact_index_extra"
-
- bundle "install --deployment", :artifice => "compact_index_extra"
+ bundle :install, artifice: "compact_index_extra"
+ bundle_config "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"
+ 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 "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do
- sudo "mkdir -p #{system_gem_path("bin")}"
- sudo "chown -R root #{system_gem_path("bin")}"
-
- gemfile <<-G
- source "#{source_uri}"
- gem "rails"
- G
- bundle! :install, :artifice => "compact_index"
- expect(the_bundle).to include_gems "rails 2.3.2"
- end
-
- it "installs the binstubs" 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" 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" 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'
+ gem 'myrack-obama'
G
- bundle! :install, :artifice => "compact_index"
- expect(out).to include("Post-install message from rack:")
+ bundle :install, artifice: "compact_index"
+ expect(out).to include("Post-install message from myrack:")
end
it "should display the post install message for a dependency" do
gemfile <<-G
source "#{source_uri}"
- gem 'rack_middleware'
+ gem 'myrack_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")
+ bundle :install, artifice: "compact_index"
+ expect(out).to include("Post-install message from myrack:")
+ expect(out).to include("Myrack's post install message")
end
context "when using basic authentication" do
let(:user) { "user" }
let(:password) { "pass" }
let(:basic_auth_source_uri) do
- uri = URI.parse(source_uri)
+ uri = Gem::URI.parse(source_uri)
uri.user = user
uri.password = password
@@ -487,114 +528,100 @@ The checksum of /versions does not match the checksum provided by the server! So
it "passes basic authentication details and strips out creds" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index_basic_authentication"
+ bundle :install, artifice: "compact_index_basic_authentication"
expect(out).not_to include("#{user}:#{password}")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "strips http basic authentication creds for modern index" do
+ it "passes basic authentication details and strips out creds also in verbose mode" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "endopint_marshal_fail_basic_authentication"
+ 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 it can't reach the server" do
- gemfile <<-G
- source "#{basic_auth_source_uri}"
- gem "rack"
- G
-
- bundle :install, :artifice => "endpoint_500"
- expect(out).not_to include("#{user}:#{password}")
- end
-
- it "strips http basic auth creds when warning about ambiguous sources" do
- gemfile <<-G
- source "#{basic_auth_source_uri}"
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- bundle! :install, :artifice => "compact_index_basic_authentication"
- expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
- expect(out).not_to include("#{user}:#{password}")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 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"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index_creds_diff_host"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "compact_index_creds_diff_host"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
describe "with authentication details in bundle config" do
before do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
end
it "reads authentication details by host name from bundle config" do
- bundle "config #{source_hostname} #{user}:#{password}"
+ bundle "config set #{source_hostname} #{user}:#{password}"
- bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 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 #{source_uri}/ #{user}:#{password}"
+ bundle "config set #{source_uri}/ #{user}:#{password}"
- bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "should use the API" do
- bundle "config #{source_hostname} #{user}:#{password}"
- bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "prefers auth supplied in the source uri" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle "config #{source_hostname} otheruser:wrong"
+ 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"
+ bundle :install, artifice: "compact_index_strict_basic_authentication"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "shows instructions if auth is not provided for the source" do
- bundle :install, :artifice => "compact_index_strict_basic_authentication"
- expect(out).to include("bundle config #{source_hostname} username:password")
+ 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 #{source_hostname} #{user}:wrong"
+ bundle "config set #{source_hostname} #{user}:wrong"
- bundle :install, :artifice => "compact_index_strict_basic_authentication"
- expect(out).to include("Bad username or password")
+ 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
@@ -604,16 +631,16 @@ The checksum of /versions does not match the checksum provided by the server! So
it "passes basic authentication details" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle! :install, :artifice => "compact_index_basic_authentication"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "compact_index_basic_authentication"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
end
- context "when ruby is compiled without openssl", :ruby_repo do
+ 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
@@ -626,180 +653,360 @@ The checksum of /versions does not match the checksum provided by the server! So
end
end
- it "explains what to do to get it" do
+ it "explains what to do to get it, and includes original error" do
gemfile <<-G
source "#{source_uri.gsub(/http/, "https")}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :env => { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }
- expect(out).to include("OpenSSL")
+ bundle :install, env: { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }, raise_on_error: false, artifice: nil
+ expect(err).to include("recompile Ruby").and include("cannot load such file")
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.
+ # a certificate validation error when RubyGems tries to connect.
gemfile <<-G
- class Net::HTTP
+ class Gem::Net::HTTP
def start
raise OpenSSL::SSL::SSLError, "certificate verify failed"
end
end
source "#{source_uri.gsub(/http/, "https")}"
- gem "rack"
+ gem "myrack"
G
- bundle :install
- expect(out).to match(/could not verify the SSL certificate/i)
+ 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
- before 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)
+ file.puts({ sources: ["https://rubygems.org"] }.to_yaml)
end
- end
- after do
- home(".gemrc").rmtree
- end
-
- it "uses other sources declared in the Gemfile" do
- gemfile <<-G
- source "#{source_uri}"
- gem 'rack'
- G
+ begin
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'myrack'
+ G
- bundle! :install, :artifice => "compact_index_forbidden"
+ bundle :install, artifice: "compact_index_forbidden"
+ ensure
+ FileUtils.rm_rf home(".gemrc")
+ end
end
end
- it "performs partial update with a non-empty range" do
+ it "performs update with etag not-modified" do
+ versions_etag = compact_index_cache_path.join(
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions.etag"
+ )
+ expect(versions_etag.file?).to eq(false)
+
gemfile <<-G
source "#{source_uri}"
- gem 'rack', '0.9.1'
+ gem 'myrack', '0.9.1'
G
- # Initial install creates the cached versions file
- bundle! :install, :artifice => "compact_index"
+ # Initial install creates the cached versions file and etag file
+ bundle :install, artifice: "compact_index"
+
+ expect(versions_etag.file?).to eq(true)
+ previous_content = versions_etag.binread
# Update the Gemfile so we can check subsequent install was successful
gemfile <<-G
source "#{source_uri}"
- gem 'rack', '1.0.0'
+ gem 'myrack', '1.0.0'
G
- # Second install should make only a partial request to /versions
- bundle! :install, :artifice => "compact_index_partial_update"
+ # Second install should match etag
+ bundle :install, artifice: "compact_index_etag_match"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(versions_etag.binread).to eq(previous_content)
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "performs full update when range is ignored" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'myrack', '0.9.1'
+ G
+
+ # Initial install creates the cached versions file and etag file
+ bundle :install, artifice: "compact_index"
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'myrack', '1.0.0'
+ G
+
+ versions = compact_index_cache_path.join(
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions"
+ )
+ # Modify the cached file. The ranged request will be based on this but,
+ # in this test, the range is ignored so this gets overwritten, allowing install.
+ versions.write "ruining this file"
+
+ bundle :install, artifice: "compact_index_range_ignored"
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "performs partial update with a non-empty range" do
+ build_repo4 do
+ build_gem "myrack", "0.9.1"
+ end
+
+ # Initial install creates the cached versions file
+ install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'myrack', '0.9.1'
+ G
+
+ build_repo4 do
+ build_gem "myrack", "1.0.0"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index_partial_update", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'myrack', '1.0.0'
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "performs partial update while local cache is updated by another process" do
gemfile <<-G
source "#{source_uri}"
- gem 'rack'
+ gem 'myrack'
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)
+ # Create a partial cache versions file
+ versions = compact_index_cache_path.join(
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions"
+ )
+ versions.dirname.mkpath
+ versions.write("created_at")
- bundle! :install, :artifice => "compact_index_concurrent_download"
+ 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"
+ expect(versions.read).to start_with("created_at")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "performs a partial update that fails digest check, then a full update" do
+ build_repo4 do
+ build_gem "myrack", "0.9.1"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'myrack', '0.9.1'
+ G
+
+ build_repo4 do
+ build_gem "myrack", "1.0.0"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index_partial_update_bad_digest", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'myrack', '1.0.0'
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no digest" do
+ build_repo4 do
+ build_gem "myrack", "0.9.1"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'myrack', '0.9.1'
+ G
+
+ build_repo4 do
+ build_gem "myrack", "1.0.0"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index_partial_update_no_digest_not_incremental", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'myrack', '1.0.0'
+ G
+
+ expect(the_bundle).to include_gems "myrack 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 'myrack', '0.9.1'
+ G
+
+ bundle :install, artifice: "compact_index"
+
+ cache_path = compact_index_cache_path.join("localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5")
+
+ # We must remove the etag so that we don't ignore the range and get a 304 Not Modified.
+ myrack_info_etag_path = File.join(cache_path, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073")
+ File.unlink(myrack_info_etag_path) if File.exist?(myrack_info_etag_path)
+
+ myrack_info_path = File.join(cache_path, "info", "myrack")
+ expected_myrack_info_content = File.read(myrack_info_path)
+
+ # Modify the cache files to make the range not satisfiable
+ File.open(myrack_info_path, "a") {|f| f << "0.9.2 |checksum:c55b525b421fd833a93171ad3d7f04528ca8e87d99ac273f8933038942a5888c" }
+
+ # Update the Gemfile so the next install does its normal things
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'myrack', '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_myrack_info_content = File.read(myrack_info_path)
+
+ expect(resulting_myrack_info_content).to eq(expected_myrack_info_content)
end
it "fails gracefully when the source URI has an invalid scheme" do
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
source "htps://rubygems.org"
- gem "rack"
+ gem "myrack"
G
- expect(exitstatus).to eq(15) if exitstatus
- expect(out).to end_with(<<-E.strip)
+ 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", :rubygems => ">= 2.3.0" do
+ describe "checksum validation" do
+ before do
+ lockfile <<-L
+ GEM
+ remote: #{source_uri}
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ #{checksums_section}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "handles checksums from the server in base64" do
+ api_checksum = checksum_digest(gem_repo1, "myrack", "1.0.0")
+ myrack_checksum = [[api_checksum].pack("H*")].pack("m0")
+ install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => myrack_checksum }
+ source "#{source_uri}"
+ gem "myrack"
+ G
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ end
+
it "raises when the checksum does not match" do
- install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", raise_on_error: false
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- expect(exitstatus).to eq(19) if exitstatus
- expect(out).
- 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: `#{system_gem_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 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 ".+?"\.\)/)
+ gem_path = default_cache_path.dirname.join("myrack-1.0.0.gem")
+
+ expect(exitstatus).to eq(37)
+ expect(err).to eq <<~E.strip
+ Bundler found mismatched checksums. This is a potential security risk.
+ myrack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222
+ from the API at http://localgemserver.test/
+ #{checksum_to_lock(gem_repo1, "myrack", "1.0.0")}
+ from the gem at #{gem_path}
+
+ If you trust the API at http://localgemserver.test/, to resolve this issue you can:
+ 1. remove the gem at #{gem_path}
+ 2. run `bundle install`
+
+ To ignore checksum security warnings, disable checksum validation with
+ `bundle config set --local disable_checksum_validation true`
+ E
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!" }
+ install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, verbose: true, raise_on_error: false
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- expect(exitstatus).to eq(5) if exitstatus
- expect(out).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest")
+ expect(exitstatus).to eq(14)
+ expect(err).to include('Invalid checksum for myrack-0.9.1: "checksum!" is not a valid SHA256 hex or base64 digest')
end
it "does not raise when disable_checksum_validation is set" do
- bundle! "config disable_checksum_validation true"
- install_gemfile! <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ bundle_config "disable_checksum_validation true"
+ install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum"
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
end
end
it "works when cache dir is world-writable" do
- install_gemfile! <<-G, :artifice => "compact_index"
+ install_gemfile <<-G, artifice: "compact_index"
File.umask(0000)
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
end
it "doesn't explode when the API dependencies are wrong" do
- install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" }
+ 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", "= 10.0.2"),
+ deps = [Gem::Dependency.new("rake", "= #{rake_version}"),
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(<<-E.strip).and include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies")
-Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}).
-Either installing with `--full-index` or running `bundle update rails` should fix the problem.
+ expect(out).to include("rails-2.3.2 from rubygems remote at #{source_uri}/ has corrupted API dependencies")
+ expect(err).to include(<<-E.strip)
+Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API (#{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"
+ install_gemfile <<-G, artifice: "compact_index"
+ source "https://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)
+ uninstall_gem("activemerchant")
+ bundle "update rails", artifice: "compact_index"
+ count = lockfile.match?("CHECKSUMS") ? 2 : 1 # Once in the specs, and once in CHECKSUMS
+ expect(lockfile.scan(/activemerchant \(/).size).to eq(count)
+ end
+
+ it "handles an API that does not provide checksums info (undocumented, support may get removed)" do
+ install_gemfile <<-G, artifice: "compact_index_no_checksums"
+ source "https://gem.repo1"
+ gem "rake"
+ G
end
end
diff --git a/spec/bundler/install/gems/dependency_api_fallback_spec.rb b/spec/bundler/install/gems/dependency_api_fallback_spec.rb
new file mode 100644
index 0000000000..c7b0c537e4
--- /dev/null
+++ b/spec/bundler/install/gems/dependency_api_fallback_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+RSpec.describe "gemcutter's dependency API" do
+ context "when Gemcutter API takes too long to respond" do
+ before do
+ bundle_config "timeout 1"
+ end
+
+ it "times out and falls back on the modern index" do
+ install_gemfile <<-G, artifice: "endpoint_timeout"
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ expect(out).to include("Fetching source index from https://gem.repo1/")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb
index d495490745..32a1b98b6d 100644
--- a/spec/bundler/install/gems/dependency_api_spec.rb
+++ b/spec/bundler/install/gems/dependency_api_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "gemcutter's dependency API" do
let(:source_hostname) { "localgemserver.test" }
@@ -8,12 +7,12 @@ RSpec.describe "gemcutter's dependency API" do
it "should use the API" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint"
+ bundle :install, artifice: "endpoint"
expect(out).to include("Fetching gem metadata from #{source_uri}")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "should URI encode gem names" do
@@ -22,8 +21,8 @@ RSpec.describe "gemcutter's dependency API" do
gem " sinatra"
G
- bundle :install, :artifice => "endpoint"
- expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.")
+ 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
@@ -32,7 +31,7 @@ RSpec.describe "gemcutter's dependency API" do
gem "rails"
G
- bundle :install, :artifice => "endpoint"
+ bundle :install, artifice: "endpoint"
expect(out).to include("Fetching gem metadata from #{source_uri}/...")
expect(the_bundle).to include_gems(
"rails 2.3.2",
@@ -50,20 +49,21 @@ RSpec.describe "gemcutter's dependency API" do
gem "net-sftp"
G
- bundle :install, :artifice => "endpoint"
+ bundle :install, artifice: "endpoint"
expect(the_bundle).to include_gems "net-sftp 1.1.1"
end
- it "should use the endpoint when using --deployment" do
+ it "should use the endpoint when using deployment mode" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint"
+ bundle :install, artifice: "endpoint"
- bundle "install --deployment", :artifice => "endpoint"
+ bundle_config "deployment true"
+ bundle :install, artifice: "endpoint"
expect(out).to include("Fetching gem metadata from #{source_uri}")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "handles git dependencies that are in rubygems" do
@@ -74,17 +74,17 @@ RSpec.describe "gemcutter's dependency API" do
gemfile <<-G
source "#{source_uri}"
- git "file:///#{lib_path("foo-1.0")}" do
+ git "#{lib_path("foo-1.0")}" do
gem 'foo'
end
G
- bundle :install, :artifice => "endpoint"
+ 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" do
+ 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"
@@ -92,41 +92,50 @@ RSpec.describe "gemcutter's dependency API" do
gemfile <<-G
source "#{source_uri}"
- gem 'foo', :git => "file:///#{lib_path("foo-1.0")}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
G
- bundle :install, :artifice => "endpoint"
+ bundle :install, artifice: "endpoint"
- bundle "install --deployment", :artifice => "endpoint"
+ bundle_config "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" do
+ 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:///#{lib_path("foo-1.0")}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
G
- bundle "install", :artifice => "endpoint"
- bundle "install --deployment", :artifice => "endpoint"
+ bundle "install", artifice: "endpoint"
+ bundle_config "deployment true"
+ bundle :install, artifice: "endpoint"
- expect(exitstatus).to eq(0) if exitstatus
expect(the_bundle).to include_gems("foo 1.0")
end
it "falls back when the API errors out" do
- simulate_platform mswin
+ simulate_platform "x86-mswin32" do
+ 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
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rcov"
+ G
- bundle :install, :artifice => "windows"
- expect(out).to include("Fetching source index from #{source_uri}")
- expect(the_bundle).to include_gems "rcov 1.0.0"
+ 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
end
it "falls back when hitting the Gemcutter Dependency Limit" do
@@ -137,10 +146,10 @@ RSpec.describe "gemcutter's dependency API" do
gem "actionmailer"
gem "activeresource"
gem "thin"
- gem "rack"
+ gem "myrack"
gem "rails"
G
- bundle :install, :artifice => "endpoint_fallback"
+ bundle :install, artifice: "endpoint_fallback"
expect(out).to include("Fetching source index from #{source_uri}")
expect(the_bundle).to include_gems(
@@ -150,7 +159,7 @@ RSpec.describe "gemcutter's dependency API" do
"activeresource 2.3.2",
"activesupport 2.3.2",
"thin 1.0.0",
- "rack 1.0.0",
+ "myrack 1.0.0",
"rails 2.3.2"
)
end
@@ -158,39 +167,39 @@ RSpec.describe "gemcutter's dependency API" do
it "falls back when Gemcutter API doesn't return proper Marshal format" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :verbose => true, :artifice => "endpoint_marshal_fail"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "falls back when the API URL returns 403 Forbidden" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :verbose => true, :artifice => "endpoint_api_forbidden"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "handles host redirects" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_host_redirect"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "endpoint_host_redirect"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "handles host redirects without Net::HTTP::Persistent" do
+ it "handles host redirects without Gem::Net::HTTP::Persistent" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
FileUtils.mkdir_p lib_path
@@ -206,42 +215,42 @@ RSpec.describe "gemcutter's dependency API" do
H
end
- bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")]
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "timeouts when Bundler::Fetcher redirects too much" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_redirect"
- expect(out).to match(/Too many redirects/)
+ 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"
+ gem "myrack"
G
- bundle "install --full-index", :artifice => "endpoint"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "should use the modern index for update" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle "update --full-index", :artifice => "endpoint"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
@@ -250,46 +259,49 @@ RSpec.describe "gemcutter's dependency API" do
build_gem "back_deps" do |s|
s.add_dependency "foo"
end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ 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"
+ 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"
+ gem "myrack", "1.0.0"
G
- bundle :install, :artifice => "endpoint_extra_api"
+ bundle :install, artifice: "endpoint_extra_api"
build_repo4 do
- build_gem "rack", "1.2" do |s|
- s.executables = "rackup"
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
end
end
gemfile <<-G
source "#{source_uri}" do; end
source "#{source_uri}/extra"
- gem "rack", "1.2"
+ gem "myrack", "1.2"
G
- bundle :install, :artifice => "endpoint_extra_api"
- expect(the_bundle).to include_gems "rack 1.2"
+ bundle :install, artifice: "endpoint_extra_api"
+ expect(the_bundle).to include_gems "myrack 1.2"
end
- it "considers all possible versions of dependencies from all api gem sources" 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.
+ it "resolves indirect dependencies to the most scoped source that includes them" 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 tries to
+ # use the version in the most scoped source, even if not compatible, and
+ # gives a resolution error
build_repo4 do
build_gem "activesupport", "1.2.0"
build_gem "somegem", "1.0.0" do |s|
@@ -299,14 +311,14 @@ RSpec.describe "gemcutter's dependency API" do
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem 'somegem', '1.0.0'
+ source "#{source_uri}/extra" do
+ gem 'somegem', '1.0.0'
+ end
G
- bundle :install, :artifice => "endpoint_extra_api"
+ bundle :install, artifice: "compact_index_extra_api", raise_on_error: false
- expect(the_bundle).to include_gems "somegem 1.0.0"
- expect(the_bundle).to include_gems "activesupport 1.2.3"
+ expect(err).to include("Could not find compatible versions")
end
it "prints API output properly with back deps" do
@@ -314,159 +326,122 @@ RSpec.describe "gemcutter's dependency API" do
build_gem "back_deps" do |s|
s.add_dependency "foo"
end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle :install, :artifice => "endpoint_extra"
+ bundle :install, artifice: "endpoint_extra"
- expect(out).to include("Fetching gem metadata from http://localgemserver.test/..")
+ 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 if the index of gems is large when doing back deps" do
+ 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"
- # need to hit the limit
- 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i|
- build_gem "gem#{i}"
- end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
- gemfile <<-G
+ install_gemfile <<-G, artifice: "endpoint_extra_missing"
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle :install, :artifice => "endpoint_extra_missing"
expect(the_bundle).to include_gems "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 => "endpoint_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" do
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
end
- FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")]
end
gemfile <<-G
source "#{source_uri}"
- source "#{source_uri}/extra"
- gem "back_deps"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
G
- bundle :install, :artifice => "endpoint_extra"
-
- bundle "install --deployment", :artifice => "endpoint_extra"
+ bundle :install, artifice: "endpoint_extra"
+ bundle_config "deployment true"
+ bundle "install", artifice: "endpoint_extra"
expect(the_bundle).to include_gems "back_deps 1.0"
end
- it "does not refetch if the only unmet dependency is bundler" do
- gemfile <<-G
- source "#{source_uri}"
-
- gem "bundler_dep"
- G
-
- bundle :install, :artifice => "endpoint"
- expect(out).to include("Fetching gem metadata from #{source_uri}")
- end
-
- it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do
- sudo "mkdir -p #{system_gem_path("bin")}"
- sudo "chown -R root #{system_gem_path("bin")}"
+ it "does not fetch all marshaled specs" do
+ build_repo2 do
+ build_gem "foo", "1.0"
+ build_gem "foo", "2.0"
+ end
- gemfile <<-G
+ install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, verbose: true
source "#{source_uri}"
- gem "rails"
- G
- bundle :install, :artifice => "endpoint"
- expect(the_bundle).to include_gems "rails 2.3.2"
- end
- it "installs the binstubs" do
- gemfile <<-G
- source "#{source_uri}"
- gem "rack"
+ gem "foo"
G
- bundle "install --binstubs", :artifice => "endpoint"
-
- gembin "rackup"
- expect(out).to eq("1.0.0")
+ expect(out).to include("foo-2.0.gemspec.rz")
+ expect(out).not_to include("foo-1.0.gemspec.rz")
end
- it "installs the bins when using --path and uses autoclean" 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 "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
- it "installs the bins when using --path and uses bundle clean" do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
- G
- bundle "install --path vendor/bundle --no-clean", :artifice => "endpoint"
+ gem "bundler_dep"
+ G
- expect(vendored_gems("bin/rackup")).to exist
+ 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 "prints post_install_messages" do
gemfile <<-G
source "#{source_uri}"
- gem 'rack-obama'
+ gem 'myrack-obama'
G
- bundle :install, :artifice => "endpoint"
- expect(out).to include("Post-install message from rack:")
+ bundle :install, artifice: "endpoint"
+ expect(out).to include("Post-install message from myrack:")
end
it "should display the post install message for a dependency" do
gemfile <<-G
source "#{source_uri}"
- gem 'rack_middleware'
+ gem 'myrack_middleware'
G
- bundle :install, :artifice => "endpoint"
- expect(out).to include("Post-install message from rack:")
- expect(out).to include("Rack's post install message")
+ bundle :install, artifice: "endpoint"
+ expect(out).to include("Post-install message from myrack:")
+ expect(out).to include("Myrack's post install message")
end
context "when using basic authentication" do
let(:user) { "user" }
let(:password) { "pass" }
let(:basic_auth_source_uri) do
- uri = URI.parse(source_uri)
+ uri = Gem::URI.parse(source_uri)
uri.user = user
uri.password = password
@@ -476,114 +451,128 @@ RSpec.describe "gemcutter's dependency API" do
it "passes basic authentication details and strips out creds" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_basic_authentication"
+ bundle :install, artifice: "endpoint_basic_authentication"
expect(out).not_to include("#{user}:#{password}")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "strips http basic authentication creds for modern index" do
+ it "passes basic authentication details and strips out creds also in verbose mode" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endopint_marshal_fail_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "strips http basic auth creds when it can't reach the server" do
+ it "strips http basic authentication creds for modern index" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_500"
+ bundle :install, artifice: "endpoint_marshal_fail_basic_authentication"
expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "strips http basic auth creds when warning about ambiguous sources" do
+ it "strips http basic auth creds when it can't reach the server" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- source "file://#{gem_repo1}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_basic_authentication"
- expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
+ bundle :install, artifice: "endpoint_500", raise_on_error: false
expect(out).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"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_creds_diff_host"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "endpoint_creds_diff_host"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ describe "with host including dashes" do
+ before do
+ gemfile <<-G
+ source "http://local-gemserver.test"
+ gem "myrack"
+ 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 "myrack 1.0.0"
+ end
end
describe "with authentication details in bundle config" do
before do
gemfile <<-G
source "#{source_uri}"
- gem "rack"
+ gem "myrack"
G
end
it "reads authentication details by host name from bundle config" do
- bundle "config #{source_hostname} #{user}:#{password}"
+ bundle "config set #{source_hostname} #{user}:#{password}"
- bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 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 #{source_uri}/ #{user}:#{password}"
+ bundle "config set #{source_uri}/ #{user}:#{password}"
- bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "should use the API" do
- bundle "config #{source_hostname} #{user}:#{password}"
- bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ 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"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "prefers auth supplied in the source uri" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle "config #{source_hostname} otheruser:wrong"
+ bundle "config set #{source_hostname} otheruser:wrong"
- bundle :install, :artifice => "endpoint_strict_basic_authentication"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "endpoint_strict_basic_authentication"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "shows instructions if auth is not provided for the source" do
- bundle :install, :artifice => "endpoint_strict_basic_authentication"
- expect(out).to include("bundle config #{source_hostname} username:password")
+ 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 #{source_hostname} #{user}:wrong"
+ bundle "config set #{source_hostname} #{user}:wrong"
- bundle :install, :artifice => "endpoint_strict_basic_authentication"
- expect(out).to include("Bad username or password")
+ bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false
+ expect(err).to include("Bad username or password")
end
end
@@ -593,16 +582,16 @@ RSpec.describe "gemcutter's dependency API" do
it "passes basic authentication details" do
gemfile <<-G
source "#{basic_auth_source_uri}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :artifice => "endpoint_basic_authentication"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install, artifice: "endpoint_basic_authentication"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
end
- context "when ruby is compiled without openssl", :ruby_repo do
+ 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
@@ -615,57 +604,53 @@ RSpec.describe "gemcutter's dependency API" do
end
end
- it "explains what to do to get it" do
+ it "explains what to do to get it, and includes original error" do
gemfile <<-G
source "#{source_uri.gsub(/http/, "https")}"
- gem "rack"
+ gem "myrack"
G
- bundle :install, :env => { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }
- expect(out).to include("OpenSSL")
+ bundle :install, artifice: "fail", env: { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }, raise_on_error: false
+ expect(err).to include("recompile Ruby").and include("cannot load such file")
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.
+ # a certificate validation error when RubyGems tries to connect.
gemfile <<-G
- class Net::HTTP
+ class Gem::Net::HTTP
def start
raise OpenSSL::SSL::SSLError, "certificate verify failed"
end
end
source "#{source_uri.gsub(/http/, "https")}"
- gem "rack"
+ gem "myrack"
G
- bundle :install
- expect(out).to match(/could not verify the SSL certificate/i)
+ 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
- before 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)
+ file.puts({ sources: ["https://rubygems.org"] }.to_yaml)
end
- end
- after do
- home(".gemrc").rmtree
- end
-
- it "uses other sources declared in the Gemfile" do
- gemfile <<-G
- source "#{source_uri}"
- gem 'rack'
- G
-
- bundle "install", :artifice => "endpoint_marshal_fail"
+ begin
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'myrack'
+ G
- expect(exitstatus).to eq(0) if exitstatus
+ bundle "install", artifice: "endpoint_marshal_fail"
+ ensure
+ FileUtils.rm_rf home(".gemrc")
+ end
end
end
end
diff --git a/spec/bundler/install/gems/env_spec.rb b/spec/bundler/install/gems/env_spec.rb
index 9b1d8e5424..6d5aa456fe 100644
--- a/spec/bundler/install/gems/env_spec.rb
+++ b/spec/bundler/install/gems/env_spec.rb
@@ -1,108 +1,107 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
+ source "https://gem.repo1"
env "BUNDLER_TEST" do
- gem "rack"
+ gem "myrack"
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"
+ expect(the_bundle).not_to include_gems "myrack"
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"
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
describe "when just setting an ENV key as a symbol" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
env :BUNDLER_TEST do
- gem "rack"
+ gem "myrack"
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"
+ expect(the_bundle).not_to include_gems "myrack"
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"
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
describe "when setting a string to match the env" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
env "BUNDLER_TEST" => "foo" do
- gem "rack"
+ gem "myrack"
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"
+ expect(the_bundle).not_to include_gems "myrack"
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"
+ expect(the_bundle).not_to include_gems "myrack"
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"
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
describe "when setting a regex to match the env" do
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
env "BUNDLER_TEST" => /foo/ do
- gem "rack"
+ gem "myrack"
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"
+ expect(the_bundle).not_to include_gems "myrack"
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"
+ expect(the_bundle).not_to include_gems "myrack"
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"
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
end
diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb
index 2c2d3c16a1..a30b53d6ad 100644
--- a/spec/bundler/install/gems/flex_spec.rb
+++ b/spec/bundler/install/gems/flex_spec.rb
@@ -1,33 +1,32 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle flex_install" do
it "installs the gems as expected" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
expect(the_bundle).to be_locked
end
it "installs even when the lockfile is invalid" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
expect(the_bundle).to be_locked
gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack', '1.0'
+ source "https://gem.repo1"
+ gem 'myrack', '1.0'
G
bundle :install
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
expect(the_bundle).to be_locked
end
@@ -35,19 +34,19 @@ RSpec.describe "bundle flex_install" do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack-obama"
+ source "https://gem.repo2"
+ gem "myrack-obama"
G
- expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0.0"
update_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack-obama", "1.0"
+ source "https://gem.repo2"
+ gem "myrack-obama", "1.0"
G
- expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0.0"
end
describe "adding new gems" do
@@ -55,38 +54,38 @@ RSpec.describe "bundle flex_install" do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
G
update_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
gem 'activesupport', '2.3.5'
G
- expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5"
end
it "keeps child dependencies pinned" do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack-obama"
+ source "https://gem.repo2"
+ gem "myrack-obama"
G
update_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack-obama"
+ source "https://gem.repo2"
+ gem "myrack-obama"
gem "thin"
G
- expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0", "thin 1.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0", "thin 1.0"
end
end
@@ -94,43 +93,43 @@ RSpec.describe "bundle flex_install" do
it "removes gems without changing the versions of remaining gems" do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
gem 'activesupport', '2.3.5'
G
update_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
G
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
expect(the_bundle).not_to include_gems "activesupport 2.3.5"
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
gem 'activesupport', '2.3.2'
G
- expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2"
+ expect(the_bundle).to include_gems "myrack 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://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
gem 'activesupport', '2.3.5'
G
update_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack'
+ source "https://gem.repo2"
+ gem 'myrack'
G
expect(the_bundle).not_to include_gems "activesupport 2.3.5"
@@ -139,110 +138,187 @@ RSpec.describe "bundle flex_install" do
it "removes child dependencies" do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'rack-obama'
+ source "https://gem.repo2"
+ gem 'myrack-obama'
gem 'activesupport'
G
- expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0", "activesupport 2.3.5"
+ expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0.0", "activesupport 2.3.5"
update_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://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"
+ expect(the_bundle).not_to include_gems "myrack-obama", "myrack"
end
end
- describe "when Gemfile conflicts with lockfile" do
+ describe "when running bundle install and Gemfile conflicts with lockfile" do
before(:each) do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack_middleware"
+ source "https://gem.repo2"
+ gem "myrack_middleware"
G
- expect(the_bundle).to include_gems "rack_middleware 1.0", "rack 0.9.1"
+ expect(the_bundle).to include_gems "myrack_middleware 1.0", "myrack 0.9.1"
- build_repo2
- update_repo2 do
- build_gem "rack-obama", "2.0" do |s|
- s.add_dependency "rack", "=1.2"
+ build_repo2 do
+ build_gem "myrack-obama", "2.0" do |s|
+ s.add_dependency "myrack", "=1.2"
end
- build_gem "rack_middleware", "2.0" do |s|
- s.add_dependency "rack", ">=1.0"
+ build_gem "myrack_middleware", "2.0" do |s|
+ s.add_dependency "myrack", ">=1.0"
end
end
gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack-obama", "2.0"
- gem "rack_middleware"
+ source "https://gem.repo2"
+ gem "myrack-obama", "2.0"
+ gem "myrack_middleware"
G
end
it "does not install gems whose dependencies are not met" do
- bundle :install
- ruby <<-RUBY
+ 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)
+ expect(err).to match(/could not find gem 'myrack-obama/i)
end
- it "suggests bundle update when the Gemfile requires different versions than the lock" do
- nice_error = <<-E.strip.gsub(/^ {8}/, "")
- Fetching source index from file:#{gem_repo2}/
- Resolving dependencies...
- Bundler could not find compatible versions for gem "rack":
- In snapshot (Gemfile.lock):
- rack (= 0.9.1)
+ it "discards the locked gems when the Gemfile requires different versions than the lock" do
+ bundle_config "force_ruby_platform true"
- In Gemfile:
- rack-obama (= 2.0) was resolved to 2.0, which depends on
- rack (= 1.2)
+ nice_error = <<~E.strip
+ Could not find compatible versions
- rack_middleware was resolved to 1.0, which depends on
- rack (= 0.9.1)
+ Because myrack-obama >= 2.0 depends on myrack = 1.2
+ and myrack = 1.2 could not be found in rubygems repository https://gem.repo2/ or installed locally,
+ myrack-obama >= 2.0 cannot be used.
+ So, because Gemfile depends on myrack-obama = 2.0,
+ version solving has failed.
+ E
- Running `bundle update` will rebuild your snapshot from scratch, using only
- the gems in your Gemfile, which may resolve the conflict.
+ 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 "force_ruby_platform true"
+
+ bad_error = <<~E.strip
+ Bundler could not find compatible versions for gem "myrack-obama":
+ In Gemfile:
+ myrack-obama (= 2.0)
E
- bundle :install, :retry => 0
- expect(out).to eq(nice_error)
+ bundle "update myrack_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 "https://gem.repo4"
+ gem "jekyll-feed", "~> 0.12"
+ G
+
+ gemfile <<-G
+ source "https://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://#{gem_repo1}"
- gem "rack"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "myrack-obama"
G
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
- gem "rack-obama"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
+ gem "myrack-obama"
G
end
- it "does something" do
- expect do
- bundle "install"
- end.not_to change { File.read(bundled_app("Gemfile.lock")) }
+ it "should work when you install" do
+ bundle "install"
- expect(out).to include("rack = 0.9.1")
- expect(out).to include("locked at 1.0.0")
- expect(out).to include("bundle update rack")
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "myrack", "0.9.1"
+ c.checksum gem_repo1, "myrack-obama", "1.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+ myrack-obama (1.0)
+ myrack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack (= 0.9.1)
+ myrack-obama
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
it "should work when you update" do
- bundle "update rack"
+ bundle "update myrack"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "myrack", "0.9.1"
+ c.checksum gem_repo1, "myrack-obama", "1.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (0.9.1)
+ myrack-obama (1.0)
+ myrack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack (= 0.9.1)
+ myrack-obama
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
end
@@ -250,30 +326,39 @@ RSpec.describe "bundle flex_install" do
it "updates the lockfile" do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- source "file://#{gem_repo2}"
- gem "rack"
+ source "https://gem.repo1"
+ source "https://gem.repo2" do
+ end
+ gem "myrack"
G
- lockfile_should_be <<-L
- GEM
- remote: file:#{gem_repo1}/
- remote: file:#{gem_repo2}/
- specs:
- rack (1.0.0)
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ myrack (1.0.0)
- PLATFORMS
- ruby
+ GEM
+ remote: https://gem.repo2/
+ specs:
- DEPENDENCIES
- rack
+ PLATFORMS
+ #{lockfile_platforms}
- BUNDLED WITH
- #{Bundler::VERSION}
+ DEPENDENCIES
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
L
end
end
@@ -283,37 +368,36 @@ RSpec.describe "bundle flex_install" do
before(:each) do
build_repo2 do
build_gem "capybara", "0.3.9" do |s|
- s.add_dependency "rack", ">= 1.0.0"
+ s.add_dependency "myrack", ">= 1.0.0"
end
- build_gem "rack", "1.1.0"
+ build_gem "myrack", "1.1.0"
build_gem "rails", "3.0.0.rc4" do |s|
- s.add_dependency "rack", "~> 1.1.0"
+ s.add_dependency "myrack", "~> 1.1.0"
end
- build_gem "rack", "1.2.1"
+ build_gem "myrack", "1.2.1"
build_gem "rails", "3.0.0" do |s|
- s.add_dependency "rack", "~> 1.2.1"
+ s.add_dependency "myrack", "~> 1.2.1"
end
end
end
- it "prints the correct error message" do
+ it "resolves them" do
# install Rails 3.0.0.rc
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://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://#{gem_repo2}"
+ source "https://gem.repo2"
gem "rails", "3.0.0"
gem "capybara", "0.3.9"
G
-
- expect(out).to include("Gemfile.lock")
+ 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..8a3a51270a
--- /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 "https://gem.repo2"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ gem 'myrack-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 "https://gem.repo2"
+ gem 'has_funding'
+ gem 'myrack-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 "ignore_funding_requests true"
+ end
+
+ it "does not display the plural fund message after installing" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ gem 'myrack-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 "https://gem.repo2"
+ gem 'has_funding'
+ gem 'myrack-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 "https://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 "https://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 "https://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 "https://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 "https://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 "https://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/gemfile_source_header_spec.rb b/spec/bundler/install/gems/gemfile_source_header_spec.rb
new file mode 100644
index 0000000000..dc35c8d741
--- /dev/null
+++ b/spec/bundler/install/gems/gemfile_source_header_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec.describe "fetching dependencies with a mirrored source" do
+ let(:mirror) { "https://server.example.org" }
+
+ before do
+ build_repo2
+
+ gemfile <<-G
+ source "#{mirror}"
+ gem 'weakling'
+ G
+
+ bundle_config "mirror.#{mirror} https://gem.repo2"
+ end
+
+ it "sets the 'X-Gemfile-Source' and 'User-Agent' headers and bundles successfully" do
+ bundle :install, artifice: "endpoint_mirror_source"
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+end
diff --git a/spec/bundler/install/gems/mirror_probe_spec.rb b/spec/bundler/install/gems/mirror_probe_spec.rb
new file mode 100644
index 0000000000..564062ccf6
--- /dev/null
+++ b/spec/bundler/install/gems/mirror_probe_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+RSpec.describe "fetching dependencies with a not available mirror" do
+ before do
+ build_repo2
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem 'weakling'
+ G
+ end
+
+ context "with a specific fallback timeout" do
+ before do
+ bundle_config_global("BUNDLE_MIRROR__HTTPS://GEM__REPO2/__FALLBACK_TIMEOUT/" => "true",
+ "BUNDLE_MIRROR__HTTPS://GEM__REPO2/" => "https://gem.mirror")
+ end
+
+ it "install a gem using the original uri when the mirror is not responding" do
+ bundle :install, env: { "BUNDLER_SPEC_FAKE_RESOLVE" => "gem.mirror" }, verbose: true
+
+ 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
+ bundle_config_global("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1",
+ "BUNDLE_MIRROR__ALL" => "https://gem.mirror")
+ end
+
+ it "install a gem using the original uri when the mirror is not responding" do
+ bundle :install, env: { "BUNDLER_SPEC_FAKE_RESOLVE" => "gem.mirror" }
+
+ 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
+ bundle_config_global("BUNDLE_MIRROR__HTTPS://GEM__REPO2/" => "https://gem.mirror")
+ end
+
+ it "fails to install the gem with a timeout error when the mirror is not responding" do
+ bundle :install, artifice: "compact_index_mirror_down", raise_on_error: false
+
+ expect(out).to be_empty
+ expect(err).to eq("Could not reach host gem.mirror. Check your network connection and try again.")
+ end
+ end
+
+ context "with a global mirror without a fallback timeout" do
+ before do
+ bundle_config_global("BUNDLE_MIRROR__ALL" => "https://gem.mirror")
+ end
+
+ it "fails to install the gem with a timeout error when the mirror is not responding" do
+ bundle :install, artifice: "compact_index_mirror_down", raise_on_error: false
+
+ expect(out).to be_empty
+ expect(err).to eq("Could not reach host gem.mirror. Check your network connection and try again.")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/mirror_spec.rb b/spec/bundler/install/gems/mirror_spec.rb
index 798156fb12..e1fbeac454 100644
--- a/spec/bundler/install/gems/mirror_spec.rb
+++ b/spec/bundler/install/gems/mirror_spec.rb
@@ -1,21 +1,20 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
- bundle "config --local mirror.http://gems.example.org http://gem-mirror.example.org"
+ bundle_config "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:#{gem_repo1}")
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(out).to include("Fetching gem metadata from https://gem.repo1")
+ expect(the_bundle).to include_gems "myrack 1.0"
end
end
@@ -23,18 +22,18 @@ RSpec.describe "bundle install with a mirror configured" do
before :each do
gemfile <<-G
# This source is bogus and doesn't have the gem we're looking for
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
- gem "rack"
+ gem "myrack"
G
- bundle "config --local mirror.file://#{gem_repo2} file://#{gem_repo1}"
+ bundle_config "mirror.https://gem.repo2 https://gem.repo1"
end
it "installs the gem from the mirror" do
- bundle :install
- expect(out).to include("Fetching source index from file:#{gem_repo1}")
- expect(out).not_to include("Fetching source index from file:#{gem_repo2}")
- expect(the_bundle).to include_gems "rack 1.0"
+ bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ expect(out).to include("Fetching gem metadata from https://gem.repo1")
+ expect(out).not_to include("Fetching gem metadata from https://gem.repo2")
+ expect(the_bundle).to include_gems "myrack 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
index dcf67e976e..d5b10d2c8f 100644
--- a/spec/bundler/install/gems/native_extensions_spec.rb
+++ b/spec/bundler/install/gems/native_extensions_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "installing a gem with native extensions", :ruby_repo do
+RSpec.describe "installing a gem with native extensions" do
it "installs" do
build_repo2 do
build_gem "c_extension" do |s|
@@ -10,7 +9,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do
require "mkmf"
name = "c_extension_bundle"
dir_config(name)
- raise "OMG" unless with_config("c_extension") == "hello"
+ raise ArgumentError unless with_config("c_extension") == "hello"
create_makefile(name)
E
@@ -34,14 +33,13 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do
end
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "c_extension"
G
- bundle "config build.c_extension --with-c_extension=hello"
+ bundle_config "build.c_extension --with-c_extension=hello"
bundle "install"
- expect(out).not_to include("extconf.rb failed")
expect(out).to include("Installing c_extension 1.0 with native extensions")
run "Bundler.require; puts CExtension.new.its_true"
@@ -55,7 +53,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do
require "mkmf"
name = "c_extension_bundle"
dir_config(name)
- raise "OMG" unless with_config("c_extension") == "hello"
+ raise ArgumentError unless with_config("c_extension") == "hello"
create_makefile(name)
E
@@ -77,16 +75,110 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do
C
end
- bundle! "config build.c_extension --with-c_extension=hello"
+ bundle_config "build.c_extension --with-c_extension=hello"
- install_gemfile! <<-G
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump}
G
- expect(out).not_to include("extconf.rb failed")
- expect(out).to include("Using c_extension 1.0")
+ expect(err).to_not include("warning: conflicting chdir during another chdir block")
- run! "Bundler.require; puts CExtension.new.its_true"
+ 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"
+ name = "c_extension_bundle_#{n}"
+ dir_config(name)
+ raise ArgumentError 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 "build.c_extension_one --with-c_extension_one=one"
+ bundle_config "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 "https://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 "https://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"
+ name = "c_extension_bundle"
+ dir_config(name)
+ raise ArgumentError 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 "build.c_extension --with-c_extension=hello --with-c_extension_bundle-dir=hola"
+
+ install_gemfile <<-G
+ source "https://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/no_build_extension_spec.rb b/spec/bundler/install/gems/no_build_extension_spec.rb
new file mode 100644
index 0000000000..31f0170433
--- /dev/null
+++ b/spec/bundler/install/gems/no_build_extension_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with --no-build-extension" do
+ before do
+ build_repo2 do
+ build_gem "with_extension" 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}/with_extension.rb", "w") do |f|
+ f.puts "WITH_EXTENSION = 'YES'"
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ it "skips building native extensions and warns when no_build_extension is set" do
+ bundle_config "no_build_extension true"
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "with_extension"
+ gem "rake"
+ G
+
+ bundle :install
+
+ build_complete = default_bundle_path("extensions").join(
+ Gem::Platform.local.to_s,
+ Gem.extension_api_version.to_s,
+ "with_extension-1.0",
+ "gem.build_complete"
+ )
+ expect(build_complete).not_to exist
+ expect(err).to include("with_extension-1.0 contains native extensions that were not built")
+ expect(err).to include("unset no_build_extension and run `bundle pristine with_extension`")
+ end
+
+ it "builds native extensions by default" do
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "with_extension"
+ gem "rake"
+ G
+
+ bundle :install
+
+ expect(out).to include("Installing with_extension 1.0 with native extensions")
+ end
+end
diff --git a/spec/bundler/install/gems/no_install_plugin_spec.rb b/spec/bundler/install/gems/no_install_plugin_spec.rb
new file mode 100644
index 0000000000..e040e6b813
--- /dev/null
+++ b/spec/bundler/install/gems/no_install_plugin_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with --no-install-plugin" do
+ before do
+ build_repo2 do
+ build_gem "with_plugin", "1.0" do |s|
+ s.write "lib/rubygems_plugin.rb", "# plugin code"
+ end
+
+ build_gem "with_plugin", "2.0"
+ end
+ end
+
+ let(:plugin_path) { default_bundle_path("plugins", "with_plugin_plugin.rb") }
+
+ it "does not generate the plugin wrapper and warns when no_install_plugin is set" do
+ bundle_config "no_install_plugin true"
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "with_plugin", "1.0"
+ G
+
+ expect(plugin_path).not_to exist
+ expect(err).to include("with_plugin-1.0 contains plugins that were not installed")
+ expect(err).to include("unset no_install_plugin and run `bundle pristine with_plugin`")
+ end
+
+ it "removes a stale plugin wrapper from a prior version when no_install_plugin is set" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "with_plugin", "1.0"
+ G
+ expect(plugin_path).to exist
+
+ bundle_config "no_install_plugin true"
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "with_plugin", "2.0"
+ G
+
+ expect(plugin_path).not_to exist
+ end
+
+ it "generates the plugin wrapper by default" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "with_plugin", "1.0"
+ G
+
+ expect(plugin_path).to exist
+ end
+end
diff --git a/spec/bundler/install/gems/post_install_spec.rb b/spec/bundler/install/gems/post_install_spec.rb
index c3ea3e7c51..e49fd2a9a3 100644
--- a/spec/bundler/install/gems/post_install_spec.rb
+++ b/spec/bundler/install/gems/post_install_spec.rb
@@ -1,31 +1,30 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
gem 'thin'
- gem 'rack-obama'
+ gem 'myrack-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 myrack:")
+ expect(out).to include("Myrack'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")
+ expect(out).to include("Post-install message from myrack-obama:")
+ expect(out).to include("Myrack-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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activesupport"
G
@@ -34,16 +33,16 @@ RSpec.describe "bundle install" do
end
end
- context "when a dependecy includes a post install message" do
+ context "when a dependency includes a post install message" do
it "should display the post install message" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack_middleware'
+ source "https://gem.repo1"
+ gem 'myrack_middleware'
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 myrack:")
+ expect(out).to include("Myrack's post install message")
end
end
end
@@ -55,7 +54,7 @@ RSpec.describe "bundle install" do
s.post_install_message = "Foo's post install message"
end
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo', :git => '#{lib_path("foo-1.0")}'
G
@@ -69,7 +68,7 @@ RSpec.describe "bundle install" do
s.post_install_message = "Foo's post install message"
end
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo', :git => '#{lib_path("foo-1.0")}'
G
bundle :install
@@ -78,7 +77,7 @@ RSpec.describe "bundle install" do
s.post_install_message = "Foo's 1.1 post install message"
end
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo', :git => '#{lib_path("foo-1.1")}'
G
bundle :install
@@ -92,7 +91,7 @@ RSpec.describe "bundle install" do
s.post_install_message = "Foo's post install message"
end
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo', :git => '#{lib_path("foo-1.0")}'
G
@@ -111,7 +110,7 @@ RSpec.describe "bundle install" do
s.post_install_message = nil
end
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem 'foo', :git => '#{lib_path("foo-1.0")}'
G
@@ -124,11 +123,11 @@ RSpec.describe "bundle install" do
context "when ignore post-install messages for gem is set" do
it "doesn't display any post-install messages" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "config ignore_messages.rack true"
+ bundle_config "ignore_messages.myrack true"
bundle :install
expect(out).not_to include("Post-install message")
@@ -138,11 +137,11 @@ RSpec.describe "bundle install" do
context "when ignore post-install messages for all gems" do
it "doesn't display any post-install messages" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "config ignore_messages true"
+ bundle_config "ignore_messages true"
bundle :install
expect(out).not_to include("Post-install message")
diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb
index 7a341fd14f..111d361aab 100644
--- a/spec/bundler/install/gems/resolving_spec.rb
+++ b/spec/bundler/install/gems/resolving_spec.rb
@@ -1,10 +1,72 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install with install-time dependencies" do
- it "installs gems with implicit rake dependencies", :ruby_repo 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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "with_implicit_rake_dep"
gem "another_implicit_rake_dep"
gem "rake"
@@ -19,48 +81,69 @@ RSpec.describe "bundle install with install-time dependencies" do
expect(out).to eq("YES\nYES")
end
- it "installs gems with a dependency with no type" do
+ it "installs gems with implicit rake dependencies without rake previously installed" do
+ with_path_as("") do
+ install_gemfile <<-G
+ source "https://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 "does not install 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(Gem.inflate(File.read(path)))
+ spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path)))
spec.dependencies.each do |d|
- d.instance_variable_set(:@type, :fail)
+ d.instance_variable_set(:@type, "fail")
end
- File.open(path, "w") do |f|
+ File.open(path, "wb") do |f|
f.write Gem.deflate(Marshal.dump(spec))
end
- install_gemfile <<-G
- source "file://#{gem_repo2}"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
gem "actionpack", "2.3.2"
G
- expect(the_bundle).to include_gems "actionpack 2.3.2", "activesupport 2.3.2"
+ expect(err).to include("Downloading actionpack-2.3.2 revealed dependencies not in the API (activesupport (= 2.3.2)).")
+
+ expect(the_bundle).not_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://#{gem_repo1}"
+ source "https://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", :ruby_repo do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ it "installs plugins depended on by other plugins" do
+ install_gemfile <<-G, env: { "DEBUG" => "1" }
+ source "https://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", :ruby_repo do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ it "installs multiple levels of dependencies" do
+ install_gemfile <<-G, env: { "DEBUG" => "1" }
+ source "https://gem.repo2"
gem "net_c"
gem "net_e"
G
@@ -68,32 +151,48 @@ RSpec.describe "bundle install with install-time dependencies" do
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 "https://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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "net_c"
gem "net_e"
G
- bundle :install, :env => { "DEBUG_RESOLVER" => "1" }
+ bundle :install, env: { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" }
- expect(err).to include("Creating possibility state for net_c")
+ 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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "net_c"
gem "net_e"
G
- bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1" }
+ bundle :install, env: { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" }
- expect(err).to include(" net_b")
- expect(err).to include(" net_build_extensions (1.0)")
+ 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
@@ -102,19 +201,438 @@ RSpec.describe "bundle install with install-time dependencies" do
context "allows only an older version" do
it "installs the older version" do
build_repo2 do
- build_gem "rack", "9001.0.0" do |s|
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "myrack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ end
+
+ install_gemfile <<-G
+ ruby "#{Gem.ruby_version}"
+ source "https://gem.repo2"
+ gem 'myrack'
+ G
+
+ expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("myrack 1.2")
+ end
+
+ it "installs the older version when using servers not implementing the compact index API" do
+ build_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ end
+
+ build_gem "myrack", "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 }
- ruby "#{RUBY_VERSION}"
- source "http://localgemserver.test/"
- gem 'rack'
+ install_gemfile <<-G, artifice: "endpoint"
+ ruby "#{Gem.ruby_version}"
+ source "https://gem.repo2"
+ gem 'myrack'
G
- expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000")
- expect(the_bundle).to include_gems("rack 1.2")
+ expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("myrack 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 "https://gem.repo2"
+ gem 'parallel_tests'
+ G
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo2, "parallel_tests", "3.8.0"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ parallel_tests (3.8.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ parallel_tests
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically updates lockfile to use the older version" do
+ bundle "install --verbose"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "parallel_tests", "3.7.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ parallel_tests (3.7.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ parallel_tests
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "gives a meaningful error if we're in frozen mode" do
+ expect do
+ bundle "install", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
+ end.not_to change { lockfile }
+
+ expect(err).to eq("parallel_tests-3.8.0 requires ruby version >= #{next_ruby_minor}, which is incompatible with the current version, #{Gem.ruby_version}")
+ 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 "https://gem.repo2"
+ gem 'rubocop'
+ G
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo2, "rubocop", "1.35.0"
+ c.checksum gem_repo2, "rubocop-ast", "1.21.0"
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ rubocop (1.35.0)
+ rubocop-ast (>= 1.20.1, < 2.0)
+ rubocop-ast (1.21.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rubocop
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically updates lockfile to use the older compatible versions" do
+ bundle "install --verbose"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "rubocop", "1.28.2"
+ c.checksum gem_repo2, "rubocop-ast", "1.17.0"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ rubocop (1.28.2)
+ rubocop-ast (>= 1.17.0, < 2.0)
+ rubocop-ast (1.17.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rubocop
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with a Gemfile and lockfile 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 "https://gem.repo4"
+ gem 'sorbet', '= 0.5.10554'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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 = <<~E.strip
+ Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository https://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 include(nice_error)
+ expect(err).to include("Your current platform (aarch64-linux) is not included in the lockfile's platforms (arm64-darwin-21)")
+ expect(err).to include("bundle lock --add-platform aarch64-linux")
+ end
+ end
+
+ context "when adding a new gem that does not resolve under all locked platforms" do
+ before do
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "arm-linux"
+ end
+
+ build_gem "sorbet-static", "0.5.10696" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.0-arm-linux)
+ nokogiri (1.14.0-x86_64-linux)
+
+ PLATFORMS
+ arm-linux
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ gem "sorbet-static"
+ G
+
+ bundle "lock", raise_on_error: false
+ end
+ end
+
+ it "raises a proper error" do
+ nice_error = <<~E.strip
+ Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository https://gem.repo4/ or installed locally.
+
+ The source contains the following gems matching 'sorbet-static':
+ * sorbet-static-0.5.10696-x86_64-linux
+ E
+ expect(err).to end_with(nice_error)
+ end
+ end
+
+ context "when locked generic variant supports current Ruby, but locked specific variant does not" do
+ let(:original_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.16.3)
+ nokogiri (1.16.3-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ before do
+ build_repo4 do
+ build_gem "nokogiri", "1.16.3"
+ build_gem "nokogiri", "1.16.3" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+
+ lockfile original_lockfile
+ end
+
+ it "keeps both variants in the lockfile when installing, and uses the generic one since it's compatible" do
+ simulate_platform "x86_64-linux" do
+ bundle "install --verbose"
+
+ expect(lockfile).to eq(original_lockfile)
+ expect(the_bundle).to include_gems("nokogiri 1.16.3")
+ end
+ end
+
+ it "keeps both variants in the lockfile when updating, and uses the generic one since it's compatible" do
+ simulate_platform "x86_64-linux" do
+ bundle "update --verbose"
+
+ expect(lockfile).to eq(original_lockfile)
+ expect(the_bundle).to include_gems("nokogiri 1.16.3")
+ end
+ 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 "https://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 "myrack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ build_gem "myrack", "1.2"
+ build_gem "foo1", "1.0"
+ end
+
+ install_gemfile <<-G, artifice: "compact_index_rate_limited"
+ ruby "#{Gem.ruby_version}"
+ source "https://gem.repo4"
+ gem 'myrack'
+ gem 'foo1'
+ G
+
+ expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("myrack 1.2")
+ end
+
+ it "installs the older not platform specific version" do
+ build_repo4 do
+ build_gem "myrack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ build_gem "myrack", "1.2" do |s|
+ s.platform = "x86-mingw32"
+ s.required_ruby_version = "> 9000"
+ end
+ build_gem "myrack", "1.2"
+ end
+
+ simulate_platform "x86-mingw32" do
+ install_gemfile <<-G, artifice: "compact_index"
+ ruby "#{Gem.ruby_version}"
+ source "https://gem.repo4"
+ gem 'myrack'
+ G
+ end
+
+ expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000")
+ expect(err).to_not include("myrack-1.2-#{Bundler.local_platform} requires ruby version > 9000")
+ expect(the_bundle).to include_gems("myrack 1.2")
end
end
@@ -127,33 +645,49 @@ RSpec.describe "bundle install with install-time dependencies" do
end
end
- let(:ruby_requirement) { %("#{RUBY_VERSION}") }
- let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" }
+ 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, raise_on_error: false
+ source "https://gem.repo2"
+ gem 'require_ruby'
+ G
+
+ expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000")
+
+ nice_error = <<~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 }
- source "http://localgemserver.test/"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
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
- Fetching gem metadata from http://localgemserver.test/.
- Fetching version metadata from http://localgemserver.test/
- Resolving dependencies...
- Bundler could not find compatible versions for gem "ruby\0":
- In Gemfile:
- ruby\0 (#{error_message_requirement})
+ nice_error = <<~E.strip
+ Could not find compatible versions
- require_ruby was resolved to 1.0, which depends on
- ruby\0 (> 9000)
-
- Could not find gem 'ruby\0 (> 9000)', which is required by gem 'require_ruby', in any of the sources.
+ 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(out).to eq(nice_error)
+ expect(err).to end_with(nice_error)
end
end
@@ -161,14 +695,13 @@ RSpec.describe "bundle install with install-time dependencies" do
describe "with a < requirement" do
let(:ruby_requirement) { %("< 5000") }
- let(:error_message_requirement) { "< 5000" }
it_behaves_like "ruby version conflicts"
end
describe "with a compound requirement" do
- let(:ruby_requirement) { %("< 5000", "> 0.1") }
- let(:error_message_requirement) { "< 5000, > 0.1" }
+ let(:reqs) { ["> 0.1", "< 5000"] }
+ let(:ruby_requirement) { reqs.map(&:dump).join(", ") }
it_behaves_like "ruby version conflicts"
end
@@ -183,13 +716,71 @@ RSpec.describe "bundle install with install-time dependencies" do
end
end
- install_gemfile <<-G
- source "file://#{gem_repo2}"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2"
gem 'require_rubygems'
G
- expect(out).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000")
- expect(out).to include("require_rubygems-1.0 requires rubygems version > 9000, which is incompatible with the current version, #{Gem::VERSION}")
+ expect(err).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000")
+ nice_error = <<~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
+
+ context "when non platform specific gems bring more dependencies", :truffleruby_only do
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0" do |s|
+ s.add_dependency "bar"
+ end
+
+ build_gem "foo", "2.0" do |s|
+ s.platform = "x86_64-linux"
+ end
+
+ build_gem "bar"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "foo"
+ G
+ end
+
+ it "locks both ruby and current platform, and resolve to ruby variants that install on truffleruby" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "foo", "1.0"
+ c.checksum gem_repo4, "bar", "1.0"
+ end
+
+ simulate_platform "x86_64-linux" do
+ bundle "install"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ bar (1.0)
+ foo (1.0)
+ bar
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ foo
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
end
end
end
diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb
index 9a79a05b32..96a305bb76 100644
--- a/spec/bundler/install/gems/standalone_spec.rb
+++ b/spec/bundler/install/gems/standalone_spec.rb
@@ -1,13 +1,18 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.shared_examples "bundle install --standalone" do
+RSpec.describe "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 "myrack-1.0.0"
+
+ expect(the_bundle).to_not include_gems("myrack")
+ end
+
it "generates a bundle/bundler/setup.rb" do
expect(bundled_app("bundle/bundler/setup.rb")).to exist
end
@@ -22,15 +27,66 @@ RSpec.shared_examples "bundle install --standalone" do
testrb << "\nrequire \"#{k}\""
testrb << "\nputs #{k.upcase}"
end
- Dir.chdir(bundled_app) do
- ruby testrb, :no_lib => true
+ 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
+ in_bundled_app %(#{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 "myrack-1.0.0"
+
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ begin
+ require "myrack"
+ rescue LoadError
+ puts "LoadError"
+ end
+ RUBY
+ ruby testrb
+
+ expect(out).to eq("LoadError")
+ end
+
it "works on a different system" do
- FileUtils.mv(bundled_app, "#{bundled_app}2")
+ 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")
@@ -41,9 +97,23 @@ RSpec.shared_examples "bundle install --standalone" do
testrb << "\nrequire \"#{k}\""
testrb << "\nputs #{k.upcase}"
end
- Dir.chdir("#{bundled_app}2") do
- ruby testrb, :no_lib => true
+ ruby testrb, dir: "#{bundled_app}2"
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+
+ it "skips activating gems" do
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ gem "do_not_activate_me"
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nrequire \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
end
+ in_bundled_app %(#{Gem.ruby} -w -e #{testrb.shellescape})
expect(out).to eq(expected_gems.values.join("\n"))
end
@@ -51,10 +121,12 @@ RSpec.shared_examples "bundle install --standalone" do
describe "with simple gems" do
before do
- install_gemfile <<-G, :standalone => true
- source "file://#{gem_repo1}"
+ gemfile <<-G
+ source "https://gem.repo1"
gem "rails"
G
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle :install, standalone: true, dir: cwd
end
let(:expected_gems) do
@@ -67,27 +139,162 @@ RSpec.shared_examples "bundle install --standalone" do
include_examples "common functionality"
end
- describe "with gems with native extension", :ruby_repo do
+ describe "with default gems and a lockfile", :ruby_repo do
+ it "works and points to the vendored copies, not to the default copies" do
+ base_system_gems "stringio", "psych", "etc", 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
+
+ bundle_config "path #{bundled_app("bundle")}"
+
+ bundle :install, standalone: true, dir: cwd, 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
+
+ it "works for gems with extensions and points to the vendored copies, not to the default copies" do
+ simulate_platform "arm64-darwin-23" do
+ base_system_gems "stringio", "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle"))
+
+ build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension
+
+ build_repo4 do
+ build_gem "baz", "1.0.0", &:add_c_extension
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "baz"
+ G
+
+ bundle_config "path #{bundled_app("bundle")}"
+
+ bundle "lock", dir: cwd
+
+ bundle :install, standalone: true, dir: cwd, env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s }
+ end
+
+ 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}/extensions/arm64-darwin-23/#{Gem.extension_api_version}/baz-1.0.0")',
+ '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/baz-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 "https://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
+
+ let(:cwd) { bundled_app }
+
+ 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 "https://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
- install_gemfile <<-G, :standalone => true
- source "file://#{gem_repo1}"
+ bundle_config "path #{bundled_app("bundle")}"
+ install_gemfile <<-G, standalone: true, dir: cwd
+ source "https://gem.repo1"
gem "very_simple_binary"
G
end
- it "generates a bundle/bundler/setup.rb with the proper paths", :rubygems => "2.4" do
- extension_line = File.read(bundled_app("bundle/bundler/setup.rb")).each_line.find {|line| line.include? "/extensions/" }.strip
- expect(extension_line).to start_with '$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/'
- expect(extension_line).to end_with '/very_simple_binary-1.0"'
+ 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|
+ 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/', __FILE__)
+ lib = File.expand_path('lib/', __dir__)
$:.unshift lib unless $:.include?(lib)
require 'bar/version'
@@ -101,14 +308,16 @@ RSpec.shared_examples "bundle install --standalone" do
end
G
end
- install_gemfile <<-G, :standalone => true
+ bundle_config "path #{bundled_app("bundle")}"
+ install_gemfile <<-G, standalone: true, dir: cwd, raise_on_error: false
+ source "https://gem.repo1"
gem "bar", :git => "#{lib_path("bar-1.0")}"
G
end
it "outputs a helpful error message" do
- expect(out).to include("You have one or more invalid gemspecs that need to be fixed.")
- expect(out).to include("bar 1.0 has an invalid gemspec")
+ expect(err).to include("You have one or more invalid gemspecs that need to be fixed.")
+ expect(err).to include("bar.gemspec is not valid")
end
end
@@ -116,11 +325,13 @@ RSpec.shared_examples "bundle install --standalone" do
before do
build_git "devise", "1.0"
- install_gemfile <<-G, :standalone => true
- source "file://#{gem_repo1}"
+ gemfile <<-G
+ source "https://gem.repo1"
gem "rails"
gem "devise", :git => "#{lib_path("devise-1.0")}"
G
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle :install, standalone: true, dir: cwd
end
let(:expected_gems) do
@@ -138,15 +349,17 @@ RSpec.shared_examples "bundle install --standalone" do
before do
build_git "devise", "1.0"
- install_gemfile <<-G, :standalone => true
- source "file://#{gem_repo1}"
+ gemfile <<-G
+ source "https://gem.repo1"
gem "rails"
group :test do
gem "rspec"
- gem "rack-test"
+ gem "myrack-test"
end
G
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle :install, standalone: true, dir: cwd
end
let(:expected_gems) do
@@ -159,74 +372,72 @@ RSpec.shared_examples "bundle install --standalone" do
include_examples "common functionality"
it "allows creating a standalone file with limited groups" do
- bundle "install --standalone default"
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle :install, standalone: "default", dir: cwd
- Dir.chdir(bundled_app) do
- load_error_ruby <<-RUBY, "spec", :no_lib => true
- $:.unshift File.expand_path("bundle")
- require "bundler/setup"
+ load_error_ruby <<-RUBY, "spec"
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
- require "actionpack"
- puts ACTIONPACK
- require "spec"
- RUBY
- end
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
expect(out).to eq("2.3.2")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
- it "allows --without to limit the groups used in a standalone" do
- bundle "install --standalone --without test"
+ it "allows `without` configuration to limit the groups used in a standalone" do
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle_config "without test"
+ bundle :install, standalone: true, dir: cwd
- Dir.chdir(bundled_app) do
- load_error_ruby <<-RUBY, "spec", :no_lib => true
- $:.unshift File.expand_path("bundle")
- require "bundler/setup"
+ load_error_ruby <<-RUBY, "spec"
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
- require "actionpack"
- puts ACTIONPACK
- require "spec"
- RUBY
- end
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
expect(out).to eq("2.3.2")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
- it "allows --path to change the location of the standalone bundle" do
- bundle "install --standalone --path path/to/bundle"
+ it "allows `path` configuration to change the location of the standalone bundle" do
+ bundle_config "path path/to/bundle"
+ bundle "install", standalone: true, dir: cwd
- Dir.chdir(bundled_app) do
- ruby <<-RUBY, :no_lib => true
- $:.unshift File.expand_path("path/to/bundle")
- require "bundler/setup"
+ ruby <<-RUBY
+ $:.unshift File.expand_path("path/to/bundle")
+ require "bundler/setup"
- require "actionpack"
- puts ACTIONPACK
- RUBY
- end
+ require "actionpack"
+ puts ACTIONPACK
+ RUBY
expect(out).to eq("2.3.2")
end
- it "allows remembered --without to limit the groups used in a standalone" do
- bundle "install --without test"
- bundle "install --standalone"
+ it "allows `without` to limit the groups used in a standalone" do
+ bundle_config "without test"
+ bundle :install, dir: cwd
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle :install, standalone: true, dir: cwd
- Dir.chdir(bundled_app) do
- load_error_ruby <<-RUBY, "spec", :no_lib => true
- $:.unshift File.expand_path("bundle")
- require "bundler/setup"
+ load_error_ruby <<-RUBY, "spec"
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
- require "actionpack"
- puts ACTIONPACK
- require "spec"
- RUBY
- end
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
expect(out).to eq("2.3.2")
- expect(err).to eq("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
end
end
@@ -239,7 +450,8 @@ RSpec.shared_examples "bundle install --standalone" do
source "#{source_uri}"
gem "rails"
G
- bundle "install --standalone", :artifice => "endpoint"
+ bundle_config "path #{bundled_app("bundle")}"
+ bundle :install, standalone: true, artifice: "endpoint", dir: cwd
end
let(:expected_gems) do
@@ -252,67 +464,61 @@ RSpec.shared_examples "bundle install --standalone" do
include_examples "common functionality"
end
end
+end
- describe "with --binstubs" do
- before do
- install_gemfile <<-G, :standalone => true, :binstubs => true
- source "file://#{gem_repo1}"
- gem "rails"
- G
- end
+RSpec.describe "bundle install --standalone run in a subdirectory" do
+ let(:cwd) { bundled_app("bob").tap(&:mkpath) }
- let(:expected_gems) do
- {
- "actionpack" => "2.3.2",
- "rails" => "2.3.2",
- }
- end
+ before do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "rails"
+ G
+ end
- include_examples "common functionality"
+ it "generates the script in the proper place" do
+ bundle :install, standalone: true, dir: cwd
- it "creates stubs that use the standalone load path" do
- Dir.chdir(bundled_app) do
- expect(`bin/rails -v`.chomp).to eql "2.3.2"
- end
- end
+ expect(bundled_app("bundle/bundler/setup.rb")).to exist
+ end
- it "creates stubs that can be executed from anywhere" do
- require "tmpdir"
- Dir.chdir(Dir.tmpdir) do
- sys_exec!(%(#{bundled_app("bin/rails")} -v))
- expect(out).to eq("2.3.2")
- end
+ context "when path set to a relative path" do
+ before do
+ bundle_config "path bundle"
end
- it "creates stubs that can be symlinked" do
- pending "File.symlink is unsupported on Windows" if Bundler::WINDOWS
+ it "generates the script in the proper place" do
+ bundle :install, standalone: true, dir: cwd
- 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", path.realpath)
+ expect(bundled_app("bundle/bundler/setup.rb")).to exist
end
end
end
-RSpec.describe "bundle install --standalone" do
- include_examples("bundle install --standalone")
-end
-
-RSpec.describe "bundle install --standalone run in a subdirectory" do
+RSpec.describe "bundle install --standalone --local" do
before do
- subdir = bundled_app("bob")
- FileUtils.mkdir_p(subdir)
- Dir.chdir(subdir)
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ system_gems "myrack-1.0.0", path: default_bundle_path
end
- include_examples("bundle install --standalone")
+ it "generates script pointing to system gems" do
+ bundle "install --standalone --local --verbose"
+
+ expect(out).to include("Using myrack 1.0.0")
+
+ load_error_ruby <<-RUBY, "spec"
+ require "./bundler/setup"
+
+ require "myrack"
+ puts MYRACK
+ require "spec"
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err_without_deprecations).to match(/cannot load such file -- spec/)
+ end
end
diff --git a/spec/bundler/install/gems/sudo_spec.rb b/spec/bundler/install/gems/sudo_spec.rb
deleted file mode 100644
index 13abffc14e..0000000000
--- a/spec/bundler/install/gems/sudo_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "when using sudo", :sudo => true do
- describe "and BUNDLE_PATH is writable" do
- context "but BUNDLE_PATH/build_info is not writable" do
- before do
- subdir = system_gem_path("cache")
- subdir.mkpath
- sudo "chmod u-w #{subdir}"
- end
-
- it "installs" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- expect(out).to_not match(/an error occurred/i)
- expect(system_gem_path("cache/rack-1.0.0.gem")).to exist
- expect(the_bundle).to include_gems "rack 1.0"
- end
- end
- end
-
- describe "and GEM_HOME is owned by root" do
- before :each do
- chown_system_gems_to_root
- end
-
- it "installs" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", '1.0'
- gem "thin"
- G
-
- expect(system_gem_path("gems/rack-1.0.0")).to exist
- expect(system_gem_path("gems/rack-1.0.0").stat.uid).to eq(0)
- expect(the_bundle).to include_gems "rack 1.0"
- end
-
- it "installs rake and a gem dependent on rake in the same session" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rake"
- gem "another_implicit_rake_dep"
- G
- bundle "install"
- expect(system_gem_path("gems/another_implicit_rake_dep-1.0")).to exist
- end
-
- it "installs when BUNDLE_PATH is owned by root" do
- bundle_path = tmp("owned_by_root")
- FileUtils.mkdir_p bundle_path
- sudo "chown -R root #{bundle_path}"
-
- ENV["BUNDLE_PATH"] = bundle_path.to_s
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", '1.0'
- G
-
- expect(bundle_path.join("gems/rack-1.0.0")).to exist
- expect(bundle_path.join("gems/rack-1.0.0").stat.uid).to eq(0)
- expect(the_bundle).to include_gems "rack 1.0"
- end
-
- it "installs when BUNDLE_PATH does not exist" do
- root_path = tmp("owned_by_root")
- FileUtils.mkdir_p root_path
- sudo "chown -R root #{root_path}"
- bundle_path = root_path.join("does_not_exist")
-
- ENV["BUNDLE_PATH"] = bundle_path.to_s
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", '1.0'
- G
-
- expect(bundle_path.join("gems/rack-1.0.0")).to exist
- expect(bundle_path.join("gems/rack-1.0.0").stat.uid).to eq(0)
- expect(the_bundle).to include_gems "rack 1.0"
- end
-
- it "installs extensions/ compiled by Rubygems 2.2", :rubygems => "2.2" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "very_simple_binary"
- G
-
- expect(system_gem_path("gems/very_simple_binary-1.0")).to exist
- binary_glob = system_gem_path("extensions/*/*/very_simple_binary-1.0")
- expect(Dir.glob(binary_glob).first).to be
- end
- end
-
- describe "and BUNDLE_PATH is not writable" do
- before do
- sudo "chmod ugo-w #{default_bundle_path}"
- end
-
- it "installs" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", '1.0'
- G
-
- expect(default_bundle_path("gems/rack-1.0.0")).to exist
- expect(the_bundle).to include_gems "rack 1.0"
- end
-
- it "cleans up the tmpdirs generated" do
- require "tmpdir"
- Dir.glob("#{Dir.tmpdir}/bundler*").each do |tmpdir|
- FileUtils.remove_entry_secure(tmpdir)
- end
-
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- tmpdirs = Dir.glob("#{Dir.tmpdir}/bundler*")
-
- expect(tmpdirs).to be_empty
- end
- end
-
- describe "and GEM_HOME is not writable" do
- it "installs" do
- gem_home = tmp("sudo_gem_home")
- sudo "mkdir -p #{gem_home}"
- sudo "chmod ugo-w #{gem_home}"
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", '1.0'
- G
-
- bundle :install, :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil }
- expect(gem_home.join("bin/rackup")).to exist
- expect(the_bundle).to include_gems "rack 1.0", :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil }
- end
- end
-
- describe "and root runs install" do
- let(:warning) { "Don't run Bundler as root." }
-
- before do
- gemfile %(source "file://#{gem_repo1}")
- end
-
- it "warns against that" do
- bundle :install, :sudo => true
- expect(out).to include(warning)
- end
-
- context "when ENV['BUNDLE_SILENCE_ROOT_WARNING'] is set" do
- it "skips the warning" do
- bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => true }
- expect(out).to_not include(warning)
- end
- end
-
- context "when silence_root_warning is passed as an option" do
- it "skips the warning" do
- bundle :install, :sudo => true, :silence_root_warning => true
- expect(out).to_not include(warning)
- end
- end
-
- context "when silence_root_warning = false" do
- it "warns against that" do
- bundle :install, :sudo => true, :silence_root_warning => false
- expect(out).to include(warning)
- end
- end
- end
-end
diff --git a/spec/bundler/install/gems/win32_spec.rb b/spec/bundler/install/gems/win32_spec.rb
index cdad9a8821..be37673aa1 100644
--- a/spec/bundler/install/gems/win32_spec.rb
+++ b/spec/bundler/install/gems/win32_spec.rb
@@ -1,27 +1,25 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install with win32-generated lockfile" do
it "should read lockfile" do
- File.open(bundled_app("Gemfile.lock"), "wb") do |f|
+ File.open(bundled_app_lock, "wb") do |f|
f << "GEM\r\n"
- f << " remote: file:#{gem_repo1}/\r\n"
+ f << " remote: https://gem.repo1/\r\n"
f << " specs:\r\n"
f << "\r\n"
- f << " rack (1.0.0)\r\n"
+ f << " myrack (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"
+ f << " myrack\r\n"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
- gem "rack"
+ gem "myrack"
G
- expect(exitstatus).to eq(0) if exitstatus
end
end
diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb
index 97eaf149c1..fb2271c830 100644
--- a/spec/bundler/install/gemspecs_spec.rb
+++ b/spec/bundler/install/gemspecs_spec.rb
@@ -1,110 +1,179 @@
# frozen_string_literal: true
-require "spec_helper"
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
+ build_gem "yaml_spec", gemspec: :yaml
end
end
it "still installs correctly" do
gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "yaml_spec"
G
bundle :install
- expect(err).to lack_errors
+ expect(err).to be_empty
end
it "still installs correctly when using path" do
- build_lib "yaml_spec", :gemspec => :yaml
+ build_lib "yaml_spec", gemspec: :yaml
install_gemfile <<-G
+ source "https://gem.repo1"
gem 'yaml_spec', :path => "#{lib_path("yaml_spec-1.0")}"
G
- expect(err).to lack_errors
+ 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'
+ source "http://localgemserver.test"
+ gem 'myrack'
G
- FileUtils.mkdir_p "#{tmp}/gems/system/specifications"
- File.open("#{tmp}/gems/system/specifications/rack-1.0.0.gemspec", "w+") do |f|
+ system_gems "myrack-1.0.0", path: default_bundle_path
+
+ FileUtils.mkdir_p "#{default_bundle_path}/specifications"
+ File.open("#{default_bundle_path}/specifications/myrack-1.0.0.gemspec", "w+") do |f|
spec = Gem::Specification.new do |s|
- s.name = "rack"
+ s.name = "myrack"
s.version = "1.0.0"
- s.add_runtime_dependency "activesupport", "2.3.2"
+ s.add_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 "activesupport 2.3.2"
+ bundle :install, artifice: "endpoint_marshal_fail" # force gemspec load
+ expect(the_bundle).to include_gems "myrack 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 "https://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 "https://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" do
- build_lib("foo", :path => bundled_app) do |s|
+ 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 "https://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|
+ 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
+ install_gemfile <<-G, raise_on_error: false
ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}'
+ source "https://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
+ it "installs gems ignoring the mismatch even when patchlevel is mismatch",
+ if: RUBY_PATCHLEVEL >= 0 do
patchlevel = RUBY_PATCHLEVEL.to_i + 1
- build_lib("foo", :path => bundled_app) do |s|
+ build_lib("foo", path: bundled_app) do |s|
s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}"
end
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}'
+ source "https://gem.repo1"
gemspec
G
- expect(out).to include("Ruby patchlevel")
- expect(out).to include("but your Gemfile specified")
- expect(exitstatus).to eq(18) if exitstatus
+ expect(the_bundle).to include_gems "foo 1.0"
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|
+ build_lib("foo", path: bundled_app) do |s|
s.required_ruby_version = version
end
- install_gemfile <<-G
+ install_gemfile <<-G, raise_on_error: false
ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby'
+ source "https://gem.repo1"
gemspec
G
- expect(out).to include("Ruby version")
- expect(out).to include("but your Gemfile specified")
- expect(exitstatus).to eq(18) if exitstatus
+ expect(err).to include("Ruby version")
+ expect(err).to include("but your Gemfile specified")
+ expect(exitstatus).to eq(18)
+ end
+
+ it "validates gemspecs just once when everything installed and lockfile up to date" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gemspec path: "#{lib_path("foo-1.0")}"
+
+ module Monkey
+ def validate(spec)
+ puts "Validate called on \#{spec.full_name}"
+ end
+ end
+ Bundler.rubygems.extend(Monkey)
+ G
+
+ bundle "install"
+
+ expect(out).to include("Validate called on foo-1.0").once
end
end
end
diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb
index 04f2380b45..1172d661ae 100644
--- a/spec/bundler/install/git_spec.rb
+++ b/spec/bundler/install/git_spec.rb
@@ -1,52 +1,80 @@
# frozen_string_literal: true
-require "spec_helper"
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")
+ build_git "foo", "1.0", path: lib_path("foo")
- install_gemfile <<-G
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo")}"
G
- bundle :install
- expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at master@#{revision_for(lib_path("foo"))[0..6]})")
- expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+ expect(out).to include("Using foo 1.0 from #{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 revision hash of the gem repository when passed a relative local path" do
+ build_git "foo", "1.0", path: lib_path("foo")
+
+ relative_path = lib_path("foo").relative_path_from(bundled_app)
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "foo", :git => "#{relative_path}"
+ G
+
+ expect(out).to include("Using foo 1.0 from #{relative_path} (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: "non-standard"
+
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at non-standard@#{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
- build_git "foo", "1.0", :path => lib_path("foo")
+ 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
+ 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
+ update_git "foo", "3.0", path: lib_path("foo"), gemspec: true
- install_gemfile! <<-G
- gem "foo", :git => "#{lib_path("foo")}", :ref => "master~2"
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo")}", :ref => "main~2"
G
- bundle! :install
- expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at master~2@#{rev})")
- expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+ expect(out).to include("Using foo 1.0 from #{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
+ update_git "foo", "4.0", path: lib_path("foo"), gemspec: true
- bundle! :update
- expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at master~2@#{rev2})")
- expect(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}"
+ bundle :update, all: true, verbose: true
+ expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at main~2@#{rev2})")
+ expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}"
end
- it "should check out git repos that are missing but not being installed" do
- build_git "foo"
+ it "allows git repos that are missing but not being installed" do
+ revision = build_git("foo").ref_for("HEAD")
gemfile <<-G
- gem "foo", :git => "file://#{lib_path("foo-1.0")}", :group => :development
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :group => :development
G
lockfile <<-L
GIT
- remote: file://#{lib_path("foo-1.0")}
+ remote: #{lib_path("foo-1.0")}
+ revision: #{revision}
specs:
foo (1.0)
@@ -57,10 +85,285 @@ RSpec.describe "bundle install" do
foo!
L
- bundle "install --path=vendor/bundle --without development"
+ bundle_config "path vendor/bundle"
+ bundle_config "without development"
+ bundle :install
expect(out).to include("Bundle complete!")
- expect(vendored_gems("bundler/gems/foo-1.0-#{revision_for(lib_path("foo-1.0"))[0..11]}")).to be_directory
+ 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 "https://gem.repo2"
+ gem "foo", :git => "#{lib_path("gems")}", :glob => "foo/*.gemspec"
+ gem "zebra", :git => "#{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 "https://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: https://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
+
+ it "allows older revisions of git source when clean true" do
+ build_git "foo", "1.0", path: lib_path("foo")
+ rev = revision_for(lib_path("foo"))
+
+ bundle_config "path vendor/bundle"
+ bundle_config "clean true"
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{rev[0..6]})")
+ expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}"
+
+ old_lockfile = lockfile
+
+ update_git "foo", "2.0", path: lib_path("foo"), gemspec: true
+ rev2 = revision_for(lib_path("foo"))
+
+ bundle :update, all: true, verbose: true
+ expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at main@#{rev2[0..6]})")
+ expect(out).to include("Removing foo (#{rev[0..11]})")
+ expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}"
+
+ lockfile(old_lockfile)
+
+ bundle :install, verbose: true
+ expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{rev[0..6]})")
+ expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}"
+ end
+
+ context "when install directory exists" do
+ let(:checkout_confirmation_log_message) { "Checking out revision" }
+ let(:using_foo_confirmation_log_message) { "Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})" }
+
+ context "and no contents besides .git directory are present" do
+ it "reinstalls gem" do
+ build_git "foo", "1.0", path: lib_path("foo")
+
+ gemfile = <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ install_gemfile gemfile, verbose: true
+
+ expect(out).to include(checkout_confirmation_log_message)
+ expect(out).to include(using_foo_confirmation_log_message)
+ expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}"
+
+ # validate that the installed directory exists and has some expected contents
+ install_directory = default_bundle_path("bundler/gems/foo-#{revision_for(lib_path("foo"))[0..11]}")
+ dot_git_directory = install_directory.join(".git")
+ lib_directory = install_directory.join("lib")
+ gemspec = install_directory.join("foo.gemspec")
+ expect([install_directory, dot_git_directory, lib_directory, gemspec]).to all exist
+
+ # remove all elements in the install directory except .git directory
+ FileUtils.rm_r(lib_directory)
+ gemspec.delete
+
+ expect(dot_git_directory).to exist
+ expect(lib_directory).not_to exist
+ expect(gemspec).not_to exist
+
+ # rerun bundle install
+ install_gemfile gemfile, verbose: true
+
+ expect(out).to include(checkout_confirmation_log_message)
+ expect(out).to include(using_foo_confirmation_log_message)
+ expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}"
+
+ # validate that it reinstalls all components
+ expect([install_directory, dot_git_directory, lib_directory, gemspec]).to all exist
+ end
+ end
+
+ context "and contents besides .git directory are present" do
+ # we want to confirm that the change to try to detect partial installs and reinstall does not
+ # result in repeatedly reinstalling the gem when it is fully installed
+ it "does not reinstall gem" do
+ build_git "foo", "1.0", path: lib_path("foo")
+
+ gemfile = <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ install_gemfile gemfile, verbose: true
+
+ expect(out).to include(checkout_confirmation_log_message)
+ expect(out).to include(using_foo_confirmation_log_message)
+ expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}"
+
+ # rerun bundle install
+ install_gemfile gemfile, verbose: true
+
+ # it isn't altogether straight-forward to validate that bundle didn't do soething on the second run, however,
+ # the presence of the 2nd log message confirms install got past the point that it would have logged the above if
+ # it was going to
+ expect(out).not_to include(checkout_confirmation_log_message)
+ expect(out).to include(using_foo_confirmation_log_message)
+ end
+ end
+ end
+ end
+
+ describe "with excluded groups" do
+ it "works if you exclude a group with a git gem", ruby: ">= 3.3" do
+ build_git "production_gem", "1.0"
+ build_git "development_gem", "1.0"
+
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "production_gem", :git => "#{lib_path("production_gem-1.0")}"
+
+ group :development do
+ gem "development_gem", :git => "#{lib_path("development_gem-1.0")}"
+ end
+ G
+
+ # First install all groups to create lockfile
+ bundle :install
+
+ # Set without and reinstall
+ bundle_config "without development"
+ bundle :install
+
+ # Verify only production gem is available
+ expect(the_bundle).to include_gems("production_gem 1.0")
+ expect(the_bundle).not_to include_gems("development_gem 1.0")
+ end
+
+ it "resolves indirect dependencies from a git source not in the requested groups" do
+ build_lib "activesupport", "1.0", path: lib_path("rails/activesupport")
+ build_git "activerecord", "1.0", path: lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 1.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "activerecord", :git => "#{lib_path("rails")}"
+
+ group :ci do
+ gem "myrack"
+ end
+ G
+
+ bundle_config "only ci"
+ bundle :install
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ expect(the_bundle).not_to include_gems("activerecord 1.0")
+ end
+
+ it "resolves indirect dependencies from a git source not in the requested groups (without compact_index dependency API)" do
+ build_lib "activesupport", "1.0", path: lib_path("rails/activesupport")
+ build_git "activerecord", "1.0", path: lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 1.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "activerecord", :git => "#{lib_path("rails")}"
+
+ group :ci do
+ gem "myrack"
+ end
+ G
+
+ # Force the RubygemsAggregate code path in find_source_requirements by
+ # making the dependency API unavailable.
+ bundle_config "only ci"
+ bundle :install, artifice: "endpoint_api_forbidden"
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ expect(the_bundle).not_to include_gems("activerecord 1.0")
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..4cffa65b2a
--- /dev/null
+++ b/spec/bundler/install/global_cache_spec.rb
@@ -0,0 +1,305 @@
+# frozen_string_literal: true
+
+RSpec.describe "global gem caching" do
+ # Uses subprocess because this setting must apply across multiple app directories (bundled_app and bundled_app2)
+ 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 cache_base
+ # Use the unified global gem cache path if available (from RubyGems),
+ # otherwise fall back to the Bundler-specific cache location
+ if Gem.respond_to?(:global_gem_cache_path)
+ Pathname.new(Gem.global_gem_cache_path)
+ else
+ home(".bundle", "cache", "gems")
+ end
+ end
+
+ def source_global_cache(*segments)
+ cache_base.join("localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments)
+ end
+
+ def source2_global_cache(*segments)
+ cache_base.join("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 "myrack"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ expect(source_global_cache("myrack-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/myrack-1.0.0.gem"), source_global_cache("myrack-1.0.0.gem"))
+
+ install_gemfile <<-G, artifice: "compact_index_no_gem"
+ source "#{source}"
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "shows a proper error message if a cached gem is corrupted" do
+ skip "This example is not working on ruby/ruby repo" if ruby_core?
+
+ source_global_cache.mkpath
+ FileUtils.touch(source_global_cache("myrack-1.0.0.gem"))
+
+ install_gemfile <<-G, artifice: "compact_index_no_gem", raise_on_error: false
+ source "#{source}"
+ gem "myrack"
+ G
+
+ expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("myrack-1.0.0.gem")}")
+ end
+
+ it "uses a shorter path for the cache to not hit filesystem limits" do
+ install_gemfile <<-G, artifice: "compact_index", verbose: true
+ source "http://#{"a" * 255}.test"
+ gem "myrack"
+ G
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ source_segment = "a" * 222 + ".a3cb26de2edfce9f509a65c611d99c4b"
+ source_cache = cache_base.join(source_segment)
+ cached_gem = source_cache.join("myrack-1.0.0.gem")
+ expect(cached_gem).to exist
+ ensure
+ # We cleanup dummy files created by this spec manually because due to a
+ # Ruby on Windows bug, `FileUtils.rm_rf` (run in our global after hook)
+ # cannot traverse directories with such long names. So we delete
+ # everything explicitly to workaround the bug. An alternative workaround
+ # would be to shell out to `rm -rf`. That also works fine, but I went with
+ # the more verbose and explicit approach. This whole ensure block can be
+ # removed once/if https://bugs.ruby-lang.org/issues/21177 is fixed, and
+ # once the fix propagates to all supported rubies.
+ File.delete cached_gem
+ Dir.rmdir source_cache
+
+ File.delete compact_index_cache_path.join(source_segment, "info", "myrack")
+ Dir.rmdir compact_index_cache_path.join(source_segment, "info")
+ File.delete compact_index_cache_path.join(source_segment, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073")
+ Dir.rmdir compact_index_cache_path.join(source_segment, "info-etags")
+ Dir.rmdir compact_index_cache_path.join(source_segment, "info-special-characters")
+ File.delete compact_index_cache_path.join(source_segment, "versions")
+ File.delete compact_index_cache_path.join(source_segment, "versions.etag")
+ Dir.rmdir compact_index_cache_path.join(source_segment)
+ end
+
+ describe "when the same gem from different sources is installed" do
+ it "should use the appropriate one from the global cache" do
+ bundle_config "path.system true"
+
+ install_gemfile <<-G, artifice: "compact_index"
+ source "#{source}"
+ gem "myrack"
+ G
+
+ pristine_system_gems
+ expect(the_bundle).not_to include_gems "myrack 1.0.0"
+ expect(source_global_cache("myrack-1.0.0.gem")).to exist
+ # myrack 1.0.0 is not installed and it is in the global cache
+
+ install_gemfile <<-G, artifice: "compact_index"
+ source "#{source2}"
+ gem "myrack", "0.9.1"
+ G
+
+ pristine_system_gems
+ expect(the_bundle).not_to include_gems "myrack 0.9.1"
+ expect(source2_global_cache("myrack-0.9.1.gem")).to exist
+ # myrack 0.9.1 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source}"
+ gem "myrack", "1.0.0"
+ G
+
+ bundle :install, artifice: "compact_index_no_gem"
+ # myrack 1.0.0 is installed and myrack 0.9.1 is not
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ expect(the_bundle).not_to include_gems "myrack 0.9.1"
+ pristine_system_gems
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "myrack", "0.9.1"
+ G
+
+ bundle :install, artifice: "compact_index_no_gem"
+ # myrack 0.9.1 is installed and myrack 1.0.0 is not
+ expect(the_bundle).to include_gems "myrack 0.9.1"
+ expect(the_bundle).not_to include_gems "myrack 1.0.0"
+ end
+
+ it "should not install if the wrong source is provided" do
+ bundle_config "path.system true"
+
+ gemfile <<-G
+ source "#{source}"
+ gem "myrack"
+ G
+
+ bundle :install, artifice: "compact_index"
+ pristine_system_gems
+ expect(the_bundle).not_to include_gems "myrack 1.0.0"
+ expect(source_global_cache("myrack-1.0.0.gem")).to exist
+ # myrack 1.0.0 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "myrack", "0.9.1"
+ G
+
+ bundle :install, artifice: "compact_index"
+ pristine_system_gems
+ expect(the_bundle).not_to include_gems "myrack 0.9.1"
+ expect(source2_global_cache("myrack-0.9.1.gem")).to exist
+ # myrack 0.9.1 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "myrack", "1.0.0"
+ G
+
+ expect(source_global_cache("myrack-1.0.0.gem")).to exist
+ expect(source2_global_cache("myrack-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")
+
+ # myrack 1.0.0 is not installed and myrack 0.9.1 is not
+ expect(the_bundle).not_to include_gems "myrack 1.0.0"
+ expect(the_bundle).not_to include_gems "myrack 0.9.1"
+
+ gemfile <<-G
+ source "#{source}"
+ gem "myrack", "0.9.1"
+ G
+
+ expect(source_global_cache("myrack-1.0.0.gem")).to exist
+ expect(source2_global_cache("myrack-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")
+
+ # myrack 0.9.1 is not installed and myrack 1.0.0 is not
+ expect(the_bundle).not_to include_gems "myrack 0.9.1"
+ expect(the_bundle).not_to include_gems "myrack 1.0.0"
+ end
+ end
+
+ describe "when installing gems from a different directory" do
+ it "uses the global cache as a source" do
+ bundle_config "path.system true"
+
+ install_gemfile <<-G, artifice: "compact_index"
+ source "#{source}"
+ gem "myrack"
+ gem "activesupport"
+ G
+
+ # Both gems are installed and in the global cache
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("myrack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+ pristine_system_gems
+ # Both gems are now only in the global cache
+ expect(the_bundle).not_to include_gems "myrack 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 "myrack"
+ G
+
+ # myrack is installed and both are in the global cache
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("myrack-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 "myrack 1.0.0", dir: bundled_app2
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", dir: bundled_app2
+ expect(source_global_cache("myrack-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
+ expect(the_bundle).not_to include_gems "myrack 1.0.0", dir: bundled_app2
+ expect(the_bundle).to include_gems "activesupport 2.3.5", dir: bundled_app2
+
+ expect(source_global_cache("myrack-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?
+ skip "fails for unknown reason when run by ruby-core" if ruby_core?
+
+ 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 "https://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,
+ "gem.repo1.443.#{Digest(:MD5).hexdigest("gem.repo1.443./")}", "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_r 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 "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
index 7a501d42b3..49360e511e 100644
--- a/spec/bundler/install/path_spec.rb
+++ b/spec/bundler/install/path_spec.rb
@@ -1,66 +1,87 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle install" do
- describe "with --path" 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'"
+ build_gem "myrack", "1.0.0", to_system: true do |s|
+ s.write "lib/myrack.rb", "puts 'FAIL'"
end
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
- it "does not use available system gems with bundle --path vendor/bundle" do
- bundle "install --path vendor/bundle"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ it "does not use available system gems with `vendor/bundle" do
+ bundle_config "path vendor/bundle"
+ bundle :install
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "uses system gems with `path.system` configured with more priority than `path`" do
+ bundle_config "path.system true"
+ bundle_config_global "path vendor/bundle"
+ bundle :install
+ run "require 'myrack'", 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
- Dir.chdir(dir) do
- bundle "install --path vendor/bundle"
- expect(out).to include("installed into ./vendor/bundle")
- end
+ bundle_config "path #{dir.join("vendor/bundle")}"
+ bundle :install, dir: dir
+ expect(out).to include("installed into `./vendor/bundle`")
+
+ FileUtils.rm_rf dir
+ end
- dir.rmtree
+ it "prints a message to let the user know where gems where installed" do
+ bundle_config "path vendor/bundle"
+ bundle :install
+ expect(out).to include("gems are installed into `./vendor/bundle`")
end
- it "prints a warning to let the user know what has happened with bundle --path vendor/bundle" do
- bundle "install --path vendor/bundle"
- expect(out).to include("gems are installed into ./vendor")
+ it "installs the bundle relatively to repository root, when Bundler run from the same directory" do
+ bundle "config set path vendor/bundle", dir: bundled_app.parent
+ bundle "install --gemfile='#{bundled_app}/Gemfile'", dir: bundled_app.parent
+ expect(out).to include("installed into `./bundled_app/vendor/bundle`")
+ expect(bundled_app("vendor/bundle")).to be_directory
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "disallows --path vendor/bundle --system" do
- bundle "install --path vendor/bundle --system"
- expect(out).to include("Please choose only one option.")
- expect(exitstatus).to eq(15) if exitstatus
+ it "installs the bundle relatively to repository root, when Bundler run from a different directory" do
+ bundle "config set path vendor/bundle", dir: bundled_app
+ bundle "install --gemfile='#{bundled_app}/Gemfile'", dir: bundled_app.parent
+ expect(out).to include("installed into `./bundled_app/vendor/bundle`")
+ expect(bundled_app("vendor/bundle")).to be_directory
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "remembers to disable system gems after the first time with bundle --path vendor/bundle" do
- bundle "install --path vendor/bundle"
- FileUtils.rm_rf bundled_app("vendor")
- bundle "install"
+ 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
- expect(vendored_gems("gems/rack-1.0.0")).to be_directory
- expect(the_bundle).to include_gems "rack 1.0.0"
+ 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
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'"
+ build_lib "myrack", "1.0.0", to_system: true do |s|
+ s.write "lib/myrack.rb", "raise 'FAIL'"
end
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
@@ -68,111 +89,126 @@ RSpec.describe "bundle install" do
if type == :env
ENV["BUNDLE_PATH"] = location
elsif type == :global
- bundle "config path #{location}", "no-color" => nil
+ bundle "config set path #{location}", "no-color" => nil
end
end
[:env, :global].each do |type|
- it "installs gems to a path if one is specified" do
- set_bundle_path(type, bundled_app("vendor2").to_s)
- bundle "install --path vendor/bundle"
+ 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 "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
+ expect(vendored_gems("gems/myrack-1.0.0")).to be_directory
+ expect(bundled_app("vendor2")).not_to be_directory
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
- it "installs gems to BUNDLE_PATH with #{type}" do
- set_bundle_path(type, bundled_app("vendor").to_s)
+ it "installs gems to ." do
+ set_bundle_path(type, ".")
+ bundle_config_global "disable_shared_gems true"
- bundle :install
+ bundle :install
- expect(bundled_app("vendor/gems/rack-1.0.0")).to be_directory
- expect(the_bundle).to include_gems "rack 1.0.0"
- end
+ paths_to_exist = %w[cache/myrack-1.0.0.gem gems/myrack-1.0.0 specifications/myrack-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 "myrack 1.0.0"
+ end
- it "installs gems to BUNDLE_PATH relative to root when relative" do
- set_bundle_path(type, "vendor")
+ it "installs gems to the path" do
+ set_bundle_path(type, bundled_app("vendor").to_s)
- FileUtils.mkdir_p bundled_app("lol")
- Dir.chdir(bundled_app("lol")) do
bundle :install
+
+ expect(bundled_app("vendor", Bundler.ruby_scope, "gems/myrack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- expect(bundled_app("vendor/gems/rack-1.0.0")).to be_directory
- expect(the_bundle).to include_gems "rack 1.0.0"
+ 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/myrack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "myrack 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_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"
+ expect(vendored_gems("gems/myrack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "sets BUNDLE_PATH as the first argument to bundle install" do
- bundle "install --path ./vendor/bundle"
+ bundle_config "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"
+ expect(vendored_gems("gems/myrack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "myrack 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 "install --path ./vendor/bundle"
+ build_gem "myrack", "1.1.0", to_system: true
+ bundle_config "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"
+ expect(vendored_gems("gems/myrack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "re-installs gems whose extensions have been deleted", :ruby_repo, :rubygems => ">= 2.3" do
- build_lib "very_simple_binary", "1.0.0", :to_system => true do |s|
+ 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://#{gem_repo1}"
+ source "https://gem.repo1"
gem "very_simple_binary"
G
- bundle "install --path ./vendor/bundle"
+ bundle_config "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"
+ expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1"
- vendored_gems("extensions").rmtree
+ FileUtils.rm_rf vendored_gems("extensions")
- run "require 'very_simple_binary_c'"
+ run "require 'very_simple_binary_c'", raise_on_error: false
expect(err).to include("Bundler::GemNotFound")
- bundle "install --path ./vendor/bundle"
+ bundle_config "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"
+ expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1"
end
end
describe "to a file" do
before do
- in_app_root do
- `touch /tmp/idontexist bundle`
- end
+ FileUtils.touch bundled_app("bundle")
end
it "reports the file exists" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "install --path bundle"
- expect(out).to match(/file already exists/)
+ bundle_config "path bundle"
+ bundle :install, raise_on_error: false
+ expect(err).to include("file already exists")
end
end
end
diff --git a/spec/bundler/install/post_bundle_message_spec.rb b/spec/bundler/install/post_bundle_message_spec.rb
deleted file mode 100644
index 4453e4190f..0000000000
--- a/spec/bundler/install/post_bundle_message_spec.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "post bundle message" do
- before :each do
- gemfile <<-G
- source "file://#{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_show_message) { "Use `bundle info [gemname]` to see where a bundled gem is installed." }
- let(:bundle_deployment_message) { "Bundled gems are installed into ./vendor" }
- let(:bundle_complete_message) { "Bundle complete!" }
- let(:bundle_updated_message) { "Bundle updated!" }
- let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." }
-
- describe "for fresh bundle install" do
- it "without any options" 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)
- end
-
- it "with --without one group" do
- bundle "install --without emo"
- 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"
- 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.")
- end
-
- it "with --without more groups" do
- bundle "install --without emo obama test"
- 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 and" do
- it "without any options" do
- bundle "install --path vendor"
- expect(out).to include(bundle_deployment_message)
- expect(out).to_not include("Gems in the group")
- expect(out).to include(bundle_complete_message)
- end
-
- it "with --without one group" do
- bundle "install --without emo --path vendor"
- expect(out).to include(bundle_deployment_message)
- expect(out).to include("Gems in the group emo were not installed")
- expect(out).to include(bundle_complete_message)
- end
-
- it "with --without two groups" do
- bundle "install --without emo test --path vendor"
- expect(out).to include(bundle_deployment_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 --path vendor"
- expect(out).to include(bundle_deployment_message)
- expect(out).to include("Gems in the groups emo, obama and test were not installed")
- expect(out).to include(bundle_complete_message)
- end
-
- it "with an absolute --path inside the cwd" do
- bundle "install --path #{bundled_app}/cache"
- 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
-
- it "with an absolute --path outside the cwd" do
- bundle "install --path #{bundled_app}_cache"
- expect(out).to include("Bundled gems are installed into #{bundled_app}_cache")
- 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
- it "should report a helpful error message" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "not-a-gem", :group => :development
- G
- expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile.")
- end
-
- it "should report a helpful error message with reference to cache if available" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
- bundle :cache
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "not-a-gem", :group => :development
- G
- expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile or in gems cached in vendor/cache.")
- end
- end
- end
-
- describe "for second bundle install run" 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 "without any options" do
- bundle :update
- expect(out).not_to include("Gems in the groups")
- expect(out).to include(bundle_updated_message)
- end
-
- it "with --without one group" do
- bundle :install, :without => :emo
- bundle :update
- expect(out).to include("Gems in the group emo were not installed")
- expect(out).to include(bundle_updated_message)
- end
-
- it "with --without two groups" do
- bundle "install --without emo test"
- bundle :update
- expect(out).to include("Gems in the groups emo and test were not installed")
- expect(out).to include(bundle_updated_message)
- end
-
- it "with --without more groups" do
- bundle "install --without emo obama test"
- bundle :update
- expect(out).to include("Gems in the groups emo, obama and test were not installed")
- expect(out).to include(bundle_updated_message)
- end
- end
-end
diff --git a/spec/bundler/install/prereleases_spec.rb b/spec/bundler/install/prereleases_spec.rb
index 6c32094d90..9f764d127c 100644
--- a/spec/bundler/install/prereleases_spec.rb
+++ b/spec/bundler/install/prereleases_spec.rb
@@ -1,11 +1,19 @@
# frozen_string_literal: true
-require "spec_helper"
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://#{gem_repo1}"
+ source "https://gem.repo2"
gem "not_released"
G
expect(the_bundle).to include_gems "not_released 1.0.pre"
@@ -13,7 +21,7 @@ RSpec.describe "bundle install" do
it "uses regular releases if available" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2"
gem "has_prerelease"
G
expect(the_bundle).to include_gems "has_prerelease 1.0"
@@ -21,7 +29,7 @@ RSpec.describe "bundle install" do
it "uses prereleases if requested" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2"
gem "has_prerelease", "1.1.pre"
G
expect(the_bundle).to include_gems "has_prerelease 1.1.pre"
@@ -30,13 +38,17 @@ RSpec.describe "bundle install" do
describe "when prerelease gems are not available" do
it "still works" do
- build_repo3
+ build_repo3 do
+ build_gem "myrack"
+ end
+ FileUtils.rm_r Dir[gem_repo3("prerelease*")]
+
install_gemfile <<-G
- source "file://#{gem_repo3}"
- gem "rack"
+ source "https://gem.repo3"
+ gem "myrack"
G
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 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..b096291d1a
--- /dev/null
+++ b/spec/bundler/install/process_lock_spec.rb
@@ -0,0 +1,113 @@
+# 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 "myrack 1.0"
+ end
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ thread.join
+ expect(the_bundle).to include_gems "myrack 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 lockfile 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 lockfile 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 lockfile and yields" do
+ processed = false
+ Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
+
+ expect(processed).to eq true
+ end
+ end
+
+ it "refreshes gem specification cache after waiting for lock" do
+ build_repo2 do
+ build_gem "myrack", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack"
+ G
+
+ # First, install the gem so it's available
+ bundle "install"
+ expect(out).to include("Installing myrack")
+
+ # Queue for thread-safe communication
+ lock_acquired = Queue.new
+ can_release_lock = Queue.new
+ install_output = Queue.new
+
+ # Thread holds lock (simulating another bundle process that just finished installing)
+ thread = Thread.new do
+ Bundler::ProcessLock.lock(default_bundle_path) do
+ # Signal that we have the lock
+ lock_acquired << true
+ # Wait until main thread signals we can release
+ can_release_lock.pop
+ end
+ end
+
+ # Wait for thread to acquire lock
+ lock_acquired.pop
+
+ # Start another install in a thread - it will wait for the lock
+ install_thread = Thread.new do
+ bundle "install", verbose: true
+ install_output << out
+ end
+
+ # Give subprocess time to start and begin waiting for lock
+ sleep 0.5
+
+ # Signal thread to release the lock
+ can_release_lock << true
+
+ # Wait for both threads to complete
+ thread.join
+ install_thread.join
+
+ second_install_out = install_output.pop
+
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ # The second install should have refreshed its cache after acquiring
+ # the lock and seen that myrack was already installed
+ expect(second_install_out).to include("Using myrack")
+ end
+ end
+end
diff --git a/spec/bundler/install/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb
index ab531bdad6..e7f64dc227 100644
--- a/spec/bundler/install/security_policy_spec.rb
+++ b/spec/bundler/install/security_policy_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "spec_helper"
+
require "rubygems/security"
# unfortunately, testing signed gems with a provided CA is extremely difficult
@@ -9,38 +9,35 @@ RSpec.describe "policies with unsigned gems" do
before do
build_security_repo
gemfile <<-G
- source "file://#{security_repo}"
- gem "rack"
+ source "https://gems.security"
+ gem "myrack"
gem "signed_gem"
G
end
it "will work after you try to deploy without a lock" do
- bundle "install --deployment"
+ bundle "install --deployment", raise_on_error: false
bundle :install
- expect(exitstatus).to eq(0) if exitstatus
- expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0"
+ expect(the_bundle).to include_gems "myrack 1.0", "signed_gem 1.0"
end
it "will fail when given invalid security policy" do
- bundle "install --trust-policy=InvalidPolicyName"
- expect(out).to include("Rubygems doesn't know about trust policy")
+ 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"
- expect(out).to include("security policy didn't allow")
+ bundle "install --trust-policy=HighSecurity", raise_on_error: false
+ expect(err).to include("security policy didn't allow")
end
- # This spec will fail on Rubygems 2 rc1 due to a bug in policy.rb. the bug is fixed in rc3.
- it "will fail with Medium Security setting due to presence of unsigned gem", :unless => ENV["RGV"] == "v2.0.0.rc.1" do
- bundle "install --trust-policy=MediumSecurity"
- expect(out).to include("security policy didn't allow")
+ 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"
- expect(exitstatus).to eq(0) if exitstatus
end
end
@@ -48,30 +45,28 @@ RSpec.describe "policies with signed gems and no CA" do
before do
build_security_repo
gemfile <<-G
- source "file://#{security_repo}"
+ source "https://gems.security"
gem "signed_gem"
G
end
it "will fail with High Security setting, gem is self-signed" do
- bundle "install --trust-policy=HighSecurity"
- expect(out).to include("security policy didn't allow")
+ 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"
- expect(out).to include("security policy didn't allow")
+ 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(exitstatus).to eq(0) if exitstatus
expect(the_bundle).to include_gems "signed_gem 1.0"
end
it "will succeed with no policy" do
bundle "install"
- expect(exitstatus).to eq(0) if exitstatus
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
index d42978ce4c..c92af7bfb0 100644
--- a/spec/bundler/install/yanked_spec.rb
+++ b/spec/bundler/install/yanked_spec.rb
@@ -1,72 +1,254 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.context "when installing a bundle that includes yanked gems" do
- before(:each) do
+ it "throws an error when the original gem version is yanked" 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://#{gem_repo4}
+ remote: https://gem.repo4
specs:
foo (10.0.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
foo (= 10.0.0)
L
- install_gemfile <<-G
- source "file://#{gem_repo4}"
- gem "foo", "10.0.0"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo4"
+ gem "foo", "10.0.0"
G
- expect(out).to include("Your bundle is locked to foo (10.0.0)")
+ expect(err).to include("Your bundle is locked to foo (10.0.0)")
+ end
+
+ context "when a platform specific yanked version is included in the lockfile, and a generic variant is available remotely" do
+ let(:original_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo4/
+ 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
+
+ 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"
+ build_gem "foo", "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 "https://gem.repo4"
+ gem "foo", "1.0.0"
+ gem "actiontext", "6.1.6"
+ G
+
+ lockfile original_lockfile
+ end
+
+ context "and a re-resolve is necessary" do
+ before do
+ gemfile gemfile.sub('"foo", "1.0.0"', '"foo", "1.0.1"')
+ end
+
+ it "reresolves, and replaces the yanked gem with the generic version, printing a warning, when the old index is used" do
+ bundle "install", artifice: "endpoint", verbose: true
+
+ expect(out).to include("Installing nokogiri 1.13.8").and include("Installing foo 1.0.1")
+ expect(lockfile).to eq(original_lockfile.sub("nokogiri (1.13.8-#{Bundler.local_platform})", "nokogiri (1.13.8)").gsub("1.0.0", "1.0.1"))
+ expect(err).to include("Some locked specs have possibly been yanked (nokogiri-1.13.8-#{Bundler.local_platform}). Ignoring them...")
+ end
+
+ it "reresolves, and replaces the yanked gem with the generic version, printing a warning, when the compact index API is used" do
+ bundle "install", artifice: "compact_index", verbose: true
+
+ expect(out).to include("Installing nokogiri 1.13.8").and include("Installing foo 1.0.1")
+ expect(lockfile).to eq(original_lockfile.sub("nokogiri (1.13.8-#{Bundler.local_platform})", "nokogiri (1.13.8)").gsub("1.0.0", "1.0.1"))
+ expect(err).to include("Some locked specs have possibly been yanked (nokogiri-1.13.8-#{Bundler.local_platform}). Ignoring them...")
+ end
+ end
+
+ it "reports the yanked gem properly when the old index is used" do
+ bundle "install", artifice: "endpoint", raise_on_error: false
+
+ expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})")
+ end
+
+ it "reports the yanked gem properly when the compact index API is used" 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
it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do
- install_gemfile <<-G
- source "file://#{gem_repo4}"
- gem "foo", "10.0.0"
+ build_repo4 do
+ build_gem "foo", "9.0.0"
+ end
+
+ bundle_config "force_ruby_platform true"
+
+ install_gemfile <<-G, raise_on_error: false
+ source "https://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: https://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 "https://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: https://gem.repo4/
+ specs:
+ bar (2.0.0)
+ foo (9.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ bar
+ foo
- expect(out).not_to include("Your bundle is locked to foo (10.0.0)")
- expect(out).to include("Could not find gem 'foo (= 10.0.0)' in any of the gem sources")
+ 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://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
lockfile <<-L
- GEM
- remote: file://#{gem_repo1}
- specs:
- rack (0.9.1)
+ GEM
+ remote: https://gem.repo1
+ specs:
+ myrack (0.9.1)
- PLATFORMS
- ruby
+ PLATFORMS
+ #{lockfile_platforms}
- DEPENDENCIES
- rack (= 0.9.1)
+ DEPENDENCIES
+ myrack (= 0.9.1)
+ L
+
+ bundle :list, raise_on_error: false
+
+ expect(err).to include("Could not find myrack-0.9.1 in locally installed gems")
+ expect(err).to_not include("Your bundle is locked to myrack (0.9.1) from")
+ expect(err).to_not include("If you haven't changed sources, that means the author of myrack (0.9.1) has removed it.")
+ expect(err).to_not include("You'll need to update your bundle to a different version of myrack (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 myrack-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 "https://gem.repo1"
+ gem "myrack", "0.9.1"
+ gem "myrack_middleware", "1.0"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo1
+ specs:
+ myrack (0.9.1)
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack (= 0.9.1)
+ myrack_middleware (1.0)
L
- bundle :list
+ bundle :list, raise_on_error: false
- expect(out).to include("Could not find rack-0.9.1 in any of the sources")
- expect(out).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.")
- expect(out).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.")
- expect(out).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.")
+ expect(err).to include("Could not find myrack-0.9.1, myrack_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 myrack (0.9.1) from")
+ expect(err).to_not include("If you haven't changed sources, that means the author of myrack (0.9.1) has removed it.")
+ expect(err).to_not include("You'll need to update your bundle to a different version of myrack (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
index b36f61338d..c9f76115dc 100644
--- a/spec/bundler/lock/git_spec.rb
+++ b/spec/bundler/lock/git_spec.rb
@@ -1,20 +1,76 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle lock with git gems" do
- before :each do
+ let(:install_gemfile_with_foo_as_a_git_dependency) do
build_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
gem 'foo', :git => "#{lib_path("foo-1.0")}"
G
end
it "doesn't break right after running lock" do
+ install_gemfile_with_foo_as_a_git_dependency
+
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
+ install_gemfile_with_foo_as_a_git_dependency
+
+ FileUtils.rm_r(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
+ install_gemfile_with_foo_as_a_git_dependency
+
+ gemfile <<-G
+ source "https://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
+ install_gemfile_with_foo_as_a_git_dependency
+
+ lockfile <<~L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{"a" * 40}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: https://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
+ install_gemfile_with_foo_as_a_git_dependency
+
update_git "foo"
bundle :install
@@ -26,10 +82,177 @@ RSpec.describe "bundle lock with git gems" do
expect(out).to eq("WIN")
end
+ it "properly clones a git source locked to an out of date ref" do
+ install_gemfile_with_foo_as_a_git_dependency
+
+ 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
+ install_gemfile_with_foo_as_a_git_dependency
+
+ # 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 "https://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: https://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
+ install_gemfile_with_foo_as_a_git_dependency
+
+ # 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 "https://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: https://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
+ install_gemfile_with_foo_as_a_git_dependency
+
run <<-RUBY
puts Bundler.rubygems.find_name('foo').first.full_gem_path
RUBY
- expect(out).to eq(bundle("show foo"))
+ expect(out).to eq(bundle("info foo --path"))
+ end
+
+ it "does not lock versions that don't exist in the repository when changing a GEM transitive dep to a GIT direct dep" do
+ build_repo4 do
+ build_gem "activesupport", "8.0.0" do |s|
+ s.add_dependency "securerandom"
+ end
+
+ build_gem "securerandom", "0.3.1"
+ end
+
+ path = lib_path("securerandom")
+
+ build_git "securerandom", "0.3.2", path: path
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ activesupport (8.0.0)
+ securerandom
+ securerandom (0.3.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "activesupport"
+ gem "securerandom", git: "#{path}"
+ G
+
+ bundle "lock"
+
+ expect(lockfile).to include("securerandom (0.3.2)")
+ end
+
+ it "does not lock versions that don't exist in the repository when changing a GIT direct dep to a GEM direct dep" do
+ build_repo4 do
+ build_gem "ruby-lsp", "0.16.1"
+ end
+
+ path = lib_path("ruby-lsp")
+ revision = build_git("ruby-lsp", "0.16.2", path: path).ref_for("HEAD")
+
+ lockfile <<~L
+ GIT
+ remote: #{path}
+ revision: #{revision}
+ specs:
+ ruby-lsp (0.16.2)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ruby-lsp!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<~G
+ source "https://gem.repo4"
+ gem "ruby-lsp"
+ G
+
+ bundle "lock"
+
+ expect(lockfile).to include("ruby-lsp (0.16.1)")
end
end
diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb
index 968c969a55..654ac02aa7 100644
--- a/spec/bundler/lock/lockfile_spec.rb
+++ b/spec/bundler/lock/lockfile_spec.rb
@@ -1,34 +1,41 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "the lockfile format" do
- include Bundler::GemHelpers
+ before do
+ build_repo2
+ end
it "generates a simple lockfile for a single source, gem" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "myrack", "1.0.0")
+ end
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2"
- gem "rack"
+ gem "myrack"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
-
+ myrack
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "updates the lockfile's bundler version if current ver. is newer" do
+ 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
@@ -36,381 +43,432 @@ RSpec.describe "the lockfile format" do
specs:
GEM
- remote: file://#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
omg!
- rack
+ myrack
BUNDLED WITH
1.8.2
L
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G, verbose: true, env: { "BUNDLER_VERSION" => Bundler::VERSION }
+ source "https://gem.repo2"
- gem "rack"
+ gem "myrack"
G
- lockfile_should_be <<-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:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "does not update the lockfile's bundler version if nothing changed during bundle install" do
+ it "does not update the lockfile's bundler version if nothing changed during bundle install, but uses the locked version" do
+ version = "2.3.0"
+
+ build_repo4 do
+ build_gem "myrack", "1.0.0"
+
+ build_bundler version
+ end
+
lockfile <<-L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo4/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
- 1.10.0
+ #{version}
L
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo4"
- gem "rack"
+ gem "myrack"
G
- lockfile_should_be <<-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:#{gem_repo1}/
+ remote: https://gem.repo4/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
- 1.10.0
+ #{version}
G
end
- it "updates the lockfile's bundler version if not present" do
+ it "adds the BUNDLED WITH section if not present" do
lockfile <<-L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
L
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2"
- gem "rack", "> 0"
+ gem "myrack", "> 0"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack (> 0)
+ myrack (> 0)
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "outputs a warning if the current is older than lockfile's bundler version" do
+ 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:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
- 9999999.1.0
+ #{older_major}
L
- simulate_bundler_version "9999999.0.0" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ install_gemfile <<-G, env: { "BUNDLER_VERSION" => Bundler::VERSION }
+ source "https://gem.repo2/"
- gem "rack"
- G
- end
+ gem "myrack"
+ G
- warning_message = "the running version of Bundler (9999999.0.0) is older " \
- "than the version that created the lockfile (9999999.1.0)"
- expect(out.scan(warning_message).size).to eq(1)
+ expect(err).to be_empty
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
- 9999999.1.0
+ #{current_version}
G
end
- it "errors if the current is a major version older than lockfile's bundler version" do
- lockfile <<-L
+ it "generates a simple lockfile for a single source, gem with dependencies" do
+ install_gemfile <<-G
+ source "https://gem.repo2/"
+
+ gem "myrack-obama"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
-
+ myrack-obama
+ #{checksums}
BUNDLED WITH
- 9999999.0.0
- L
+ #{Bundler::VERSION}
+ G
+ end
+ it "generates a simple lockfile for a single source, gem with a version requirement" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
- gem "rack"
+ gem "myrack-obama", ">= 1.0"
G
- expect(exitstatus > 0) if exitstatus
- expect(out).to include("You must use Bundler 9999999 or greater with this lockfile.")
- end
-
- it "shows a friendly error when running with a new bundler 2 lockfile" do
- lockfile <<-L
- GEM
- remote: https://rails-assets.org/
- specs:
- rails-assets-bootstrap (3.3.4)
- rails-assets-jquery (>= 1.9.1)
- rails-assets-jquery (2.1.4)
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ end
+ expect(lockfile).to eq <<~G
GEM
- remote: https://rubygems.org/
+ remote: https://gem.repo2/
specs:
- rake (10.4.2)
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- rails-assets-bootstrap!
- rake
-
+ myrack-obama (>= 1.0)
+ #{checksums}
BUNDLED WITH
- 9999999.0.0
- L
-
- install_gemfile <<-G
- source 'https://rubygems.org'
- gem 'rake'
-
- source 'https://rails-assets.org' do
- gem 'rails-assets-bootstrap'
- end
+ #{Bundler::VERSION}
G
-
- expect(exitstatus > 0) if exitstatus
- expect(out).to include("You must use Bundler 9999999 or greater with this lockfile.")
end
- it "warns when updating bundler major version" do
- lockfile <<-L
- GEM
- remote: file:#{gem_repo1}/
- specs:
- rack (1.0.0)
+ it "generates a lockfile without credentials" do
+ bundle "config set https://localgemserver.test/ user:pass"
- PLATFORMS
- #{generic_local_platform}
+ install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true)
+ source "https://gem.repo1"
- DEPENDENCIES
- rack
+ source "https://localgemserver.test/" do
- BUNDLED WITH
- 1.10.0
- L
+ end
- simulate_bundler_version "9999999.0.0" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://user:pass@othergemserver.test/" do
+ gem "myrack-obama", ">= 1.0"
+ end
+ G
- gem "rack"
- G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
end
- expect(out).to include("Warning: the lockfile is being updated to Bundler " \
- "9999999, after which you will be unable to return to Bundler 1.")
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo1/
+ specs:
- lockfile_should_be <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://localgemserver.test/
specs:
- rack (1.0.0)
+
+ GEM
+ remote: https://othergemserver.test/
+ specs:
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
-
+ myrack-obama (>= 1.0)!
+ #{checksums}
BUNDLED WITH
- 9999999.0.0
+ #{Bundler::VERSION}
G
end
- it "generates a simple lockfile for a single source, gem with dependencies" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
-
- gem "rack-obama"
- G
+ it "does not add credentials to lockfile when it does not have them already" do
+ bundle "config set http://localgemserver.test/ user:pass"
- lockfile_should_be <<-G
- GEM
- remote: file:#{gem_repo1}/
- specs:
- rack (1.0.0)
- rack-obama (1.0)
- rack
+ gemfile <<~G
+ source "https://gem.repo1"
- PLATFORMS
- #{generic_local_platform}
+ source "http://localgemserver.test/" do
- DEPENDENCIES
- rack-obama
+ end
- BUNDLED WITH
- #{Bundler::VERSION}
+ source "http://user:pass@othergemserver.test/" do
+ gem "myrack-obama", ">= 1.0"
+ end
G
- end
- it "generates a simple lockfile for a single source, gem with a version requirement" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ end
- gem "rack-obama", ">= 1.0"
- G
+ lockfile_without_credentials = <<~L
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+
+ GEM
+ remote: http://othergemserver.test/
+ specs:
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
- lockfile_should_be <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
- rack-obama (1.0)
- rack
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack-obama (>= 1.0)
-
+ myrack-obama (>= 1.0)!
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
- G
+ #{Bundler::VERSION}
+ L
+
+ lockfile lockfile_without_credentials
+
+ # when not re-resolving
+ bundle "install", artifice: "endpoint_strict_basic_authentication", quiet: true
+ expect(lockfile).to eq lockfile_without_credentials
+
+ # when re-resolving with full unlock
+ bundle "update", artifice: "endpoint_strict_basic_authentication"
+ expect(lockfile).to eq lockfile_without_credentials
+
+ # when re-resolving without ful unlocking
+ bundle "update myrack-obama", artifice: "endpoint_strict_basic_authentication"
+ expect(lockfile).to eq lockfile_without_credentials
end
- it "generates a lockfile wihout credentials for a configured source" do
- bundle "config http://localgemserver.test/ user:pass"
+ it "keeps credentials in lockfile if already there" do
+ bundle "config set http://localgemserver.test/ user:pass"
- install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
- source "http://localgemserver.test/"
- source "http://user:pass@othergemserver.test/"
+ gemfile <<~G
+ source "https://gem.repo1"
- gem "rack-obama", ">= 1.0"
+ source "http://localgemserver.test/" do
+
+ end
+
+ source "http://user:pass@othergemserver.test/" do
+ gem "myrack-obama", ">= 1.0"
+ end
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ end
+
+ lockfile_with_credentials = <<~L
GEM
remote: http://localgemserver.test/
+ specs:
+
+ GEM
remote: http://user:pass@othergemserver.test/
specs:
- rack (1.0.0)
- rack-obama (1.0)
- rack
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack-obama (>= 1.0)
-
+ myrack-obama (>= 1.0)!
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
- G
+ #{Bundler::VERSION}
+ L
+
+ lockfile lockfile_with_credentials
+
+ bundle "install", artifice: "endpoint_strict_basic_authentication", quiet: true
+
+ expect(lockfile).to eq lockfile_with_credentials
end
it "generates lockfiles with multiple requirements" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
gem "net-sftp"
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "net-sftp", "1.1.1"
+ c.checksum gem_repo2, "net-ssh", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
net-sftp (1.1.1)
net-ssh (>= 1.0.0, < 1.99.0)
net-ssh (1.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
net-sftp
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
expect(the_bundle).to include_gems "net-sftp 1.1.1", "net-ssh 1.0.0"
@@ -420,41 +478,47 @@ RSpec.describe "the lockfile format" do
git = build_git "foo"
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GIT
remote: #{lib_path("foo-1.0")}
- revision: #{git.ref_for("master")}
+ revision: #{git.ref_for("main")}
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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")
+ build_lib "omg", path: lib_path("omg")
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
platforms :#{not_local_tag} do
gem "omg", :path => "#{lib_path("omg")}"
end
- gem "rack"
+ gem "myrack"
G
lockfile <<-L
@@ -464,64 +528,75 @@ RSpec.describe "the lockfile format" do
specs:
GEM
- remote: file://#{gem_repo1}/
+ remote: https://gem.repo2//
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
#{not_local}
DEPENDENCIES
omg!
- rack
+ myrack
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
bundle "install"
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "serializes global git sources" do
git = build_git "foo"
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
+
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}" do
gem "foo"
end
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GIT
remote: #{lib_path("foo-1.0")}
- revision: #{git.ref_for("master")}
+ revision: #{git.ref_for("main")}
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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"
+ update_git "foo", branch: "omg"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GIT
remote: #{lib_path("foo-1.0")}
revision: #{git.ref_for("omg")}
@@ -530,28 +605,34 @@ RSpec.describe "the lockfile format" do
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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"
+ update_git "foo", tag: "omg"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GIT
remote: #{lib_path("foo-1.0")}
revision: #{git.ref_for("omg")}
@@ -560,73 +641,172 @@ RSpec.describe "the lockfile format" do
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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: @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 "https://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: https://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: https://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"
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
+
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo-1.0")}"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
PATH
remote: #{lib_path("foo-1.0")}
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "serializes pinned path sources to the lockfile even when packaging" do
build_lib "foo"
- install_gemfile! <<-G
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo-1.0")}"
G
- bundle! "package --all"
- bundle! "install --local"
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
- lockfile_should_be <<-G
+ bundle :cache
+ bundle :install, local: true
+
+ expect(lockfile).to eq <<~G
PATH
remote: #{lib_path("foo-1.0")}
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
@@ -634,18 +814,24 @@ RSpec.describe "the lockfile format" do
build_lib "foo"
bar = build_git "bar"
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ c.no_checksum "bar", "1.0"
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ end
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
- gem "rack"
+ gem "myrack"
gem "foo", :path => "#{lib_path("foo-1.0")}"
gem "bar", :git => "#{lib_path("bar-1.0")}"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GIT
remote: #{lib_path("bar-1.0")}
- revision: #{bar.ref_for("master")}
+ revision: #{bar.ref_for("main")}
specs:
bar (1.0)
@@ -655,68 +841,114 @@ RSpec.describe "the lockfile format" do
foo (1.0)
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
bar!
foo!
- rack
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ it "removes redundant sources" do
+ install_gemfile <<-G
+ source "https://gem.repo2/"
+
+ gem "myrack", :source => "https://gem.repo2/"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ end
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack!
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "lists gems alphabetically" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
gem "thin"
gem "actionpack"
- gem "rack-obama"
+ gem "myrack-obama"
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "actionpack", "2.3.2"
+ c.checksum gem_repo2, "activesupport", "2.3.2"
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ c.checksum gem_repo2, "thin", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
actionpack (2.3.2)
activesupport (= 2.3.2)
activesupport (2.3.2)
- rack (1.0.0)
- rack-obama (1.0)
- rack
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
thin (1.0)
- rack
+ myrack
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
actionpack
- rack-obama
+ myrack-obama
thin
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "orders dependencies' dependencies in alphabetical order" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
gem "rails"
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "actionmailer", "2.3.2"
+ c.checksum gem_repo2, "actionpack", "2.3.2"
+ c.checksum gem_repo2, "activerecord", "2.3.2"
+ c.checksum gem_repo2, "activeresource", "2.3.2"
+ c.checksum gem_repo2, "activesupport", "2.3.2"
+ c.checksum gem_repo2, "rails", "2.3.2"
+ c.checksum gem_repo2, "rake", rake_version
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
actionmailer (2.3.2)
activesupport (= 2.3.2)
@@ -732,29 +964,44 @@ RSpec.describe "the lockfile format" do
actionpack (= 2.3.2)
activerecord (= 2.3.2)
activeresource (= 2.3.2)
- rake (= 10.0.2)
- rake (10.0.2)
+ rake (= #{rake_version})
+ rake (#{rake_version})
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
rails
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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/ruby/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://#{gem_repo1}"
+ source "https://gem.repo2"
gem 'double_deps'
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "double_deps", "1.0"
+ c.checksum gem_repo2, "net-ssh", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
double_deps (1.0)
net-ssh
@@ -762,563 +1009,1347 @@ RSpec.describe "the lockfile format" do
net-ssh (1.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
double_deps
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "does not add the :require option to the lockfile" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
- gem "rack-obama", ">= 1.0", :require => "rack/obama"
+ gem "myrack-obama", ">= 1.0", :require => "myrack/obama"
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
- rack-obama (1.0)
- rack
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack-obama (>= 1.0)
-
+ myrack-obama (>= 1.0)
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "does not add the :group option to the lockfile" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
- gem "rack-obama", ">= 1.0", :group => :test
+ gem "myrack-obama", ">= 1.0", :group => :test
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ c.checksum gem_repo2, "myrack-obama", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
- rack-obama (1.0)
- rack
+ myrack (1.0.0)
+ myrack-obama (1.0)
+ myrack
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack-obama (>= 1.0)
-
+ myrack-obama (>= 1.0)
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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")
+ build_lib "foo", path: bundled_app("foo")
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
install_gemfile <<-G
- path "foo"
- gem "foo"
+ source "https://gem.repo1"
+ path "foo" do
+ gem "foo"
+ end
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
PATH
remote: foo
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- foo
-
+ foo!
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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"))
+ build_lib "foo", path: bundled_app(File.join("..", "foo"))
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
install_gemfile <<-G
- path "../foo"
- gem "foo"
+ source "https://gem.repo1"
+ path "../foo" do
+ gem "foo"
+ end
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
PATH
remote: ../foo
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- foo
-
+ foo!
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{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")
+ build_lib "foo", path: bundled_app("foo")
install_gemfile <<-G
- path File.expand_path("../foo", __FILE__)
- gem "foo"
+ source "https://gem.repo1"
+ path File.expand_path("foo", __dir__) do
+ gem "foo"
+ end
G
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
PATH
remote: foo
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- foo
-
+ foo!
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "stores relative paths when the path is provided for gemspec" do
- build_lib("foo", :path => tmp.join("foo"))
+ build_lib("foo", path: tmp("foo"))
+
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "1.0"
+ end
install_gemfile <<-G
+ source "https://gem.repo1"
gemspec :path => "../foo"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
PATH
remote: ../foo
specs:
foo (1.0)
GEM
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
foo!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
it "keeps existing platforms in the lockfile" do
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "myrack", "1.0.0"
+ end
+
lockfile <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
java
DEPENDENCIES
- rack
-
+ myrack
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo2/"
- gem "rack"
+ gem "myrack"
G
- platforms = ["java", generic_local_platform.to_s].sort
+ checksums.checksum(gem_repo2, "myrack", "1.0.0")
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{platforms[0]}
- #{platforms[1]}
+ #{lockfile_platforms("java", local_platform, defaults: [])}
DEPENDENCIES
- rack
-
+ myrack
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "persists the spec's platform to the lockfile" do
- build_gem "platform_specific", "1.0.0", :to_system => true do |s|
- s.platform = Gem::Platform.new("universal-java-16")
+ it "adds compatible platform specific variants to the lockfile, even if resolution fallback to ruby due to some other incompatible platform specific variant" do
+ simulate_platform "arm64-darwin-23" do
+ build_repo4 do
+ build_gem "google-protobuf", "3.25.1"
+ build_gem "google-protobuf", "3.25.1" do |s|
+ s.platform = "arm64-darwin-23"
+ end
+ build_gem "google-protobuf", "3.25.1" do |s|
+ s.platform = "x64-mingw-ucrt"
+ s.required_ruby_version = "> #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "google-protobuf"
+ G
+ bundle "lock --add-platform x64-mingw-ucrt"
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo4, "google-protobuf", "3.25.1"
+ c.checksum gem_repo4, "google-protobuf", "3.25.1", "arm64-darwin-23"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ google-protobuf (3.25.1)
+ google-protobuf (3.25.1-arm64-darwin-23)
+
+ PLATFORMS
+ arm64-darwin-23
+ ruby
+ x64-mingw-ucrt
+
+ DEPENDENCIES
+ google-protobuf
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ 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" do
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gem "platform_specific"
+ G
+
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum gem_repo2, "platform_specific", "1.0", "universal-java-16"
+ end
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ platform_specific (1.0-universal-java-16)
+
+ PLATFORMS
+ universal-java-16
+
+ DEPENDENCIES
+ platform_specific
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+
+ it "does not add duplicate gems" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "activesupport", "2.3.5")
+ c.checksum(gem_repo2, "myrack", "1.0.0")
end
- simulate_platform "universal-java-16"
+ install_gemfile <<-G
+ source "https://gem.repo2/"
+ gem "myrack"
+ G
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "platform_specific"
+ source "https://gem.repo2/"
+ gem "myrack"
+ gem "activesupport"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- platform_specific (1.0-java)
+ activesupport (2.3.5)
+ myrack (1.0.0)
PLATFORMS
- java
+ #{lockfile_platforms}
DEPENDENCIES
- platform_specific
+ activesupport
+ myrack
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "myrack", "1.0.0")
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2/"
+ gem "myrack"
+ gem "myrack"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "does not add duplicate gems" do
+ it "does not add duplicate dependencies with versions" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "myrack", "1.0.0")
+ end
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo2/"
+ gem "myrack", "1.0"
+ gem "myrack", "1.0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack (= 1.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
G
+ end
+
+ it "does not add duplicate dependencies in different groups" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "myrack", "1.0.0")
+ end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport"
+ source "https://gem.repo2/"
+ gem "myrack", "1.0", :group => :one
+ gem "myrack", "1.0", :group => :two
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- activesupport (2.3.5)
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- activesupport
- rack
+ myrack (= 1.0)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ it "raises if two different versions are used" do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2/"
+ gem "myrack", "1.0"
+ gem "myrack", "1.1"
+ G
+
+ expect(bundled_app_lock).not_to exist
+ expect(err).to include "myrack (= 1.0) and myrack (= 1.1)"
+ end
+
+ it "raises if two different sources are used" do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2/"
+ gem "myrack"
+ gem "myrack", :git => "git://hubz.com"
+ G
+
+ expect(bundled_app_lock).not_to exist
+ expect(err).to include "myrack (>= 0) should come from an unspecified source and git://hubz.com"
+ end
+
+ it "works correctly with multiple version dependencies" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "myrack", "0.9.1")
+ end
+
+ install_gemfile <<-G
+ source "https://gem.repo2/"
+ gem "myrack", "> 0.9", "< 1.0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack (> 0.9, < 1.0)
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "does not add duplicate dependencies" do
+ it "captures the Ruby version in the lockfile" do
+ checksums = checksums_section_when_enabled do |c|
+ c.checksum(gem_repo2, "myrack", "0.9.1")
+ end
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "rack"
+ source "https://gem.repo2/"
+ ruby '#{Gem.ruby_version}'
+ gem "myrack", "> 0.9", "< 1.0"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (0.9.1)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack (> 0.9, < 1.0)
+ #{checksums}
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
- it "does not add duplicate dependencies with versions" do
+ it "automatically fixes the lockfile when it's missing deps" do
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0"
- gem "rack", "1.0"
+ source "https://gem.repo2"
+ gem "myrack_middleware"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (0.9.1)
+ myrack_middleware (1.0)
+ myrack (= 0.9.1)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- rack (= 1.0)
+ myrack_middleware
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "raises a clear error when frozen mode is set and lockfile is missing deps, and does not install any gems" do
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
+ source "https://gem.repo2"
+ gem "myrack_middleware"
G
+
+ expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
+ expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile")
+ expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
end
- it "does not add duplicate dependencies in different groups" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0", :group => :one
- gem "rack", "1.0", :group => :two
+ it "raises a clear error when frozen mode is set and lockfile is missing entries in CHECKSUMS section, and does not install any gems" do
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+
+ CHECKSUMS
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
+ source "https://gem.repo2"
+ gem "myrack_middleware"
G
- lockfile_should_be <<-G
+ expect(err).to eq <<~L.strip
+ Your lockfile is missing a CHECKSUMS entry for \"myrack_middleware\", but can't be updated because frozen mode is set
+
+ Run `bundle install` elsewhere and add the updated Gemfile.lock to version control.
+ L
+
+ expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
+ end
+
+ it "raises a clear error when frozen mode is set and lockfile has empty checksums in CHECKSUMS section, and does not install any gems" do
+ lockfile <<-L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (0.9.1)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- rack (= 1.0)
+ myrack
+
+ CHECKSUMS
+ myrack (0.9.1)
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
+ source "https://gem.repo2"
+ gem "myrack"
G
+
+ expect(err).to eq <<~L.strip
+ Your lockfile has an empty CHECKSUMS entry for \"myrack\", but can't be updated because frozen mode is set
+
+ Run `bundle install` elsewhere and add the updated Gemfile.lock to version control.
+ L
+
+ expect(the_bundle).not_to include_gems "myrack 0.9.1"
end
- it "raises if two different versions are used" do
+ it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do
+ build_repo4 do
+ build_gem "other_dep", "0.9"
+ build_gem "other_dep", "1.0"
+
+ build_gem "myrack", "0.9.1"
+
+ build_gem "myrack_middleware", "1.0" do |s|
+ s.add_dependency "myrack", "= 0.9.1"
+ s.add_dependency "other_dep", "= 0.9"
+ end
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack_middleware (1.0)
+ other_dep (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+ other_dep
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0"
- gem "rack", "1.1"
+ source "https://gem.repo4"
+ gem "myrack_middleware"
+ gem "other_dep"
G
- expect(bundled_app("Gemfile.lock")).not_to exist
- expect(out).to include "rack (= 1.0) and rack (= 1.1)"
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (0.9.1)
+ myrack_middleware (1.0)
+ myrack (= 0.9.1)
+ other_dep (= 0.9)
+ other_dep (0.9)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+ other_dep
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
- it "raises if two different sources are used" do
+ it "automatically fixes the lockfile when it's missing multiple deps, they conflict with other locked deps, but conflicts are fixable" do
+ build_repo4 do
+ build_gem "other_dep", "0.9"
+ build_gem "other_dep", "1.0"
+
+ build_gem "myrack", "0.9.1"
+
+ build_gem "myrack_middleware", "1.0" do |s|
+ s.add_dependency "myrack", "= 0.9.1"
+ end
+
+ build_gem "another_dep_middleware", "1.0" do |s|
+ s.add_dependency "other_dep", "= 0.9"
+ end
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack_middleware (1.0)
+ another_dep_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+ another_dep_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "rack", :git => "git://hubz.com"
+ source "https://gem.repo4"
+ gem "myrack_middleware"
+ gem "another_dep_middleware"
G
- expect(bundled_app("Gemfile.lock")).not_to exist
- expect(out).to include "rack (>= 0) should come from an unspecified source and git://hubz.com (at master)"
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ another_dep_middleware (1.0)
+ other_dep (= 0.9)
+ myrack (0.9.1)
+ myrack_middleware (1.0)
+ myrack (= 0.9.1)
+ other_dep (0.9)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ another_dep_middleware
+ myrack_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
- it "works correctly with multiple version dependencies" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "> 0.9", "< 1.0"
+ it "raises a resolution error when lockfile is missing deps, they conflict with other locked deps, and conflicts are not fixable" do
+ build_repo4 do
+ build_gem "other_dep", "0.9"
+ build_gem "other_dep", "1.0"
+
+ build_gem "myrack", "0.9.1"
+
+ build_gem "myrack_middleware", "1.0" do |s|
+ s.add_dependency "myrack", "= 0.9.1"
+ s.add_dependency "other_dep", "= 0.9"
+ end
+ end
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack_middleware (1.0)
+ other_dep (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+ other_dep
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo4"
+ gem "myrack_middleware"
+ gem "other_dep", "1.0"
G
- lockfile_should_be <<-G
+ expect(err).to eq <<~ERROR.strip
+ Could not find compatible versions
+
+ Because every version of myrack_middleware depends on other_dep = 0.9
+ and Gemfile depends on myrack_middleware >= 0,
+ other_dep = 0.9 is required.
+ So, because Gemfile depends on other_dep = 1.0,
+ version solving has failed.
+ ERROR
+ 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:#{gem_repo1}/
+ remote: https://gem.repo4/
specs:
- rack (0.9.1)
PLATFORMS
ruby
DEPENDENCIES
- rack (> 0.9, < 1.0)
+ direct_dependency
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
- end
- it "captures the Ruby version in the lockfile" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- ruby '#{RUBY_VERSION}'
- gem "rack", "> 0.9", "< 1.0"
+ source "https://gem.repo4"
+
+ gem "direct_dependency"
G
- lockfile_should_be <<-G
+ expect(lockfile).to eq <<~G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo4/
specs:
- rack (0.9.1)
+ direct_dependency (4.5.6)
+ indirect_dependency
+ indirect_dependency (1.2.3)
PLATFORMS
- ruby
+ #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])}
DEPENDENCIES
- rack (> 0.9, < 1.0)
+ direct_dependency
- RUBY VERSION
- ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "automatically fixes the lockfile when it's missing deps and the full index is in use" do
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "myrack_middleware"
G
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ myrack (0.9.1)
+ myrack_middleware (1.0)
+ myrack (= 0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
end
- # Some versions of the Bundler 1.1 RC series introduced corrupted
- # lockfiles. There were two major problems:
- #
- # * multiple copies of the same GIT section appeared in the lockfile
- # * when this happened, those sections got multiple copies of gems
- # in those sections.
- it "fixes corrupted lockfiles" do
- build_git "omg", :path => lib_path("omg")
- revision = revision_for(lib_path("omg"))
+ it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version, and with no explicit Gemfile requirement" do
+ git = build_git "foo"
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "omg", :git => "#{lib_path("omg")}", :branch => 'master'
+ gemfile <<~G
+ source "https://gem.repo1/"
+ gem "foo", git: "#{lib_path("foo-1.0")}"
G
- bundle "install --path vendor"
- expect(the_bundle).to include_gems "omg 1.0"
+ # If the lockfile erroneously lists platform versions of the gem
+ # that don't match the locked version of the git repo we should remove them.
- # Create a Gemfile.lock that has duplicate GIT sections
- lockfile <<-L
+ lockfile <<~L
GIT
- remote: #{lib_path("omg")}
- revision: #{revision}
- branch: master
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("main")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: https://gem.repo1/
specs:
- omg (1.0)
+ foo (1.1-x86_64-linux-gnu)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+ expect(lockfile).to eq <<~L
GIT
- remote: #{lib_path("omg")}
- revision: #{revision}
- branch: master
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("main")}
specs:
- omg (1.0)
+ foo (1.0)
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{local}
+ #{lockfile_platforms}
DEPENDENCIES
- omg!
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version" do
+ git = build_git "foo"
+
+ gemfile <<~G
+ source "https://gem.repo1/"
+ gem "foo", "= 1.0", git: "#{lib_path("foo-1.0")}"
+ G
+
+ # If the lockfile erroneously lists platform versions of the gem
+ # that don't match the locked version of the git repo we should remove them.
+
+ lockfile <<~L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("main")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: https://gem.repo1/
+ specs:
+ foo (1.1-x86_64-linux-gnu)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo (= 1.0)!
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
- FileUtils.rm_rf(bundled_app("vendor"))
bundle "install"
- expect(the_bundle).to include_gems "omg 1.0"
- # Confirm that duplicate specs do not appear
- expect(File.read(bundled_app("Gemfile.lock"))).to eq(strip_whitespace(<<-L))
+ expect(lockfile).to eq <<~L
GIT
- remote: #{lib_path("omg")}
- revision: #{revision}
- branch: master
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("main")}
specs:
- omg (1.0)
+ foo (1.0)
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
PLATFORMS
- #{local}
+ #{lockfile_platforms}
DEPENDENCIES
- omg!
+ foo (= 1.0)!
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
end
- it "raises a helpful error message when the lockfile is missing deps" do
- lockfile <<-L
+ it "automatically fixes the lockfile when it has incorrect deps, keeping the locked version" do
+ build_repo4 do
+ build_gem "net-smtp", "0.5.0" do |s|
+ s.add_dependency "net-protocol"
+ end
+
+ build_gem "net-smtp", "0.5.1" do |s|
+ s.add_dependency "net-protocol"
+ end
+
+ build_gem "net-protocol", "0.2.2"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "net-smtp"
+ G
+
+ lockfile <<~L
GEM
- remote: file:#{gem_repo1}/
+ remote: #{file_uri_for(gem_repo4)}/
specs:
- rack_middleware (1.0)
+ net-protocol (0.2.2)
+ net-smtp (0.5.0)
PLATFORMS
- #{local}
+ #{lockfile_platforms}
DEPENDENCIES
- rack_middleware
+ net-smtp
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
L
- install_gemfile <<-G
- source "file:#{gem_repo1}"
- gem "rack_middleware"
+ bundle "install"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ net-protocol (0.2.2)
+ net-smtp (0.5.0)
+ net-protocol
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ net-smtp
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "successfully updates the lockfile when a new gem is added in the Gemfile includes a gem that shouldn't be included" do
+ build_repo4 do
+ build_gem "logger", "1.7.0"
+ build_gem "rack", "3.2.0"
+ build_gem "net-smtp", "0.5.0"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "logger"
+ gem "net-smtp"
+
+ install_if -> { false } do
+ gem 'rack', github: 'rack/rack'
+ end
G
- expect(out).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("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.")
+ lockfile <<~L
+ GIT
+ remote: https://github.com/rack/rack.git
+ revision: 2fface9ac09fc582a81386becd939c987ad33f99
+ specs:
+ rack (3.2.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ logger (1.7.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ logger
+ rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(lockfile).to eq <<~L
+ GIT
+ remote: https://github.com/rack/rack.git
+ revision: 2fface9ac09fc582a81386becd939c987ad33f99
+ specs:
+ rack (3.2.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ logger (1.7.0)
+ net-smtp (0.5.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ logger
+ net-smtp
+ rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ 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 "https://gem.repo4"
+ gem "minitest-bisect"
+ G
+
+ # Corrupt lockfile (completely missing path_expander)
+ lockfile <<~L
+ GEM
+ remote: https://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: https://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 "https://gem.repo4"
+ gem "minitest-bisect"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://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 lockfile includes \"minitest-bisect\" but not some of its dependencies")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://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("Gemfile.lock"))
+ File.utime(time, time, bundled_app_lock)
end
before(:each) do
build_repo2
install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "rack"
+ source "https://gem.repo2"
+ gem "myrack"
G
set_lockfile_mtime_to_known_value
end
it "generates Gemfile.lock with \\n line endings" do
- expect(File.read(bundled_app("Gemfile.lock"))).not_to match("\r\n")
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(File.read(bundled_app_lock)).not_to match("\r\n")
+ expect(the_bundle).to include_gems "myrack 1.0"
end
context "during updates" do
it "preserves Gemfile.lock \\n line endings" do
- update_repo2
-
- expect { bundle "update" }.to change { File.mtime(bundled_app("Gemfile.lock")) }
- expect(File.read(bundled_app("Gemfile.lock"))).not_to match("\r\n")
- expect(the_bundle).to include_gems "rack 1.2"
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ 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 "myrack 1.2"
end
it "preserves Gemfile.lock \\n\\r line endings" do
- update_repo2
- win_lock = File.read(bundled_app("Gemfile.lock")).gsub(/\n/, "\r\n")
- File.open(bundled_app("Gemfile.lock"), "wb") {|f| f.puts(win_lock) }
+ skip "needs to be adapted" if Gem.win_platform?
+
+ update_repo2 do
+ build_gem "myrack", "1.2" do |s|
+ s.executables = "myrackup"
+ 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" }.to change { File.mtime(bundled_app("Gemfile.lock")) }
- expect(File.read(bundled_app("Gemfile.lock"))).to match("\r\n")
- expect(the_bundle).to include_gems "rack 1.2"
+ expect { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) }
+ expect(File.read(bundled_app_lock)).to match("\r\n")
+
+ expect(the_bundle).to include_gems "myrack 1.2"
end
end
@@ -1326,25 +2357,23 @@ RSpec.describe "the lockfile format" do
it "preserves Gemfile.lock \\n line endings" do
expect do
ruby <<-RUBY
- require 'rubygems'
require 'bundler'
Bundler.setup
RUBY
- end.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ 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("Gemfile.lock")).gsub(/\n/, "\r\n")
- File.open(bundled_app("Gemfile.lock"), "wb") {|f| f.puts(win_lock) }
+ 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 'rubygems'
require 'bundler'
Bundler.setup
RUBY
- end.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ end.not_to change { File.mtime(bundled_app_lock) }
end
end
end
@@ -1352,30 +2381,36 @@ RSpec.describe "the lockfile format" do
it "refuses to install if Gemfile.lock contains conflict markers" do
lockfile <<-L
GEM
- remote: file://#{gem_repo1}/
+ remote: https://gem.repo2//
specs:
<<<<<<<
- rack (1.0.0)
+ myrack (1.0.0)
=======
- rack (1.0.1)
+ myrack (1.0.1)
>>>>>>>
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
- error = install_gemfile(<<-G)
- source "file://#{gem_repo1}"
- gem "rack"
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo2/"
+ gem "myrack"
G
- expect(error).to match(/your Gemfile.lock contains merge conflicts/i)
- expect(error).to match(/git checkout HEAD -- Gemfile.lock/i)
+ expect(err).to match(/your Gemfile.lock contains merge conflicts/i)
+ expect(err).to match(/git checkout HEAD -- Gemfile.lock/i)
+ end
+
+ private
+
+ def previous_major(version)
+ version.split(".").map.with_index {|v, i| i == 0 ? v.to_i - 1 : v }.join(".")
end
end
diff --git a/spec/bundler/other/bundle_ruby_spec.rb b/spec/bundler/other/bundle_ruby_spec.rb
deleted file mode 100644
index 09fa2c223b..0000000000
--- a/spec/bundler/other/bundle_ruby_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "bundle_ruby" do
- context "without patchlevel" do
- it "returns the ruby version" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.9.3", :engine => 'ruby', :engine_version => '1.9.3'
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("ruby 1.9.3")
- end
-
- it "engine defaults to MRI" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.9.3"
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("ruby 1.9.3")
- end
-
- it "handles jruby" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine => 'jruby', :engine_version => '1.6.5'
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("ruby 1.8.7 (jruby 1.6.5)")
- end
-
- it "handles rbx" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine => 'rbx', :engine_version => '1.2.4'
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("ruby 1.8.7 (rbx 1.2.4)")
- end
-
- it "raises an error if engine is used but engine version is not" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine => 'rbx'
-
- gem "foo"
- G
-
- bundle_ruby
- expect(exitstatus).not_to eq(0) if exitstatus
-
- bundle_ruby
- expect(out).to include("Please define :engine_version")
- end
-
- it "raises an error if engine_version is used but engine is not" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine_version => '1.2.4'
-
- gem "foo"
- G
-
- bundle_ruby
- expect(exitstatus).not_to eq(0) if exitstatus
-
- bundle_ruby
- expect(out).to include("Please define :engine")
- end
-
- it "raises an error if engine version doesn't match ruby version for MRI" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
-
- gem "foo"
- G
-
- bundle_ruby
- expect(exitstatus).not_to eq(0) if exitstatus
-
- bundle_ruby
- expect(out).to include("ruby_version must match the :engine_version for MRI")
- end
-
- it "should print if no ruby version is specified" do
- gemfile <<-G
- source "file://#{gem_repo1}"
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("No ruby version specified")
- end
- end
-
- context "when using patchlevel" do
- it "returns the ruby version" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.9.3", :patchlevel => '429', :engine => 'ruby', :engine_version => '1.9.3'
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("ruby 1.9.3p429")
- end
-
- it "handles an engine" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.9.3", :patchlevel => '392', :engine => 'jruby', :engine_version => '1.7.4'
-
- gem "foo"
- G
-
- bundle_ruby
-
- expect(out).to include("ruby 1.9.3p392 (jruby 1.7.4)")
- end
- end
-end
diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb
index 8b34a457ef..a2c745b070 100644
--- a/spec/bundler/other/cli_dispatch_spec.rb
+++ b/spec/bundler/other/cli_dispatch_spec.rb
@@ -1,22 +1,20 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle command names" do
it "work when given fully" do
- bundle "install"
- expect(err).to lack_errors
- expect(out).not_to match(/Ambiguous command/)
+ bundle "install", raise_on_error: false
+ expect(err).to eq("Could not locate Gemfile")
+ expect(stdboth).not_to include("Ambiguous command")
end
it "work when not ambiguous" do
- bundle "ins"
- expect(err).to lack_errors
- expect(out).not_to match(/Ambiguous command/)
+ bundle "ins", raise_on_error: false
+ expect(err).to eq("Could not locate Gemfile")
+ expect(stdboth).not_to include("Ambiguous command")
end
it "print a friendly error when ambiguous" do
- bundle "in"
- expect(err).to lack_errors
- expect(out).to match(/Ambiguous command/)
+ bundle "in", raise_on_error: false
+ expect(err).to eq("Ambiguous command in matches [info, init, install]")
end
end
diff --git a/spec/bundler/other/cli_man_pages_spec.rb b/spec/bundler/other/cli_man_pages_spec.rb
new file mode 100644
index 0000000000..4e8f155309
--- /dev/null
+++ b/spec/bundler/other/cli_man_pages_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle commands" do
+ it "expects all commands to have all options and subcommands documented" do
+ check_commands!(Bundler::CLI)
+
+ Bundler::CLI.subcommand_classes.each_value do |klass|
+ check_commands!(klass)
+ end
+ end
+
+ private
+
+ def check_commands!(command_class)
+ command_class.commands.each do |command_name, command|
+ if command.is_a?(Bundler::Thor::HiddenCommand)
+ man_page = man_page(command_name)
+ expect(man_page).not_to exist
+ expect(main_man_page.read).not_to include("bundle #{command_name}")
+ elsif command_class == Bundler::CLI
+ man_page = man_page(command_name)
+ expect(man_page).to exist
+
+ check_options!(command, man_page)
+ else
+ man_page = man_page(command.ancestor_name)
+ expect(man_page).to exist
+
+ check_options!(command, man_page)
+ check_subcommand!(command_name, man_page)
+ end
+ end
+ end
+
+ def check_options!(command, man_page)
+ command.options.each do |_, option|
+ check_option!(option, man_page)
+ end
+ end
+
+ def check_option!(option, man_page)
+ man_page_content = man_page.read
+
+ aliases = option.aliases
+ formatted_aliases = aliases.sort.map {|name| "`#{name}`" }.join(", ") if aliases
+
+ help = if option.type == :boolean
+ "* #{append_aliases("`#{option.switch_name}`", formatted_aliases)}:"
+ elsif option.enum
+ formatted_aliases = "`#{option.switch_name}`" if aliases.empty? && option.lazy_default
+ "* #{prepend_aliases(option.enum.sort.map {|enum| "`#{option.switch_name}=#{enum}`" }.join(", "), formatted_aliases)}:"
+ else
+ names = [option.switch_name, *aliases]
+ value =
+ case option.type
+ when :array then "<list>"
+ when :numeric then "<number>"
+ else option.name.upcase
+ end
+
+ value = option.type != :numeric && option.lazy_default ? "[=#{value}]" : "=#{value}"
+
+ "* #{names.map {|name| "`#{name}#{value}`" }.join(", ")}:"
+ end
+
+ if option.banner.include?("(removed)")
+ expect(man_page_content).not_to include(help)
+ else
+ expect(man_page_content).to include(help)
+ end
+ end
+
+ def check_subcommand!(name, man_page)
+ expect(man_page.read).to match(name)
+ end
+
+ def append_aliases(text, aliases)
+ return text if aliases.empty?
+
+ "#{text}, #{aliases}"
+ end
+
+ def prepend_aliases(text, aliases)
+ return text if aliases.empty?
+
+ "#{aliases}, #{text}"
+ end
+
+ def man_page_content(command_name)
+ man_page(command_name).read
+ end
+
+ def man_page(command_name)
+ source_root.join("lib/bundler/man/bundle-#{command_name}.1.ronn")
+ end
+
+ def main_man_page
+ source_root.join("lib/bundler/man/bundle.1.ronn")
+ end
+end
diff --git a/spec/bundler/other/ext_spec.rb b/spec/bundler/other/ext_spec.rb
index 2d6ab941b8..a883eefe06 100644
--- a/spec/bundler/other/ext_spec.rb
+++ b/spec/bundler/other/ext_spec.rb
@@ -1,67 +1,50 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "Gem::Specification#match_platform" do
+RSpec.describe "Gem::Specification#installable_on_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)
+ expect(darwin.installable_on_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)
+ expect(lazy_spec.installable_on_platform?(pl("x86-mingw32"))).to eq(true)
+ expect(lazy_spec.installable_on_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
-end
-
RSpec.describe "Gem::SourceIndex#refresh!" do
before do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
- it "does not explode when called", :rubygems => "1.7" do
- run "Gem.source_index.refresh!"
- run "Gem::SourceIndex.new([]).refresh!"
+ 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
+
+RSpec.describe "Gem::NameTuple" do
+ describe "#initialize" do
+ it "creates a Gem::NameTuple with equality regardless of platform type" do
+ gem_platform = Gem::NameTuple.new "a", v("1"), pl("x86_64-linux")
+ str_platform = Gem::NameTuple.new "a", v("1"), "x86_64-linux"
+ expect(gem_platform).to eq(str_platform)
+ expect(gem_platform.hash).to eq(str_platform.hash)
+ expect(gem_platform.to_a).to eq(str_platform.to_a)
+ end
end
- it "does not explode when called", :rubygems => "< 1.7" do
- run "Gem.source_index.refresh!"
- run "Gem::SourceIndex.from_gems_in([]).refresh!"
+ describe "#lock_name" do
+ it "returns the lock name" do
+ expect(Gem::NameTuple.new("a", v("1.0.0"), pl("x86_64-linux")).lock_name).to eq("a (1.0.0-x86_64-linux)")
+ expect(Gem::NameTuple.new("a", v("1.0.0"), "ruby").lock_name).to eq("a (1.0.0)")
+ expect(Gem::NameTuple.new("a", v("1.0.0")).lock_name).to eq("a (1.0.0)")
+ end
end
end
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
index 465d769538..ab7589d698 100644
--- a/spec/bundler/other/major_deprecation_spec.rb
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -1,248 +1,798 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "major deprecations" do
- let(:warnings) { out } # change to err in 2.0
+ let(:warnings) { err }
- context "in a .99 version" do
+ describe "Bundler" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
+
+ describe ".clean_env" do
+ before do
+ source = "Bundler.clean_env"
+ bundle "exec ruby -e #{source.dump}", raise_on_error: false
+ end
+
+ it "is removed in favor of .unbundled_env and shows a helpful error message about it" do
+ expect(err).to include \
+ "`Bundler.clean_env` has been removed in favor of `Bundler.unbundled_env`. " \
+ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`" \
+ end
+ end
+
+ describe ".with_clean_env" do
+ before do
+ source = "Bundler.with_clean_env {}"
+ bundle "exec ruby -e #{source.dump}", raise_on_error: false
+ end
+
+ it "is removed in favor of .unbundled_env and shows a helpful error message about it" do
+ expect(err).to include(
+ "`Bundler.with_clean_env` has been removed in favor of `Bundler.with_unbundled_env`. " \
+ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`"
+ )
+ end
+ end
+
+ describe ".clean_system" do
+ before do
+ source = "Bundler.clean_system('ls')"
+ bundle "exec ruby -e #{source.dump}", raise_on_error: false
+ end
+
+ it "is removed in favor of .unbundled_system and shows a helpful error message about it" do
+ expect(err).to include(
+ "`Bundler.clean_system` has been removed 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`" \
+ )
+ end
+ end
+
+ describe ".clean_exec" do
+ before do
+ source = "Bundler.clean_exec('ls')"
+ bundle "exec ruby -e #{source.dump}", raise_on_error: false
+ end
+
+ it "is removed in favor of .unbundled_exec and shows a helpful error message about it" do
+ expect(err).to include(
+ "`Bundler.clean_exec` has been removed 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`" \
+ )
+ end
+ end
+
+ describe ".environment" do
+ before do
+ source = "Bundler.environment"
+ bundle "exec ruby -e #{source.dump}", raise_on_error: false
+ end
+
+ it "is removed in favor of .load and shows a helpful error message about it" do
+ expect(err).to include "Bundler.environment has been removed in favor of Bundler.load"
+ end
+ end
+ end
+
+ describe "bundle exec --no-keep-file-descriptors" do
before do
- simulate_bundler_version "1.99.1"
- bundle "config --delete major_deprecations"
+ bundle "exec --no-keep-file-descriptors -e 1", raise_on_error: false
end
- it "prints major deprecations without being configured" do
- ruby <<-R
- require "bundler"
- Bundler::SharedHelpers.major_deprecation(Bundler::VERSION)
- R
+ it "is removed and shows a helpful error message about it" do
+ expect(err).to include "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to"
+ end
+ end
- expect(warnings).to have_major_deprecation("1.99.1")
+ 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
- before do
- bundle "config major_deprecations true"
+ context "bundle check --path" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "check --path vendor/bundle", raise_on_error: false
+ end
- install_gemfile <<-G
- source "file:#{gem_repo1}"
- ruby #{RUBY_VERSION.dump}
- gem "rack"
- G
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--path` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set path 'vendor/bundle'`, " \
+ "and stop using this flag"
+ )
+ end
end
- describe "bundle_ruby" do
- it "prints a deprecation" do
- bundle_ruby
- out.gsub! "\nruby #{RUBY_VERSION}", ""
- expect(warnings).to have_major_deprecation "the bundle_ruby executable has been removed in favor of `bundle platform --ruby`"
+ context "bundle check --path=" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "check --path=vendor/bundle", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--path` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set path 'vendor/bundle'`, " \
+ "and stop using this flag"
+ )
end
end
- describe "Bundler" do
- describe ".clean_env" do
- it "is deprecated in favor of .original_env" do
- source = "Bundler.clean_env"
- bundle "exec ruby -e #{source.dump}"
- expect(warnings).to have_major_deprecation "`Bundler.clean_env` has weird edge cases, use `.original_env` instead"
+ context "bundle binstubs --path=" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "binstubs myrack --path=binpath", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--path` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set bin 'binpath'`, " \
+ "and stop using this flag"
+ )
+ end
+ end
+
+ context "bundle cache --all" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "cache --all --verbose", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--all` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set cache_all true`, " \
+ "and stop using this flag"
+ )
+ end
+ end
+
+ context "bundle cache --no-all" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "cache --no-all", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--no-all` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set cache_all false`, " \
+ "and stop using this flag"
+ )
+ end
+ end
+
+ context "bundle cache --path" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "cache --path foo", raise_on_error: false
+ end
+
+ it "should print a removal error" do
+ expect(err).to include(
+ "The `--path` flag has been removed because its semantics were 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
+ end
+
+ context "bundle cache --path=" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "cache --path=foo", raise_on_error: false
+ end
+
+ it "should print a deprecation warning" do
+ expect(err).to include(
+ "The `--path` flag has been removed because its semantics were 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
+ end
+
+ context "bundle cache --frozen" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "cache --frozen", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--frozen` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set frozen true`, " \
+ "and stop using this flag"
+ )
+ end
+ end
+
+ context "bundle cache --no-prune" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "cache --no-prune", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `--no-prune` flag has been removed because it relied on being " \
+ "remembered across bundler invocations, which bundler no longer " \
+ "does. Instead please use `bundle config set no_prune true`, " \
+ "and stop using this flag"
+ )
+ end
+ end
+
+ describe "bundle config" do
+ describe "old list interface" do
+ before do
+ bundle "config"
+ end
+
+ it "warns", bundler: "4" 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: "5"
end
- describe ".environment" do
- it "is deprecated in favor of .load" do
- source = "Bundler.environment"
- bundle "exec ruby -e #{source.dump}"
- expect(warnings).to have_major_deprecation "Bundler.environment has been removed in favor of Bundler.load"
+ describe "old get interface" do
+ before do
+ bundle "config waka", raise_on_error: false
end
+
+ it "warns", bundler: "4" 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: "5"
end
- shared_examples_for "environmental deprecations" do |trigger|
- describe "ruby version", :ruby => "< 2.0" do
- it "requires a newer ruby version" do
- instance_eval(&trigger)
- expect(warnings).to have_major_deprecation "Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}"
- end
+ describe "old set interface" do
+ before do
+ bundle "config waka wakapun"
end
- describe "rubygems version", :rubygems => "< 2.0" do
- it "requires a newer rubygems version" do
- instance_eval(&trigger)
- expect(warnings).to have_major_deprecation "Bundler will only support rubygems >= 2.0, you are running #{Gem::VERSION}"
- end
+ it "warns", bundler: "4" 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: "5"
end
- describe "-rbundler/setup" do
- it_behaves_like "environmental deprecations", proc { ruby "require 'bundler/setup'" }
+ describe "old set interface with --local" do
+ before do
+ bundle "config --local waka wakapun"
+ end
+
+ it "warns", bundler: "4" 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: "5"
end
- describe "Bundler.setup" do
- it_behaves_like "environmental deprecations", proc { ruby "require 'bundler'; Bundler.setup" }
+ describe "old set interface with --global" do
+ before do
+ bundle "config --global waka wakapun"
+ end
+
+ it "warns", bundler: "4" 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: "5"
end
- describe "bundle check" do
- it_behaves_like "environmental deprecations", proc { bundle :check }
+ describe "old unset interface" do
+ before do
+ bundle "config --delete waka"
+ end
+
+ it "warns", bundler: "4" 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: "5"
end
- describe "bundle update --quiet" do
- it "does not print any deprecations" do
- bundle :update, :quiet => true
- expect(warnings).not_to have_major_deprecation
+ describe "old unset interface with --local" do
+ before do
+ bundle "config --delete --local waka"
+ end
+
+ it "warns", bundler: "4" 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: "5"
end
- describe "bundle install --binstubs" do
- it "should output a deprecation warning" do
- gemfile <<-G
- gem 'rack'
- G
+ describe "old unset interface with --global" do
+ before do
+ bundle "config --delete --global waka"
+ end
- bundle :install, :binstubs => true
- expect(warnings).to have_major_deprecation a_string_including("The --binstubs option will be removed")
+ it "warns", bundler: "4" 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: "5"
end
end
- context "when bundle is run" do
+ describe "bundle update" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
+
+ it "warns when no options are given", bundler: "4" 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: "5"
+
+ 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, raise_on_error: false
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include("The --binstubs option has been removed in favor of `bundle binstubs --all`")
+ end
+ 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://#{gem_repo1}"
- gem "rack"
+ gemfile "gems.rb", <<-G
+ source "https://gem.repo1"
+ gem "myrack"
G
bundle :install
- expect(err).not_to have_major_deprecation
- expect(out).not_to have_major_deprecation
+ expect(deprecations).to be_empty
end
- it "should print a Gemfile deprecation warning" do
+ it "should print a proper warning, and use gems.rb" do
+ gemfile "gems.rb", "source 'https://gem.repo1'"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- expect(warnings).to have_major_deprecation("gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock.")
+ 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 "myrack 1.0"
end
+ end
- context "with flags" do
- it "should print a deprecation warning about autoremembering flags" do
- install_gemfile <<-G, :path => "vendor/bundle"
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ context "bundle install with flags" do
+ before do
+ bundle_config "path vendor/bundle"
- expect(warnings).to have_major_deprecation a_string_including(
- "flags passed to commands will no longer be automatically remembered."
- )
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ 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" => ["path.system", "true"],
+ "without" => ["without", "'development'"],
+ "with" => ["with", "'development'"],
+ }.each do |name, expectations|
+ option_name, value = *expectations
+ flag_name = "--#{name}"
+ args = %w[true false].include?(value) ? flag_name : "#{flag_name} #{value}"
+
+ context "with the #{flag_name} flag" do
+ before do
+ bundle "install" # to create a lockfile, which deployment or frozen need
+
+ bundle "install #{args}", raise_on_error: false
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "The `#{flag_name}` flag has been removed because it relied on " \
+ "being remembered across bundler invocations, which bundler no " \
+ "longer does. Instead please use `bundle config set " \
+ "#{option_name} #{value}`, and stop using this flag"
+ )
+ end
end
end
end
+ context "bundle install with multiple sources" do
+ before do
+ install_gemfile <<-G, raise_on_error: false
+ source "https://gem.repo3"
+ source "https://gem.repo1"
+ G
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include(
+ "This Gemfile contains multiple global sources. " \
+ "Each source after the first must include a block to indicate which gems " \
+ "should come from that source"
+ )
+ end
+
+ it "doesn't show lockfile deprecations if there's a lockfile" do
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo3/
+ remote: https://gem.repo1/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ bundle "install", raise_on_error: false
+
+ expect(err).to include(
+ "This Gemfile contains multiple global sources. " \
+ "Each source after the first must include a block to indicate which gems " \
+ "should come from that source"
+ )
+ expect(err).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 "frozen true"
+ bundle "install", raise_on_error: false
+
+ expect(err).to include(
+ "This Gemfile contains multiple global sources. " \
+ "Each source after the first must include a block to indicate which gems " \
+ "should come from that source"
+ )
+ expect(err).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
+ end
+
+ context "bundle install with a lockfile with a single rubygems section with multiple remotes" do
+ before do
+ build_repo3 do
+ build_gem "myrack", "0.9.1"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ source "https://gem.repo3" do
+ gem 'myrack'
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo1/
+ remote: https://gem.repo3/
+ specs:
+ myrack (0.9.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ myrack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "shows an error" do
+ bundle "install", raise_on_error: false
+
+ expect(err).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
+ end
+
+ context "bundle install with a lockfile including X64_MINGW_LEGACY platform" do
+ before do
+ gemfile <<~G
+ source "https://gem.repo1"
+ gem "rake"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (10.3.2)
+
+ PLATFORMS
+ ruby
+ x64-mingw32
+
+ DEPENDENCIES
+ rake
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "warns a helpful error" do
+ bundle "install", raise_on_error: false
+
+ expect(err).to include("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.")
+ end
+ end
+
+ context "with a global path source" do
+ before do
+ build_lib "foo"
+
+ install_gemfile <<-G, raise_on_error: false
+ path "#{lib_path("foo-1.0")}"
+ gem 'foo'
+ G
+ end
+
+ it "shows an error" do
+ expect(err).to include("You can no longer specify a path source by itself")
+ end
+ end
+
context "when Bundler.setup is run in a ruby script" do
- it "should print a single deprecation warning" do
+ before do
+ create_file "gems.rb", "source 'https://gem.repo1'"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :group => :test
+ source "https://gem.repo1"
+ gem "myrack", :group => :test
G
ruby <<-RUBY
- require 'rubygems'
require 'bundler'
- require 'bundler/vendored_thor'
- Bundler.ui = Bundler::UI::Shell.new
Bundler.setup
Bundler.setup
RUBY
+ end
- expect(warnings).to have_major_deprecation("gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock.")
+ 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
- it "should print a capistrano deprecation warning" do
- ruby(<<-RUBY)
+ before do
+ ruby <<-RUBY, raise_on_error: false
require 'bundler/deployment'
RUBY
+ end
- expect(warnings).to have_major_deprecation("Bundler no longer integrates " \
+ it "should print a capistrano deprecation warning" do
+ expect(err).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
end
- describe Bundler::Dsl do
+ context "when `bundler/capistrano` is required in a ruby script" do
before do
- @rubygems = double("rubygems")
- allow(Bundler::Source::Rubygems).to receive(:new) { @rubygems }
+ ruby <<-RUBY, raise_on_error: false
+ require 'bundler/capistrano'
+ RUBY
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include("[REMOVED] The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler")
+ end
+ end
+
+ context "when `bundler/vlad` is required in a ruby script" do
+ before do
+ ruby <<-RUBY, raise_on_error: false
+ require 'bundler/vlad'
+ RUBY
+ end
+
+ it "fails with a helpful error" do
+ expect(err).to include("[REMOVED] The Bundler task for Vlad")
+ end
+ end
+
+ context "bundle show" do
+ before do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
end
- context "with github gems" do
- it "warns about the https change" do
- msg = "The :github option uses the git: protocol, which is not secure. " \
- "Bundler 2.0 will use the https: protocol, which is secure. Enable this change now by " \
- "running `bundle config github.https true`."
- expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(msg)
- subject.gem("sparks", :github => "indirect/sparks")
+ context "with --outdated flag" do
+ before do
+ bundle "show --outdated", raise_on_error: false
end
- it "upgrades to https on request" do
- Bundler.settings["github.https"] = true
- subject.gem("sparks", :github => "indirect/sparks")
- expect(Bundler::SharedHelpers).to receive(:major_deprecation).never
- github_uri = "https://github.com/indirect/sparks.git"
- expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ it "fails with a helpful message" do
+ expect(err).to include("the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`")
end
end
+ end
+
+ context "bundle remove" do
+ before do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
- context "with bitbucket gems" do
- it "warns about removal" do
- allow(Bundler.ui).to receive(:deprecate)
- msg = "The :bitbucket git source is deprecated, and will be removed " \
- "in Bundler 2.0. Add this code to your Gemfile to ensure it " \
- "continues to work:\n git_source(:bitbucket) do |repo_name|\n " \
- " \"https://\#{user_name}@bitbucket.org/\#{user_name}/\#{repo_name}" \
- ".git\"\n end\n"
- expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(msg)
- subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails")
+ context "with --install" do
+ it "fails with a helpful message" do
+ bundle "remove myrack --install", raise_on_error: false
+
+ expect(err).to include "The `--install` flag has been removed. `bundle install` is triggered by default."
end
end
+ end
+
+ context "bundle viz" do
+ before do
+ bundle "viz", raise_on_error: false
+ end
+
+ it "fails with a helpful message" do
+ expect(err).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph"
+ end
+ end
+
+ context "bundle inject" do
+ before do
+ bundle "inject", raise_on_error: false
+ end
+
+ it "fails with a helpful message" do
+ expect(err).to include "The `inject` command has been replaced by the `add` command"
+ end
+ end
- context "with gist gems" do
- it "warns about removal" do
- allow(Bundler.ui).to receive(:deprecate)
- msg = "The :gist git source is deprecated, and will be removed " \
- "in Bundler 2.0. Add this code to your Gemfile to ensure it " \
- "continues to work:\n git_source(:gist) do |repo_name|\n " \
- " \"https://gist.github.com/\#{repo_name}.git\"\n" \
- " end\n"
- expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(msg)
- subject.gem("not-really-a-gem", :gist => "1234")
+ context "bundle plugin install --local_git" do
+ before do
+ build_git "foo" do |s|
+ s.write "plugins.rb"
end
end
+
+ it "fails with a helpful message" do
+ bundle "plugin install foo --local_git #{lib_path("foo-1.0")}", raise_on_error: false
+
+ expect(err).to include "--local_git has been removed, use --git"
+ end
end
- context "bundle list" do
- it "prints a deprecation warning" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ describe "removing rubocop" do
+ before do
+ bundle_config_global "gem.mit false"
+ bundle_config_global "gem.test false"
+ bundle_config_global "gem.coc false"
+ bundle_config_global "gem.ci false"
+ bundle_config_global "gem.changelog false"
+ end
+
+ context "bundle gem --rubocop" do
+ before do
+ bundle "gem my_new_gem --rubocop", raise_on_error: false
+ end
- bundle :list
+ it "prints an error" do
+ expect(err).to include \
+ "--rubocop has been removed, use --linter=rubocop"
+ end
+ end
- out.gsub!(/gems included.*?\[DEPRECATED/im, "[DEPRECATED")
+ context "bundle gem --no-rubocop" do
+ before do
+ bundle "gem my_new_gem --no-rubocop", raise_on_error: false
+ end
- expect(warnings).to have_major_deprecation("use `bundle show` instead of `bundle list`")
+ it "prints an error" do
+ expect(err).to include \
+ "--no-rubocop has been removed, use --no-linter"
+ end
end
end
- context "bundle console" do
- it "prints a deprecation warning" do
- bundle "console"
+ context " bundle gem --ext parameter with no value" do
+ it "prints error when used before gem name" do
+ bundle "gem --ext foo", raise_on_error: false
+ expect(err).to include "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
+ end
- expect(warnings).to have_major_deprecation \
- "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`"
+ it "prints error when used after gem name" do
+ bundle "gem foo --ext", raise_on_error: false
+ expect(err).to include "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
end
end
end
diff --git a/spec/bundler/other/platform_spec.rb b/spec/bundler/other/platform_spec.rb
deleted file mode 100644
index 6adbcef111..0000000000
--- a/spec/bundler/other/platform_spec.rb
+++ /dev/null
@@ -1,1292 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "bundle platform" do
- context "without flags" do
- it "returns all the output" do
- gemfile <<-G
- source "file://#{gem_repo1}"
-
- #{ruby_version_correct}
-
- gem "foo"
- G
-
- bundle "platform"
- expect(out).to eq(<<-G.chomp)
-Your platform is: #{RUBY_PLATFORM}
-
-Your app has gems that work on these platforms:
-* ruby
-
-Your Gemfile specifies a Ruby version requirement:
-* ruby #{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://#{gem_repo1}"
-
- #{ruby_version_correct_patchlevel}
-
- gem "foo"
- G
-
- bundle "platform"
- expect(out).to eq(<<-G.chomp)
-Your platform is: #{RUBY_PLATFORM}
-
-Your app has gems that work on these platforms:
-* ruby
-
-Your Gemfile specifies a Ruby version requirement:
-* ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
-
-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://#{gem_repo1}"
-
- gem "foo"
- G
-
- bundle "platform"
- expect(out).to eq(<<-G.chomp)
-Your platform is: #{RUBY_PLATFORM}
-
-Your app has gems that work on these platforms:
-* ruby
-
-Your Gemfile does not specify a Ruby version requirement.
-G
- end
-
- it "doesn't match the ruby version requirement" do
- gemfile <<-G
- source "file://#{gem_repo1}"
-
- #{ruby_version_incorrect}
-
- gem "foo"
- G
-
- bundle "platform"
- expect(out).to eq(<<-G.chomp)
-Your platform is: #{RUBY_PLATFORM}
-
-Your app has gems that work on these platforms:
-* ruby
-
-Your Gemfile specifies a Ruby version requirement:
-* ruby #{not_local_ruby_version}
-
-Your Ruby version is #{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://#{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://#{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://#{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://#{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 "raises an error if engine is used but engine version is not" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine => 'rbx'
-
- gem "foo"
- G
-
- bundle "platform"
-
- expect(exitstatus).not_to eq(0) if exitstatus
- end
-
- it "raises an error if engine_version is used but engine is not" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine_version => '1.2.4'
-
- gem "foo"
- G
-
- bundle "platform"
-
- expect(exitstatus).not_to eq(0) if exitstatus
- end
-
- it "raises an error if engine version doesn't match ruby version for MRI" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
-
- gem "foo"
- G
-
- bundle "platform"
-
- expect(exitstatus).not_to eq(0) if exitstatus
- end
-
- it "should print if no ruby version is specified" do
- gemfile <<-G
- source "file://#{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
- ruby "< 1.8.7"
- G
-
- lockfile <<-L
- GEM
- 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.0p127")
- end
-
- it "handles when there is a requirement in the gemfile" do
- gemfile <<-G
- 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
- 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 \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{local_engine_version}\"" }
- let(:ruby_version_correct_engineless) { "ruby \"#{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 \"#{RUBY_VERSION}\", :engine => \"#{not_local_tag}\", :engine_version => \"#{RUBY_VERSION}\"" }
- let(:engine_version_incorrect) { "ruby \"#{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) if exitstatus
- expect(out).to be_include("Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version}")
- end
-
- def should_be_engine_incorrect
- expect(exitstatus).to eq(18) if exitstatus
- expect(out).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) if exitstatus
- expect(out).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) if exitstatus
- expect(out).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) if exitstatus
- expect(out).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://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_correct}
- G
-
- expect(bundled_app("Gemfile.lock")).to exist
- end
-
- it "installs fine with any engine" do
- simulate_ruby_engine "jruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_correct_engineless}
- G
-
- expect(bundled_app("Gemfile.lock")).to exist
- end
- end
-
- it "installs fine when the patchlevel matches" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_correct_patchlevel}
- G
-
- expect(bundled_app("Gemfile.lock")).to exist
- end
-
- it "doesn't install when the ruby version doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_incorrect}
- G
-
- expect(bundled_app("Gemfile.lock")).not_to exist
- should_be_ruby_version_incorrect
- end
-
- it "doesn't install when engine doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{engine_incorrect}
- G
-
- expect(bundled_app("Gemfile.lock")).not_to exist
- should_be_engine_incorrect
- end
-
- it "doesn't install when engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{engine_version_incorrect}
- G
-
- expect(bundled_app("Gemfile.lock")).not_to exist
- should_be_engine_version_incorrect
- end
- end
-
- it "doesn't install when patchlevel doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
-
- expect(bundled_app("Gemfile.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://#{gem_repo1}"
- gem "rack"
- G
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_correct}
- G
-
- bundle :check
- expect(exitstatus).to eq(0) if exitstatus
- expect(out).to eq("Resolving dependencies...\nThe Gemfile's dependencies are satisfied")
- end
-
- it "checks fine with any engine" do
- simulate_ruby_engine "jruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_correct_engineless}
- G
-
- bundle :check
- expect(exitstatus).to eq(0) if exitstatus
- expect(out).to eq("Resolving dependencies...\nThe Gemfile's dependencies are satisfied")
- end
- end
-
- it "fails when ruby version doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{ruby_version_incorrect}
- G
-
- bundle :check
- should_be_ruby_version_incorrect
- end
-
- it "fails when engine doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{engine_incorrect}
- G
-
- bundle :check
- should_be_engine_incorrect
- end
-
- it "fails when engine version doesn't match" do
- simulate_ruby_engine "ruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{engine_version_incorrect}
- G
-
- bundle :check
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
-
- bundle :check
- should_be_patchlevel_incorrect
- end
- end
-
- context "bundle update" do
- before do
- build_repo2
-
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
- G
- end
-
- it "updates successfully when the ruby version matches" do
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
-
- #{ruby_version_correct}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle "update"
- expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
- end
-
- it "updates fine with any engine" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
-
- #{ruby_version_correct_engineless}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle "update"
- expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
- end
- end
-
- it "fails when ruby version doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
-
- #{ruby_version_incorrect}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle :update
- should_be_ruby_version_incorrect
- end
-
- it "fails when ruby engine doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
-
- #{engine_incorrect}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle :update
- should_be_engine_incorrect
- end
-
- it "fails when ruby engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport"
- gem "rack-obama"
-
- #{engine_version_incorrect}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle :update
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle :update
- should_be_patchlevel_incorrect
- end
- end
-
- context "bundle show" do
- before do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
- G
- end
-
- it "prints path if ruby version is correct" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
-
- #{ruby_version_correct}
- G
-
- bundle "show rails"
- 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" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
-
- #{ruby_version_correct_engineless}
- G
-
- bundle "show rails"
- expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
- end
- end
-
- it "fails if ruby version doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
-
- #{ruby_version_incorrect}
- G
-
- bundle "show rails"
- should_be_ruby_version_incorrect
- end
-
- it "fails if engine doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
-
- #{engine_incorrect}
- G
-
- bundle "show rails"
- should_be_engine_incorrect
- end
-
- it "fails if engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rails"
-
- #{engine_version_incorrect}
- G
-
- bundle "show rails"
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
- update_repo2 do
- build_gem "activesupport", "3.0"
- end
-
- bundle "show rails"
- should_be_patchlevel_incorrect
- end
- end
-
- context "bundle cache" do
- before do
- gemfile <<-G
- gem 'rack'
- G
-
- system_gems "rack-1.0.0"
- end
-
- it "copies the .gem file to vendor/cache when ruby version matches" do
- gemfile <<-G
- 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" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- gem 'rack'
-
- #{ruby_version_correct_engineless}
- G
-
- bundle :cache
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
- end
- end
-
- it "fails if the ruby version doesn't match" do
- gemfile <<-G
- gem 'rack'
-
- #{ruby_version_incorrect}
- G
-
- bundle :cache
- should_be_ruby_version_incorrect
- end
-
- it "fails if the engine doesn't match" do
- gemfile <<-G
- gem 'rack'
-
- #{engine_incorrect}
- G
-
- bundle :cache
- should_be_engine_incorrect
- end
-
- it "fails if the engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- gem 'rack'
-
- #{engine_version_incorrect}
- G
-
- bundle :cache
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
-
- bundle :cache
- should_be_patchlevel_incorrect
- end
- end
-
- context "bundle pack" do
- before do
- gemfile <<-G
- gem 'rack'
- G
-
- system_gems "rack-1.0.0"
- end
-
- it "copies the .gem file to vendor/cache when ruby version matches" do
- gemfile <<-G
- gem 'rack'
-
- #{ruby_version_correct}
- G
-
- bundle :pack
- 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" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- gem 'rack'
-
- #{ruby_version_correct_engineless}
- G
-
- bundle :pack
- expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
- end
- end
-
- it "fails if the ruby version doesn't match" do
- gemfile <<-G
- gem 'rack'
-
- #{ruby_version_incorrect}
- G
-
- bundle :pack
- should_be_ruby_version_incorrect
- end
-
- it "fails if the engine doesn't match" do
- gemfile <<-G
- gem 'rack'
-
- #{engine_incorrect}
- G
-
- bundle :pack
- should_be_engine_incorrect
- end
-
- it "fails if the engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- gem 'rack'
-
- #{engine_version_incorrect}
- G
-
- bundle :pack
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
-
- bundle :pack
- 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"
- end
-
- it "activates the correct gem when ruby version matches" do
- gemfile <<-G
- gem "rack", "0.9.1"
-
- #{ruby_version_correct}
- G
-
- bundle "exec rackup"
- expect(out).to eq("0.9.1")
- end
-
- it "activates the correct gem when ruby version matches any engine" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- gem "rack", "0.9.1"
-
- #{ruby_version_correct_engineless}
- G
-
- bundle "exec rackup"
- expect(out).to eq("0.9.1")
- end
- end
-
- it "fails when the ruby version doesn't match" do
- gemfile <<-G
- gem "rack", "0.9.1"
-
- #{ruby_version_incorrect}
- G
-
- bundle "exec rackup"
- should_be_ruby_version_incorrect
- end
-
- it "fails when the engine doesn't match" do
- gemfile <<-G
- gem "rack", "0.9.1"
-
- #{engine_incorrect}
- G
-
- bundle "exec rackup"
- should_be_engine_incorrect
- end
-
- # it "fails when the engine version doesn't match" do
- # simulate_ruby_engine "jruby" do
- # gemfile <<-G
- # gem "rack", "0.9.1"
- #
- # #{engine_version_incorrect}
- # G
- #
- # bundle "exec rackup"
- # should_be_engine_version_incorrect
- # end
- # end
-
- it "fails when patchlevel doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
-
- bundle "exec rackup"
- should_be_patchlevel_incorrect
- end
- end
-
- context "bundle console" do
- before do
- install_gemfile <<-G
- source "file://#{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" do
- gemfile <<-G
- source "file://#{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 any engine" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- source "file://#{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
- end
-
- it "fails when ruby version doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport", :group => :test
- gem "rack_middleware", :group => :development
-
- #{ruby_version_incorrect}
- G
-
- bundle "console"
- should_be_ruby_version_incorrect
- end
-
- it "fails when engine doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport", :group => :test
- gem "rack_middleware", :group => :development
-
- #{engine_incorrect}
- G
-
- bundle "console"
- should_be_engine_incorrect
- end
-
- it "fails when engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport", :group => :test
- gem "rack_middleware", :group => :development
-
- #{engine_version_incorrect}
- G
-
- bundle "console"
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- gem "activesupport", :group => :test
- gem "rack_middleware", :group => :development
-
- #{patchlevel_incorrect}
- G
-
- bundle "console"
- should_be_patchlevel_incorrect
- end
- end
-
- context "Bundler.setup" do
- before do
- install_gemfile <<-G
- source "file://#{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://#{gem_repo1}"
- gem "yard"
- gem "rack"
-
- #{ruby_version_correct}
- G
-
- FileUtils.rm(bundled_app("Gemfile.lock"))
-
- run "1"
- expect(bundled_app("Gemfile.lock")).to exist
- end
-
- it "makes a Gemfile.lock if setup succeeds for any engine" do
- simulate_ruby_engine "jruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "yard"
- gem "rack"
-
- #{ruby_version_correct_engineless}
- G
-
- FileUtils.rm(bundled_app("Gemfile.lock"))
-
- run "1"
- expect(bundled_app("Gemfile.lock")).to exist
- end
- end
-
- it "fails when ruby version doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "yard"
- gem "rack"
-
- #{ruby_version_incorrect}
- G
-
- FileUtils.rm(bundled_app("Gemfile.lock"))
-
- ruby <<-R
- require 'rubygems'
- require 'bundler/setup'
- R
-
- expect(bundled_app("Gemfile.lock")).not_to exist
- should_be_ruby_version_incorrect
- end
-
- it "fails when engine doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "yard"
- gem "rack"
-
- #{engine_incorrect}
- G
-
- FileUtils.rm(bundled_app("Gemfile.lock"))
-
- ruby <<-R
- require 'rubygems'
- require 'bundler/setup'
- R
-
- expect(bundled_app("Gemfile.lock")).not_to exist
- should_be_engine_incorrect
- end
-
- it "fails when engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "yard"
- gem "rack"
-
- #{engine_version_incorrect}
- G
-
- FileUtils.rm(bundled_app("Gemfile.lock"))
-
- ruby <<-R
- require 'rubygems'
- require 'bundler/setup'
- R
-
- expect(bundled_app("Gemfile.lock")).not_to exist
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when patchlevel doesn't match" do
- install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "yard"
- gem "rack"
-
- #{patchlevel_incorrect}
- G
-
- FileUtils.rm(bundled_app("Gemfile.lock"))
-
- ruby <<-R
- require 'rubygems'
- require 'bundler/setup'
- R
-
- expect(bundled_app("Gemfile.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://#{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://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{ruby_version_correct}
- G
-
- bundle "outdated"
- expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5")
- expect(out).to include("foo (newest 1.0")
- end
-
- it "returns list of outdated gems when the ruby version matches for any engine" do
- simulate_ruby_engine "jruby" do
- update_repo2 do
- build_gem "activesupport", "3.0"
- update_git "foo", :path => lib_path("foo")
- end
-
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{ruby_version_correct_engineless}
- G
-
- bundle "outdated"
- expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
- expect(out).to include("foo (newest 1.0")
- end
- 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://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{ruby_version_incorrect}
- G
-
- bundle "outdated"
- 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://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{engine_incorrect}
- G
-
- bundle "outdated"
- should_be_engine_incorrect
- end
-
- it "fails when the engine version doesn't match" do
- simulate_ruby_engine "jruby" do
- update_repo2 do
- build_gem "activesupport", "3.0"
- update_git "foo", :path => lib_path("foo")
- end
-
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{engine_version_incorrect}
- G
-
- bundle "outdated"
- should_be_engine_version_incorrect
- end
- end
-
- it "fails when the patchlevel doesn't match" do
- simulate_ruby_engine "jruby" do
- update_repo2 do
- build_gem "activesupport", "3.0"
- update_git "foo", :path => lib_path("foo")
- end
-
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{patchlevel_incorrect}
- G
-
- bundle "outdated"
- should_be_patchlevel_incorrect
- end
- end
-
- it "fails when the patchlevel is a fixnum" do
- simulate_ruby_engine "jruby" do
- update_repo2 do
- build_gem "activesupport", "3.0"
- update_git "foo", :path => lib_path("foo")
- end
-
- gemfile <<-G
- source "file://#{gem_repo2}"
- gem "activesupport", "2.3.5"
- gem "foo", :git => "#{lib_path("foo")}"
-
- #{patchlevel_fixnum}
- G
-
- bundle "outdated"
- should_be_patchlevel_fixnum
- end
- end
- end
-end
diff --git a/spec/bundler/other/ssl_cert_spec.rb b/spec/bundler/other/ssl_cert_spec.rb
deleted file mode 100644
index 2de4dfdd0c..0000000000
--- a/spec/bundler/other/ssl_cert_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-require "bundler/ssl_certs/certificate_manager"
-
-RSpec.describe "SSL Certificates", :rubygems_master do
- hosts = %w(
- rubygems.org
- index.rubygems.org
- rubygems.global.ssl.fastly.net
- staging.rubygems.org
- )
-
- hosts.each do |host|
- it "can securely connect to #{host}", :realworld do
- Bundler::SSLCerts::CertificateManager.new.connect_to(host)
- end
- end
-end
diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb
index 6ad782b758..05d535a70c 100644
--- a/spec/bundler/plugins/command_spec.rb
+++ b/spec/bundler/plugins/command_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "command plugins" do
before do
@@ -19,7 +18,7 @@ RSpec.describe "command plugins" do
end
end
- bundle "plugin install command-mah --source file://#{gem_repo2}"
+ bundle "plugin install command-mah --source https://gem.repo2"
end
it "executes without arguments" do
@@ -30,7 +29,7 @@ RSpec.describe "command plugins" do
end
it "accepts the arguments" do
- build_repo2 do
+ update_repo2 do
build_plugin "the-echoer" do |s|
s.write "plugins.rb", <<-RUBY
module Resonance
@@ -47,15 +46,49 @@ RSpec.describe "command plugins" do
end
end
- bundle "plugin install the-echoer --source file://#{gem_repo2}"
+ bundle "plugin install the-echoer --source https://gem.repo2"
expect(out).to include("Installed plugin the-echoer")
- bundle "echo tacos tofu lasange", "no-color" => false
+ bundle "echo tacos tofu lasange"
expect(out).to eq("You gave me tacos, tofu, lasange")
end
+ it "passes help flag to plugin" do
+ update_repo2 do
+ build_plugin "helpful" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module Helpful
+ class Command
+ Bundler::Plugin::API.command "greet", self
+
+ def exec(command, args)
+ if args.include?("--help") || args.include?("-h")
+ puts "Usage: bundle greet [NAME]"
+ else
+ puts "Hello"
+ end
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install helpful --source https://gem.repo2"
+ expect(out).to include("Installed plugin helpful")
+
+ bundle "greet --help"
+ expect(out).to eq("Usage: bundle greet [NAME]")
+
+ bundle "greet -h"
+ expect(out).to eq("Usage: bundle greet [NAME]")
+
+ bundle "help greet"
+ expect(out).to eq("Usage: bundle greet [NAME]")
+ end
+
it "raises error on redeclaration of command" do
- build_repo2 do
+ update_repo2 do
build_plugin "copycat" do |s|
s.write "plugins.rb", <<-RUBY
module CopyCat
@@ -70,12 +103,10 @@ RSpec.describe "command plugins" do
end
end
- bundle "plugin install copycat --source file://#{gem_repo2}"
+ bundle "plugin install copycat --source https://gem.repo2", raise_on_error: false
expect(out).not_to include("Installed plugin copycat")
- expect(out).to include("Failed to install plugin")
-
- expect(out).to include("Command(s) `mahcommand` declared by copycat are already registered.")
+ 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
index 9850d850ac..ad8a4daeff 100644
--- a/spec/bundler/plugins/hook_spec.rb
+++ b/spec/bundler/plugins/hook_spec.rb
@@ -1,28 +1,331 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "hook plugins" do
- before do
- build_repo2 do
- build_plugin "before-install-plugin" do |s|
- s.write "plugins.rb", <<-RUBY
- Bundler::Plugin::API.hook "before-install-all" do |deps|
- puts "gems to be installed \#{deps.map(&:name).join(", ")}"
- end
- RUBY
+ 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 https://gem.repo2"
+ end
+
+ it "runs before all rubygems are installed" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ gem "myrack"
+ G
+
+ expect(out).to include "gems to be installed rake, myrack"
+ 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 https://gem.repo2"
+ end
+
+ it "runs before each rubygem is installed" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ gem "myrack"
+ G
+
+ expect(out).to include "installing gem rake"
+ expect(out).to include "installing gem myrack"
+ 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 https://gem.repo2"
+ end
+
+ it "runs after each all rubygems are installed" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ gem "myrack"
+ G
+
+ expect(out).to include "installed gems rake, myrack"
+ 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 https://gem.repo2"
+ end
+
+ it "runs after each rubygem is installed" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ gem "myrack"
+ G
+
+ expect(out).to include "installed gem rake : installed"
+ expect(out).to include "installed gem myrack : installed"
+ end
+ end
+
+ context "before-require-all hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-require-all-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE_ALL do |deps|
+ puts "gems to be required \#{deps.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-require-all-plugin --source https://gem.repo2"
+ end
+
+ it "runs before all rubygems are required" do
+ install_gemfile_and_bundler_require
+ expect(out).to include "gems to be required rake, myrack"
+ end
+ end
+
+ context "before-require hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-require-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE do |dep|
+ puts "requiring gem \#{dep.name}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-require-plugin --source https://gem.repo2"
+ end
+
+ it "runs before each rubygem is required" do
+ install_gemfile_and_bundler_require
+ expect(out).to include "requiring gem rake"
+ expect(out).to include "requiring gem myrack"
+ end
+ end
+
+ context "after-require-all hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-require-all-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE_ALL do |deps|
+ puts "required gems \#{deps.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-require-all-plugin --source https://gem.repo2"
+ end
+
+ it "runs after all rubygems are required" do
+ install_gemfile_and_bundler_require
+ expect(out).to include "required gems rake, myrack"
+ end
+ end
+
+ context "after-require hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-require-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE do |dep|
+ puts "required gem \#{dep.name}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-require-plugin --source https://gem.repo2"
+ end
+
+ it "runs after each rubygem is required" do
+ install_gemfile_and_bundler_require
+ expect(out).to include "required gem rake"
+ expect(out).to include "required gem myrack"
+ end
+ end
+
+ context "before-eval hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-eval-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_EVAL do |gemfile, lockfile|
+ puts "hooked eval start of \#{File.basename(gemfile)} to \#{File.basename(lockfile)}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-eval-plugin --source https://gem.repo2"
+ end
+
+ it "runs before the Gemfile is evaluated" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ G
+
+ expect(out).to include "hooked eval start of Gemfile to Gemfile.lock"
+ end
+ end
+
+ context "after-eval hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-eval-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_EVAL do |defn|
+ puts "hooked eval after with gems \#{defn.dependencies.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-eval-plugin --source https://gem.repo2"
+ end
+
+ it "runs after the Gemfile is evaluated" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "rake"
+ G
+
+ expect(out).to include "hooked eval after with gems myrack, rake"
+ end
+ end
+
+ context "before-fetch and after-fetch hooks" do
+ before do
+ build_repo2 do
+ build_plugin "fetch-timing-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ @timing_start = nil
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_FETCH do |spec|
+ @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ puts "gem \#{spec.name} started fetch at \#{@timing_start}"
+ end
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_FETCH do |spec|
+ timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ puts "gem \#{spec.name} took \#{timing_end - @timing_start} to fetch"
+ @timing_start = nil
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install fetch-timing-plugin --source https://gem.repo2"
end
- bundle "plugin install before-install-plugin --source file://#{gem_repo2}"
+ it "runs around each gem download" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "rake"
+ gem "myrack"
+ G
+
+ expect(out).to include "gem rake started fetch at"
+ expect(out).to match(/gem rake took \d+\.\d+ to fetch/)
+ expect(out).to include "gem myrack started fetch at"
+ expect(out).to match(/gem myrack took \d+\.\d+ to fetch/)
+ end
+ end
+
+ context "before-git-fetch and after-git-fetch hooks" do
+ before do
+ build_repo2 do
+ build_plugin "git-fetch-timing-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ @timing_start = nil
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_BEFORE_FETCH do |source|
+ @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ puts "git source \#{source.name} started fetch at \#{@timing_start}"
+ end
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_AFTER_FETCH do |source|
+ timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ puts "git source \#{source.name} took \#{timing_end - @timing_start} to fetch"
+ @timing_start = nil
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install git-fetch-timing-plugin --source https://gem.repo2"
+ end
+
+ it "runs around each git source fetch" do
+ build_git "foo", "1.0", path: lib_path("foo")
+
+ relative_path = lib_path("foo").relative_path_from(bundled_app)
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "foo", :git => "#{relative_path}"
+ G
+
+ expect(out).to include "git source foo started fetch at"
+ expect(out).to match(/git source foo took \d+\.\d+ to fetch/)
+ end
end
- it "runs after a rubygem is installed" do
+ def install_gemfile_and_bundler_require
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rake"
- gem "rack"
+ gem "myrack"
G
- expect(out).to include "gems to be installed rake, rack"
+ ruby <<-RUBY
+ require "bundler"
+ Bundler.require
+ RUBY
end
end
diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb
index e2d351181c..dcacf764be 100644
--- a/spec/bundler/plugins/install_spec.rb
+++ b/spec/bundler/plugins/install_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundler plugin install" do
before do
@@ -10,21 +9,54 @@ RSpec.describe "bundler plugin install" do
end
it "shows proper message when gem in not found in the source" do
- bundle "plugin install no-foo --source file://#{gem_repo1}"
+ bundle "plugin install no-foo --source https://gem.repo1", raise_on_error: false
- expect(out).to include("Could not find")
+ 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://#{gem_repo2}"
+ bundle "plugin install foo --source https://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 https://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", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_SOURCES" => "https://gem.repo2" }
+
+ 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 https://gem.repo2"
+ end
+
+ it "doesn't install plugin again" do
+ bundle "plugin install foo --source https://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://#{gem_repo2}"
+ bundle "plugin install foo kung-foo --source https://gem.repo2"
expect(out).to include("Installed plugin foo")
expect(out).to include("Installed plugin kung-foo")
@@ -38,13 +70,50 @@ RSpec.describe "bundler plugin install" do
build_plugin "kung-foo", "1.1"
end
- bundle "plugin install foo kung-foo --version '1.0' --source file://#{gem_repo2}"
+ bundle "plugin install foo kung-foo --version '1.0' --source https://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 https://gem.repo2 --verbose"
+ expect(out).to include("Installing foo 1.0")
+
+ bundle "plugin install foo --source https://gem.repo2 --verbose"
+ expect(out).to include("Installing foo 1.1")
+
+ bundle "plugin install foo --source https://gem.repo2 --verbose"
+ expect(out).to include("Using foo 1.1")
+ end
+
+ it "raises an error when when --branch specified" do
+ bundle "plugin install foo --branch main --source https://gem.repo2", raise_on_error: false
+
+ expect(out).not_to include("Installed plugin foo")
+
+ expect(err).to include("--branch can only be used with git sources")
+ end
+
+ it "raises an error when --ref specified" do
+ bundle "plugin install foo --ref v1.2.3 --source https://gem.repo2", raise_on_error: false
+
+ expect(err).to include("--ref can only be used with git sources")
+ end
+
+ it "raises error when both --branch and --ref options are specified" do
+ bundle "plugin install foo --source https://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|
@@ -58,11 +127,11 @@ RSpec.describe "bundler plugin install" do
end
end
RUBY
- s.require_paths = %w(lib src)
+ s.require_paths = %w[lib src]
s.write("src/fubar.rb")
end
end
- bundle "plugin install testing --source file://#{gem_repo2}"
+ bundle "plugin install testing --source https://gem.repo2"
bundle "check2", "no-color" => false
expect(out).to eq("mate")
@@ -75,19 +144,19 @@ RSpec.describe "bundler plugin install" do
build_plugin "kung-foo", "1.1"
end
- bundle "plugin install foo kung-foo --version '1.0' --source file://#{gem_repo2}"
+ bundle "plugin install foo kung-foo --version '1.0' --source https://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
+ update_repo2 do
build_gem "charlie"
end
- bundle "plugin install charlie --source file://#{gem_repo2}"
+ bundle "plugin install charlie --source https://gem.repo2", raise_on_error: false
- expect(out).to include("plugins.rb was not found")
+ 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
@@ -99,12 +168,12 @@ RSpec.describe "bundler plugin install" do
build_repo2 do
build_plugin "chaplin" do |s|
s.write "plugins.rb", <<-RUBY
- raise "I got you man"
+ raise RuntimeError, "threw exception on load"
RUBY
end
end
- bundle "plugin install chaplin --source file://#{gem_repo2}"
+ bundle "plugin install chaplin --source https://gem.repo2", raise_on_error: false
expect(global_plugin_gem("chaplin-1.0")).not_to be_directory
@@ -118,19 +187,72 @@ RSpec.describe "bundler plugin install" do
s.write "plugins.rb"
end
- bundle "plugin install foo --git file://#{lib_path("foo-1.0")}"
+ bundle "plugin install foo --git #{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 --git #{lib_path("foo-1.0")}"
expect(out).to include("Installed plugin foo")
plugin_should_be_installed("foo")
end
end
+ context "path plugins" do
+ it "installs from a path source" do
+ build_lib "path_plugin" do |s|
+ s.write "plugins.rb"
+ end
+ bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}"
+
+ expect(out).to include("Installed plugin path_plugin")
+ plugin_should_be_installed("path_plugin")
+ end
+
+ it "installs from a relative path source" do
+ build_lib "path_plugin" do |s|
+ s.write "plugins.rb"
+ end
+ path = lib_path("path_plugin-1.0").relative_path_from(bundled_app)
+ bundle "plugin install path_plugin --path #{path}"
+
+ expect(out).to include("Installed plugin path_plugin")
+ plugin_should_be_installed("path_plugin")
+ end
+
+ it "installs from a relative path source when inside an app" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ gemfile ""
+
+ build_lib "ga-plugin" do |s|
+ s.write "plugins.rb"
+ end
+
+ path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app)
+ bundle "plugin install ga-plugin --path #{path}"
+
+ plugin_should_be_installed("ga-plugin")
+ expect(local_plugin_gem("foo-1.0")).not_to be_directory
+ 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://#{gem_repo2}'
+ source 'https://gem.repo2'
plugin 'foo'
- gem 'rack', "1.0.0"
+ gem 'myrack', "1.0.0"
G
bundle "install"
@@ -139,17 +261,54 @@ RSpec.describe "bundler plugin install" do
expect(out).to include("Bundle complete!")
- expect(the_bundle).to include_gems("rack 1.0.0")
+ expect(the_bundle).to include_gems("myrack 1.0.0")
plugin_should_be_installed("foo")
end
+ it "overrides the index with the new plugin version" do
+ gemfile <<-G
+ source 'https://gem.repo2'
+ plugin 'foo', "1.0"
+ gem 'myrack', "1.0.0"
+ G
+
+ bundle "install"
+
+ update_repo2 do
+ build_plugin "foo", "2.0.0"
+ end
+
+ gemfile <<-G
+ source 'https://gem.repo2'
+ plugin 'foo', "2.0"
+ gem 'myrack', "1.0.0"
+ G
+
+ bundle "install"
+
+ expected = local_plugin_gem("foo-2.0.0", "lib").to_s
+ expect(Bundler::Plugin.index.load_paths("foo")).to eq([expected])
+ end
+
+ it "respects bundler groups" do
+ gemfile <<-G
+ source 'https://gem.repo2'
+ plugin 'foo'
+ gem 'myrack', "1.0.0"
+ G
+
+ bundle "install", env: { "BUNDLE_WITHOUT" => "default" }
+
+ expect(out).to include("Bundle complete! 1 Gemfile dependency, 0 gems now installed.")
+ end
+
it "accepts plugin version" do
update_repo2 do
build_plugin "foo", "1.1.0"
end
- install_gemfile <<-G
- source 'file://#{gem_repo2}'
+ gemfile <<-G
+ source 'https://gem.repo2'
plugin 'foo', "1.0"
G
@@ -168,12 +327,65 @@ RSpec.describe "bundler plugin install" do
end
install_gemfile <<-G
+ source "https://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 "https://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
+
+ it "accepts relative path sources" do
+ build_lib "ga-plugin" do |s|
+ s.write "plugins.rb"
+ end
+
+ path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app)
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ plugin 'ga-plugin', :path => "#{path}"
+ 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 'https://gem.repo2'
+ gem 'myrack', "1.0.0"
+ G
+
+ bundle_config "deployment true"
+ install_gemfile <<-G
+ source 'https://gem.repo2'
+ plugin 'foo'
+ gem 'myrack', "1.0.0"
+ G
+
+ expect(out).to include("Installed plugin foo")
+
+ expect(out).to include("Bundle complete!")
+
+ expect(the_bundle).to include_gems("myrack 1.0.0")
+ plugin_should_be_installed("foo")
+ end
+ end
end
context "inline gemfiles" do
@@ -182,20 +394,21 @@ RSpec.describe "bundler plugin install" do
require "bundler/inline"
gemfile do
- source 'file://#{gem_repo2}'
+ source 'https://gem.repo2'
plugin 'foo'
end
RUBY
- ruby code
+ ruby code, artifice: "compact_index", 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://#{gem_repo2}"
+ bundle "plugin install foo --source https://gem.repo2"
plugin_should_be_installed("foo")
expect(local_plugin_gem("foo-1.0")).to be_directory
@@ -218,7 +431,7 @@ RSpec.describe "bundler plugin install" do
end
# inside the app
- gemfile "source 'file://#{gem_repo2}'\nplugin 'fubar'"
+ gemfile "source 'https://gem.repo2'\nplugin 'fubar'"
bundle "install"
update_repo2 do
@@ -236,21 +449,16 @@ RSpec.describe "bundler plugin install" do
end
# outside the app
- Dir.chdir tmp
- bundle "plugin install fubar --source file://#{gem_repo2}"
+ bundle "plugin install fubar --source https://gem.repo2", dir: tmp
end
it "inside the app takes precedence over global plugin" do
- Dir.chdir bundled_app
-
bundle "shout"
expect(out).to eq("local_one")
end
it "outside the app global plugin is used" do
- Dir.chdir tmp
-
- bundle "shout"
+ bundle "shout", dir: tmp
expect(out).to eq("global_one")
end
end
diff --git a/spec/bundler/plugins/list_spec.rb b/spec/bundler/plugins/list_spec.rb
new file mode 100644
index 0000000000..30e3f82467
--- /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 https://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 https://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
index 2ae34caf73..4cd4a1a931 100644
--- a/spec/bundler/plugins/source/example_spec.rb
+++ b/spec/bundler/plugins/source/example_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "real source plugins" do
context "with a minimal source plugin" do
@@ -7,7 +6,6 @@ RSpec.describe "real source plugins" do
build_repo2 do
build_plugin "bundler-source-mpath" do |s|
s.write "plugins.rb", <<-RUBY
- require "fileutils"
require "bundler-source-mpath"
class MPath < Bundler::Plugin::API
@@ -35,8 +33,13 @@ RSpec.describe "real source plugins" do
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
@@ -49,7 +52,7 @@ RSpec.describe "real source plugins" do
build_lib "a-path-gem"
gemfile <<-G
- source "file://#{gem_repo2}" # plugin source
+ source "https://gem.repo2" # plugin source
source "#{lib_path("a-path-gem-1.0")}", :type => :mpath do
gem "a-path-gem"
end
@@ -64,10 +67,14 @@ RSpec.describe "real source plugins" do
expect(the_bundle).to include_gems("a-path-gem 1.0")
end
- it "writes to lock file" do
+ it "writes to lockfile" do
bundle "install"
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "a-path-gem", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
PLUGIN SOURCE
remote: #{lib_path("a-path-gem-1.0")}
type: mpath
@@ -75,17 +82,17 @@ RSpec.describe "real source plugins" do
a-path-gem (1.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
a-path-gem!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
@@ -94,18 +101,18 @@ RSpec.describe "real source plugins" do
run <<-RUBY
puts Bundler.rubygems.find_name('a-path-gem').first.full_gem_path
RUBY
- expect(out).to eq(bundle("show a-path-gem"))
+ expect(out).to eq(bundle("info a-path-gem --path"))
end
it "installs the gem executables" do
- build_lib "gem-with-bin" do |s|
+ build_lib "gem_with_bin" do |s|
s.executables = ["foo"]
end
install_gemfile <<-G
- source "file://#{gem_repo2}" # plugin source
- source "#{lib_path("gem-with-bin-1.0")}", :type => :mpath do
- gem "gem-with-bin"
+ source "https://gem.repo2" # plugin source
+ source "#{lib_path("gem_with_bin-1.0")}", :type => :mpath do
+ gem "gem_with_bin"
end
G
@@ -114,36 +121,38 @@ RSpec.describe "real source plugins" do
end
describe "bundle cache/package" do
- let(:uri_hash) { Digest::SHA1.hexdigest(lib_path("a-path-gem-1.0").to_s) }
+ 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 "cache --all"
+ 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")
+ FileUtils.rm_r 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 bundle --path" do
- bundle "install --path vendor/bundle"
- bundle "cache --all"
+ it "copies repository to vendor cache and uses it even when installed with `path` configured" do
+ bundle_config "path vendor/bundle"
+ bundle :install
+ 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")
+ FileUtils.rm_r 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 "install --path vendor/bundle"
- bundle "package --all"
+ bundle_config "path vendor/bundle"
+ bundle :install
+ 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")
+ FileUtils.rm_r lib_path("a-path-gem-1.0")
expect(the_bundle).to include_gems("a-path-gem 1.0")
end
end
@@ -158,7 +167,7 @@ RSpec.describe "real source plugins" do
a-path-gem (1.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
PLATFORMS
@@ -168,7 +177,7 @@ RSpec.describe "real source plugins" do
a-path-gem!
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
@@ -185,6 +194,8 @@ RSpec.describe "real source plugins" 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"
@@ -193,7 +204,7 @@ RSpec.describe "real source plugins" do
def initialize(opts)
super
- @ref = options["ref"] || options["branch"] || options["tag"] || "master"
+ @ref = options["ref"] || options["branch"] || options["tag"] || "main"
@unlocked = false
end
@@ -224,9 +235,11 @@ RSpec.describe "real source plugins" do
mkdir_p(install_path.dirname)
rm_rf(install_path)
`git clone --no-checkout --quiet "\#{cache_path}" "\#{install_path}"`
- Dir.chdir install_path do
- `git reset --hard \#{revision}`
- end
+ 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)
@@ -235,7 +248,7 @@ RSpec.describe "real source plugins" do
def options_to_lock
opts = {"revision" => revision}
- opts["ref"] = ref if ref != "master"
+ opts["ref"] = ref if ref != "main"
opts
end
@@ -276,9 +289,8 @@ RSpec.describe "real source plugins" do
cache_repo
end
- Dir.chdir cache_path do
- `git rev-parse --verify \#{@ref}`.strip
- end
+ output, _status = Open3.capture2e("git rev-parse --verify \#{@ref}", :chdir => cache_path)
+ output.strip
end
def base_name
@@ -293,13 +305,7 @@ RSpec.describe "real source plugins" do
@install_path ||= begin
git_scope = "\#{base_name}-\#{shortref_for_path(revision)}"
- path = gem_install_dir.join(git_scope)
-
- if !path.exist? && requires_sudo?
- user_bundle_path.join(ruby_scope).join(git_scope)
- else
- path
- end
+ gem_install_dir.join(git_scope)
end
end
@@ -314,8 +320,8 @@ RSpec.describe "real source plugins" do
build_git "ma-gitp-gem"
gemfile <<-G
- source "file://#{gem_repo2}" # plugin source
- source "file://#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
+ source "https://gem.repo2" # plugin source
+ source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
gem "ma-gitp-gem"
end
G
@@ -327,30 +333,34 @@ RSpec.describe "real source plugins" do
expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
end
- it "writes to lock file" do
+ it "writes to lockfile" do
revision = revision_for(lib_path("ma-gitp-gem-1.0"))
bundle "install"
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "ma-gitp-gem", "1.0"
+ end
+
+ expect(lockfile).to eq <<~G
PLUGIN SOURCE
- remote: file://#{lib_path("ma-gitp-gem-1.0")}
+ remote: #{lib_path("ma-gitp-gem-1.0")}
type: gitp
revision: #{revision}
specs:
ma-gitp-gem (1.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
ma-gitp-gem!
-
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
@@ -359,14 +369,14 @@ RSpec.describe "real source plugins" do
revision = revision_for(lib_path("ma-gitp-gem-1.0"))
lockfile <<-G
PLUGIN SOURCE
- remote: file://#{lib_path("ma-gitp-gem-1.0")}
+ remote: #{lib_path("ma-gitp-gem-1.0")}
type: gitp
revision: #{revision}
specs:
ma-gitp-gem (1.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
PLATFORMS
@@ -376,7 +386,7 @@ RSpec.describe "real source plugins" do
ma-gitp-gem!
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
@@ -390,7 +400,7 @@ RSpec.describe "real source plugins" do
bundle "install"
run <<-RUBY
- require 'ma-gitp-gem'
+ require 'ma/gitp/gem'
puts "WIN" unless defined?(MAGITPGEM_PREV_REF)
RUBY
expect(out).to eq("WIN")
@@ -401,17 +411,17 @@ RSpec.describe "real source plugins" do
bundle "update ma-gitp-gem"
run <<-RUBY
- require 'ma-gitp-gem'
+ 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
+ update_git "ma-gitp-gem", "1.1", path: lib_path("ma-gitp-gem-1.0"), gemspec: true
gemfile <<-G
- source "file://#{gem_repo2}" # plugin source
- source "file://#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
+ source "https://gem.repo2" # plugin source
+ source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
gem "ma-gitp-gem", "1.1"
end
G
@@ -424,21 +434,21 @@ RSpec.describe "real source plugins" do
describe "bundle cache with gitp" do
it "copies repository to vendor cache and uses it" do
git = build_git "foo"
- ref = git.ref_for("master", 11)
+ ref = git.ref_for("main", 11)
install_gemfile <<-G
- source "file://#{gem_repo2}" # plugin source
+ source "https://gem.repo2" # plugin source
source '#{lib_path("foo-1.0")}', :type => :gitp do
gem "foo"
end
G
- bundle "cache --all"
+ 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")
+ FileUtils.rm_r lib_path("foo-1.0")
expect(the_bundle).to include_gems "foo 1.0"
end
end
diff --git a/spec/bundler/plugins/source_spec.rb b/spec/bundler/plugins/source_spec.rb
index 0448bc409a..995e50e653 100644
--- a/spec/bundler/plugins/source_spec.rb
+++ b/spec/bundler/plugins/source_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundler source plugin" do
describe "plugins dsl eval for #source with :type option" do
@@ -17,11 +16,12 @@ RSpec.describe "bundler source plugin" do
it "installs bundler-source-* gem when no handler for source is present" do
install_gemfile <<-G
- source "file://#{gem_repo2}"
- source "file://#{lib_path("gitp")}", :type => :psource do
+ source "https://gem.repo2"
+ source "#{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
@@ -38,8 +38,8 @@ RSpec.describe "bundler source plugin" do
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
- source "file://#{lib_path("gitp")}", :type => :psource do
+ source "https://gem.repo2"
+ source "#{lib_path("gitp")}", :type => :psource do
end
G
@@ -62,11 +62,11 @@ RSpec.describe "bundler source plugin" do
context "explicit presence in gemfile" do
before do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
plugin "another-psource"
- source "file://#{lib_path("gitp")}", :type => :psource do
+ source "#{lib_path("gitp")}", :type => :psource do
end
G
end
@@ -76,6 +76,7 @@ RSpec.describe "bundler source plugin" do
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
@@ -87,11 +88,11 @@ RSpec.describe "bundler source plugin" do
context "explicit default source" do
before do
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
plugin "bundler-source-psource"
- source "file://#{lib_path("gitp")}", :type => :psource do
+ source "#{lib_path("gitp")}", :type => :psource do
end
G
end
@@ -101,6 +102,7 @@ RSpec.describe "bundler source plugin" do
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
diff --git a/spec/bundler/plugins/uninstall_spec.rb b/spec/bundler/plugins/uninstall_spec.rb
new file mode 100644
index 0000000000..dedcc9f37c
--- /dev/null
+++ b/spec/bundler/plugins/uninstall_spec.rb
@@ -0,0 +1,74 @@
+# 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 https://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
+
+ it "doesn't wipe out path plugins" do
+ build_lib "path_plugin" do |s|
+ s.write "plugins.rb"
+ end
+ path = lib_path("path_plugin-1.0")
+ expect(path).to be_a_directory
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+
+ install_gemfile <<-G
+ source 'https://gem.repo2'
+ plugin 'path_plugin', :path => "#{path}"
+ gem 'myrack', '1.0.0'
+ G
+
+ plugin_should_be_installed("path_plugin")
+ expect(Bundler::Plugin.index.plugin_path("path_plugin")).to eq path
+
+ bundle "plugin uninstall path_plugin"
+ expect(out).to include("Uninstalled plugin path_plugin")
+ plugin_should_not_be_installed("path_plugin")
+ # the actual gem still exists though
+ expect(path).to be_a_directory
+ end
+
+ describe "with --all" do
+ it "uninstalls all installed plugins" do
+ bundle "plugin install foo kung-foo --source https://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..e68674c030
--- /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(File.expand_path(filename, source_root)).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(File.expand_path(filename, source_root)).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
index b87b4a0731..16b7f18788 100644
--- a/spec/bundler/quality_spec.rb
+++ b/spec/bundler/quality_spec.rb
@@ -1,42 +1,8 @@
# frozen_string_literal: true
-require "spec_helper"
-if defined?(Encoding) && Encoding.default_external.name != "UTF-8"
- # Poor man's ruby -E UTF-8, since it works on 1.8.7
- Encoding.default_external = Encoding.find("UTF-8")
-end
+require "set"
RSpec.describe "The library itself" do
- def check_for_spec_defs_with_single_quotes(filename)
- failing_lines = []
-
- File.readlines(filename).each_with_index do |line, number|
- failing_lines << number + 1 if line =~ /^ *(describe|it|context) {1}'{1}/
- end
-
- return if failing_lines.empty?
- "#{filename} uses inconsistent single quotes on lines #{failing_lines.join(", ")}"
- end
-
- def check_for_debugging_mechanisms(filename)
- debugging_mechanisms_regex = /
- (binding\.pry)|
- (debugger)|
- (sleep\s*\(?\d+)|
- (fit\s*\(?("|\w))
- /x
-
- failing_lines = []
- File.readlines(filename).each_with_index do |line, number|
- if line =~ debugging_mechanisms_regex && !line.end_with?("# ignore quality_spec\n")
- failing_lines << number + 1
- end
- end
-
- return if failing_lines.empty?
- "#{filename} has debugging mechanisms (like binding.pry, sleep, debugger, rspec focusing, etc.) on lines #{failing_lines.join(", ")}"
- end
-
def check_for_git_merge_conflicts(filename)
merge_conflicts_regex = /
<<<<<<<|
@@ -45,8 +11,8 @@ RSpec.describe "The library itself" do
/x
failing_lines = []
- File.readlines(filename).each_with_index do |line, number|
- failing_lines << number + 1 if line =~ merge_conflicts_regex
+ each_line(filename) do |line, number|
+ failing_lines << number + 1 if line&.match?(merge_conflicts_regex)
end
return if failing_lines.empty?
@@ -54,9 +20,12 @@ RSpec.describe "The library itself" do
end
def check_for_tab_characters(filename)
+ # Because Go uses hard tabs
+ return if filename.end_with?(".go.tt")
+
failing_lines = []
- File.readlines(filename).each_with_index do |line, number|
- failing_lines << number + 1 if line =~ /\t/
+ each_line(filename) do |line, number|
+ failing_lines << number + 1 if line.include?("\t")
end
return if failing_lines.empty?
@@ -65,19 +34,28 @@ RSpec.describe "The library itself" do
def check_for_extra_spaces(filename)
failing_lines = []
- File.readlines(filename).each_with_index do |line, number|
- next if line =~ /^\s+#.*\s+\n$/
- next if %w(LICENCE.md).include?(line)
- failing_lines << number + 1 if line =~ /\s+\n$/
+ 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_extraneous_quotes(filename)
+ failing_lines = []
+ each_line(filename) do |line, number|
+ failing_lines << number + 1 if /\u{2019}/.match?(line)
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has an extraneous quote on lines #{failing_lines.join(", ")}"
+ end
+
def check_for_expendable_words(filename)
failing_line_message = []
- useless_words = %w(
+ useless_words = %w[
actually
basically
clearly
@@ -85,12 +63,12 @@ RSpec.describe "The library itself" do
obviously
really
simply
- )
+ ]
pattern = /\b#{Regexp.union(useless_words)}\b/i
- File.readlines(filename).each_with_index do |line, number|
+ each_line(filename) do |line, number|
next unless word_found = pattern.match(line)
- failing_line_message << "#{filename} has '#{word_found}' on line #{number + 1}. Avoid using these kinds of weak modifiers."
+ 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?
@@ -100,164 +78,184 @@ RSpec.describe "The library itself" do
failing_line_message = []
specific_pronouns = /\b(he|she|his|hers|him|her|himself|herself)\b/i
- File.readlines(filename).each_with_index do |line, number|
+ each_line(filename) do |line, number|
next unless word_found = specific_pronouns.match(line)
- failing_line_message << "#{filename} has '#{word_found}' on line #{number + 1}. Use more generic pronouns in documentation."
+ 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
- RSpec::Matchers.define :be_well_formed do
- match(&:empty?)
-
- failure_message do |actual|
- actual.join("\n")
- end
- end
-
- it "has no malformed whitespace", :ruby_repo do
- exempt = /\.gitmodules|\.marshal|fixtures|vendor|ssl_certs|LICENSE/
+ it "has no malformed whitespace" do
+ exempt = /\.gitmodules|fixtures|vendor|LICENSE|vcr_cassettes|rbreadline\.diff|index\.txt$/
error_messages = []
- Dir.chdir(File.expand_path("../..", __FILE__)) do
- `git ls-files -z`.split("\x0").each do |filename|
- next if filename =~ exempt
- error_messages << check_for_tab_characters(filename)
- error_messages << check_for_extra_spaces(filename)
- end
+ 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 "uses double-quotes consistently in specs", :ruby_repo do
- included = /spec/
+ it "has no extraneous quotes" do
+ exempt = /vendor|vcr_cassettes|LICENSE|rbreadline\.diff/
error_messages = []
- Dir.chdir(File.expand_path("../", __FILE__)) do
- `git ls-files -z`.split("\x0").each do |filename|
- next unless filename =~ included
- error_messages << check_for_spec_defs_with_single_quotes(filename)
- end
+ tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ error_messages << check_for_extraneous_quotes(filename)
end
expect(error_messages.compact).to be_well_formed
end
- it "does not include any leftover debugging or development mechanisms", :ruby_repo do
- exempt = %r{quality_spec.rb|support/helpers}
+ it "does not include any unresolved merge conflicts" do
error_messages = []
- Dir.chdir(File.expand_path("../", __FILE__)) do
- `git ls-files -z`.split("\x0").each do |filename|
- next if filename =~ exempt
- error_messages << check_for_debugging_mechanisms(filename)
- end
+ exempt = %r{lock/lockfile_spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser}
+ 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 "does not include any unresolved merge conflicts", :ruby_repo do
+ it "maintains language quality of the documentation" do
error_messages = []
- exempt = %r{lock/lockfile_spec|quality_spec}
- Dir.chdir(File.expand_path("../", __FILE__)) do
- `git ls-files -z`.split("\x0").each do |filename|
- next if filename =~ exempt
- error_messages << check_for_git_merge_conflicts(filename)
- end
- end
- expect(error_messages.compact).to be_well_formed
- end
-
- it "maintains language quality of the documentation", :ruby_repo do
- included = /ronn/
- error_messages = []
- Dir.chdir(File.expand_path("../../man", __FILE__)) do
- `git ls-files -z`.split("\x0").each do |filename|
- next unless filename =~ included
- error_messages << check_for_expendable_words(filename)
- error_messages << check_for_specific_pronouns(filename)
- end
+ man_tracked_files.each do |filename|
+ 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", :ruby_repo do
+ it "maintains language quality of sentences used in source code" do
error_messages = []
- exempt = /vendor/
- Dir.chdir(File.expand_path("../../lib", __FILE__)) do
- `git ls-files -z`.split("\x0").each do |filename|
- next if filename =~ exempt
- error_messages << check_for_expendable_words(filename)
- error_messages << check_for_specific_pronouns(filename)
- end
+ 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", :ruby_repo do
- exemptions = %w(
+ it "documents all used settings" do
+ exemptions = %w[
+ gem.changelog
+ gem.ci
gem.coc
+ gem.linter
gem.mit
+ gem.bundle
+ gem.rubocop
+ gem.test
+ git.allow_insecure
inline
- warned_version
- )
+ trust-policy
+ ]
all_settings = Hash.new {|h, k| h[k] = [] }
- documented_settings = exemptions
+ 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" }
- Dir.chdir(File.expand_path("../../lib", __FILE__)) do
- key_pattern = /([a-z\._-]+)/i
- `git ls-files -z`.split("\x0").each do |filename|
- File.readlines(filename).each_with_index do |line, number|
- line.scan(/Bundler\.settings\[:#{key_pattern}\]/).flatten.each {|s| all_settings[s] << "referenced at `lib/#{filename}:#{number.succ}`" }
- end
+ 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
- documented_settings = File.read("../man/bundle-config.ronn").scan(/^\* `#{key_pattern}`/).flatten
end
+ settings_section = File.read(source_root.join("lib/bundler/man/bundle-config.1.ronn")).split(/^## /).find {|section| section.start_with?("LIST OF AVAILABLE KEYS") }
+ documented_settings = settings_section.scan(/^\* `#{key_pattern}`/).flatten
- documented_settings.each {|s| all_settings.delete(s) }
- exemptions.each {|s| all_settings.delete(s) }
+ 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", :ruby_repo do
- Dir.chdir(root) do
- begin
- gem_command! :build, "bundler.gemspec"
- if Bundler.rubygems.provides?(">= 2.4")
- # older rubygems have weird warnings, and we won't actually be using them
- # to build the gem for releases anyways
- expect(err).to be_empty, "bundler should build as a gem without warnings, but\n#{err}"
- end
- ensure
- # clean up the .gem generated
- FileUtils.rm("bundler-#{Bundler::VERSION}.gem")
- end
+ it "can still be built" do
+ with_built_bundler do |gem_path|
+ expect(File.exist?(gem_path)).to be true
end
end
- it "does not contain any warnings", :ruby_repo do
- Dir.chdir(root.join("lib")) do
- exclusions = %w(
- bundler/capistrano.rb
- bundler/gem_tasks.rb
- bundler/vlad.rb
- )
- lib_files = `git ls-files -z`.split("\x0").grep(/\.rb$/) - exclusions
- lib_files.reject! {|f| f.start_with?("bundler/vendor") }
- lib_files.map! {|f| f.chomp(".rb") }
- sys_exec!("ruby -w -I.") do |input, _, _|
- lib_files.each do |f|
- input.puts "require '#{f}'"
- end
+ it "ships the correct set of files" do
+ git_list = tracked_files.reject {|f| f.start_with?("spec/") }
+
+ gem_list = loaded_gemspec.files
+ gem_list.map! {|f| f.sub(%r{\Aexe/}, "libexec/") } if ruby_core?
+
+ 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 = stdboth.split("\n")
+ # ignore warnings around deprecated Object#=~ method in RubyGems
+ warnings.reject! {|w| w =~ %r{rubygems\/version.rb.*deprecated\ Object#=~} }
- expect(@err.split("\n")).to be_well_formed
- expect(@out.split("\n")).to be_well_formed
+ 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
+
+ # We don't want our artifice code to activate bundler, but it needs to use the
+ # namespaced implementation of `Net::HTTP`. So we duplicate the file in
+ # bundler that loads that.
+ it "keeps vendored_net_http spec code in sync with the lib implementation" do
+ lib_implementation_path = File.join(source_lib_dir, "bundler", "vendored_net_http.rb")
+ expect(File.exist?(lib_implementation_path)).to be_truthy
+ lib_code = File.read(lib_implementation_path)
+
+ spec_implementation_path = File.join(spec_dir, "support", "vendored_net_http.rb")
+ expect(File.exist?(spec_implementation_path)).to be_truthy
+ spec_code = File.read(spec_implementation_path)
+
+ expect(lib_code).to eq(spec_code)
+ end
+
+ private
+
+ def each_line(filename, &block)
+ File.readlines(File.expand_path(filename, source_root), 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
deleted file mode 100644
index 468fa3644c..0000000000
--- a/spec/bundler/realworld/dependency_api_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-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 File.expand_path("../../support/artifice/endpoint_timeout", __FILE__)
- require "thread"
- @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)
- end
-
- after do
- Artifice.deactivate
- @t.kill
- @t.join
- end
-
- it "times out and falls back on the modern index" do
- gemfile <<-G
- source "#{@server_uri}"
- gem "rack"
-
- old_v, $VERBOSE = $VERBOSE, nil
- Bundler::Fetcher.api_timeout = 1
- $VERBOSE = old_v
- G
-
- bundle :install
- 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..0741560395
--- /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 "bundler"
+ require "#{spec_dir}/support/artifice/vcr"
+ require "bundler/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
index 302fd57cf0..391aa0cef6 100644
--- a/spec/bundler/realworld/edgecases_spec.rb
+++ b/spec/bundler/realworld/edgecases_spec.rb
@@ -1,78 +1,47 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "real world edgecases", :realworld => true, :sometimes => true do
+RSpec.describe "real world edgecases", realworld: true do
def rubygems_version(name, requirement)
- require "bundler/source/rubygems/remote"
- require "bundler/fetcher"
- source = Bundler::Source::Rubygems::Remote.new(URI("https://rubygems.org"))
- fetcher = Bundler::Fetcher.new(source)
- index = fetcher.specs([name], nil)
- rubygem = index.search(Gem::Dependency.new(name, requirement)).last
- if rubygem.nil?
- raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
- "Found specs:\n#{index.send(:specs).inspect}"
- end
- "#{name} (#{rubygem.version})"
+ ruby <<-RUBY
+ require "#{spec_dir}/support/artifice/vcr"
+ require "bundler"
+ require "bundler/source/rubygems/remote"
+ require "bundler/fetcher"
+ rubygem = Bundler.ui.silence do
+ remote = Bundler::Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org"))
+ source = Bundler::Source::Rubygems.new
+ fetcher = Bundler::Fetcher.new(remote)
+ index = fetcher.specs([#{name.dump}], source)
+ requirement = Gem::Requirement.create(#{requirement.dump})
+ index.search(#{name.dump}).select {|spec| requirement.satisfied_by?(spec.version) }.last
+ end
+ if rubygem.nil?
+ raise ArgumentError, "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
+ "Found specs:\n\#{index.send(:specs).inspect}"
+ end
+ puts "#{name} (\#{rubygem.version})"
+ RUBY
end
- # there is no rbx-relative-require gem that will install on 1.9
- it "ignores extra gems with bad platforms", :ruby => "~> 1.8.7" do
- gemfile <<-G
- source "https://rubygems.org"
- gem "linecache", "0.46"
- G
- bundle :lock
- expect(err).to lack_errors
- expect(exitstatus).to eq(0) if exitstatus
- end
-
- # https://github.com/bundler/bundler/issues/1202
- it "bundle cache works with rubygems 1.3.7 and pre gems",
- :ruby => "~> 1.8.7", :rubygems => "~> 1.3.7" do
- install_gemfile <<-G
- source "https://rubygems.org"
- gem "rack", "1.3.0.beta2"
- gem "will_paginate", "3.0.pre2"
- G
- bundle :cache
- expect(out).not_to include("Removing outdated .gem files from vendor/cache")
- end
-
- # https://github.com/bundler/bundler/issues/1486
- # this is a hash collision that only manifests on 1.8.7
- it "finds the correct child versions", :ruby => "~> 1.8.7" do
- gemfile <<-G
- source "https://rubygems.org"
-
- gem 'i18n', '~> 0.6.0'
- gem 'activesupport', '~> 3.0.5'
- gem 'activerecord', '~> 3.0.5'
- gem 'builder', '~> 2.1.2'
- G
- bundle :lock
- expect(lockfile).to include("activemodel (3.0.5)")
- end
-
- it "resolves dependencies correctly", :ruby => "1.9.3" do
+ it "resolves dependencies correctly" do
gemfile <<-G
source "https://rubygems.org"
- gem 'rails', '~> 3.0'
+ 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", "~> 3.0"))
+ 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", :ruby => "1.9.3" do
+ it "installs the latest version of gxapi_rails" do
gemfile <<-G
source "https://rubygems.org"
gem "sass-rails"
- gem "rails", "~> 3"
+ 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
@@ -94,289 +63,13 @@ RSpec.describe "real world edgecases", :realworld => true, :sometimes => true do
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", :ruby => "2.1" do
- # from https://github.com/bundler/bundler/issues/5031
-
- 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)
-
- BUNDLED WITH
- 1.13.1
- L
-
- bundle! "lock --update paperclip"
-
- expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0"))
- end
-
- # https://github.com/bundler/bundler/issues/1500
- it "does not fail install because of gem plugins" do
- realworld_system_gems("open_gem --version 1.4.2", "rake --version 0.9.2")
- gemfile <<-G
- source "https://rubygems.org"
-
- gem 'rack', '1.0.1'
- G
-
- bundle "install --path vendor/bundle"
- expect(err).not_to include("Could not find rake")
- expect(err).to lack_errors
- end
-
- it "checks out git repos when the lockfile is corrupted" do
- gemfile <<-G
- source "https://rubygems.org"
-
- gem 'activerecord', :github => 'carlhuda/rails-bundler-test', :branch => 'master'
- gem 'activesupport', :github => 'carlhuda/rails-bundler-test', :branch => 'master'
- gem 'actionpack', :github => 'carlhuda/rails-bundler-test', :branch => 'master'
- G
-
- lockfile <<-L
- GIT
- remote: git://github.com/carlhuda/rails-bundler-test.git
- revision: 369e28a87419565f1940815219ea9200474589d4
- branch: master
- specs:
- actionpack (3.2.2)
- activemodel (= 3.2.2)
- activesupport (= 3.2.2)
- builder (~> 3.0.0)
- erubis (~> 2.7.0)
- journey (~> 1.0.1)
- rack (~> 1.4.0)
- rack-cache (~> 1.2)
- rack-test (~> 0.6.1)
- sprockets (~> 2.1.2)
- activemodel (3.2.2)
- activesupport (= 3.2.2)
- builder (~> 3.0.0)
- activerecord (3.2.2)
- activemodel (= 3.2.2)
- activesupport (= 3.2.2)
- arel (~> 3.0.2)
- tzinfo (~> 0.3.29)
- activesupport (3.2.2)
- i18n (~> 0.6)
- multi_json (~> 1.0)
-
- GIT
- remote: git://github.com/carlhuda/rails-bundler-test.git
- revision: 369e28a87419565f1940815219ea9200474589d4
- branch: master
- specs:
- actionpack (3.2.2)
- activemodel (= 3.2.2)
- activesupport (= 3.2.2)
- builder (~> 3.0.0)
- erubis (~> 2.7.0)
- journey (~> 1.0.1)
- rack (~> 1.4.0)
- rack-cache (~> 1.2)
- rack-test (~> 0.6.1)
- sprockets (~> 2.1.2)
- activemodel (3.2.2)
- activesupport (= 3.2.2)
- builder (~> 3.0.0)
- activerecord (3.2.2)
- activemodel (= 3.2.2)
- activesupport (= 3.2.2)
- arel (~> 3.0.2)
- tzinfo (~> 0.3.29)
- activesupport (3.2.2)
- i18n (~> 0.6)
- multi_json (~> 1.0)
-
- GIT
- remote: git://github.com/carlhuda/rails-bundler-test.git
- revision: 369e28a87419565f1940815219ea9200474589d4
- branch: master
- specs:
- actionpack (3.2.2)
- activemodel (= 3.2.2)
- activesupport (= 3.2.2)
- builder (~> 3.0.0)
- erubis (~> 2.7.0)
- journey (~> 1.0.1)
- rack (~> 1.4.0)
- rack-cache (~> 1.2)
- rack-test (~> 0.6.1)
- sprockets (~> 2.1.2)
- activemodel (3.2.2)
- activesupport (= 3.2.2)
- builder (~> 3.0.0)
- activerecord (3.2.2)
- activemodel (= 3.2.2)
- activesupport (= 3.2.2)
- arel (~> 3.0.2)
- tzinfo (~> 0.3.29)
- activesupport (3.2.2)
- i18n (~> 0.6)
- multi_json (~> 1.0)
-
- GEM
- remote: https://rubygems.org/
- specs:
- arel (3.0.2)
- builder (3.0.0)
- erubis (2.7.0)
- hike (1.2.1)
- i18n (0.6.0)
- journey (1.0.3)
- multi_json (1.1.0)
- rack (1.4.1)
- rack-cache (1.2)
- rack (>= 0.4)
- rack-test (0.6.1)
- rack (>= 1.0)
- sprockets (2.1.2)
- hike (~> 1.2)
- rack (~> 1.0)
- tilt (~> 1.1, != 1.3.0)
- tilt (1.3.3)
- tzinfo (0.3.32)
-
- PLATFORMS
- ruby
-
- DEPENDENCIES
- actionpack!
- activerecord!
- activesupport!
- L
-
- bundle :lock
- expect(err).to eq("")
- expect(exitstatus).to eq(0) if exitstatus
- end
-
- it "outputs a helpful error message when gems have invalid gemspecs" do
- install_gemfile <<-G, :standalone => true
+ it "outputs a helpful warning when gems have a gemspec with invalid `require_paths`" 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(out).to include("You have one or more invalid gemspecs that need to be fixed.")
- expect(out).to include("resque-scheduler 2.2.0 has an invalid gemspec")
+ 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
end
diff --git a/spec/bundler/realworld/ffi_spec.rb b/spec/bundler/realworld/ffi_spec.rb
new file mode 100644
index 0000000000..bede372b41
--- /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
+
+ in_bundled_app "gcc -g -o libfoo.so -shared -fpic libfoo.c"
+
+ install_gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'ffi', force_ruby_platform: true
+ G
+
+ bundle "exec ruby foo.rb"
+
+ expect(out).to eq("OK")
+ end
+end
diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile b/spec/bundler/realworld/fixtures/tapioca/Gemfile
new file mode 100644
index 0000000000..447d715706
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gem "tapioca"
diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock
new file mode 100644
index 0000000000..c2df2f9229
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock
@@ -0,0 +1,49 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ erubi (1.13.1)
+ netrc (0.11.0)
+ parallel (1.26.3)
+ prism (1.3.0)
+ rbi (0.2.2)
+ prism (~> 1.0)
+ sorbet-runtime (>= 0.5.9204)
+ sorbet (0.5.11725)
+ sorbet-static (= 0.5.11725)
+ sorbet-runtime (0.5.11725)
+ sorbet-static (0.5.11725-aarch64-linux)
+ sorbet-static (0.5.11725-universal-darwin)
+ sorbet-static (0.5.11725-x86_64-linux)
+ sorbet-static-and-runtime (0.5.11725)
+ sorbet (= 0.5.11725)
+ sorbet-runtime (= 0.5.11725)
+ spoom (1.5.0)
+ erubi (>= 1.10.0)
+ prism (>= 0.28.0)
+ sorbet-static-and-runtime (>= 0.5.10187)
+ thor (>= 0.19.2)
+ tapioca (0.16.6)
+ bundler (>= 2.2.25)
+ netrc (>= 0.11.0)
+ parallel (>= 1.21.0)
+ rbi (~> 0.2)
+ sorbet-static-and-runtime (>= 0.5.11087)
+ spoom (>= 1.2.0)
+ thor (>= 1.2.0)
+ yard-sorbet
+ thor (1.4.0)
+ yard (0.9.42)
+ yard-sorbet (0.9.0)
+ sorbet-runtime
+ yard
+
+PLATFORMS
+ aarch64-linux
+ universal-darwin
+ x86_64-linux
+
+DEPENDENCIES
+ tapioca
+
+BUNDLED WITH
+ 4.1.0.dev
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..5687bbd975
--- /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", "~> 10.0"
+gem "warbler", "~> 2.1"
diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock
new file mode 100644
index 0000000000..05f3bc4e3f
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock
@@ -0,0 +1,35 @@
+PATH
+ remote: demo
+ specs:
+ demo (1.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ jruby-jars (10.0.0.1)
+ jruby-rack (1.2.7)
+ ostruct (0.6.3)
+ rake (13.3.0)
+ rexml (3.4.2)
+ rubyzip (3.3.0)
+ warbler (2.1.0)
+ jruby-jars (>= 9.4, < 10.1)
+ jruby-rack (>= 1.2.3, < 1.3)
+ ostruct (~> 0.6.2)
+ rake (~> 13.0, >= 13.0.3)
+ rexml (~> 3.0)
+ rubyzip (>= 3.0.0)
+
+PLATFORMS
+ arm64-darwin
+ java
+ ruby
+ universal-java
+
+DEPENDENCIES
+ demo!
+ jruby-jars (~> 10.0)
+ warbler (~> 2.1)
+
+BUNDLED WITH
+ 4.1.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
deleted file mode 100644
index ba888d43bd..0000000000
--- a/spec/bundler/realworld/gemfile_source_header_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-require "thread"
-
-RSpec.describe "fetching dependencies with a mirrored source", :realworld => true, :rubygems => ">= 2.0" do
- let(:mirror) { "https://server.example.org" }
- let(:original) { "http://127.0.0.1:#{@port}" }
-
- before do
- setup_server
- bundle "config --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
-
- 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 File.expand_path("../../support/artifice/endpoint_mirror_source", __FILE__)
-
- @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..9eff74f1c9
--- /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
deleted file mode 100644
index 93dca0c173..0000000000
--- a/spec/bundler/realworld/mirror_probe_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-require "thread"
-
-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
-
- 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
-
- 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
-
- expect(out).to include("Fetching source index from #{mirror}")
- expect(out).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
- expect(out).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
- expect(out).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
- expect(out).to include("Could not fetch specs from #{mirror}")
- end
-
- it "prints each error and warning on a new line" do
- gemfile <<-G
- source "#{original}"
- gem 'weakling'
- G
-
- bundle :install
-
- expect(out).to eq "Fetching source index from #{mirror}/
-
-Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/
-Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/
-Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/
-Could not fetch specs from #{mirror}/"
- 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
-
- expect(out).to include("Fetching source index from #{mirror}")
- expect(out).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
- expect(out).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
- expect(out).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
- expect(out).to include("Could not fetch specs from #{mirror}")
- end
- end
-
- def setup_server
- @server_port = find_unused_port
- @server_uri = "http://#{host}:#{@server_port}"
-
- require File.expand_path("../../support/artifice/endpoint", __FILE__)
-
- @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
index 6950bead19..b57fdfd0ee 100644
--- a/spec/bundler/realworld/parallel_spec.rb
+++ b/spec/bundler/realworld/parallel_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require "spec_helper"
-RSpec.describe "parallel", :realworld => true, :sometimes => true do
+RSpec.describe "parallel", realworld: true do
it "installs" do
gemfile <<-G
source "https://rubygems.org"
@@ -10,22 +9,15 @@ RSpec.describe "parallel", :realworld => true, :sometimes => true do
gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+
G
- bundle :install, :jobs => 4, :env => { "DEBUG" => "1" }
+ bundle :install, jobs: 4, env: { "DEBUG" => "1" }
- if Bundler.rubygems.provides?(">= 2.1.0")
- expect(out).to match(/[1-3]: /)
- else
- expect(out).to include("is not threadsafe")
- end
+ expect(out).to match(/[1-3]: /)
- bundle "show activesupport"
+ bundle "info activesupport --path"
expect(out).to match(/activesupport/)
- bundle "show faker"
+ bundle "info faker --path"
expect(out).to match(/faker/)
-
- bundle "config jobs"
- expect(out).to match(/: "4"/)
end
it "updates" do
@@ -42,33 +34,26 @@ RSpec.describe "parallel", :realworld => true, :sometimes => true do
gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+
G
- bundle :update, :jobs => 4, :env => { "DEBUG" => "1" }
+ bundle :update, jobs: 4, env: { "DEBUG" => "1" }, all: true
- if Bundler.rubygems.provides?(">= 2.1.0")
- expect(out).to match(/[1-3]: /)
- else
- expect(out).to include("is not threadsafe")
- end
+ expect(out).to match(/[1-3]: /)
- bundle "show activesupport"
+ bundle "info activesupport --path"
expect(out).to match(/activesupport-3\.2\.\d+/)
- bundle "show faker"
+ bundle "info faker --path"
expect(out).to match(/faker/)
-
- bundle "config jobs"
- expect(out).to match(/: "4"/)
end
it "works with --standalone" do
- gemfile <<-G, :standalone => true
+ gemfile <<-G
source "https://rubygems.org"
gem "diff-lcs"
G
- bundle :install, :standalone => true, :jobs => 4
+ bundle :install, standalone: true, jobs: 4
- ruby <<-RUBY, :no_lib => true
+ ruby <<-RUBY
$:.unshift File.expand_path("bundle")
require "bundler/setup"
diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb
new file mode 100644
index 0000000000..5d36ba7455
--- /dev/null
+++ b/spec/bundler/realworld/slow_perf_spec.rb
@@ -0,0 +1,35 @@
+# 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
+
+ bundle "lock", env: { "DEBUG_RESOLVER" => "1" }
+ expect(out).to include("Solution found after 1 attempts")
+ 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
+
+ bundle "lock", env: { "DEBUG_RESOLVER" => "1" }
+ expect(out).to include("Solution found after 2 attempts")
+ end
+end
diff --git a/spec/bundler/resolver/basic_spec.rb b/spec/bundler/resolver/basic_spec.rb
index 9e93847ab5..185df1b1c7 100644
--- a/spec/bundler/resolver/basic_spec.rb
+++ b/spec/bundler/resolver/basic_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "Resolving" do
before :each do
@@ -7,33 +6,47 @@ RSpec.describe "Resolving" do
end
it "resolves a single gem" do
- dep "rack"
+ dep "myrack"
- should_resolve_as %w(rack-1.1)
+ should_resolve_as %w[myrack-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)
+ should_resolve_as %w[actionpack-2.3.5 activesupport-2.3.5 myrack-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)
+ 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)
+ 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)
+ 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
@@ -42,20 +55,70 @@ RSpec.describe "Resolving" do
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)
+ 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 myrack-1.1 myrack-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
+ @index = a_unresolvable_child_index
dep "chef_app_error"
expect do
resolve
- end.to raise_error(Bundler::VersionConflict)
+ end.to raise_error(Bundler::SolveFailure)
+ end
+
+ it "does not try to re-resolve including prereleases if gems involved don't have prereleases" do
+ @index = a_unresolvable_child_index
+ dep "chef_app_error"
+ expect(Bundler.ui).not_to receive(:debug).with("Retrying resolution...", any_args)
+ 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) }
+ %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
@@ -64,14 +127,15 @@ RSpec.describe "Resolving" do
dep "c"
expect do
resolve
- end.to raise_error(Bundler::VersionConflict, <<-E.strip)
-Bundler could not find compatible versions for gem "a":
- In Gemfile:
- b was resolved to 1.0, which depends on
- a (>= 2)
-
- c was resolved to 1.0, which depends on
- a (< 1)
+ 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
@@ -80,7 +144,7 @@ Bundler could not find compatible versions for gem "a":
dep "circular_app"
expect do
- resolve
+ Bundler::SpecSet.new(resolve).sort
end.to raise_error(Bundler::CyclicDependencyError, /please remove either gem 'bar' or gem 'foo'/i)
end
@@ -88,14 +152,14 @@ Bundler could not find compatible versions for gem "a":
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)
+ 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)
+ should_resolve_and_include %w[foo-3.0.5]
end
it "takes into account required_ruby_version" do
@@ -115,17 +179,12 @@ Bundler could not find compatible versions for gem "a":
s.required_ruby_version = "~> 2.0.0"
end
- gem "ruby\0", "1.8.7"
+ gem "Ruby\0", "1.8.7"
end
dep "foo"
- dep "ruby\0", "1.8.7"
+ dep "Ruby\0", "1.8.7"
- deps = []
- @deps.each do |d|
- deps << Bundler::DepProxy.new(d, "ruby")
- end
-
- should_resolve_and_include %w(foo-1.0.0 bar-1.0.0), [{}, []]
+ should_resolve_and_include %w[foo-1.0.0 bar-1.0.0]
end
context "conservative" do
@@ -139,7 +198,7 @@ Bundler could not find compatible versions for gem "a":
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)
+ gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
end
dep "foo"
@@ -147,23 +206,23 @@ Bundler could not find compatible versions for gem "a":
@base = Bundler::SpecSet.new([])
# locked represents versions in lockfile
- @locked = locked(%w(foo 1.4.3), %w(bar 2.0.3))
+ @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)
+ should_conservative_resolve_and_include :patch, true, %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)
+ should_conservative_resolve_and_include [:patch, :strict], true, %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))
+ @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)
+ 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
@@ -172,45 +231,45 @@ Bundler could not find compatible versions for gem "a":
# 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.
- # http://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)
+ # 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)
+ @base = Bundler::SpecSet.new([Bundler::LazySpecification.new("bar", Gem::Version.new("2.0.3"), nil)])
+ 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)
+ 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)
+ 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)
+ should_conservative_resolve_and_include :minor, true, %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)
+ should_conservative_resolve_and_include [:minor, :strict], true, %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)
+ should_conservative_resolve_and_include :major, true, %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)
+ should_conservative_resolve_and_include [:major, :strict], true, %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
@@ -221,7 +280,7 @@ Bundler could not find compatible versions for gem "a":
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)
+ gem "bar", %w[2.0.5 2.1.1 2.2.3]
end
dep "foo"
@@ -229,30 +288,135 @@ Bundler could not find compatible versions for gem "a":
@base = Bundler::SpecSet.new([])
# locked represents versions in lockfile
- @locked = locked(%w(foo 1.4.3), %w(bar 2.2.3))
+ @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)
+ should_conservative_resolve_and_include :patch, true, %w[foo-1.4.4 bar-2.1.1]
end
it "cannot revert to a previous version in strict mode level patch" do
- # the strict option removes the version required to match, so a version conflict results
- expect do
- should_conservative_resolve_and_include [:patch, :strict], [], %w(foo-1.4.3 bar-2.1.1)
- end.to raise_error Bundler::VersionConflict, /#{Regexp.escape("Could not find gem 'bar (~> 2.1.0)'")}/
+ # fall back to the locked resolution since strict means we can't regress either version
+ should_conservative_resolve_and_include [:patch, :strict], true, %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)
+ should_conservative_resolve_and_include :minor, true, %w[foo-1.5.0 bar-2.0.5]
end
it "cannot revert to a previous version in strict mode level minor" do
- # the strict option removes the version required to match, so a version conflict results
- expect do
- should_conservative_resolve_and_include [:minor, :strict], [], %w(foo-1.4.3 bar-2.1.1)
- end.to raise_error Bundler::VersionConflict, /#{Regexp.escape("Could not find gem 'bar (~> 2.0.0)'")}/
+ # fall back to the locked resolution since strict means we can't regress either version
+ should_conservative_resolve_and_include [:minor, :strict], true, %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 "myrack", "3.0.0"
+
+ gem "standalone_migrations", "7.1.0" do
+ dep "myrack", "~> 2.0"
+ end
+
+ gem "standalone_migrations", "2.0.4" do
+ dep "standalone_migrations", ">= 0"
+ end
+
+ gem "standalone_migrations", "1.0.13" do
+ dep "myrack", ">= 0"
+ end
+ end
+
+ dep "myrack", "~> 3.0"
+ dep "standalone_migrations"
+
+ should_resolve_as %w[myrack-3.0.0 standalone_migrations-2.0.4]
+ end
+
+ it "ignores versions that incorrectly depend on themselves" do
+ @index = build_index do
+ gem "myrack", "3.0.0"
+
+ gem "standalone_migrations", "7.1.0" do
+ dep "myrack", "~> 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 "myrack", ">= 0"
+ end
+ end
+
+ dep "myrack", "~> 3.0"
+ dep "standalone_migrations"
+
+ should_resolve_as %w[myrack-3.0.0 standalone_migrations-1.0.13]
+ end
+
+ it "does not ignore versions that incorrectly depend on themselves when dependency_api is not available" do
+ @index = build_index do
+ gem "myrack", "3.0.0"
+
+ gem "standalone_migrations", "7.1.0" do
+ dep "myrack", "~> 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 "myrack", ">= 0"
end
end
+
+ dep "myrack", "~> 3.0"
+ dep "standalone_migrations"
+
+ should_resolve_without_dependency_api %w[myrack-3.0.0 standalone_migrations-2.0.4]
+ end
+
+ it "resolves fine cases that need joining unbounded disjoint ranges" do
+ @index = build_index do
+ gem "inspec", "5.22.3" do
+ dep "ruby", ">= 3.2.2"
+ dep "train-kubernetes", ">= 0.1.7"
+ end
+
+ gem "ruby", "3.2.2"
+
+ gem "train-kubernetes", "0.1.12" do
+ dep "k8s-ruby", ">= 0.14.0"
+ end
+
+ gem "train-kubernetes", "0.1.10" do
+ dep "k8s-ruby", "= 0.10.5"
+ end
+
+ gem "train-kubernetes", "0.1.7" do
+ dep "k8s-ruby", ">= 0.10.5"
+ end
+
+ gem "k8s-ruby", "0.10.5" do
+ dep "ruby","< 3.2.2"
+ end
+
+ gem "k8s-ruby", "0.11.0" do
+ dep "ruby", ">= 3.2.2"
+ end
+
+ gem "k8s-ruby", "0.14.0" do
+ dep "ruby", "< 3.2.2"
+ end
+ end
+
+ dep "inspec", "5.22.3"
+ dep "ruby", "3.2.2"
+
+ should_resolve_as %w[inspec-5.22.3 ruby-3.2.2 train-kubernetes-0.1.7 k8s-ruby-0.11.0]
end
end
diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb
index 90d6f637ce..a1d095d024 100644
--- a/spec/bundler/resolver/platform_spec.rb
+++ b/spec/bundler/resolver/platform_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "Resolving platform craziness" do
describe "with cross-platform gems" do
@@ -11,28 +10,339 @@ RSpec.describe "Resolving platform craziness" do
dep "nokogiri"
platforms "ruby", "java"
- should_resolve_as %w(nokogiri-1.4.2 nokogiri-1.4.2-java weakling-0.0.3)
+ 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)
+ 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)
+ 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-mingw-ucrt"
+ gem "foo", "1.1.0"
+ end
+ dep "foo"
+ platforms "x64-mingw-ucrt"
+
+ 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-mingw-ucrt" do
+ dep "bar", "< 1"
+ end
+ end
+ dep "foo"
+ platforms "x64-mingw-ucrt"
+
+ 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-mingw-ucrt"
+ end
+ dep "foo"
+ platforms "x64-mingw-ucrt"
+
+ should_resolve_as %w[foo-1.0.0-x64-mingw-ucrt]
+ end
+
+ describe "on a linux platform" 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-mingw-ucrt"
+ gem "foo", "1.1.0"
+ gem "foo", "1.1.0", "x64-mingw-ucrt" 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-mingw-ucrt"
+ 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-mingw-ucrt"
+ gem "foo", "1.1.0" do |s|
+ s.required_ruby_version = [">= 2.0"]
+ end
+ gem "foo", "1.1.0", "x64-mingw-ucrt" 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-mingw-ucrt"
+
+ 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-mingw-ucrt"
+ gem "foo", "1.1.0" do |s|
+ s.required_ruby_version = [">= 2.0"]
+ end
+ gem "foo", "1.1.0", "x64-mingw-ucrt" 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-mingw-ucrt"
+
+ 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" do |platform|
+ platforms "mingw32 mswin32 x64-mingw-ucrt" do |platform|
gem "thin", "1.2.7", platform
end
gem "win32-api", "1.5.1", "universal-mingw32"
@@ -43,32 +353,44 @@ RSpec.describe "Resolving platform craziness" do
# win32 is hardcoded to get CPU x86 in rubygems
platforms "mswin32"
dep "thin"
- should_resolve_as %w(thin-1.2.7-x86-mswin32)
+ 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)
+ should_resolve_as %w[thin-1.2.7-mingw32]
end
- it "finds x64-mingw gems" do
- platforms "x64-mingw32"
+ it "finds x64-mingw-ucrt gems" do
+ platforms "x64-mingw-ucrt"
dep "thin"
- should_resolve_as %w(thin-1.2.7-x64-mingw32)
+ should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt]
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)
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
end
it "finds universal-mingw gems on x64-mingw" do
- platform "x64-mingw32"
+ platform "x64-mingw-ucrt"
+ dep "win32-api"
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
+ end
+
+ 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
+
+ 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)
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
end
end
@@ -87,15 +409,15 @@ RSpec.describe "Resolving platform craziness" do
dep "baz", " ~> 1.1.0"
end
- gem "baz", %w(1.0.0 1.1.0 1.2.0)
+ gem "baz", %w[1.0.0 1.1.0 1.2.0]
end
end
- it "reports on the conflict" do
+ it "takes the ruby version as fallback" do
platforms "ruby", "java"
dep "foo"
- should_conflict_on "baz"
+ 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/env_helpers_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb
new file mode 100644
index 0000000000..c4ebdd1fd2
--- /dev/null
+++ b/spec/bundler/runtime/env_helpers_spec.rb
@@ -0,0 +1,236 @@
+# frozen_string_literal: true
+
+RSpec.describe "env helpers" do
+ def bundle_exec_ruby(args, options = {})
+ build_bundler_context options.dup
+ bundle "exec '#{Gem.ruby}' #{args}", options
+ end
+
+ def build_bundler_context(options = {})
+ bundle "config set path vendor/bundle", options.dup
+ gemfile "source 'https://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(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(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
+ original = ruby('puts ENV.to_a.map {|e| e.join("=") }.sort.join("\n")', artifice: "fail")
+ 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"), artifice: "fail"
+ expect(out).to eq original
+ end
+ end
+
+ describe "Bundler.unbundled_env" do
+ it "should delete BUNDLE_PATH" do
+ create_file("source.rb", <<-RUBY)
+ print Bundler.unbundled_env.has_key?('BUNDLE_PATH')
+ RUBY
+ ENV["BUNDLE_PATH"] = "./foo"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(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 Bundler.unbundled_env['RUBYOPT']
+ RUBY
+ setup_require = "-r#{lib_dir}/bundler/setup"
+ ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 #{setup_require} #{ENV["RUBYOPT"]}"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(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 Bundler.unbundled_env['RUBYOPT']
+ RUBY
+ ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 -rbundler/setup #{ENV["RUBYOPT"]}"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(stdboth).not_to include("-rbundler/setup")
+ end
+
+ it "should delete BUNDLER_SETUP even if it was present in original env" do
+ create_file("source.rb", <<-RUBY)
+ print Bundler.unbundled_env.has_key?('BUNDLER_SETUP')
+ RUBY
+ ENV["BUNDLER_ORIG_BUNDLER_SETUP"] = system_gem_path("gems/bundler-#{Bundler::VERSION}/lib/bundler/setup").to_s
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(stdboth).to include "false"
+ end
+
+ it "should restore RUBYLIB", :ruby_repo do
+ create_file("source.rb", <<-RUBY)
+ print Bundler.unbundled_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(stdboth).to include("/foo-original")
+ end
+
+ it "should restore the original MANPATH" do
+ create_file("source.rb", <<-RUBY)
+ print Bundler.unbundled_env['MANPATH']
+ RUBY
+ ENV["MANPATH"] = "/foo"
+ ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(stdboth).to include("/foo-original")
+ end
+ 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 { ENV.to_hash }
+ 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_unbundled_env" do
+ it "should set ENV to unbundled_env in the block" do
+ expected = Bundler.unbundled_env
+ actual = Bundler.with_unbundled_env { ENV.to_hash }
+ 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.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.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_unbundled_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/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb
index ff27d0b415..89cee21b00 100644
--- a/spec/bundler/runtime/executable_spec.rb
+++ b/spec/bundler/runtime/executable_spec.rb
@@ -1,149 +1,141 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "Running bin/* commands" do
before :each do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
G
end
it "runs the bundled command when in the bundle" do
- bundle "install --binstubs"
+ bundle "binstubs myrack"
- build_gem "rack", "2.0", :to_system => true do |s|
- s.executables = "rackup"
+ build_gem "myrack", "2.0", to_system: true do |s|
+ s.executables = "myrackup"
end
- gembin "rackup"
+ gembin "myrackup"
expect(out).to eq("1.0.0")
end
- it "allows the location of the gem stubs to be specified" do
- bundle "install --binstubs gbin"
+ it "allows the location of the gem stubs to be configured" do
+ bundle_config "bin gbin"
+ bundle "binstubs myrack"
expect(bundled_app("bin")).not_to exist
- expect(bundled_app("gbin/rackup")).to exist
+ expect(bundled_app("gbin/myrackup")).to exist
- gembin bundled_app("gbin/rackup")
+ gembin bundled_app("gbin/myrackup")
expect(out).to eq("1.0.0")
end
it "allows absolute paths as a specification of where to install bin stubs" do
- bundle "install --binstubs #{tmp}/bin"
+ bundle_config "bin #{tmp("bin")}"
+ bundle "binstubs myrack"
- gembin tmp("bin/rackup")
+ gembin tmp("bin/myrackup")
expect(out).to eq("1.0.0")
end
it "uses the default ruby install name when shebang is not specified" do
- bundle "install --binstubs"
- expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}\n")
+ bundle "binstubs myrack"
+ expect(File.readlines(bundled_app("bin/myrackup")).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 "install --binstubs --shebang ruby-foo"
- expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env ruby-foo\n")
+ bundle "binstubs myrack", shebang: "ruby-foo"
+ expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env ruby-foo\n")
end
it "runs the bundled command when out of the bundle" do
- bundle "install --binstubs"
+ bundle "binstubs myrack"
- build_gem "rack", "2.0", :to_system => true do |s|
- s.executables = "rackup"
+ build_gem "myrack", "2.0", to_system: true do |s|
+ s.executables = "myrackup"
end
- Dir.chdir(tmp) do
- gembin "rackup"
- expect(out).to eq("1.0.0")
- end
+ gembin "myrackup", 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"
+ build_lib "myrack", path: lib_path("myrack") do |s|
+ s.executables = "myrackup"
end
gemfile <<-G
- gem "rack", :path => "#{lib_path("rack")}"
+ source "https://gem.repo1"
+ gem "myrack", :path => "#{lib_path("myrack")}"
G
- bundle "install --binstubs"
+ bundle "binstubs myrack"
- build_gem "rack", "2.0", :to_system => true do |s|
- s.executables = "rackup"
+ build_gem "myrack", "2.0", to_system: true do |s|
+ s.executables = "myrackup"
end
- gembin "rackup"
+ gembin "myrackup"
expect(out).to eq("1.0")
end
- it "don't bundle da bundla" do
- build_gem "bundler", Bundler::VERSION, :to_system => true do |s|
- s.executables = "bundle"
- end
-
+ it "does not create a bundle binstub" do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "bundler"
G
- bundle "install --binstubs"
+ bundle "binstubs bundler"
expect(bundled_app("bin/bundle")).not_to exist
+
+ expect(err).to include("Bundler itself does not use binstubs because its version is selected by RubyGems")
end
it "does not generate bin stubs if the option was not specified" do
bundle "install"
- expect(bundled_app("bin/rackup")).not_to exist
+ expect(bundled_app("bin/myrackup")).not_to exist
end
- it "allows you to stop installing binstubs" do
- bundle "install --binstubs bin/"
- bundled_app("bin/rackup").rmtree
- bundle "install --binstubs \"\""
+ it "rewrites bins on binstubs with --force option" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ create_file("bin/myrackup", "OMG")
- expect(bundled_app("bin/rackup")).not_to exist
+ bundle "binstubs myrack", { force: true }
- bundle "config bin"
- expect(out).to include("You have not configured a value for `bin`")
+ expect(bundled_app("bin/myrackup").read.strip).to_not eq("OMG")
end
- it "remembers that the option was specified" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "activesupport"
- G
+ it "use BUNDLE_GEMFILE gemfile for binstub" do
+ # context with bin/bundler w/ default Gemfile
+ bundle "binstubs bundler"
- bundle "install --binstubs"
+ # generate other Gemfile with executable gem
+ build_repo2 do
+ build_gem("bindir") {|s| s.executables = "foo" }
+ end
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "activesupport"
- gem "rack"
+ gemfile("OtherGemfile", <<-G)
+ source "https://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"
- expect(bundled_app("bin/rackup")).to exist
- end
-
- it "rewrites bins on --binstubs (to maintain backwards compatibility)" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
-
- bundle "install --binstubs bin/"
+ # remove user settings
+ ENV["BUNDLE_GEMFILE"] = nil
- File.open(bundled_app("bin/rackup"), "wb") do |file|
- file.print "OMG"
- end
+ # run binstub for non default Gemfile
+ gembin "foo"
- bundle "install"
-
- expect(bundled_app("bin/rackup").read).to_not eq("OMG")
+ 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
index 7cb0f32c0c..b855142e60 100644
--- a/spec/bundler/runtime/gem_tasks_spec.rb
+++ b/spec/bundler/runtime/gem_tasks_spec.rb
@@ -1,29 +1,74 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "require 'bundler/gem_tasks'" do
- before :each do
+ let(:define_local_gem_using_gem_tasks) 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
- $:.unshift("#{bundler_path}")
require "bundler/gem_tasks"
RAKEFILE
end
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gem "rake"
+ G
end
- it "includes the relevant tasks" do
- with_gem_path_as(Spec::Path.base_system_gems.to_s) do
- sys_exec "#{rake} -T"
+ let(:define_local_gem_with_extensions_using_gem_tasks_and_gemspec_dsl) 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"
+ s.extensions = "ext/extconf.rb"
+ end
+ GEMSPEC
+ end
+
+ bundled_app("Rakefile").open("w") do |f|
+ f.write <<-RAKEFILE
+ require "bundler/gem_tasks"
+ RAKEFILE
+ end
+
+ Dir.mkdir bundled_app("ext")
+
+ bundled_app("ext/extconf.rb").open("w") do |f|
+ f.write <<-EXTCONF
+ require "mkmf"
+ File.write("Makefile", dummy_makefile($srcdir).join)
+ EXTCONF
end
- expect(err).to eq("")
+ install_gemfile <<-G
+ source "https://gem.repo1"
+
+ gemspec
+
+ gem "rake"
+ G
+ end
+
+ it "includes the relevant tasks" do
+ define_local_gem_using_gem_tasks
+
+ in_bundled_app "rake -T"
+
+ expect(err).to be_empty
expected_tasks = [
"rake build",
"rake clean",
@@ -33,11 +78,81 @@ RSpec.describe "require 'bundler/gem_tasks'" do
]
tasks = out.lines.to_a.map {|s| s.split("#").first.strip }
expect(tasks & expected_tasks).to eq(expected_tasks)
- expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "defines a working `rake install` task", :ruby_repo do
+ define_local_gem_using_gem_tasks
+
+ in_bundled_app "rake install"
+
+ expect(err).to be_empty
+
+ bundle "exec rake install"
+
+ expect(err).to be_empty
+ end
+
+ it "defines a working `rake install` task for local gems with extensions", :ruby_repo do
+ define_local_gem_with_extensions_using_gem_tasks_and_gemspec_dsl
+
+ bundle "exec rake install"
+
+ expect(err).to be_empty
+ end
+
+ context "rake build when path has spaces", :ruby_repo do
+ before do
+ define_local_gem_using_gem_tasks
+
+ spaced_bundled_app = tmp("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
+ define_local_gem_using_gem_tasks
+
+ bracketed_bundled_app = tmp("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
+ define_local_gem_using_gem_tasks
+
+ bundle_config "path vendor/bundle"
+ end
+
+ it "works", :ruby_repo do
+ install_gemfile <<-G
+ source "https://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
- require "bundler/gem_tasks"
- expect(CLOBBER).to include("pkg")
+ define_local_gem_using_gem_tasks
+
+ in_bundled_app %(rake -e 'load "Rakefile"; puts CLOBBER.inspect')
+
+ expect(out).to eq '["pkg"]'
end
end
diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb
index e816799d08..c6f9bbdbd7 100644
--- a/spec/bundler/runtime/inline_spec.rb
+++ b/spec/bundler/runtime/inline_spec.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundler/inline#gemfile" do
def script(code, options = {})
- requires = ["bundler/inline"]
- requires.unshift File.expand_path("../../support/artifice/" + options.delete(:artifice) + ".rb", __FILE__) if options.key?(:artifice)
- requires = requires.map {|r| "require '#{r}'" }.join("\n")
- @out = ruby("#{requires}\n\n" + code, options)
+ options[:artifice] ||= "compact_index"
+ options[:env] ||= { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ ruby("require 'bundler/inline'\n\n" + code, options)
end
before :each do
@@ -29,7 +27,7 @@ RSpec.describe "bundler/inline#gemfile" do
s.write "lib/four.rb", "puts 'four'"
end
- build_lib "five", "1.0.0", :no_default => true do |s|
+ build_lib "five", "1.0.0", no_default: true do |s|
s.write "lib/mofive.rb", "puts 'five'"
end
@@ -44,27 +42,26 @@ RSpec.describe "bundler/inline#gemfile" do
build_lib "eight", "1.0.0" do |s|
s.write "lib/eight.rb", "puts 'eight'"
end
-
- build_lib "four", "1.0.0" do |s|
- s.write "lib/four.rb", "puts 'four'"
- end
end
it "requires the gems" do
script <<-RUBY
gemfile do
- path "#{lib_path}"
- gem "two"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "two"
+ end
end
RUBY
expect(out).to eq("two")
- expect(exitstatus).to be_zero if exitstatus
- script <<-RUBY
+ script <<-RUBY, raise_on_error: false
gemfile do
- path "#{lib_path}"
- gem "eleven"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "eleven"
+ end
end
puts "success"
@@ -75,48 +72,60 @@ RSpec.describe "bundler/inline#gemfile" do
script <<-RUBY
gemfile(true) do
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
end
RUBY
- expect(out).to include("Rack's post install message")
- expect(exitstatus).to be_zero if exitstatus
+ expect(out).to include("Myrack's post install message")
- script <<-RUBY, :artifice => "endpoint"
+ script <<-RUBY, artifice: "endpoint"
gemfile(true) do
- source "https://notaserver.com"
+ source "https://notaserver.test"
gem "activesupport", :require => true
end
RUBY
expect(out).to include("Installing activesupport")
- err.gsub! %r{.*lib/sinatra/base\.rb:\d+: warning: constant ::Fixnum is deprecated$}, ""
- err.strip!
- expect(err).to lack_errors
- expect(exitstatus).to be_zero if exitstatus
+ err_lines = err.split("\n")
+ err_lines.reject! {|line| line =~ /\.rb:\d+: warning: / }
+ expect(err_lines).to be_empty
end
it "lets me use my own ui object" do
- script <<-RUBY, :artifice => "endpoint"
+ script <<-RUBY, artifice: "endpoint"
require 'bundler'
- class MyBundlerUI < Bundler::UI::Silent
+ class MyBundlerUI < Bundler::UI::Shell
def confirm(msg, newline = nil)
puts "CONFIRMED!"
end
end
- gemfile(true, :ui => MyBundlerUI.new) do
- source "https://notaserver.com"
+ my_ui = MyBundlerUI.new
+ my_ui.level = "confirm"
+ gemfile(true, :ui => my_ui) do
+ source "https://notaserver.test"
gem "activesupport", :require => true
end
RUBY
expect(out).to eq("CONFIRMED!\nCONFIRMED!")
- expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "has an option for quiet installation" do
+ script <<-RUBY, artifice: "endpoint"
+ require 'bundler/inline'
+
+ gemfile(true, :quiet => true) do
+ source "https://notaserver.test"
+ gem "activesupport", :require => true
+ end
+ RUBY
+
+ expect(out).to be_empty
end
it "raises an exception if passed unknown arguments" do
- script <<-RUBY
+ script <<-RUBY, raise_on_error: false
gemfile(true, :arglebargle => true) do
path "#{lib_path}"
gem "two"
@@ -133,29 +142,77 @@ RSpec.describe "bundler/inline#gemfile" do
require 'bundler'
options = { :ui => Bundler::UI::Shell.new }
gemfile(false, options) do
- path "#{lib_path}"
- gem "two"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "two"
+ end
end
puts "OKAY" if options.key?(:ui)
RUBY
expect(out).to match("OKAY")
- expect(exitstatus).to be_zero if exitstatus
end
it "installs quietly if necessary when the install option is not set" do
script <<-RUBY
gemfile do
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+
+ puts MYRACK
+ 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 "myrack" do |s|
+ s.add_dependency "myrackdep"
+ end
+
+ build_gem "myrackdep", "1.0.0"
+ end
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ gemfile do
+ source "https://gem.repo4"
+ gem "myrack"
end
- puts RACK
+ require "myrackdep"
+ puts MYRACKDEP
+ 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 "myrack" do |s|
+ s.add_dependency "myrackdep"
+ end
+
+ build_gem "myrackdep", "1.0.0"
+ end
+
+ script <<-RUBY, artifice: "compact_index_extra_api"
+ gemfile do
+ source "https://test.repo"
+ source "https://test.repo/extra" do
+ gem "myrack"
+ end
+ end
+
+ require "myrackdep"
+ puts MYRACKDEP
RUBY
expect(out).to eq("1.0.0")
expect(err).to be_empty
- expect(exitstatus).to be_zero if exitstatus
end
it "installs quietly from git if necessary when the install option is not set" do
@@ -163,6 +220,7 @@ RSpec.describe "bundler/inline#gemfile" do
baz_ref = build_git("baz", "2.0.0").ref_for("HEAD")
script <<-RUBY
gemfile do
+ source "https://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
@@ -173,19 +231,20 @@ RSpec.describe "bundler/inline#gemfile" do
expect(out).to eq("1.0.0\n2.0.0")
expect(err).to be_empty
- expect(exitstatus).to be_zero if exitstatus
end
it "allows calling gemfile twice" do
script <<-RUBY
gemfile do
path "#{lib_path}" do
+ source "https://gem.repo1"
gem "two"
end
end
gemfile do
path "#{lib_path}" do
+ source "https://gem.repo1"
gem "four"
end
end
@@ -193,12 +252,118 @@ RSpec.describe "bundler/inline#gemfile" do
expect(out).to eq("two\nfour")
expect(err).to be_empty
- expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "doesn't reinstall already installed gems" do
+ system_gems "myrack-1.0.0"
+
+ script <<-RUBY
+ require 'bundler'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+
+ gemfile(true, ui: ui) do
+ source "https://gem.repo1"
+ gem "activesupport"
+ gem "myrack"
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ expect(out).not_to include("Installing myrack")
+ expect(err).to be_empty
+ end
+
+ it "installs gems in later gemfile calls" do
+ system_gems "myrack-1.0.0"
+
+ script <<-RUBY
+ require 'bundler'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+ gemfile(true, ui: ui) do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+
+ gemfile(true, ui: ui) do
+ source "https://gem.repo1"
+ gem "activesupport"
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ expect(out).not_to include("Installing myrack")
+ expect(err).to be_empty
+ end
+
+ it "doesn't reinstall already installed gems in later gemfile calls" do
+ system_gems "myrack-1.0.0"
+
+ script <<-RUBY
+ require 'bundler'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+ gemfile(true, ui: ui) do
+ source "https://gem.repo1"
+ gem "activesupport"
+ end
+
+ gemfile(true, ui: ui) do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ expect(out).not_to include("Installing myrack")
+ expect(err).to be_empty
+ end
+
+ it "installs gems with native extensions in later gemfile calls" do
+ system_gems "myrack-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 'bundler'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+ gemfile(true, ui: ui) do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+
+ gemfile(true, ui: ui) do
+ source "https://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"
+ source "https://notaserver.test"
gem "rake"
G
@@ -215,40 +380,94 @@ RSpec.describe "bundler/inline#gemfile" do
rake
BUNDLED WITH
- 1.13.6
+ #{Bundler::VERSION}
G
- in_app_root do
- script <<-RUBY
- gemfile do
- source "file://#{gem_repo1}"
- gem "rack"
- end
+ script <<-RUBY
+ gemfile do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
- puts RACK
- RUBY
- end
+ puts MYRACK
+ RUBY
expect(err).to be_empty
- expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "does not leak Gemfile.lock versions to the installation output" do
+ gemfile <<-G
+ source "https://notaserver.test"
+ 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 "https://gem.repo1"
+ gem "rake", "#{rake_version}"
+ end
+ RUBY
+
+ expect(out).to include("Installing rake #{rake_version}")
+ 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", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ gemfile do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+
+ puts MYRACK
+ RUBY
+
+ expect(last_command.stderr).to be_empty
+ end
+
+ it "installs inline gems when deployment is set" do
+ script <<-RUBY, env: { "BUNDLE_DEPLOYMENT" => "true", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ gemfile do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+
+ puts MYRACK
+ 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"] = ""
- in_app_root do
- script <<-RUBY
- gemfile do
- source "file://#{gem_repo1}"
- gem "rack"
- end
+ script <<-RUBY
+ gemfile do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
- puts RACK
- RUBY
- end
+ puts MYRACK
+ RUBY
expect(err).to be_empty
- expect(exitstatus).to be_zero if exitstatus
end
it "installs inline gems when BUNDLE_BIN is set" do
@@ -256,13 +475,277 @@ RSpec.describe "bundler/inline#gemfile" do
script <<-RUBY
gemfile do
- source "file://#{gem_repo1}"
- gem "rack" # has the rackup executable
+ source "https://gem.repo1"
+ gem "myrack" # has the myrackup executable
end
- puts RACK
+ puts MYRACK
RUBY
- expect(exitstatus).to eq(0) if exitstatus
+ 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", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ gemfile(true) do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+ RUBY
+ expect(last_command).to be_success
+ expect(system_gem_path("gems/myrack-1.0.0")).to exist
+ end
+ end
+
+ it "skips platform warnings" do
+ bundle_config "force_ruby_platform true"
+
+ script <<-RUBY
+ gemfile(true) do
+ source "https://gem.repo1"
+ gem "myrack", 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 "no_install true"
+
+ script <<-RUBY
+ gemfile do
+ source "https://gem.repo1"
+ gem "myrack"
+ end
+ RUBY
+
+ expect(last_command).to be_success
+ expect(system_gem_path("gems/myrack-1.0.0")).to exist
+ end
+
+ it "preserves previous BUNDLE_GEMFILE value" do
+ ENV["BUNDLE_GEMFILE"] = ""
+ script <<-RUBY
+ gemfile do
+ source "https://gem.repo1"
+ gem "myrack"
+ 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 "https://gem.repo1"
+ gem "myrack"
+ 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"), env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ gemfile do
+ source "https://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 "does not load default timeout", rubygems: ">= 3.5.0" do
+ default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false
+ skip "timeout isn't a default gem" if default_timeout_version.empty?
+
+ build_repo4 do
+ build_gem "timeout", "999"
+ end
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ require "bundler/inline"
+
+ gemfile(true) do
+ source "https://gem.repo4"
+
+ gem "timeout"
+ end
+ RUBY
+
+ expect(out).to include("Installing timeout 999")
+ end
+
+ it "does not upcase ENV" do
+ script <<-RUBY
+ require 'bundler/inline'
+
+ ENV['Test_Variable'] = 'value string'
+ puts("before: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}")
+
+ gemfile do
+ source "https://gem.repo1"
+ end
+
+ puts("after: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}")
+ RUBY
+
+ expect(out).to include("before: [\"Test_Variable\"]")
+ expect(out).to include("after: [\"Test_Variable\"]")
+ end
+
+ it "does not create a lockfile" do
+ script <<-RUBY
+ require 'bundler/inline'
+
+ gemfile do
+ source "https://gem.repo1"
+ end
+
+ puts Dir.glob("Gemfile.lock")
+ RUBY
+
+ expect(out).to be_empty
+ end
+
+ it "does not reset ENV" do
+ script <<-RUBY
+ require 'bundler/inline'
+
+ gemfile do
+ source "https://gem.repo1"
+
+ ENV['FOO'] = 'bar'
+ end
+
+ puts ENV['FOO']
+ RUBY
+
+ expect(out).to eq("bar")
+ end
+
+ it "does not load specified version of psych and stringio", :ruby_repo do
+ build_repo4 do
+ build_gem "psych", "999"
+ build_gem "stringio", "999"
+ end
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ require "bundler/inline"
+
+ gemfile(true) do
+ source "https://gem.repo4"
+
+ gem "psych"
+ gem "stringio"
+ end
+ RUBY
+
+ expect(out).to include("Installing psych 999")
+ expect(out).to include("Installing stringio 999")
+ if Gem.respond_to?(:use_psych?) && Gem.use_psych?
+ expect(out).to include("The psych gem was resolved to 999")
+ expect(out).to include("The stringio gem was resolved to 999")
+ end
+ end
+
+ it "installs a conflicting default gem and non-default gems together" do
+ build_repo4 do
+ build_gem "securerandom", "999"
+ build_gem "myrack", "1.0.0"
+ end
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ gemfile(true) do
+ source "https://gem.repo4"
+ gem "securerandom"
+ gem "myrack"
+ end
+
+ puts MYRACK
+ RUBY
+
+ expect(out).to include("Installing securerandom 999")
+ expect(out).to include("Installing myrack 1.0.0")
+ expect(out).to include("1.0.0")
+ expect(err).to be_empty
+ end
+
+ it "installs a conflicting default gem alongside git sources" do
+ build_repo4 do
+ build_gem "securerandom", "999"
+ end
+
+ build_git "foo", "1.0.0"
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ gemfile(true) do
+ source "https://gem.repo4"
+ gem "securerandom"
+ gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump}
+ end
+
+ puts FOO
+ RUBY
+
+ expect(out).to include("Installing securerandom 999")
+ expect(out).to include("1.0.0")
+ expect(err).to be_empty
+ end
+
+ it "leaves a lockfile in the same directory as the inline script alone" do
+ install_gemfile <<~G
+ source "https://gem.repo1"
+ gem "foo"
+ G
+
+ original_lockfile = lockfile
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ require "bundler/inline"
+
+ gemfile(true) do
+ source "https://gem.repo1"
+
+ gem "myrack"
+ end
+ RUBY
+
+ expect(lockfile).to eq(original_lockfile)
+ end
end
diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb
index d0e308ed3e..472cde87c5 100644
--- a/spec/bundler/runtime/load_spec.rb
+++ b/spec/bundler/runtime/load_spec.rb
@@ -1,25 +1,21 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "Bundler.load" do
- before :each do
- system_gems "rack-1.0.0"
- end
-
describe "with a gemfile" do
before(:each) do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
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")
+ expect(Bundler.load.dependencies).to have_dep("myrack", ">= 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}")
+ expect(Bundler.load.gems).to have_gem("myrack-1.0.0", "bundler-#{Bundler::VERSION}")
end
it "ignores blank BUNDLE_GEMFILEs" do
@@ -32,18 +28,20 @@ RSpec.describe "Bundler.load" do
describe "with a gems.rb file" do
before(:each) do
- create_file "gems.rb", <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ gemfile "gems.rb", <<-G
+ source "https://gem.repo1"
+ gem "myrack"
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")
+ expect(Bundler.load.dependencies).to have_dep("myrack", ">= 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}")
+ expect(Bundler.load.gems).to have_gem("myrack-1.0.0", "bundler-#{Bundler::VERSION}")
end
end
@@ -62,7 +60,7 @@ RSpec.describe "Bundler.load" do
end
it "does not find a Gemfile above the testing directory" do
- bundler_gemfile = tmp.join("../Gemfile")
+ bundler_gemfile = Pathname.new(__dir__).join("../../Gemfile")
unless File.exist?(bundler_gemfile)
FileUtils.touch(bundler_gemfile)
@remove_bundler_gemfile = true
@@ -70,16 +68,16 @@ RSpec.describe "Bundler.load" do
begin
expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound)
ensure
- bundler_gemfile.rmtree if @remove_bundler_gemfile
+ FileUtils.rm_rf bundler_gemfile if @remove_bundler_gemfile
end
end
end
describe "when called twice" do
it "doesn't try to load the runtime twice" do
- system_gems "rack-1.0.0", "activesupport-2.3.5"
- gemfile <<-G
- gem "rack"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
gem "activesupport", :group => :test
G
@@ -87,7 +85,7 @@ RSpec.describe "Bundler.load" do
require "bundler"
Bundler.setup :default
Bundler.require :default
- puts RACK
+ puts MYRACK
begin
require "activesupport"
rescue LoadError
@@ -101,11 +99,11 @@ RSpec.describe "Bundler.load" do
describe "not hurting brittle rubygems" do
it "does not inject #source into the generated YAML of the gem specs" do
- system_gems "activerecord-2.3.2", "activesupport-2.3.2"
- gemfile <<-G
+ install_gemfile <<-G
+ source "https://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:/)
diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb
index 4df934e71f..6d96758956 100644
--- a/spec/bundler/runtime/platform_spec.rb
+++ b/spec/bundler/runtime/platform_spec.rb
@@ -1,30 +1,29 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "Bundler.setup with multi platform stuff" do
it "raises a friendly error when gems are missing locally" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
lockfile <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0)
+ myrack (1.0)
PLATFORMS
#{local_tag}
DEPENDENCIES
- rack
+ myrack
G
ruby <<-R
begin
require 'bundler'
- Bundler.setup
+ Bundler.ui.silence { Bundler.setup }
rescue Bundler::GemNotFound => e
puts "WIN"
end
@@ -33,10 +32,10 @@ RSpec.describe "Bundler.setup with multi platform stuff" do
expect(out).to eq("WIN")
end
- it "will resolve correctly on the current platform when the lockfile was targetted for a different one" do
+ it "will resolve correctly on the current platform when the lockfile was targeted for a different one" do
lockfile <<-G
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
nokogiri (1.4.2-java)
weakling (= 0.0.3)
@@ -49,21 +48,147 @@ RSpec.describe "Bundler.setup with multi platform stuff" do
nokogiri
G
- system_gems "nokogiri-1.4.2"
+ simulate_platform "x86-darwin-10" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "nokogiri"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+ 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 "racca", "~> 1.5.2"
+ end
+
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.platform = Bundler.local_platform
+ s.add_dependency "racca", "~> 1.4"
+ end
+
+ build_gem "mini_portile2", "2.5.0"
+ build_gem "racca", "1.5.2"
+ end
+
+ checksums = checksums_section do |c|
+ c.checksum gem_repo4, "mini_portile2", "2.5.0"
+ c.checksum gem_repo4, "nokogiri", "1.11.1"
+ c.checksum gem_repo4, "nokogiri", "1.11.1", Bundler.local_platform
+ c.checksum gem_repo4, "racca", "1.5.2"
+ end
+
+ good_lockfile = <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ mini_portile2 (2.5.0)
+ nokogiri (1.11.1)
+ mini_portile2 (~> 2.5.0)
+ racca (~> 1.5.2)
+ nokogiri (1.11.1-#{Bundler.local_platform})
+ racca (~> 1.4)
+ racca (1.5.2)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ nokogiri (~> 1.11)
+ #{checksums}
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<-G
+ source "https://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
- simulate_platform "x86-darwin-10"
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo4"
gem "nokogiri"
G
- expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.11.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ 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 "https://gem.repo1"
+ gem "nokogiri"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: https://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:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
nokogiri (1.4.2-java)
weakling (= 0.0.3)
@@ -76,48 +201,268 @@ RSpec.describe "Bundler.setup with multi platform stuff" do
nokogiri
G
- simulate_platform "x86-darwin-100"
+ simulate_platform "x86-darwin-100" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
- system_gems "nokogiri-1.4.2", "platform_specific-1.0-x86-darwin-100"
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100"
+ end
+ end
- gemfile <<-G
- source "file://#{gem_repo1}"
+ it "allows specifying only-ruby-platform on jruby", :jruby_only do
+ install_gemfile <<-G
+ source "https://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"
+ bundle_config "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
- simulate_platform "java"
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ bundle_config "force_ruby_platform true"
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
+ 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 "https://gem.repo1"
gem "nokogiri"
gem "platform_specific"
G
- bundle! "config force_ruby_platform true"
+ bundle_config "force_ruby_platform true"
- bundle! "install"
+ bundle "install"
- expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY"
+ 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 "https://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 "https://gem.repo1"
+ gem "platform_specific"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://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 "https://gem.repo4"
+ gem "libv8"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://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 "https://gem.repo1"
+ gem "platform_specific"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://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, verbose: true
+ source "https://gem.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://#{gem_repo1}"
- gem "nokogiri", :platforms => [:mingw, :mswin, :x64_mingw, :jruby]
+ simulate_platform "x86-mswin32" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "nokogiri", :platforms => [:windows, :jruby]
gem "platform_specific"
G
- bundle! "config force_ruby_platform true"
+ bundle_config "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 "myrack"
+ end
- bundle! "install"
+ gemfile <<-G
+ source "https://gem.repo1"
+ gemspec
+ G
+ bundle :lock
- expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+ simulate_platform "x86-mswin32" do
+ bundle_config "force_ruby_platform true"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "myrack 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_platform "x64-mingw-ucrt" do
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ platform_specific (1.0-x86-mingw32)
+ requires_platform_specific (1.0)
+ platform_specific
+
+ PLATFORMS
+ x64-mingw-ucrt
+ x86-mingw32
+
+ DEPENDENCIES
+ requires_platform_specific
+ L
+
+ install_gemfile <<-G, verbose: true
+ source "https://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-mingw-ucrt"
+ end
+ end
+
+ %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw-ucrt aarch64-mingw-ucrt].each do |platform|
+ it "allows specifying platform windows on #{platform} platform" do
+ simulate_platform platform do
+ lockfile <<-L
+ GEM
+ remote: https://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 "https://gem.repo1"
+ gem "platform_specific", :platforms => [:windows]
+ G
+
+ 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
index b68313726b..46613286d2 100644
--- a/spec/bundler/runtime/require_spec.rb
+++ b/spec/bundler/runtime/require_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "Bundler.require" do
before :each do
@@ -22,7 +21,7 @@ RSpec.describe "Bundler.require" do
s.write "lib/four.rb", "puts 'four'"
end
- build_lib "five", "1.0.0", :no_default => true do |s|
+ build_lib "five", "1.0.0", no_default: true do |s|
s.write "lib/mofive.rb", "puts 'five'"
end
@@ -47,19 +46,21 @@ RSpec.describe "Bundler.require" do
end
gemfile <<-G
- path "#{lib_path}"
- 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
+ source "https://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
- gem "ten", :install_if => lambda { ENV["BUNDLER_TEST"] == "ten" }
G
end
@@ -86,7 +87,7 @@ RSpec.describe "Bundler.require" do
# required in resolver order instead of gemfile order
run("Bundler.require(:not)")
- expect(out.split("\n").sort).to eq(%w(seven three))
+ expect(out.split("\n").sort).to eq(%w[seven three])
# test require: true
run "Bundler.require(:require_true)"
@@ -112,15 +113,15 @@ RSpec.describe "Bundler.require" do
it "raises an exception if a require is specified but the file does not exist" do
gemfile <<-G
- path "#{lib_path}"
- gem "two", :require => 'fail'
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "two", :require => 'fail'
+ end
G
- load_error_run <<-R, "fail"
- Bundler.require
- R
+ run "Bundler.require", raise_on_error: false
- expect(err).to eq_err("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to include("cannot load such file -- fail")
end
it "displays a helpful message if the required gem throws an error" do
@@ -129,11 +130,13 @@ RSpec.describe "Bundler.require" do
end
gemfile <<-G
- path "#{lib_path}"
- gem "faulty"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "faulty"
+ end
G
- run "Bundler.require"
+ 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
@@ -144,20 +147,15 @@ RSpec.describe "Bundler.require" do
end
gemfile <<-G
- path "#{lib_path}"
- gem "loadfuuu"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "loadfuuu"
+ end
G
- cmd = <<-RUBY
- begin
- Bundler.require
- rescue LoadError => e
- $stderr.puts "ZOMG LOAD ERROR: \#{e.message}"
- end
- RUBY
- run(cmd)
+ run "Bundler.require", raise_on_error: false
- expect(err).to eq_err("ZOMG LOAD ERROR: cannot load such file -- load-bar")
+ expect(err_without_deprecations).to include("cannot load such file -- load-bar")
end
describe "with namespaced gems" do
@@ -165,13 +163,14 @@ RSpec.describe "Bundler.require" do
build_lib "jquery-rails", "1.0.0" do |s|
s.write "lib/jquery/rails.rb", "puts 'jquery/rails'"
end
- lib_path("jquery-rails-1.0.0/lib/jquery-rails.rb").rmtree
end
it "requires gem names that are namespaced" do
gemfile <<-G
- path '#{lib_path}'
- gem 'jquery-rails'
+ source "https://gem.repo1"
+ path '#{lib_path}' do
+ gem 'jquery-rails'
+ end
G
run "Bundler.require"
@@ -179,12 +178,15 @@ RSpec.describe "Bundler.require" do
end
it "silently passes if the require fails" do
- build_lib "bcrypt-ruby", "1.0.0", :no_default => true do |s|
+ build_lib "bcrypt-ruby", "1.0.0", no_default: true do |s|
s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'"
end
gemfile <<-G
- path "#{lib_path}"
- gem "bcrypt-ruby"
+ source "https://gem.repo1"
+
+ path "#{lib_path}" do
+ gem "bcrypt-ruby"
+ end
G
cmd = <<-RUBY
@@ -193,19 +195,20 @@ RSpec.describe "Bundler.require" do
RUBY
ruby(cmd)
- expect(err).to lack_errors
+ expect(err).to be_empty
end
it "does not mangle explicitly given requires" do
gemfile <<-G
- path "#{lib_path}"
- gem 'jquery-rails', :require => 'jquery-rails'
+ source "https://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).to eq_err("ZOMG LOAD ERROR")
+ run "Bundler.require", raise_on_error: false
+
+ expect(err_without_deprecations).to include("cannot load such file -- jquery-rails")
end
it "handles the case where regex fails" do
@@ -214,43 +217,32 @@ RSpec.describe "Bundler.require" do
end
gemfile <<-G
- path "#{lib_path}"
- gem "load-fuuu"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "load-fuuu"
+ end
G
- cmd = <<-RUBY
- begin
- Bundler.require
- rescue LoadError => e
- $stderr.puts "ZOMG LOAD ERROR" if e.message.include?("Could not open library 'libfuuu-1.0'")
- end
- RUBY
- run(cmd)
+ run "Bundler.require", raise_on_error: false
- expect(err).to eq_err("ZOMG LOAD ERROR")
+ expect(err_without_deprecations).to include("libfuuu-1.0").and include("cannot open shared object file")
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
- lib_path("load-fuuu-1.0.0/lib/load-fuuu.rb").rmtree
gemfile <<-G
- path "#{lib_path}"
- gem "load-fuuu"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "load-fuuu"
+ end
G
- cmd = <<-RUBY
- begin
- Bundler.require
- rescue LoadError => e
- $stderr.puts "ZOMG LOAD ERROR: \#{e.message}"
- end
- RUBY
- run(cmd)
+ run "Bundler.require", raise_on_error: false
- expect(err).to eq_err("ZOMG LOAD ERROR: cannot load such file -- load-bar")
+ expect(err_without_deprecations).to include("cannot load such file -- load-bar")
end
end
@@ -294,9 +286,11 @@ RSpec.describe "Bundler.require" do
it "works when the gems are in the Gemfile in the correct order" do
gemfile <<-G
- path "#{lib_path}"
- gem "two"
- gem "one"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "two"
+ gem "one"
+ end
G
run "Bundler.require"
@@ -305,12 +299,13 @@ RSpec.describe "Bundler.require" do
describe "a gem with different requires for different envs" do
before(:each) do
- build_gem "multi_gem", :to_system => true do |s|
+ 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 "https://gem.repo1"
gem "multi_gem", :require => "one", :group => :one
gem "multi_gem", :require => "two", :group => :two
G
@@ -334,9 +329,11 @@ RSpec.describe "Bundler.require" do
it "fails when the gems are in the Gemfile in the wrong order" do
gemfile <<-G
- path "#{lib_path}"
- gem "one"
- gem "two"
+ source "https://gem.repo1"
+ path "#{lib_path}" do
+ gem "one"
+ gem "two"
+ end
G
run "Bundler.require"
@@ -345,30 +342,30 @@ RSpec.describe "Bundler.require" do
describe "with busted gems" do
it "should be busted" do
- build_gem "busted_require", :to_system => true do |s|
+ 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 "https://gem.repo1"
gem "busted_require"
G
- load_error_run <<-R, "no_such_file_omg"
- Bundler.require
- R
- expect(err).to eq_err("ZOMG LOAD ERROR")
+ run "Bundler.require", raise_on_error: false
+
+ expect(err_without_deprecations).to include("cannot load such file -- no_such_file_omg")
end
end
end
- it "does not load rubygems gemspecs that are used", :rubygems => ">= 2.5.2" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ it "does not load rubygems gemspecs that are used" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
G
- run! <<-R
- path = File.join(Gem.dir, "specifications", "rack-1.0.0.gemspec")
+ run <<-R
+ path = File.join(Gem.dir, "specifications", "myrack-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|
@@ -376,7 +373,7 @@ RSpec.describe "Bundler.require" do
end
R
- run! <<-R
+ run <<-R
Bundler.require
puts "WIN"
R
@@ -384,14 +381,15 @@ RSpec.describe "Bundler.require" do
expect(out).to eq("WIN")
end
- it "does not load git gemspecs that are used", :rubygems => ">= 2.5.2" do
+ it "does not load git gemspecs that are used" do
build_git "foo"
- install_gemfile! <<-G
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- run! <<-R
+ 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
@@ -400,7 +398,47 @@ RSpec.describe "Bundler.require" do
end
R
- run! <<-R
+ run <<-R
+ Bundler.require
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not load plugins" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ create_file "plugins/rubygems_plugin.rb", "puts 'FAIL'"
+
+ run <<~R, env: { "RUBYLIB" => rubylib.unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) }
+ Bundler.require
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not extract gemspecs from application cache packages" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle :cache
+
+ path = cached_gem("myrack-1.0.0")
+
+ run <<-R
+ File.open("#{path}", "w") do |f|
+ f.write "broken package"
+ end
+ R
+
+ run <<-R
Bundler.require
puts "WIN"
R
@@ -412,31 +450,31 @@ 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://#{gem_repo1}"
+ source "https://gem.repo1"
platforms :#{not_local_tag} do
- gem "fail", :require => "omgomg"
+ gem "platform_specific", :require => "omgomg"
end
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
G
run "Bundler.require"
- expect(err).to lack_errors
+ expect(err).to be_empty
end
it "requires gems pinned to multiple platforms, including the current one" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
platforms :#{not_local_tag}, :#{local_tag} do
- gem "rack", :require => "rack"
+ gem "myrack", :require => "myrack"
end
G
- run "Bundler.require; puts RACK"
+ run "Bundler.require; puts MYRACK"
expect(out).to eq("1.0.0")
- expect(err).to lack_errors
+ expect(err).to be_empty
end
end
diff --git a/spec/bundler/runtime/requiring_spec.rb b/spec/bundler/runtime/requiring_spec.rb
new file mode 100644
index 0000000000..f0e0aeacaf
--- /dev/null
+++ b/spec/bundler/runtime/requiring_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.describe "Requiring bundler" do
+ it "takes care of requiring rubygems when entrypoint is bundler/setup" do
+ sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => "--disable=gems" })
+
+ expect(stdboth).to eq("true")
+ end
+
+ it "takes care of requiring rubygems when requiring just bundler" do
+ sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler -e'puts true'", env: { "RUBYOPT" => "--disable=gems" })
+
+ expect(stdboth).to eq("true")
+ 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..176c2a3121
--- /dev/null
+++ b/spec/bundler/runtime/self_management_spec.rb
@@ -0,0 +1,279 @@
+# frozen_string_literal: true
+
+RSpec.describe "Self management" do
+ describe "auto switching" do
+ let(:previous_minor) do
+ "9.3.0"
+ end
+
+ let(:current_version) do
+ "9.4.0"
+ end
+
+ before do
+ build_repo4 do
+ build_bundler previous_minor
+
+ build_bundler current_version
+
+ build_gem "myrack", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "myrack"
+ G
+
+ pristine_system_gems "bundler-#{current_version}"
+ end
+
+ it "installs locked version when using system path and uses it" do
+ lockfile_bundled_with(previous_minor)
+
+ bundle_config "path.system true"
+ bundle "install"
+ expect(out).to include("Bundler #{current_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", artifice: nil
+ expect(out).to eq("Removing bundler (#{current_version})")
+
+ # App now uses locked version
+ bundle "-v", artifice: nil
+ expect(out).to eq(previous_minor)
+
+ # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff
+ unless ruby_core?
+ # App now uses locked version, even when not using the CLI directly
+ file = bundled_app("bin/bundle_version.rb")
+ create_file file, <<-RUBY
+ #!#{Gem.ruby}
+ require 'bundler/setup'
+ puts '#{previous_minor}'
+ RUBY
+ file.chmod(0o777)
+ cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb"
+ in_bundled_app cmd
+ expect(out).to eq(previous_minor)
+ end
+
+ # Subsequent installs use the locked version without reinstalling
+ bundle "install --verbose", artifice: nil
+ expect(out).to include("Using bundler #{previous_minor}")
+ expect(out).not_to include("Bundler #{current_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 "path vendor/bundle"
+ bundle "install"
+ expect(out).to include("Bundler #{current_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 eq(previous_minor)
+
+ # Preserves original gem home when auto-switching
+ bundle "exec ruby -e 'puts Bundler.original_env[\"GEM_HOME\"]'"
+ expect(out).to eq(ENV["GEM_HOME"])
+
+ # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff
+ unless ruby_core?
+ # App now uses locked version, even when not using the CLI directly
+ file = bundled_app("bin/bundle_version.rb")
+ create_file file, <<-RUBY
+ #!#{Gem.ruby}
+ require 'bundler/setup'
+ puts '#{previous_minor}'
+ RUBY
+ file.chmod(0o777)
+ cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb"
+ in_bundled_app cmd
+ expect(out).to eq(previous_minor)
+ end
+
+ # 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 #{current_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 "deployment true"
+ bundle "install"
+ expect(out).to include("Bundler #{current_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 eq(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 #{current_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(current_version)
+ end
+
+ it "does not try to install when --local is passed" do
+ lockfile_bundled_with(previous_minor)
+ system_gems "myrack-1.0.0", path: local_gem_path
+
+ bundle "install --local"
+ expect(out).not_to match(/Installing Bundler/)
+
+ bundle "-v"
+ expect(out).to eq(current_version)
+ end
+
+ it "shows a discrete message if locked bundler does not exist" do
+ missing_minor = "#{current_version[0]}.999.999"
+
+ lockfile_bundled_with(missing_minor)
+
+ bundle "install"
+ 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 #{current_version}")
+
+ bundle "-v"
+ expect(out).to eq(current_version)
+ end
+
+ it "installs BUNDLE_VERSION version when using bundle config version x.y.z" do
+ lockfile_bundled_with(current_version)
+
+ bundle_config "version #{previous_minor}"
+ bundle "install"
+ expect(out).to include("Bundler #{current_version} is running, but your configuration was #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+
+ bundle "-v"
+ expect(out).to eq(previous_minor)
+ end
+
+ it "requires the right bundler version from the config and run bundle CLI without re-exec" do
+ unless Bundler.rubygems.provides?(">= 4.1.0.dev")
+ skip "This spec can only run when Gem::BundlerVersionFinder.bundler_versions reads bundler configs"
+ end
+
+ lockfile_bundled_with(current_version)
+
+ bundle_config "version #{previous_minor}"
+ bundle_config "path.system true"
+ bundle "install"
+
+ script = bundled_app("script.rb")
+ create_file(script, "p 'executed once'")
+
+ bundle "-v", env: { "RUBYOPT" => "-r#{script}" }
+ expect(out).to eq(%("executed once"\n9.3.0))
+ end
+
+ it "does not try to install when using bundle config version global" do
+ lockfile_bundled_with(previous_minor)
+
+ bundle_config "version system"
+ bundle "install"
+ expect(out).not_to match(/restarting using that version/)
+
+ bundle "-v"
+ expect(out).to eq(current_version)
+ end
+
+ it "does not try to install when using bundle config version <dev-version>" do
+ lockfile_bundled_with(previous_minor)
+
+ bundle_config "version #{previous_minor}.dev"
+ bundle "install"
+ expect(out).not_to match(/restarting using that version/)
+
+ bundle "-v"
+ expect(out).to eq(current_version)
+ end
+
+ it "ignores malformed lockfile version" do
+ lockfile_bundled_with("2.3.")
+
+ bundle "install --verbose"
+ expect(out).to include("Using bundler #{current_version}")
+ end
+
+ it "uses the right original script when re-execing, if `$0` has been changed to something that's not a script", :ruby_repo do
+ system_gems "bundler-9.9.9", path: local_gem_path
+
+ test = bundled_app("test.rb")
+
+ create_file test, <<~RUBY
+ $0 = "this is the program name"
+ require "bundler/setup"
+ RUBY
+
+ lockfile_bundled_with("9.9.9")
+
+ in_bundled_app "#{Gem.ruby} #{test}", raise_on_error: false
+ expect(err).to include("Could not find myrack-1.0.0")
+ expect(err).not_to include("this is the program name")
+ end
+
+ it "uses modified $0 when re-execing, if `$0` has been changed to a script", :ruby_repo do
+ system_gems "bundler-9.9.9", path: local_gem_path
+
+ runner = bundled_app("runner.rb")
+
+ create_file runner, <<~RUBY
+ $0 = ARGV.shift
+ load $0
+ RUBY
+
+ script = bundled_app("script.rb")
+ create_file script, <<~RUBY
+ require "bundler/setup"
+ RUBY
+
+ lockfile_bundled_with("9.9.9")
+
+ in_bundled_app "#{Gem.ruby} #{runner} #{script}", raise_on_error: false
+ expect(err).to include("Could not find myrack-1.0.0")
+ end
+
+ private
+
+ def lockfile_bundled_with(version)
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ myrack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack
+
+ BUNDLED WITH
+ #{version}
+ L
+ end
+ end
+end
diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb
index dc7af07188..ceb6fcf66a 100644
--- a/spec/bundler/runtime/setup_spec.rb
+++ b/spec/bundler/runtime/setup_spec.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
-require "spec_helper"
+
+require "tmpdir"
RSpec.describe "Bundler.setup" do
describe "with no arguments" do
it "makes all groups available" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :group => :test
+ source "https://gem.repo1"
+ gem "myrack", :group => :test
G
ruby <<-RUBY
- require 'rubygems'
require 'bundler'
Bundler.setup
- require 'rack'
- puts RACK
+ require 'myrack'
+ puts MYRACK
RUBY
- expect(err).to lack_errors
+ expect(err).to be_empty
expect(out).to eq("1.0.0")
end
end
@@ -25,58 +25,54 @@ RSpec.describe "Bundler.setup" do
describe "when called with groups" do
before(:each) do
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "yard"
- gem "rack", :group => :test
+ gem "myrack", :group => :test
G
end
it "doesn't make all groups available" do
ruby <<-RUBY
- require 'rubygems'
require 'bundler'
Bundler.setup(:default)
begin
- require 'rack'
+ require 'myrack'
rescue LoadError
puts "WIN"
end
RUBY
- expect(err).to lack_errors
+ expect(err).to be_empty
expect(out).to eq("WIN")
end
it "accepts string for group name" do
ruby <<-RUBY
- require 'rubygems'
require 'bundler'
Bundler.setup(:default, 'test')
- require 'rack'
- puts RACK
+ require 'myrack'
+ puts MYRACK
RUBY
- expect(err).to lack_errors
+ 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 'rubygems'
require 'bundler'
Bundler.setup
Bundler.setup(:default)
- require 'rack'
- puts RACK
+ require 'myrack'
+ puts MYRACK
RUBY
- expect(err).to lack_errors
+ 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 'rubygems'
require 'bundler'
Bundler.setup(:default)
Bundler.setup(:default, :test)
@@ -88,21 +84,21 @@ RSpec.describe "Bundler.setup" do
puts "FAIL"
end
RUBY
- expect(err).to lack_errors
+ expect(err).to be_empty
expect(out).to match("WIN")
end
it "handles multiple non-additive invocations" do
- ruby <<-RUBY
+ ruby <<-RUBY, raise_on_error: false
require 'bundler'
Bundler.setup(:default, :test)
Bundler.setup(:default)
- require 'rack'
+ require 'myrack'
puts "FAIL"
RUBY
- expect(err).to match("rack")
+ expect(err).to match("myrack")
expect(err).to match("LoadError")
expect(out).not_to match("FAIL")
end
@@ -110,50 +106,45 @@ RSpec.describe "Bundler.setup" do
context "load order" do
def clean_load_path(lp)
- without_bundler_load_path = ruby!("puts $LOAD_PATH").split("\n")
- lp = lp - [
- bundler_path.to_s,
- bundler_path.join("gems/bundler-#{Bundler::VERSION}/lib").to_s,
- tmp("rubygems/lib").to_s,
- root.join("../lib").expand_path.to_s,
- ] - without_bundler_load_path
- lp.map! {|p| p.sub(/^#{system_gem_path}/, "") }
+ 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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- ENV["RUBYOPT"] = "-Idash_i_dir"
+ ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -Idash_i_dir"
ENV["RUBYLIB"] = "rubylib_dir"
ruby <<-RUBY
- require 'rubygems'
require 'bundler'
Bundler.setup
puts $LOAD_PATH
RUBY
load_path = out.split("\n")
- rack_load_order = load_path.index {|path| path.include?("rack") }
+ myrack_load_order = load_path.index {|path| path.include?("myrack") }
- expect(err).to eq("")
- expect(load_path[1]).to include "dash_i_dir"
- expect(load_path[2]).to include "rubylib_dir"
- expect(rack_load_order).to be > 0
+ expect(err).to be_empty
+ expect(load_path).to include(a_string_ending_with("dash_i_dir"), "rubylib_dir")
+ expect(myrack_load_order).to be > 0
end
- it "orders the load path correctly when there are dependencies", :ruby_repo do
+ it "orders the load path correctly when there are dependencies" do
+ bundle_config "path.system true"
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "rails"
G
- ruby! <<-RUBY
- require 'rubygems'
+ ruby <<-RUBY
require 'bundler'
+ gem "bundler", "#{Bundler::VERSION}" if #{ruby_core?}
Bundler.setup
puts $LOAD_PATH
RUBY
@@ -167,20 +158,21 @@ RSpec.describe "Bundler.setup" do
"/gems/actionpack-2.3.2/lib",
"/gems/actionmailer-2.3.2/lib",
"/gems/activesupport-2.3.2/lib",
- "/gems/rake-10.0.2/lib"
+ "/gems/rake-#{rake_version}/lib"
)
end
it "falls back to order the load path alphabetically for backwards compatibility" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
+ bundle_config "path.system true"
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "weakling"
gem "duradura"
gem "terranova"
G
- ruby! <<-RUBY
- require 'rubygems'
+ ruby <<-RUBY
require 'bundler/setup'
puts $LOAD_PATH
RUBY
@@ -197,12 +189,11 @@ RSpec.describe "Bundler.setup" do
it "raises if the Gemfile was not yet installed" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
ruby <<-R
- require 'rubygems'
require 'bundler'
begin
@@ -218,95 +209,148 @@ RSpec.describe "Bundler.setup" do
it "doesn't create a Gemfile.lock if the setup fails" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- ruby <<-R
- require 'rubygems'
+ ruby <<-R, raise_on_error: false
require 'bundler'
Bundler.setup
R
- expect(bundled_app("Gemfile.lock")).not_to exist
+ 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://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- lockfile = File.read(bundled_app("Gemfile.lock"))
+ lockfile = File.read(bundled_app_lock)
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "nosuchgem", "10.0"
G
- ruby <<-R
- require 'rubygems'
+ ruby <<-R, raise_on_error: false
require 'bundler'
Bundler.setup
R
- expect(File.read(bundled_app("Gemfile.lock"))).to eq(lockfile)
+ expect(File.read(bundled_app_lock)).to eq(lockfile)
end
it "makes a Gemfile.lock if setup succeeds" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- File.read(bundled_app("Gemfile.lock"))
+ File.read(bundled_app_lock)
- FileUtils.rm(bundled_app("Gemfile.lock"))
+ FileUtils.rm(bundled_app_lock)
run "1"
- expect(bundled_app("Gemfile.lock")).to exist
+ expect(bundled_app_lock).to exist
end
- it "uses BUNDLE_GEMFILE to locate the gemfile if present" do
- gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
- G
+ 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 "https://gem.repo1"
+ gem "myrack"
+ G
- gemfile bundled_app("4realz"), <<-G
- source "file://#{gem_repo1}"
- gem "activesupport", "2.3.5"
- G
+ gemfile bundled_app("4realz"), <<-G
+ source "https://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 "https://gem.repo1"
+ G
+
+ bundle "install"
+ bundle_config "deployment true"
- ENV["BUNDLE_GEMFILE"] = bundled_app("4realz").to_s
- bundle :install
+ 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
+
+ context "user sets it via `config set --local gemfile`" do
+ it "uses the value in the config" do
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ gemfile bundled_app("CustomGemfile"), <<-G
+ source "https://gem.repo1"
+ gem "activesupport", "2.3.5"
+ G
+
+ bundle_config "gemfile #{bundled_app("CustomGemfile")}"
+ bundle "install"
+
+ ruby <<-R
+ require 'bundler'
+ Bundler.setup
+ require 'activesupport'
+ puts ACTIVESUPPORT
+ R
- expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(out).to eq("2.3.5")
+ 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://#{gem_repo1}"
- gem "rack", "1.0.0"
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
G
- build_gem "rack", "1.0", :to_system => true do |s|
- s.write "lib/rack.rb", "RACK = 'FAIL'"
+ build_gem "myrack", "1.0", to_system: true do |s|
+ s.write "lib/myrack.rb", "MYRACK = 'FAIL'"
end
- expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
describe "integrate with rubygems" do
describe "by replacing #gem" do
before :each do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
end
@@ -323,23 +367,10 @@ RSpec.describe "Bundler.setup" do
expect(out).to eq("WIN")
end
- it "version_requirement is now deprecated in rubygems 1.4.0+ when gem is missing" do
- run <<-R
- begin
- gem "activesupport"
- puts "FAIL"
- rescue LoadError
- puts "WIN"
- end
- R
-
- expect(err).to lack_errors
- end
-
it "replaces #gem but raises when the version is wrong" do
run <<-R
begin
- gem "rack", "1.0.0"
+ gem "myrack", "1.0.0"
puts "FAIL"
rescue LoadError
puts "WIN"
@@ -348,39 +379,27 @@ RSpec.describe "Bundler.setup" do
expect(out).to eq("WIN")
end
-
- it "version_requirement is now deprecated in rubygems 1.4.0+ when the version is wrong" do
- run <<-R
- begin
- gem "rack", "1.0.0"
- puts "FAIL"
- rescue LoadError
- puts "WIN"
- end
- R
-
- expect(err).to lack_errors
- end
end
describe "by hiding system gems" do
before :each do
system_gems "activesupport-2.3.5"
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://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")
+ expect(out).to include("bundler-#{Bundler::VERSION}").and include("yard-1.0")
+ expect(out).not_to include("activesupport-2.3.5")
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 =~ /vendor_ruby/ }.split("/")[2]
- run "puts 'worked!'", :env => { "GEM_PATH" => substring }
+ 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
@@ -389,35 +408,37 @@ RSpec.describe "Bundler.setup" do
describe "with paths" do
it "activates the gems in the path source" do
- system_gems "rack-1.0.0"
+ system_gems "myrack-1.0.0"
- build_lib "rack", "1.0.0" do |s|
- s.write "lib/rack.rb", "puts 'WIN'"
+ build_lib "myrack", "1.0.0" do |s|
+ s.write "lib/myrack.rb", "puts 'WIN'"
end
gemfile <<-G
- path "#{lib_path("rack-1.0.0")}"
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ path "#{lib_path("myrack-1.0.0")}" do
+ gem "myrack"
+ end
G
- run "require 'rack'"
+ run "require 'myrack'"
expect(out).to eq("WIN")
end
end
describe "with git" do
before do
- build_git "rack", "1.0.0"
+ build_git "myrack", "1.0.0"
gemfile <<-G
- gem "rack", :git => "#{lib_path("rack-1.0.0")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-1.0.0")}"
G
end
it "provides a useful exception when the git repo is not checked out yet" do
- run "1"
- expect(err).to match(/the git source #{lib_path('rack-1.0.0')} is not yet checked out. Please run `bundle install`/i)
+ run "1", raise_on_error: false
+ expect(err).to match(/the git source #{lib_path("myrack-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
@@ -426,7 +447,6 @@ RSpec.describe "Bundler.setup" do
break_git!
ruby <<-R
- require 'rubygems'
require 'bundler'
begin
@@ -443,12 +463,11 @@ RSpec.describe "Bundler.setup" do
it "provides a good exception if the lockfile is unavailable" do
bundle "install"
- FileUtils.rm(bundled_app("Gemfile.lock"))
+ FileUtils.rm(bundled_app_lock)
break_git!
ruby <<-R
- require "rubygems"
require "bundler"
begin
@@ -459,125 +478,125 @@ RSpec.describe "Bundler.setup" do
end
R
- run "puts 'FAIL'"
+ 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 "install --path vendor/bundle"
- FileUtils.rm_rf vendored_gems("cache")
- expect(the_bundle).to include_gems "rack 1.0.0"
+ bundle :install
+ FileUtils.rm_r default_cache_path
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
it "does not randomly change the path when specifying --path and the bundle directory becomes read only" do
- bundle "install --path vendor/bundle"
+ bundle_config "path vendor/bundle"
+ bundle :install
- with_read_only("**/*") do
- expect(the_bundle).to include_gems "rack 1.0.0"
+ with_read_only("#{bundled_app}/**/*") do
+ expect(the_bundle).to include_gems "myrack 1.0.0"
end
end
it "finds git gem when default bundle path becomes read only" do
+ bundle_config "path .bundle"
bundle "install"
- with_read_only("#{Bundler.bundle_path}/**/*") do
- expect(the_bundle).to include_gems "rack 1.0.0"
+ with_read_only("#{bundled_app(".bundle")}/**/*") do
+ expect(the_bundle).to include_gems "myrack 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"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
bundle :install
- expect(out).to match(/at #{lib_path('local-rack')}/)
- FileUtils.rm_rf(lib_path("local-rack"))
- run "require 'rack'"
- expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/)
+ FileUtils.rm_r(lib_path("local-myrack"))
+ run "require 'myrack'", raise_on_error: false
+ expect(err).to match(/Cannot use local override for myrack-0.8 because #{Regexp.escape(lib_path("local-myrack").to_s)} does not exist/)
end
it "explodes if branch is not given on runtime" do
- build_git "rack", "0.8"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
bundle :install
- expect(out).to match(/at #{lib_path('local-rack')}/)
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}"
G
- run "require 'rack'"
+ run "require 'myrack'", 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"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
bundle :install
- expect(out).to match(/at #{lib_path('local-rack')}/)
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "changed"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "changed"
G
- run "require 'rack'"
- expect(err).to match(/is using branch master but Gemfile specifies changed/)
+ run "require 'myrack'", 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"
+ build_git "myrack", "0.8"
- FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "master", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :ref => "main", :branch => "main"
G
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "master", :branch => "nonexistant"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :ref => "main", :branch => "nonexistent"
G
- bundle %(config local.rack #{lib_path("local-rack")})
- run "require 'rack'"
- expect(err).to match(/is using branch master but Gemfile specifies nonexistant/)
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ run "require 'myrack'", 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
- install_gemfile <<-G, :without => :rails
- source "file://#{gem_repo1}"
+ bundle_config "without rails"
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "activesupport"
group :rails do
@@ -585,14 +604,15 @@ RSpec.describe "Bundler.setup" do
end
G
- install_gems "activesupport-2.3.5"
+ system_gems "activesupport-2.3.5"
- expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => :default
+ 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
- install_gemfile <<-G, :without => :rails
- source "file://#{gem_repo1}"
+ bundle_config "without rails"
+ install_gemfile <<-G
+ source "https://gem.repo1"
gem "activesupport"
group :rails do
@@ -600,94 +620,105 @@ RSpec.describe "Bundler.setup" do
end
G
- install_gems "activesupport-2.3.5"
+ system_gems "activesupport-2.3.5"
expect(the_bundle).to include_gems "activesupport 2.3.2"
end
- it "remembers --without and does not include groups passed to Bundler.setup" do
- install_gemfile <<-G, :without => :rails
- source "file://#{gem_repo1}"
- gem "activesupport"
+ it "remembers --without and does not bail on bare Bundler.setup, even in the case of path gems no longer available" do
+ bundle_config "without development"
- group :rack do
- gem "rack"
- end
+ path = bundled_app(File.join("vendor", "foo"))
+ build_lib "foo", path: path
- group :rails do
- gem "rails", "2.3.2"
- end
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "activesupport", "2.3.2"
+ gem 'foo', :path => 'vendor/foo', :group => :development
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
+ FileUtils.rm_r(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
- end
- # Unfortunately, gem_prelude does not record the information about
- # activated gems, so this test cannot work on 1.9 :(
- if RUBY_VERSION < "1.9"
- describe "preactivated gems" do
- it "raises an exception if a pre activated gem conflicts with the bundle" do
- system_gems "thin-1.0", "rack-1.0.0"
- build_gem "thin", "1.1", :to_system => true do |s|
- s.add_dependency "rack"
+ 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
- gemfile <<-G
- gem "thin", "1.0"
- G
+ install_gemfile <<~G
+ source "https://gem.repo4"
+ gem "depends_on_bundler"
+ G
- ruby <<-R
- require 'rubygems'
- gem "thin"
- require 'bundler'
- begin
- Bundler.setup
- puts "FAIL"
- rescue Gem::LoadError => e
- puts e.message
- end
- R
+ 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
- expect(out).to eq("You have already activated thin 1.1, but your Gemfile requires thin 1.0. Prepending `bundle exec` to your command may solve this.")
- end
+ it "doesn't fail in frozen mode when bundler is a Gemfile dependency" do
+ install_gemfile <<~G
+ source "https://gem.repo4"
+ gem "bundler"
+ G
- it "version_requirement is now deprecated in rubygems 1.4.0+" do
- system_gems "thin-1.0", "rack-1.0.0"
- build_gem "thin", "1.1", :to_system => true do |s|
- s.add_dependency "rack"
- end
+ bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" }
+ expect(err).to be_empty
+ end
- gemfile <<-G
- gem "thin", "1.0"
- G
+ it "doesn't re-resolve when deleting dependencies" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ gem "actionpack"
+ G
- ruby <<-R
- require 'rubygems'
- gem "thin"
- require 'bundler'
- begin
- Bundler.setup
- puts "FAIL"
- rescue Gem::LoadError => e
- puts e.message
- end
- R
+ install_gemfile <<-G, verbose: true
+ source "https://gem.repo1"
+ gem "myrack"
+ G
- expect(err).to lack_errors
- end
+ 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 "without rails"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "activesupport"
+
+ group :myrack do
+ gem "myrack"
+ end
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ expect(the_bundle).not_to include_gems "activesupport 2.3.2", groups: :myrack
+ expect(the_bundle).to include_gems "myrack 1.0.0", groups: :myrack
end
end
- # Rubygems returns loaded_from as a string
+ # 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
+ build_git "no-gemspec", gemspec: false
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
gem "foo", :git => "#{lib_path("foo-1.0")}"
gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}"
G
@@ -701,44 +732,163 @@ RSpec.describe "Bundler.setup" do
expect(out).to be_empty
end
- it "does not load all gemspecs", :rubygems => ">= 2.3" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ it "has gem_dir pointing to local repo" do
+ build_lib "foo", "1.0", path: bundled_app
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gemspec
+ G
+
+ run <<-R
+ puts Gem.loaded_specs['foo'].gem_dir
+ R
+
+ expect(out).to eq(bundled_app.to_s)
+ end
+
+ it "does not load all gemspecs" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
G
- run! <<-R
- File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f|
+ run <<-R
+ File.open(File.join(Gem.dir, "specifications", "invalid.gemspec"), "w") do |f|
f.write <<-RUBY
# -*- encoding: utf-8 -*-
-# stub: broken 1.0.0 ruby lib
+# stub: invalid 1.0.0 ruby lib
Gem::Specification.new do |s|
- s.name = "broken"
+ s.name = "invalid"
s.version = "1.0.0"
- raise "BROKEN GEMSPEC"
+ s.authors = ["Invalid Author"]
+ s.files = ["lib/invalid.rb"]
+ s.add_dependency "nonexistent-gem", "~> 999.999.999"
+ s.validate!
end
RUBY
end
R
- run! <<-R
- puts "WIN"
+ run <<-R
+ File.open(File.join(Gem.dir, "specifications", "invalid-ext.gemspec"), "w") do |f|
+ f.write <<-RUBY
+# -*- encoding: utf-8 -*-
+# stub: invalid-ext 1.0.0 ruby lib
+# stub: a.ext\\0b.ext
+
+Gem::Specification.new do |s|
+ s.name = "invalid-ext"
+ s.version = "1.0.0"
+ s.authors = ["Invalid Author"]
+ s.files = ["lib/invalid.rb"]
+ s.required_ruby_version = "~> 0.8.0"
+ s.validate!
+end
+ RUBY
+ end
+ # Need to write the gem.build_complete file,
+ # otherwise the full spec is loaded to check the installed_by_version
+ extensions_dir = Gem.default_ext_dir_for(Gem.dir) || File.join(Gem.dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version)
+ Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "invalid-ext-1.0.0"))
+ File.open(File.join(extensions_dir, "invalid-ext-1.0.0", "gem.build_complete"), "w") {}
R
- expect(out).to eq("WIN")
+ run <<-R
+ puts "Success"
+ R
+
+ expect(out).to eq("Success")
end
it "ignores empty gem paths" do
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
ENV["GEM_HOME"] = ""
bundle %(exec ruby -e "require 'set'")
- expect(err).to lack_errors
+ expect(err).to be_empty
+ end
+
+ it "can require rubygems without warnings, when using a local cache", :truffleruby do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ bundle "package"
+ bundle %(exec ruby -w -e "require 'rubygems'")
+
+ 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 "https://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 "https://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 "https://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
@@ -746,12 +896,12 @@ end
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)
+ s.require_paths = %w[lib src]
end
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
gem "requirepaths", :require => nil
G
@@ -759,26 +909,17 @@ end
expect(out).to eq("yay")
end
- it "should clean $LOAD_PATH properly", :ruby_repo do
+ it "should clean $LOAD_PATH properly" do
gem_name = "very_simple_binary"
full_gem_name = gem_name + "-1.0"
- ext_dir = File.join(tmp "extenstions", full_gem_name)
- install_gem full_gem_name
+ system_gems full_gem_name
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
G
ruby <<-R
- if Gem::Specification.method_defined? :extension_dir
- 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 }
- end
-
require 'bundler'
gem '#{gem_name}'
@@ -792,83 +933,128 @@ end
expect(out).to eq("true\ntrue")
end
- it "stubs out Gem.refresh so it does not reveal system gems" do
- system_gems "rack-1.0.0"
+ 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 'https://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 "myrack-1.0.0"
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activesupport"
G
run <<-R
- puts Bundler.rubygems.find_name("rack").inspect
+ puts Bundler.rubygems.installed_specs.map(&:name)
Gem.refresh
- puts Bundler.rubygems.find_name("rack").inspect
+ puts Bundler.rubygems.installed_specs.map(&:name)
R
- expect(out).to eq("[]\n[]")
+ 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
+ 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 "https://gem.repo1"
gem 'foo', '1.2.3', :path => 'vendor/foo'
G
- Dir.chdir(bundled_app.parent) do
- run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app("Gemfile") }
- require 'foo'
- R
- end
- expect(err).to lack_errors
+ 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[1..-1], "foo")
+ 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
+ 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 "https://gem.repo1"
gem 'foo', '1.2.3', :path => '#{relative_path}'
G
bundle :install
- Dir.chdir(bundled_app.parent) do
- run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app("Gemfile") }
- require 'foo'
- R
- end
+ run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent
+ require 'foo'
+ R
- expect(err).to lack_errors
+ 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
+ build_git "no_gemspec", gemspec: false
install_gemfile <<-G
- gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}"
+ source "https://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 NOGEMSPEC
+ require 'no_gemspec'
+ puts NO_GEMSPEC
R
expect(out).to eq("1.0")
@@ -877,10 +1063,10 @@ end
describe "with bundled and system gems" do
before :each do
- system_gems "rack-1.0.0"
+ system_gems "myrack-1.0.0"
install_gemfile <<-G
- source "file://#{gem_repo1}"
+ source "https://gem.repo1"
gem "activesupport", "2.3.5"
G
@@ -888,10 +1074,8 @@ end
it "does not pull in system gems" do
run <<-R
- require 'rubygems'
-
begin;
- require 'rack'
+ require 'myrack'
rescue LoadError
puts 'WIN'
end
@@ -913,13 +1097,13 @@ end
it "raises an exception if gem is used to invoke a system gem not in the bundle" do
run <<-R
begin
- gem 'rack'
+ gem 'myrack'
rescue LoadError => e
puts e.message
end
R
- expect(out).to eq("rack is not part of the bundle. Add it to your Gemfile.")
+ expect(out).to eq("myrack is not part of the bundle. Add it to your Gemfile.")
end
it "sets GEM_HOME appropriately" do
@@ -930,11 +1114,12 @@ end
describe "with system gems in the bundle" do
before :each do
- system_gems "rack-1.0.0"
+ bundle_config "path.system true"
+ system_gems "myrack-1.0.0"
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "1.0.0"
+ source "https://gem.repo1"
+ gem "myrack", "1.0.0"
gem "activesupport", "2.3.5"
G
end
@@ -943,18 +1128,15 @@ end
run "puts Gem.path"
paths = out.split("\n")
expect(paths).to include(system_gem_path.to_s)
- expect(paths).to include(default_bundle_path.to_s)
end
end
describe "with a gemspec that requires other files" do
before :each do
- build_git "bar", :gemspec => false do |s|
+ 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/', __FILE__)
- $:.unshift lib unless $:.include?(lib)
- require 'bar/version'
+ require_relative 'lib/bar/version'
Gem::Specification.new do |s|
s.name = 'bar'
@@ -967,6 +1149,7 @@ end
end
gemfile <<-G
+ source "https://gem.repo1"
gem "bar", :git => "#{lib_path("bar-1.0")}"
G
end
@@ -978,14 +1161,15 @@ end
end
it "error intelligently if the gemspec has a LoadError" do
- ref = update_git "bar", :gemspec => false do |s|
+ 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
+ bundle :install, raise_on_error: false
- expect(out.lines.map(&:chomp)).to include(
+ expect(err.lines.map(&:chomp)).to include(
a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"),
- RUBY_VERSION >= "1.9" ? a_string_starting_with("Does it try to require a relative path? That's been removed in Ruby 1.9.") : "",
" # from #{default_bundle_path "bundler", "gems", "bar-1.0-#{ref[0, 12]}", "bar.gemspec"}:1",
" > require 'foobarbaz'"
)
@@ -996,54 +1180,59 @@ end
ruby <<-RUBY
require 'bundler'
+ bundler_module = class << Bundler; self; end
+ bundler_module.send(:remove_method, :require)
def Bundler.require(path)
- raise "LOSE"
+ raise StandardError, "didn't use binding from top level"
end
Bundler.load
RUBY
- expect(err).to lack_errors
- expect(out).to eq("")
+ 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
- gem "bundler", :path => "#{File.expand_path("..", lib)}"
+ source "https://gem.repo1"
+ gem "bundler", :path => "#{root}"
G
bundle %(exec ruby -e "require 'bundler'; Bundler.setup")
- expect(err).to lack_errors
+ expect(err).to be_empty
end
end
describe "when BUNDLED WITH" do
def lock_with(bundler_version = nil)
- lock = <<-L
+ lock = <<~L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
L
if bundler_version
- lock += "\n BUNDLED WITH\n #{bundler_version}\n"
+ lock += "\nBUNDLED WITH\n #{bundler_version}\n"
end
lock
end
before do
+ bundle_config "path.system true"
+
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
@@ -1051,7 +1240,7 @@ end
it "does not change the lock" do
lockfile lock_with(nil)
ruby "require 'bundler/setup'"
- lockfile_should_be lock_with(nil)
+ expect(lockfile).to eq lock_with(nil)
end
end
@@ -1059,17 +1248,18 @@ end
it "does not change the lock or warn" do
lockfile lock_with(Bundler::VERSION.succ)
ruby "require 'bundler/setup'"
- expect(out).to eq("")
- expect(err).to eq("")
- lockfile_should_be lock_with(Bundler::VERSION.succ)
+ 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 'bundler/setup'"
- lockfile_should_be lock_with("1.10.1")
+ expect(lockfile).to eq lock_with("1.10.1")
end
end
end
@@ -1078,27 +1268,32 @@ end
let(:ruby_version) { nil }
def lock_with(ruby_version = nil)
- lock = <<-L
+ checksums = checksums_section do |c|
+ c.checksum gem_repo1, "myrack", "1.0.0"
+ end
+
+ lock = <<~L
GEM
- remote: file:#{gem_repo1}/
+ remote: https://gem.repo1/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- #{generic_local_platform}
+ #{lockfile_platforms}
DEPENDENCIES
- rack
+ myrack
+ #{checksums}
L
if ruby_version
- lock += "\n RUBY VERSION\n ruby #{ruby_version}\n"
+ lock += "\nRUBY VERSION\n ruby #{ruby_version}\n"
end
- lock += <<-L
+ lock += <<~L
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
L
lock
@@ -1107,38 +1302,76 @@ end
before do
install_gemfile <<-G
ruby ">= 0"
- source "file:#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
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 }
+ # Skipped on ruby-core because `ruby "require 'bundler/setup'"` does not
+ # activate bundler as a gem there, so Source::Metadata falls back to a
+ # synthetic spec whose cache_file does not exist on disk and
+ # LockfileGenerator#bundler_checksum drops the bundler checksum, while
+ # the on-disk lockfile still has it.
+ it "does not change the lock", :ruby_repo 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 eq("")
- expect(err).to eq("")
+ 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 }
+ expect { ruby "require 'bundler/setup'" }.not_to change { lockfile }
end
end
end
describe "with gemified standard libraries" do
- it "does not load Psych", :ruby => "~> 2.2" do
- gemfile ""
+ it "does not load Digest", :ruby_repo 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'
+
+ s.add_dependency 'digest'
+ end
+ G
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+
+ bundle :install, env: { "BUNDLE_LOCKFILE_CHECKSUMS" => "false" }
+
+ ruby <<-RUBY, artifice: nil
+ require 'bundler/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 'https://gem.repo1'"
ruby <<-RUBY
require 'bundler/setup'
puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined"
@@ -1151,8 +1384,8 @@ end
end
it "does not load openssl" do
- install_gemfile! ""
- ruby! <<-RUBY
+ install_gemfile "source 'https://gem.repo1'"
+ ruby <<-RUBY, artifice: nil
require "bundler/setup"
puts defined?(OpenSSL) || "undefined"
require "openssl"
@@ -1161,32 +1394,82 @@ end
expect(out).to eq("undefined\nconstant")
end
- describe "default gem activation", :ruby_repo do
+ it "does not load uri while reading gemspecs", rubygems: ">= 3.6.0.dev" do
+ Dir.mkdir bundled_app("test")
+
+ create_file(bundled_app("test/test.gemspec"), <<-G)
+ Gem::Specification.new do |s|
+ s.name = "test"
+ s.version = "1.0.0"
+ s.summary = "test"
+ s.authors = ['John Doe']
+ s.homepage = 'https://example.com'
+ end
+ G
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "test", path: "#{bundled_app("test")}"
+ G
+
+ ruby <<-RUBY, artifice: nil
+ require "bundler/setup"
+ puts defined?(URI) || "undefined"
+ require "uri"
+ puts defined?(URI) || "undefined"
+ RUBY
+ expect(out).to eq("undefined\nconstant")
+ end
+
+ it "activates default gems when they are part of the bundle, but not installed explicitly", :ruby_repo do
+ default_delegate_version = ruby "gem 'delegate'; require 'delegate'; puts Delegator::VERSION"
+
+ build_repo2 do
+ build_gem "delegate", default_delegate_version
+ end
+
+ gemfile "source \"https://gem.repo2\"; gem 'delegate'"
+
+ ruby <<-RUBY
+ require "bundler/setup"
+ require "delegate"
+ puts defined?(::Delegator) ? "Delegator defined" : "Delegator undefined"
+ RUBY
+
+ expect(out).to eq("Delegator defined")
+ expect(err).to be_empty
+ end
+
+ describe "default gem activation" do
let(:exemptions) do
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7") || ENV["RGV"] == "master"
- []
- else
- %w(io-console openssl)
- end << "bundler"
+ exempts = %w[did_you_mean bundler uri pathname]
+ 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(:code) { strip_whitespace(<<-RUBY) }
- require "rubygems"
-
- if Gem::Specification.instance_methods.map(&:to_sym).include?(:activate)
- 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
+ let(:activation_warning_hack) { <<~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
- require "bundler/setup"
+ 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) { <<~RUBY }
require "pp"
loaded_specs = Gem.loaded_specs.dup
#{exemptions.inspect}.each {|s| loaded_specs.delete(s) }
@@ -1200,90 +1483,228 @@ end
RUBY
it "activates no gems with -rbundler/setup" do
- install_gemfile! ""
- ruby!(code)
- expect(err).to eq("")
+ install_gemfile "source 'https://gem.repo1'"
+ ruby code, env: { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" }, artifice: nil
expect(out).to eq("{}")
end
it "activates no gems with bundle exec" do
- install_gemfile! ""
+ install_gemfile "source 'https://gem.repo1'"
create_file("script.rb", code)
- bundle! "exec ruby ./script.rb"
- expect(err).to eq("")
+ 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
- # TODO: remove once https://github.com/erikhuda/thor/pull/539 is released
- exemptions << "io-console"
+ skip "not executable" if Gem.win_platform?
- install_gemfile! ""
+ install_gemfile "source 'https://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
- expect(err).to eq("")
+ bundle "exec ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt }
expect(out).to eq("{}")
end
- 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
-
- it "activates newer versions of default gems" do
+ it "does not load net-http-pipeline too early" do
build_repo4 do
- default_gems.each do |g|
- build_gem g, "999999"
- end
+ build_gem "net-http-pipeline", "1.0.1"
end
- install_gemfile! <<-G
- source "file:#{gem_repo4}"
- #{default_gems}.each do |g|
- gem g, "999999"
- end
+ system_gems "net-http-pipeline-1.0.1", gem_repo: gem_repo4
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "net-http-pipeline", "1.0.1"
G
- expect(the_bundle).to include_gems(*default_gems.map {|g| "#{g} 999999" })
+ bundle_config "path vendor/bundle"
+
+ bundle :install
+
+ bundle :check
+
+ expect(out).to eq("The Gemfile's dependencies are satisfied")
end
- it "activates older versions of default gems" do
- build_repo4 do
- default_gems.each do |g|
- build_gem g, "0.0.0.a"
+ 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 "https://gem.repo4"
+ gem "#{g}", "999999"
+ G
+
+ expect(the_bundle).to include_gem("#{g} 999999", env: { "RUBYOPT" => activation_warning_hack_rubyopt }, artifice: nil)
end
- default_gems.reject! {|g| exemptions.include?(g) }
+ it "activates older versions of #{g}", :ruby_repo do
+ skip if exemptions.include?(g)
- install_gemfile! <<-G
- source "file:#{gem_repo4}"
- #{default_gems}.each do |g|
- gem g, "0.0.0.a"
+ build_repo4 do
+ build_gem g, "0.0.0.a"
end
- G
- expect(the_bundle).to include_gems(*default_gems.map {|g| "#{g} 0.0.0.a" })
+ install_gemfile <<-G
+ source "https://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 }, artifice: nil)
+ end
end
end
end
describe "after setup" do
- it "allows calling #gem on random objects" do
+ it "keeps Kernel#gem private" do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+
+ ruby <<-RUBY, raise_on_error: false
+ require "bundler/setup"
+ Object.new.gem "myrack"
+ puts "FAIL"
+ RUBY
+
+ expect(stdboth).not_to include "FAIL"
+ expect(err).to match(/private method [`']gem'/)
+ end
+
+ it "keeps Kernel#require private" do
install_gemfile <<-G
- source "file:#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- ruby! <<-RUBY
+
+ ruby <<-RUBY, raise_on_error: false
require "bundler/setup"
- Object.new.gem "rack"
- puts Gem.loaded_specs["rack"].full_name
+ Object.new.require "myrack"
+ puts "FAIL"
RUBY
- expect(out).to eq("rack-1.0.0")
+
+ expect(stdboth).not_to include "FAIL"
+ expect(err).to match(/private method [`']require'/)
+ 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 "https://gem.repo1"
+ gem "yard"
+ gem "myrack", :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("myrack, 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 'https://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 'https://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
+
+ it "performs an automatic bundle install" do
+ build_repo4 do
+ build_gem "myrack", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack", :group => :test
+ G
+
+ bundle_config "auto_install 1"
+
+ ruby <<-RUBY, artifice: "compact_index"
+ require 'bundler/setup'
+ RUBY
+ expect(err).to be_empty
+ expect(out).to include("Installing myrack 1.0.0")
+ end
+
+ context "in a read-only filesystem" do
+ before do
+ gemfile <<-G
+ source "https://gem.repo4"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+
+ PLATFORMS
+ x86_64-darwin-19
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "should fail loudly if the lockfile platforms don't include the current platform" do
+ simulate_platform "x86_64-linux" do
+ ruby <<-RUBY, raise_on_error: false, env: { "BUNDLER_SPEC_READ_ONLY" => "true", "BUNDLER_FORCE_TTY" => "true" }
+ require "bundler/setup"
+ RUBY
+ end
+
+ expect(err).to include("Your lockfile is missing the current platform, but can't be updated because file system is read-only")
end
end
end
diff --git a/spec/bundler/runtime/with_clean_env_spec.rb b/spec/bundler/runtime/with_clean_env_spec.rb
deleted file mode 100644
index d18a0de485..0000000000
--- a/spec/bundler/runtime/with_clean_env_spec.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe "Bundler.with_env helpers" do
- describe "Bundler.original_env" do
- before do
- gemfile ""
- bundle "install --path vendor/bundle"
- end
-
- it "should return the PATH present before bundle was activated", :ruby_repo do
- code = "print Bundler.original_env['PATH']"
- path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo"
- with_path_as(path) do
- result = bundle("exec ruby -e #{code.dump}")
- expect(result).to eq(path)
- end
- end
-
- it "should return the GEM_PATH present before bundle was activated" do
- code = "print Bundler.original_env['GEM_PATH']"
- gem_path = ENV["GEM_PATH"] + ":/foo"
- with_gem_path_as(gem_path) do
- result = bundle("exec ruby -e #{code.inspect}")
- expect(result).to eq(gem_path)
- end
- end
-
- it "works with nested bundle exec invocations", :ruby_repo do
- create_file("exe.rb", <<-'RB')
- count = ARGV.first.to_i
- exit if count < 0
- STDERR.puts "#{count} #{ENV["PATH"].end_with?(":/foo")}"
- if count == 2
- ENV["PATH"] = "#{ENV["PATH"]}:/foo"
- end
- exec("ruby", __FILE__, (count - 1).to_s)
- RB
- path = `getconf PATH`.strip + File::PATH_SEPARATOR + File.dirname(Gem.ruby)
- with_path_as(path) do
- bundle!("exec ruby #{bundled_app("exe.rb")} 2")
- end
- expect(err).to eq <<-EOS.strip
-2 false
-1 true
-0 true
- EOS
- end
- end
-
- describe "Bundler.clean_env" do
- before do
- gemfile ""
- bundle "install --path vendor/bundle"
- end
-
- it "should delete BUNDLE_PATH" do
- code = "print Bundler.clean_env.has_key?('BUNDLE_PATH')"
- ENV["BUNDLE_PATH"] = "./foo"
- result = bundle("exec ruby -e #{code.inspect}")
- expect(result).to eq("false")
- end
-
- it "should remove '-rbundler/setup' from RUBYOPT" do
- code = "print Bundler.clean_env['RUBYOPT']"
- ENV["RUBYOPT"] = "-W2 -rbundler/setup"
- result = bundle("exec ruby -e #{code.inspect}")
- expect(result).not_to include("-rbundler/setup")
- end
-
- it "should clean up RUBYLIB", :ruby_repo do
- code = "print Bundler.clean_env['RUBYLIB']"
- ENV["RUBYLIB"] = root.join("lib").to_s + File::PATH_SEPARATOR + "/foo"
- result = bundle("exec ruby -e #{code.inspect}")
- expect(result).to eq("/foo")
- end
-
- it "should restore the original MANPATH" do
- code = "print Bundler.clean_env['MANPATH']"
- ENV["MANPATH"] = "/foo"
- ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original"
- result = bundle("exec ruby -e #{code.inspect}")
- expect(result).to eq("/foo-original")
- end
- 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 { ENV.to_hash }
- 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" do
- it "should set ENV to clean_env in the block" do
- expected = Bundler.clean_env
- actual = Bundler.with_clean_env { ENV.to_hash }
- expect(actual).to eq(expected)
- end
-
- it "should restore the environment after execution" do
- Bundler.with_clean_env do
- ENV["FOO"] = "hello"
- end
-
- expect(ENV).not_to have_key("FOO")
- end
- end
-
- describe "Bundler.clean_system", :ruby => ">= 1.9" do
- it "runs system inside with_clean_env" do
- Bundler.clean_system(%(echo 'if [ "$BUNDLE_PATH" = "" ]; then exit 42; else exit 1; fi' | /bin/sh))
- expect($?.exitstatus).to eq(42)
- end
- end
-
- describe "Bundler.clean_exec", :ruby => ">= 1.9" do
- it "runs exec inside with_clean_env" do
- pid = Kernel.fork do
- Bundler.clean_exec(%(echo 'if [ "$BUNDLE_PATH" = "" ]; then exit 42; else exit 1; fi' | /bin/sh))
- end
- Process.wait(pid)
- expect($?.exitstatus).to eq(42)
- end
- end
-end
diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb
index 7293f0e57b..27ddc6a771 100644
--- a/spec/bundler/spec_helper.rb
+++ b/spec/bundler/spec_helper.rb
@@ -1,156 +1,192 @@
# frozen_string_literal: true
-$:.unshift File.expand_path("..", __FILE__)
-$:.unshift File.expand_path("../../lib", __FILE__)
-require "bundler/psyched_yaml"
-require "fileutils"
-require "uri"
-require "digest/sha1"
-require File.expand_path("../support/path.rb", __FILE__)
+require "psych"
+require "bundler/vendored_fileutils"
+require "bundler/vendored_uri"
+require "digest"
-begin
- require "rubygems"
- spec = Gem::Specification.load(Spec::Path.gemspec.to_s)
- rspec = spec.dependencies.find {|d| d.name == "rspec" }
- gem "rspec", rspec.requirement.to_s
- require "rspec"
-rescue LoadError
- abort "Run rake spec:deps to install development dependencies"
-end
-
-if File.expand_path(__FILE__) =~ %r{([^\w/\.])}
+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
+# Bundler CLI will have different help text depending on whether any of these
+# variables is set, since the `-e` flag `bundle gem` with require an explicit
+# value if they are not set, but will use their value by default if set. So make
+# sure they are `nil` before loading bundler to get a consistent help text,
+# since some tests rely on that.
+ENV["EDITOR"] = nil
+ENV["VISUAL"] = nil
+ENV["BUNDLER_EDITOR"] = nil
require "bundler"
-# Require the correct version of popen for the current platform
-if RbConfig::CONFIG["host_os"] =~ /mingw|mswin/
- begin
- require "win32/open3"
- rescue LoadError
- abort "Run `gem install win32-open3` to be able to run specs"
+# If we use shared GEM_HOME and install multiple versions, it may cause
+# unexpected test failures.
+gem "diff-lcs", "< 2.0"
+
+require "rspec/core"
+require "rspec/expectations"
+require "rspec/mocks"
+require "rspec/support/differ"
+gem "rubygems-generate_index"
+require "rubygems/indexer"
+
+require_relative "support/builders"
+require_relative "support/checksums"
+require_relative "support/filters"
+require_relative "support/helpers"
+require_relative "support/indexes"
+require_relative "support/matchers"
+require_relative "support/permissions"
+require_relative "support/platforms"
+require_relative "support/shards"
+
+begin
+ raise LoadError if File.exist?(File.expand_path("../../lib/bundler/bundler.gemspec", __dir__))
+
+ gem "simplecov_json_formatter"
+ require "simplecov"
+
+ SimpleCov.start do
+ command_name "bundler:#{Process.pid}"
+ root File.expand_path("../bundler", __dir__)
+ coverage_dir File.expand_path("../coverage", __dir__)
+
+ add_filter "/spec/"
+ add_filter "/test/"
+ add_filter "/lib/rubygems/"
+ add_filter "/lib/bundler/vendor/"
+ add_filter "/tool/"
+ add_filter "/tmp/"
+ add_filter ".gemspec"
end
-else
- require "open3"
-end
-Dir["#{File.expand_path("../support", __FILE__)}/*.rb"].each do |file|
- require file unless file.end_with?("hax.rb")
+ SimpleCov.print_error_status = false
+ SimpleCov.at_exit do
+ $stdout = File.open(File::NULL, "w")
+ SimpleCov.result.format!
+ ensure
+ $stdout = STDOUT
+ end
+rescue LoadError
+ # SimpleCov is not installed
end
$debug = false
-Spec::Rubygems.setup
-FileUtils.rm_rf(Spec::Path.gem_repo1)
-ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -r#{Spec::Path.spec_dir}/support/hax.rb"
-ENV["BUNDLE_SPEC_RUN"] = "true"
-ENV["BUNDLE_PLUGINS"] = "true"
-
-# Don't wrap output in tests
-ENV["THOR_COLUMNS"] = "10000"
-
-Spec::CodeClimate.setup
-
module Gem
- def self.ruby= ruby
+ def self.ruby=(ruby)
@ruby = ruby
end
end
RSpec.configure do |config|
config.include Spec::Builders
+ config.include Spec::Checksums
config.include Spec::Helpers
config.include Spec::Indexes
config.include Spec::Matchers
config.include Spec::Path
- config.include Spec::Rubygems
config.include Spec::Platforms
- config.include Spec::Sudo
config.include Spec::Permissions
+ config.include Spec::Shards
# 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.backtrace_exclusion_patterns <<
+ %r{./spec/(spec_helper\.rb|support/.+)}
+
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
+ config.fail_fast ||= 25 if ENV["CI"]
+
+ config.bisect_runner = :shell
+
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
- if ENV["BUNDLER_SUDO_TESTS"] && Spec::Sudo.present?
- config.filter_run :sudo => true
- else
- config.filter_run_excluding :sudo => true
+ c.max_formatted_output_length = 1000
end
- if ENV["BUNDLER_REALWORLD_TESTS"]
- config.filter_run :realworld => true
- else
- config.filter_run_excluding :realworld => true
+ config.mock_with :rspec do |mocks|
+ mocks.allow_message_expectations_on_nil = false
end
- git_version = Bundler::Source::Git::GitProxy.new(nil, nil, nil).version
+ config.before :suite do
+ Gem.ruby = ENV["RUBY"] if ENV["RUBY"]
+
+ require_relative "support/rubygems_ext"
+ Spec::Rubygems.test_setup
+
+ # Disable retry delays in tests to speed them up
+ Bundler::Retry.default_base_delay = 0
+
+ # Simulate bundler has not yet been loaded
+ ENV.replace(ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) })
+
+ ENV["BUNDLER_SPEC_RUN"] = "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["XDG_CACHE_HOME"] = nil
+ ENV["GEMRC"] = nil
+
+ # Prevent tests from modifying the user's global git config.
+ # GIT_CONFIG_GLOBAL and GIT_CONFIG_NOSYSTEM are available since Git 2.32.
+ git_version = `git --version`[/(\d+\.\d+\.\d+)/, 1]
+ if Gem::Version.new(git_version) >= Gem::Version.new("2.32")
+ ENV["GIT_CONFIG_GLOBAL"] = File.join(ENV["HOME"], ".gitconfig")
+ ENV["GIT_CONFIG_NOSYSTEM"] = "1"
+ end
- config.filter_run_excluding :ruby => LessThanProc.with(RUBY_VERSION)
- config.filter_run_excluding :rubygems => LessThanProc.with(Gem::VERSION)
- config.filter_run_excluding :git => LessThanProc.with(git_version)
- config.filter_run_excluding :rubygems_master => (ENV["RGV"] != "master")
- config.filter_run_excluding :ruby_repo => !!(ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"])
+ # Don't wrap output in tests
+ ENV["THOR_COLUMNS"] = "10000"
- config.filter_run_when_matching :focus unless ENV["CI"]
+ extend(Spec::Builders)
- original_wd = Dir.pwd
- original_env = ENV.to_hash
+ build_repo1
- config.expect_with :rspec do |c|
- c.syntax = :expect
+ reset!
end
- config.before :suite do
- @orig_ruby = if ENV['BUNDLE_RUBY']
- ruby = Gem.ruby
- Gem.ruby = ENV['BUNDLE_RUBY']
- ruby
- end
- end
+ config.around :each do |example|
+ default_system_gems
- config.before :all do
- build_repo1
- # HACK: necessary until rspec-mocks > 3.5.0 is used
- # see https://github.com/bundler/bundler/pull/5363#issuecomment-278089256
- if RUBY_VERSION < "1.9"
- FileUtils.module_eval do
- alias_method :mkpath, :mkdir_p
- module_function :mkpath
+ 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
- end
-
- config.before :each do
+ ensure
reset!
- system_gems []
- in_app_root
- @all_output = String.new
end
- config.after :each do |example|
- @all_output.strip!
- if example.exception && !@all_output.empty?
- warn @all_output unless config.formatters.grep(RSpec::Core::Formatters::DocumentationFormatter).empty?
- message = example.exception.message + "\n\nCommands:\n#{@all_output}"
- (class << example.exception; self; end).send(:define_method, :message) do
- message
- end
- end
+ Spec::Shards::EXAMPLE_MAPPINGS.each do |tag, file_paths|
+ file_pattern = Regexp.union(file_paths.map {|path| Regexp.new(Regexp.escape(path) + "$") })
- Dir.chdir(original_wd)
- ENV.replace(original_env)
+ config.define_derived_metadata(file_path: file_pattern) do |metadata|
+ metadata[tag] = true
+ end
end
- config.after :suite do
- Gem.ruby = @orig_ruby
- end
+ config.before(:context) do |example|
+ metadata = example.class.metadata
+ if metadata[:type] != :aruba && !metadata[:realworld] && metadata.keys.none? {|k| Spec::Shards::EXAMPLE_MAPPINGS.keys.include?(k) }
+ warn "#{metadata[:file_path]} is not assigned to any shard. see spec/support/shards.rb for details."
+ end
+ end unless Spec::Path.ruby_core?
end
diff --git a/spec/bundler/support/activate.rb b/spec/bundler/support/activate.rb
new file mode 100644
index 0000000000..143b77833d
--- /dev/null
+++ b/spec/bundler/support/activate.rb
@@ -0,0 +1,9 @@
+# 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.to_s)
+bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate)
diff --git a/spec/bundler/support/artifice/compact_index.rb b/spec/bundler/support/artifice/compact_index.rb
index 9111ed8211..ebc4d0ae5b 100644
--- a/spec/bundler/support/artifice/compact_index.rb
+++ b/spec/bundler/support/artifice/compact_index.rb
@@ -1,121 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-$LOAD_PATH.unshift Dir[base_system_gems.join("gems/compact_index*/lib")].first.to_s
-require "compact_index"
-
-class CompactIndexAPI < Endpoint
- helpers do
- def load_spec(name, version, platform, gem_repo)
- full_name = "#{name}-#{version}"
- full_name += "-#{platform}" if platform != "ruby"
- Marshal.load(Gem.inflate(File.open(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")).read))
- 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 => 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)
- if body.respond_to?(:byteslice)
- body.byteslice(range)
- else # pre-1.9.3
- body.unpack("@#{range.first}a#{range.end + 1}").first
- end
- end
-
- def gems(gem_repo = 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
- nil
- end
- CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil,
- deps, spec.required_ruby_version, spec.required_rubygems_version)
- 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")
- file.delete if file.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
+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
index 6d15b54b85..f771f7d1f0 100644
--- a/spec/bundler/support/artifice/compact_index_api_missing.rb
+++ b/spec/bundler/support/artifice/compact_index_api_missing.rb
@@ -1,17 +1,13 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexApiMissing < CompactIndexAPI
get "/fetch/actual/gem/:id" do
- $stderr.puts params[:id]
- if params[:id] == "rack-1.0.gemspec.rz"
- halt 404
- else
- File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
- end
+ 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
index bffb5b9e2b..b9115cdd86 100644
--- a/spec/bundler/support/artifice/compact_index_basic_authentication.rb
+++ b/spec/bundler/support/artifice/compact_index_basic_authentication.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexBasicAuthentication < CompactIndexAPI
before do
@@ -11,4 +10,6 @@ class CompactIndexBasicAuthentication < CompactIndexAPI
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
index 4ac328736c..83b147d2ae 100644
--- a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
+++ b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
@@ -1,15 +1,16 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexChecksumMismatch < CompactIndexAPI
get "/versions" do
- headers "ETag" => quote("123")
+ headers "Repr-Digest" => "sha-256=:ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=:"
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain"
- body ""
+ body "content does not match the checksum"
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
index b788a852cf..5d55b8a72b 100644
--- a/spec/bundler/support/artifice/compact_index_concurrent_download.rb
+++ b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
@@ -1,18 +1,18 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+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.read(versions) == "" || raise("Original file should be present and empty")
+ # Verify the original content hasn't been deleted, e.g. on a retry
+ data = File.binread(versions)
+ data == "created_at" || raise("Original file should be present with expected content")
# Verify this is only requested once for a partial download
- env["HTTP_RANGE"] || raise("Missing Range header for expected partial download")
+ env["HTTP_RANGE"] == "bytes=#{data.bytesize - 1}-" || 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
@@ -20,7 +20,7 @@ class CompactIndexConcurrentDownload < CompactIndexAPI
etag_response do
file = tmp("versions.list")
- file.delete if file.file?
+ FileUtils.rm_f(file)
file = CompactIndex::VersionsFile.new(file.to_s)
file.create(gems)
file.contents
@@ -28,4 +28,6 @@ class CompactIndexConcurrentDownload < CompactIndexAPI
end
end
+require_relative "helpers/artifice"
+
Artifice.activate_with(CompactIndexConcurrentDownload)
diff --git a/spec/bundler/support/artifice/compact_index_cooldown.rb b/spec/bundler/support/artifice/compact_index_cooldown.rb
new file mode 100644
index 0000000000..85e3173c98
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_cooldown.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index_cooldown"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexCooldownAPI)
diff --git a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
index 0c417f0580..282e9c8961 100644
--- a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
+++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexCredsDiffHost < CompactIndexAPI
helpers do
@@ -10,7 +9,7 @@ class CompactIndexCredsDiffHost < CompactIndexAPI
end
def authorized?
- auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w(user pass)
+ auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w[user pass]
end
def protected!
@@ -25,14 +24,16 @@ class CompactIndexCredsDiffHost < CompactIndexAPI
end
get "/gems/:id" do
- redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ redirect "http://diffhost.test/no/creds/#{params[:id]}"
end
get "/no/creds/:id" do
if request.host.include?("diffhost") && !auth.provided?
- File.read("#{gem_repo1}/gems/#{params[:id]}")
+ 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_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb
new file mode 100644
index 0000000000..6c62166051
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_etag_match.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexEtagMatch < CompactIndexAPI
+ get "/versions" do
+ raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"]
+ headers "ETag" => env["HTTP_IF_NONE_MATCH"]
+ status 304
+ body ""
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexEtagMatch)
diff --git a/spec/bundler/support/artifice/compact_index_extra.rb b/spec/bundler/support/artifice/compact_index_extra.rb
index 8a87fc4343..cd41b3ecca 100644
--- a/spec/bundler/support/artifice/compact_index_extra.rb
+++ b/spec/bundler/support/artifice/compact_index_extra.rb
@@ -1,36 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
-
-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.read("#{gem_repo2}/specs.4.8.gz")
- end
-
- get "/extra/prerelease_specs.4.8.gz" do
- File.read("#{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.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
- end
-
- get "/extra/gems/:id" do
- File.read("#{gem_repo2}/gems/#{params[:id]}")
- end
-end
+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
index 844a9ca9f2..8b9d304ab4 100644
--- a/spec/bundler/support/artifice/compact_index_extra_api.rb
+++ b/spec/bundler/support/artifice/compact_index_extra_api.rb
@@ -1,51 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
-
-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")
- file.delete if file.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.read("#{gem_repo4}/specs.4.8.gz")
- end
-
- get "/extra/prerelease_specs.4.8.gz" do
- File.read("#{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.read("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
- end
-
- get "/extra/gems/:id" do
- File.read("#{gem_repo4}/gems/#{params[:id]}")
- end
-end
+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
index 2af5ce9c27..255c89afdb 100644
--- a/spec/bundler/support/artifice/compact_index_extra_missing.rb
+++ b/spec/bundler/support/artifice/compact_index_extra_missing.rb
@@ -1,16 +1,17 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index_extra", __FILE__)
-Artifice.deactivate
+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.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ 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
index b25eea94e7..18c30ed9a2 100644
--- a/spec/bundler/support/artifice/compact_index_forbidden.rb
+++ b/spec/bundler/support/artifice/compact_index_forbidden.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexForbidden < CompactIndexAPI
get "/versions" do
@@ -9,4 +8,6 @@ class CompactIndexForbidden < CompactIndexAPI
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
index 6c1ab2def6..4f82bf3812 100644
--- a/spec/bundler/support/artifice/compact_index_host_redirect.rb
+++ b/spec/bundler/support/artifice/compact_index_host_redirect.rb
@@ -1,10 +1,9 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexHostRedirect < CompactIndexAPI
- get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do
redirect "http://bundler.localgemserver.test#{request.path_info}"
end
@@ -17,4 +16,6 @@ class CompactIndexHostRedirect < CompactIndexAPI
end
end
+require_relative "helpers/artifice"
+
Artifice.activate_with(CompactIndexHostRedirect)
diff --git a/spec/bundler/support/artifice/compact_index_mirror_down.rb b/spec/bundler/support/artifice/compact_index_mirror_down.rb
new file mode 100644
index 0000000000..88983c715d
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_mirror_down.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+require_relative "helpers/artifice"
+require_relative "helpers/rack_request"
+
+module Artifice
+ module Net
+ class HTTPMirrorDown < HTTP
+ def connect
+ raise SocketError if address == "gem.mirror"
+
+ super
+ end
+ end
+
+ HTTP.endpoint = CompactIndexAPI
+ end
+
+ replace_net_http(Net::HTTPMirrorDown)
+end
diff --git a/spec/bundler/support/artifice/compact_index_no_checksums.rb b/spec/bundler/support/artifice/compact_index_no_checksums.rb
new file mode 100644
index 0000000000..ecb7fc7d7c
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_no_checksums.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexNoChecksums < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ gem.versions.map(&:number).join("\n")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexNoChecksums)
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
index bf6feab877..f111d91ef9 100644
--- a/spec/bundler/support/artifice/compact_index_partial_update.rb
+++ b/spec/bundler/support/artifice/compact_index_partial_update.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexPartialUpdate < CompactIndexAPI
# Stub the server to never return 304s. This simulates the behaviour of
@@ -17,21 +16,23 @@ class CompactIndexPartialUpdate < CompactIndexAPI
)
# Verify a cached copy of the versions file exists
- unless File.read(cached_versions_path).start_with?("created_at: ")
+ 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.read(cached_versions_path).bytesize - 1}-"
- raise("Range header should be present, and start from the index of the final byte of the cache.")
+ 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. #{env["HTTP_RANGE"].inspect}")
end
etag_response do
# Return the exact contents of the cache.
- File.read(cached_versions_path)
+ 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_bad_digest.rb b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb
new file mode 100644
index 0000000000..ac04336636
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+# The purpose of this Artifice is to test that an incremental response is invalidated
+# and a second request is issued for the full content.
+class CompactIndexPartialUpdateBadDigest < CompactIndexAPI
+ def partial_update_bad_digest
+ response_body = yield
+ if request.env["HTTP_RANGE"]
+ headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest("wrong digest on ranged request")}:"
+ else
+ headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:"
+ end
+ 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_bad_digest do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents([], calculate_info_checksums: true)
+ end
+ end
+
+ get "/info/:name" do
+ partial_update_bad_digest do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexPartialUpdateBadDigest)
diff --git a/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb
new file mode 100644
index 0000000000..99bae039f0
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+# The purpose of this Artifice is to test that an incremental response is ignored
+# when the digest is not present to verify that the partial response is valid.
+class CompactIndexPartialUpdateNoDigestNotIncremental < CompactIndexAPI
+ def partial_update_no_digest
+ 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_digest 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_digest 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(CompactIndexPartialUpdateNoDigestNotIncremental)
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_ignored.rb b/spec/bundler/support/artifice/compact_index_range_ignored.rb
new file mode 100644
index 0000000000..2303682c1f
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_range_ignored.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexRangeIgnored < CompactIndexAPI
+ # Stub the server to not return 304 so that we don't bypass all the logic
+ 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).size > 0
+ 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.delete("HTTP_RANGE")
+ raise("Expected client to write the full response on the first try")
+ end
+
+ 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(CompactIndexRangeIgnored)
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
index ff1d3e43bc..f7ba393239 100644
--- a/spec/bundler/support/artifice/compact_index_redirects.rb
+++ b/spec/bundler/support/artifice/compact_index_redirects.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexRedirect < CompactIndexAPI
get "/fetch/actual/gem/:id" do
@@ -17,4 +16,6 @@ class CompactIndexRedirect < CompactIndexAPI
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
index 49a072d2b9..96259385e7 100644
--- a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
+++ b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexStrictBasicAuthentication < CompactIndexAPI
before do
@@ -11,9 +10,11 @@ class CompactIndexStrictBasicAuthentication < CompactIndexAPI
# Only accepts password == "password"
unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
- halt 403, "Authentication failed"
+ 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
index 25935f5e5d..15850599b6 100644
--- a/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb
+++ b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+require_relative "helpers/compact_index"
class CompactIndexWrongDependencies < CompactIndexAPI
get "/info/:name" do
@@ -13,4 +12,6 @@ class CompactIndexWrongDependencies < CompactIndexAPI
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
index 3a12a59ae7..9bd2ca0a9d 100644
--- a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
+++ b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-require File.expand_path("../compact_index", __FILE__)
-Artifice.deactivate
+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 }
+ # This generates the hexdigest "2222222222222222222222222222222222222222222222222222222222222222"
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=" }
versions = gem ? gem.versions : []
versions.each {|v| v.checksum = checksum }
CompactIndex.info(versions)
@@ -16,4 +16,6 @@ class CompactIndexWrongGemChecksum < CompactIndexAPI
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
index 771d431f22..15242a7942 100644
--- a/spec/bundler/support/artifice/endpoint.rb
+++ b/spec/bundler/support/artifice/endpoint.rb
@@ -1,73 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../../path.rb", __FILE__)
-require Spec::Path.root.join("lib/bundler/deprecate")
-include Spec::Path
-$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,rack,tilt,sinatra}-*/lib")].map(&:to_s))
-require "artifice"
-require "sinatra/base"
-
-class Endpoint < Sinatra::Base
- GEM_REPO = Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1)
- set :raise_errors, true
- set :show_exceptions, false
-
- helpers do
- def dependencies_for(gem_names, gem_repo = GEM_REPO)
- return [] if gem_names.nil? || gem_names.empty?
-
- require "rubygems"
- require "bundler"
- Bundler::Deprecate.skip_during do
- 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
- end
-
- def load_spec(name, version, platform, gem_repo)
- full_name = "#{name}-#{version}"
- full_name += "-#{platform}" if platform != "ruby"
- Marshal.load(Gem.inflate(File.open(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")).read))
- end
- end
-
- get "/quick/Marshal.4.8/:id" do
- redirect "/fetch/actual/gem/#{params[:id]}"
- end
-
- get "/fetch/actual/gem/:id" do
- File.read("#{GEM_REPO}/quick/Marshal.4.8/#{params[:id]}")
- end
-
- get "/gems/:id" do
- File.read("#{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.read("#{GEM_REPO}/specs.4.8.gz")
- end
-
- get "/prerelease_specs.4.8.gz" do
- File.read("#{GEM_REPO}/prerelease_specs.4.8.gz")
- end
-end
+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
index 993630b79e..9dd373bbf6 100644
--- a/spec/bundler/support/artifice/endpoint_500.rb
+++ b/spec/bundler/support/artifice/endpoint_500.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require File.expand_path("../../path.rb", __FILE__)
-include Spec::Path
-$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,rack,tilt,sinatra}-*/lib")].map(&:to_s))
+require_relative "../path"
-require "artifice"
-require "sinatra/base"
+$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths)
-Artifice.deactivate
+require "sinatra/base"
class Endpoint500 < Sinatra::Base
before do
@@ -15,4 +12,6 @@ class Endpoint500 < Sinatra::Base
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
index 21ad9117ed..6bdc5896d6 100644
--- a/spec/bundler/support/artifice/endpoint_api_forbidden.rb
+++ b/spec/bundler/support/artifice/endpoint_api_forbidden.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointApiForbidden < Endpoint
get "/api/v1/dependencies" do
@@ -9,4 +8,6 @@ class EndpointApiForbidden < Endpoint
end
end
+require_relative "helpers/artifice"
+
Artifice.activate_with(EndpointApiForbidden)
diff --git a/spec/bundler/support/artifice/endpoint_api_missing.rb b/spec/bundler/support/artifice/endpoint_api_missing.rb
deleted file mode 100644
index 6f5b5f1323..0000000000
--- a/spec/bundler/support/artifice/endpoint_api_missing.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-
-Artifice.deactivate
-
-class EndpointApiMissing < Endpoint
- get "/fetch/actual/gem/:id" do
- $stderr.puts params[:id]
- if params[:id] == "rack-1.0.gemspec.rz"
- halt 404
- else
- File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
- end
- end
-end
-
-Artifice.activate_with(EndpointApiMissing)
diff --git a/spec/bundler/support/artifice/endpoint_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_basic_authentication.rb
index 9fafd51a3d..e8e3569e63 100644
--- a/spec/bundler/support/artifice/endpoint_basic_authentication.rb
+++ b/spec/bundler/support/artifice/endpoint_basic_authentication.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointBasicAuthentication < Endpoint
before do
@@ -11,4 +10,6 @@ class EndpointBasicAuthentication < Endpoint
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
index cd152848fe..9cbb4de61a 100644
--- a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
+++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointCredsDiffHost < Endpoint
helpers do
@@ -10,7 +9,7 @@ class EndpointCredsDiffHost < Endpoint
end
def authorized?
- auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w(user pass)
+ auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w[user pass]
end
def protected!
@@ -25,14 +24,16 @@ class EndpointCredsDiffHost < Endpoint
end
get "/gems/:id" do
- redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ redirect "http://diffhost.test/no/creds/#{params[:id]}"
end
get "/no/creds/:id" do
if request.host.include?("diffhost") && !auth.provided?
- File.read("#{gem_repo1}/gems/#{params[:id]}")
+ 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
index ed4e87e65f..021fd435fe 100644
--- a/spec/bundler/support/artifice/endpoint_extra.rb
+++ b/spec/bundler/support/artifice/endpoint_extra.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointExtra < Endpoint
get "/extra/api/v1/dependencies" do
@@ -9,11 +8,11 @@ class EndpointExtra < Endpoint
end
get "/extra/specs.4.8.gz" do
- File.read("#{gem_repo2}/specs.4.8.gz")
+ File.binread("#{gem_repo2}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
- File.read("#{gem_repo2}/prerelease_specs.4.8.gz")
+ File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
@@ -21,12 +20,14 @@ class EndpointExtra < Endpoint
end
get "/extra/fetch/actual/gem/:id" do
- File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
- File.read("#{gem_repo2}/gems/#{params[:id]}")
+ 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
index 1e9e1dc60d..a965af6e73 100644
--- a/spec/bundler/support/artifice/endpoint_extra_api.rb
+++ b/spec/bundler/support/artifice/endpoint_extra_api.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointExtraApi < Endpoint
get "/extra/api/v1/dependencies" do
@@ -10,11 +9,11 @@ class EndpointExtraApi < Endpoint
end
get "/extra/specs.4.8.gz" do
- File.read("#{gem_repo4}/specs.4.8.gz")
+ File.binread("#{gem_repo4}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
- File.read("#{gem_repo4}/prerelease_specs.4.8.gz")
+ File.binread("#{gem_repo4}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
@@ -22,12 +21,14 @@ class EndpointExtraApi < Endpoint
end
get "/extra/fetch/actual/gem/:id" do
- File.read("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
+ File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
- File.read("#{gem_repo4}/gems/#{params[:id]}")
+ 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
index dc79705a26..73e2defb32 100644
--- a/spec/bundler/support/artifice/endpoint_extra_missing.rb
+++ b/spec/bundler/support/artifice/endpoint_extra_missing.rb
@@ -1,16 +1,17 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint_extra", __FILE__)
-Artifice.deactivate
+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.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ 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
index 8a85a41784..742e563f07 100644
--- a/spec/bundler/support/artifice/endpoint_fallback.rb
+++ b/spec/bundler/support/artifice/endpoint_fallback.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointFallback < Endpoint
DEPENDENCY_LIMIT = 60
@@ -15,4 +14,6 @@ class EndpointFallback < Endpoint
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
index 250416d8cc..6ce51bed93 100644
--- a/spec/bundler/support/artifice/endpoint_host_redirect.rb
+++ b/spec/bundler/support/artifice/endpoint_host_redirect.rb
@@ -1,10 +1,9 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointHostRedirect < Endpoint
- get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do
redirect "http://bundler.localgemserver.test#{request.path_info}"
end
@@ -13,4 +12,6 @@ class EndpointHostRedirect < Endpoint
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
index 0fb75ebf31..74ce321de6 100644
--- a/spec/bundler/support/artifice/endpoint_marshal_fail.rb
+++ b/spec/bundler/support/artifice/endpoint_marshal_fail.rb
@@ -1,12 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint_fallback", __FILE__)
-Artifice.deactivate
-
-class EndpointMarshalFail < EndpointFallback
- get "/api/v1/dependencies" do
- "f0283y01hasf"
- end
-end
+require_relative "helpers/endpoint_marshal_fail"
+require_relative "helpers/artifice"
Artifice.activate_with(EndpointMarshalFail)
diff --git a/spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb
index f1f8dc5700..ea4cfbe965 100644
--- a/spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb
+++ b/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint_marshal_fail", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint_marshal_fail"
class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail
before do
@@ -11,4 +10,6 @@ class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail
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
index 9fb58ecb29..fed7a746b9 100644
--- a/spec/bundler/support/artifice/endpoint_mirror_source.rb
+++ b/spec/bundler/support/artifice/endpoint_mirror_source.rb
@@ -1,14 +1,17 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
+
+require_relative "helpers/endpoint"
class EndpointMirrorSource < Endpoint
get "/gems/:id" do
- if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/"
- File.read("#{gem_repo1}/gems/#{params[:id]}")
+ if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/" && request.env["HTTP_USER_AGENT"].start_with?("bundler")
+ 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
index f80d7600c2..84f546ba9d 100644
--- a/spec/bundler/support/artifice/endpoint_redirect.rb
+++ b/spec/bundler/support/artifice/endpoint_redirect.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointRedirect < Endpoint
get "/fetch/actual/gem/:id" do
@@ -13,4 +12,6 @@ class EndpointRedirect < Endpoint
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
index 4b32cbbf5b..dff360c5c5 100644
--- a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
+++ b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint"
class EndpointStrictBasicAuthentication < Endpoint
before do
@@ -11,9 +10,11 @@ class EndpointStrictBasicAuthentication < Endpoint
# Only accepts password == "password"
unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
- halt 403, "Authentication failed"
+ 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
index b15650f226..86b793e499 100644
--- a/spec/bundler/support/artifice/endpoint_timeout.rb
+++ b/spec/bundler/support/artifice/endpoint_timeout.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
-require File.expand_path("../endpoint_fallback", __FILE__)
-Artifice.deactivate
+require_relative "helpers/endpoint_fallback"
class EndpointTimeout < EndpointFallback
- SLEEP_TIMEOUT = 15
+ 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
index 1059c6df4e..5ddbc4e590 100644
--- a/spec/bundler/support/artifice/fail.rb
+++ b/spec/bundler/support/artifice/fail.rb
@@ -1,20 +1,11 @@
# frozen_string_literal: true
-require "net/http"
-begin
- require "net/https"
-rescue LoadError
- nil # net/https or openssl
-end
-
-# We can't use artifice here because it uses rack
-
-module Artifice; end # for < 2.0, Net::HTTP::Persistent::SSLReuse
+require_relative "../vendored_net_http"
-class Fail < Net::HTTP
- # Net::HTTP uses a @newimpl instance variable to decide whether
+class Fail < Gem::Net::HTTP
+ # Gem::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
+ # Gem::Net::HTTP, we must set it
@newimpl = true
def request(req, body = nil, &block)
@@ -26,14 +17,11 @@ class Fail < Net::HTTP
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"]}")
+ Errno::ENETUNREACH.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}")
end
end
-# Replace Net::HTTP with our failing subclass
-::Net.class_eval do
- remove_const(:HTTP)
- const_set(:HTTP, ::Fail)
-end
+require_relative "helpers/artifice"
+
+# Replace Gem::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..788268295c
--- /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 Gem::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(::Gem::Net::HTTP)
+ end
+
+ def self.replace_net_http(value)
+ ::Gem::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..e684aa8628
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/compact_index.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require_relative "endpoint"
+
+$LOAD_PATH.unshift Spec::Path.tmp_root.join("compact_index/lib").to_s
+require "compact_index"
+require "digest"
+
+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
+ etag = Digest::MD5.hexdigest(response_body)
+ headers "ETag" => quote(etag)
+ return if not_modified?(etag)
+ headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:"
+ 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?(etag)
+ etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
+
+ return unless etags.include?(etag)
+ status 304
+ body ""
+ end
+
+ def requested_range_for(response_body)
+ ranges = Rack::Utils.get_byte_ranges(env["HTTP_RANGE"], 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].flat_map do |filename|
+ spec_index = gem_repo.join(filename)
+ next [] unless File.exist?(spec_index)
+
+ Marshal.load(File.binread(spec_index)).map do |name, version, platform|
+ load_spec(name, version, platform, gem_repo)
+ end
+ end
+ end
+
+ specs.group_by(&:name).map do |name, versions|
+ gem_versions = versions.map do |spec|
+ deps = spec.runtime_dependencies.map do |d|
+ reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ")
+ CompactIndex::Dependency.new(d.name, reqs)
+ end
+ begin
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") do
+ Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").hexdigest
+ end
+ rescue StandardError
+ checksum = nil
+ end
+ build_gem_version(spec, deps, checksum)
+ end
+ CompactIndex::Gem.new(name, gem_versions)
+ end
+ end
+ end
+
+ def build_gem_version(spec, deps, checksum)
+ 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
+ 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_cooldown.rb b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb
new file mode 100644
index 0000000000..9920fd2c95
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "compact_index"
+
+class CompactIndexCooldownAPI < CompactIndexAPI
+ helpers do
+ def build_gem_version(spec, deps, checksum)
+ created_at = spec.date&.utc&.iso8601
+ CompactIndex::GemVersionV2.new(spec.version.version, spec.platform.to_s, checksum, nil,
+ deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s, created_at)
+ 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..9590611dfe
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/endpoint.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require_relative "../../path"
+
+$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths)
+
+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
+ set :host_authorization, permitted_hosts: [".example.org", ".local", ".mirror", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"]
+
+ 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.binread(gem_repo.join(filename)))
+ end.inject(:+)
+
+ all_specs.filter_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.runtime_dependencies.map do |dep|
+ [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")]
+ end,
+ }
+ end
+ 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..05ff034463
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/rack_request.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require "rack/test"
+require_relative "../../vendored_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 < ::Gem::Net::HTTP
+ class << self
+ attr_accessor :endpoint
+ end
+
+ # Gem::Net::HTTP uses a @newimpl instance variable to decide whether
+ # to use a legacy implementation. Since we are subclassing
+ # Gem::Net::HTTP, we must set it
+ @newimpl = true
+
+ # We don't need to connect, so blank out this method
+ def connect
+ end
+
+ # Replace the Gem::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 Gem::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 Gem::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 Gem::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 Gem::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 = ::Gem::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
+ res = ::Gem::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/vcr.rb b/spec/bundler/support/artifice/vcr.rb
new file mode 100644
index 0000000000..0bf5ade8f6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require_relative "../vendored_net_http"
+require_relative "../path"
+
+CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes".freeze
+USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt".freeze
+CASSETTE_NAME = ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME") { "realworld" }
+
+class BundlerVCRHTTP < Gem::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.git_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 = ::Gem::Net::BufferedIO.new(response_file)
+ ::Gem::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 Gem::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
index c18ca454ec..3056540beb 100644
--- a/spec/bundler/support/artifice/windows.rb
+++ b/spec/bundler/support/artifice/windows.rb
@@ -1,22 +1,17 @@
# frozen_string_literal: true
-require File.expand_path("../../path.rb", __FILE__)
-include Spec::Path
-
-$LOAD_PATH.unshift Dir[base_system_gems.join("gems/artifice*/lib")].first.to_s
-$LOAD_PATH.unshift(*Dir[base_system_gems.join("gems/rack-*/lib")])
-$LOAD_PATH.unshift Dir[base_system_gems.join("gems/tilt*/lib")].first.to_s
-$LOAD_PATH.unshift Dir[base_system_gems.join("gems/sinatra*/lib")].first.to_s
-require "artifice"
-require "sinatra/base"
-Artifice.deactivate
+require_relative "../path"
+
+$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths)
+
+require "sinatra/base"
class Windows < Sinatra::Base
set :raise_errors, true
set :show_exceptions, false
helpers do
- def gem_repo
+ def default_gem_repo
Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1)
end
end
@@ -28,7 +23,7 @@ class Windows < Sinatra::Base
files.each do |file|
get "/#{file}" do
- File.read gem_repo.join(file)
+ File.binread default_gem_repo.join(file)
end
end
@@ -45,4 +40,6 @@ class Windows < Sinatra::Base
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..2eade4137b
--- /dev/null
+++ b/spec/bundler/support/build_metadata.rb
@@ -0,0 +1,53 @@
+# 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, version: Bundler::VERSION)
+ build_metadata = {
+ git_commit_sha: git_commit_sha,
+ built_at: release_date_for(version, dir: dir),
+ }
+
+ replace_build_metadata(build_metadata, dir: dir)
+ end
+
+ def reset_build_metadata(dir: source_root)
+ build_metadata = {
+ built_at: nil,
+ }
+
+ replace_build_metadata(build_metadata, dir: dir)
+ 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" : git("rev-parse --short HEAD", source_root).strip
+ end
+
+ def release_date_for(version, dir:)
+ changelog = File.expand_path("CHANGELOG.md", dir)
+ File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog)
+ end
+
+ extend self
+ end
+end
diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb
index db128d497b..43ab7e053d 100644
--- a/spec/bundler/support/builders.rb
+++ b/spec/bundler/support/builders.rb
@@ -1,9 +1,19 @@
# frozen_string_literal: true
+
require "bundler/shared_helpers"
require "shellwords"
+require "fileutils"
+require "rubygems/package"
+
+require_relative "build_metadata"
module Spec
module Builders
+ def self.extended(mod)
+ mod.extend Path
+ mod.extend Helpers
+ end
+
def self.constantize(name)
name.delete("-").upcase
end
@@ -18,28 +28,35 @@ module Spec
def build_repo1
build_repo gem_repo1 do
- build_gem "rack", %w(0.9.1 1.0.0) do |s|
- s.executables = "rackup"
- s.post_install_message = "Rack's post install message"
+ FileUtils.cp rake_path, "#{gem_repo1}/gems/"
+
+ build_gem "coffee-script-source"
+ build_gem "git"
+ build_gem "puma"
+ build_gem "minitest"
+
+ build_gem "myrack", %w[0.9.1 1.0.0] do |s|
+ s.executables = "myrackup"
+ s.post_install_message = "Myrack's post install message"
end
build_gem "thin" do |s|
- s.add_dependency "rack"
+ s.add_dependency "myrack"
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"
+ build_gem "myrack-obama" do |s|
+ s.add_dependency "myrack"
+ s.post_install_message = "Myrack-obama's post install message"
end
- build_gem "rack_middleware", "1.0" do |s|
- s.add_dependency "rack", "0.9.1"
+ build_gem "myrack_middleware", "1.0" do |s|
+ s.add_dependency "myrack", "0.9.1"
end
build_gem "rails", "2.3.2" do |s|
s.executables = "rails"
- s.add_dependency "rake", "10.0.2"
+ s.add_dependency "rake", rake_version
s.add_dependency "actionpack", "2.3.2"
s.add_dependency "activerecord", "2.3.2"
s.add_dependency "actionmailer", "2.3.2"
@@ -57,78 +74,72 @@ module Spec
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 "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 "rails_fail" do |s|
- s.add_dependency "activesupport", "= 1.2.3"
+ build_gem "rspec", "1.2.7", no_default: true do |s|
+ s.write "lib/spec.rb", "SPEC = '1.2.7'"
end
- build_gem "missing_dep" do |s|
- s.add_dependency "not_here"
+ build_gem "myrack-test", no_default: true do |s|
+ s.write "lib/myrack/test.rb", "MYRACK_TEST = '1.0'"
end
- build_gem "rspec", "1.2.7", :no_default => true do |s|
- s.write "lib/spec.rb", "SPEC = '1.2.7'"
+ build_gem "platform_specific" do |s|
+ s.platform = "java"
end
- build_gem "rack-test", :no_default => true do |s|
- s.write "lib/rack/test.rb", "RACK_TEST = '1.0'"
+ build_gem "platform_specific" do |s|
+ s.platform = "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}'"
+ s.platform = "x86-mswin32"
end
build_gem "platform_specific" do |s|
- s.platform = "java"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 JAVA'"
+ s.platform = "x64-mswin64"
end
build_gem "platform_specific" do |s|
- s.platform = "ruby"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ s.platform = "x86-mingw32"
end
build_gem "platform_specific" do |s|
- s.platform = "x86-mswin32"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 MSWIN'"
+ s.platform = "x64-mingw-ucrt"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "aarch64-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"
@@ -137,54 +148,12 @@ module Spec
build_gem "duradura", "7.0"
- build_gem "multiple_versioned_deps" do |s|
- s.add_dependency "weakling", ">= 0.0.1", "< 0.1"
- end
-
- build_gem "not_released", "1.0.pre"
-
- build_gem "has_prerelease", "1.0"
- build_gem "has_prerelease", "1.1.pre"
-
- build_gem "with_development_dependency" do |s|
- s.add_development_dependency "activesupport", "= 2.3.5"
- end
-
- build_gem "with_license" do |s|
- s.license = "MIT"
- end
-
- build_gem "with_implicit_rake_dep" do |s|
- s.extensions << "Rakefile"
- s.write "Rakefile", <<-RUBY
- task :default do
- path = File.expand_path("../lib", __FILE__)
- 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", __FILE__)
- 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
-
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'"
+ s.write "bin/bundle", "#!/usr/bin/env ruby\nputs 'FAIL'"
end
# The bundler 0.8 gem has a rubygems plugin that always loads :(
@@ -193,149 +162,63 @@ module Spec
s.write "lib/rubygems_plugin.rb", "require 'bundler/omg' ; puts 'FAIL'"
end
- build_gem "bundler_dep" do |s|
- s.add_dependency "bundler"
- end
-
# The yard gem iterates over Gem.source_index looking for plugins
build_gem "yard" do |s|
s.write "lib/yard.rb", <<-Y
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.10")
- specs = Gem::Specification
- else
- specs = Gem.source_index.find_name('')
- end
- specs.sort_by(&:name).each do |gem|
+ Gem::Specification.sort_by(&:name).each do |gem|
puts gem.full_name
end
Y
end
- # 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
-
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
- # 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", __FILE__)
- 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
-
- # 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
-
build_gem "foo"
-
- # 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
end
- def build_repo2(&blk)
- FileUtils.rm_rf gem_repo2
- FileUtils.cp_r gem_repo1, gem_repo2
- update_repo2(&blk) if block_given?
+ def build_repo2(**kwargs, &blk)
+ FileUtils.cp_r gem_repo1, gem_repo2, remove_destination: true
+ update_repo2(**kwargs, &blk) if block_given?
end
- def build_repo3
- build_repo gem_repo3 do
- build_gem "rack"
+ # A repo that has no pre-installed gems included. (The caller completely
+ # determines the contents with the block.)
+ #
+ # If the repo already exists, `#update_repo` will be called.
+ def build_repo3(**kwargs, &blk)
+ if File.exist?(gem_repo3)
+ update_repo(gem_repo3, &blk)
+ else
+ build_repo gem_repo3, **kwargs, &blk
end
- FileUtils.rm_rf Dir[gem_repo3("prerelease*")]
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)
+ # Like build_repo3, this is a repo that has no pre-installed gems included.
+ #
+ # If the repo already exists, `#udpate_repo` will be called
+ def build_repo4(**kwargs, &blk)
+ if File.exist?(gem_repo4)
+ update_repo gem_repo4, &blk
+ else
+ build_repo gem_repo4, **kwargs, &blk
+ end
end
- def update_repo4(&blk)
- update_repo(gem_repo4, &blk)
+ def update_repo2(**kwargs, &blk)
+ update_repo(gem_repo2, **kwargs, &blk)
end
- def update_repo2
- update_repo gem_repo2 do
- build_gem "rack", "1.2" do |s|
- s.executables = "rackup"
- end
- yield if block_given?
- end
+ def update_repo3(&blk)
+ update_repo(gem_repo3, &blk)
end
def build_security_repo
build_repo security_repo do
- build_gem "rack"
+ build_gem "myrack"
build_gem "signed_gem" do |s|
cert = "signing-cert.pem"
@@ -348,37 +231,60 @@ module Spec
end
end
- def build_repo(path, &blk)
- return if File.directory?(path)
- rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+ # A minimal fake irb console
+ def build_dummy_irb(version = "9.9.9")
+ build_gem "irb", version do |s|
+ s.write "lib/irb.rb", <<-RUBY
+ class IRB
+ class << self
+ def toplevel_binding
+ unless defined?(@toplevel_binding) && @toplevel_binding
+ TOPLEVEL_BINDING.eval %{
+ def self.__irb__; binding; end
+ IRB.instance_variable_set(:@toplevel_binding, __irb__)
+ class << self; undef __irb__; end
+ }
+ end
+ @toplevel_binding.eval('private')
+ @toplevel_binding
+ end
- if rake_path.nil?
- Spec::Path.base_system_gems.rmtree
- Spec::Rubygems.setup
- rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+ def __irb__
+ 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 __irb__
+ end
+ end
+ RUBY
end
+ end
- if rake_path
- FileUtils.mkdir_p("#{path}/gems")
- FileUtils.cp rake_path, "#{path}/gems/"
- else
- abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again."
- end
+ def build_repo(path, **kwargs, &blk)
+ return if File.directory?(path)
- update_repo(path, &blk)
+ FileUtils.mkdir_p("#{path}/gems")
+
+ update_repo(path,**kwargs, &blk)
end
- def update_repo(path)
- if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`"
+ def update_repo(path, build_compact_index: true)
+ exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") && RUBY_ENGINE != "jruby" ? "#{Module.nesting.first}#build_repo" : "build_repo"
+ if path == gem_repo1 && caller_locations(1, 1).first.label != exempted_caller
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_gems do
- Dir.chdir(path) { gem_command! :generate_index }
- end
+ options = { build_compact: build_compact_index }
+ Gem::Indexer.new(path, options).generate_index
ensure
@_build_path = nil
@_build_repo = nil
@@ -390,7 +296,7 @@ module Spec
index
end
- def build_spec(name, version, platform = nil, &block)
+ def build_spec(name, version = "0.0.1", platform = nil, &block)
Array(version).map do |v|
Gem::Specification.new do |s|
s.name = name
@@ -403,14 +309,14 @@ module Spec
end
end
- def build_dep(name, requirements = Gem::Requirement.default, type = :runtime)
- Bundler::Dependency.new(name, :version => requirements)
- end
-
def build_lib(name, *args, &blk)
build_with(LibBuilder, name, args, &blk)
end
+ def build_bundler(*args, &blk)
+ build_with(BundlerBuilder, "bundler", args, &blk)
+ end
+
def build_gem(name, *args, &blk)
build_with(GemBuilder, name, args, &blk)
end
@@ -419,20 +325,20 @@ module Spec
opts = args.last.is_a?(Hash) ? args.last : {}
builder = opts[:bare] ? GitBareBuilder : GitBuilder
spec = build_with(builder, name, args, &block)
- GitReader.new(opts[:path] || lib_path(spec.full_name))
+ 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(opts[:path] || lib_path(spec.full_name))
+ 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
+ private
def build_with(builder, name, args, &blk)
@_build_path ||= nil
@@ -446,7 +352,6 @@ module Spec
Array(versions).each do |version|
spec = builder.new(self, name, version)
- spec.authors = ["no one"] if !spec.authors || spec.authors.empty?
yield spec if block_given?
spec._build(options)
end
@@ -517,6 +422,58 @@ module Spec
alias_method :dep, :runtime
end
+ class BundlerBuilder
+ def initialize(context, name, version)
+ @context = context
+ @spec = Spec::Path.loaded_gemspec.dup
+ @spec.version = version || Bundler::VERSION
+ end
+
+ def required_ruby_version
+ @spec.required_ruby_version
+ end
+
+ def required_ruby_version=(x)
+ @spec.required_ruby_version = x
+ end
+
+ def _build(options = {})
+ full_name = "bundler-#{@spec.version}"
+ build_path = (options[:build_path] || @context.tmp) + full_name
+ bundler_path = build_path + "#{full_name}.gem"
+
+ FileUtils.mkdir_p build_path
+
+ @context.shipped_files.each do |shipped_file|
+ target_shipped_file = shipped_file
+ target_shipped_file = shipped_file.sub(/\Alibexec/, "exe") if @context.ruby_core?
+ target_shipped_file = build_path + target_shipped_file
+ target_shipped_dir = File.dirname(target_shipped_file)
+ FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir)
+ FileUtils.cp File.expand_path(shipped_file, @context.source_root), target_shipped_file, preserve: true
+ end
+
+ @context.replace_version_file(@spec.version, dir: build_path)
+ @context.replace_changelog(@spec.version, dir: build_path) if options[:released]
+
+ Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @spec.version.to_s)
+
+ Dir.chdir build_path do
+ Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do
+ Gem::Package.build(@spec)
+ end
+ end
+
+ if block_given?
+ yield(bundler_path)
+ else
+ FileUtils.mv bundler_path, options[:path]
+ end
+ ensure
+ FileUtils.rm_rf build_path
+ end
+ end
+
class LibBuilder
def initialize(context, name, version)
@context = context
@@ -530,6 +487,7 @@ module Spec
s.email = "foo@bar.baz"
s.homepage = "http://example.com"
s.license = "MIT"
+ s.required_ruby_version = ">= 3.0"
end
@files = {}
end
@@ -546,26 +504,18 @@ module Spec
@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
+ shebang = "#!/usr/bin/env ruby\n"
@spec.files << executable
- write executable, "#{shebang}require '#{@name}' ; puts #{Builders.constantize(@name)}"
+ 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"
-
- # exit 1 unless with_config("simple")
-
- extension_name = "very_simple_binary_c"
+ 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
@@ -575,11 +525,11 @@ module Spec
end
create_makefile extension_name
RUBY
- write "ext/very_simple_binary.c", <<-C
+ write "ext/#{name}.c", <<-C
#include "ruby.h"
- void Init_very_simple_binary_c() {
- rb_define_module("VerySimpleBinaryInC");
+ void Init_#{name}_c(void) {
+ rb_define_module("#{Builders.constantize(name)}_IN_C");
}
C
end
@@ -589,11 +539,20 @@ module Spec
if options[:rubygems_version]
@spec.rubygems_version = options[:rubygems_version]
- def @spec.mark_version; end
- def @spec.validate; 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
@@ -603,139 +562,120 @@ module Spec
@files["#{name}.gemspec"] = @spec.to_ruby
end
- unless options[:no_default]
- gem_source = options[:source] || "path@#{path}"
- @files = _default_files.
- merge("lib/#{name}/source.rb" => "#{Builders.constantize(name)}_SOURCE = #{gem_source.to_s.dump}").
- merge(@files)
- end
-
- @spec.authors = ["no one"]
-
@files.each do |file, source|
- file = Pathname.new(path).join(file)
- FileUtils.mkdir_p(file.dirname)
- File.open(file, "w") {|f| f.puts source }
+ full_path = Pathname.new(path).join(file)
+ FileUtils.mkdir_p(full_path.dirname)
+ File.open(full_path, "w") {|f| f.puts source }
+ FileUtils.chmod("+x", full_path) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file)
end
- @spec.files = @files.keys
path
end
def _default_files
- @_default_files ||= begin
- platform_string = " #{@spec.platform}" unless @spec.platform == Gem::Platform::RUBY
- { "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'" }
- end
+ @_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))
- Dir.chdir(path) do
- `git init`
- `git add *`
- `git config user.email "lol@wut.com"`
- `git config user.name "lolwut"`
- `git commit -m 'OMG INITIAL COMMIT'`
- end
+ 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))
- Dir.chdir(path) do
- `git init --bare`
- end
+ super(options.merge(path: path))
+ @context.git("init --bare", path)
end
end
class GitUpdater < LibBuilder
- def silently(str)
- `#{str} 2>#{Bundler::NULL}`
- end
-
def _build(options)
libpath = options[:path] || _default_path
update_gemspec = options[:gemspec] || false
source = options[:source] || "git@#{libpath}"
- Dir.chdir(libpath) do
- silently "git checkout master"
-
- if branch = options[:branch]
- raise "You can't specify `master` as the branch" if branch == "master"
- escaped_branch = Shellwords.shellescape(branch)
-
- if `git branch | grep #{escaped_branch}`.empty?
- silently("git branch #{escaped_branch}")
- end
-
- silently("git checkout #{escaped_branch}")
- elsif tag = options[:tag]
- `git tag #{Shellwords.shellescape(tag)}`
- elsif options[:remote]
- silently("git remote add origin file://#{options[:remote]}")
- elsif options[:push]
- silently("git push origin #{options[:push]}")
- end
+ 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 = `git rev-parse HEAD`.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))
- `git add *`
- `git commit -m "BUMP"`
+ 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 :path
+ attr_reader :context, :path
- def initialize(path)
+ def initialize(context, path)
+ @context = context
@path = path
end
def ref_for(ref, len = nil)
- ref = git "rev-parse #{ref}"
+ ref = context.git "rev-parse #{ref}", path
ref = ref[0..len] if len
ref
end
-
- private
-
- def git(cmd)
- Bundler::SharedHelpers.with_clean_git_env do
- Dir.chdir(@path) { `git #{cmd}`.strip }
- end
- end
end
class GemBuilder < LibBuilder
def _build(opts)
- lib_path = super(opts.merge(:path => @context.tmp(".tmp/#{@spec.full_name}"), :no_default => opts[:no_default]))
- Dir.chdir(lib_path) do
- destination = opts[:path] || _default_path
- FileUtils.mkdir_p(destination)
-
- @spec.authors = ["that guy"] if !@spec.authors || @spec.authors.empty?
-
- Bundler.rubygems.build(@spec, opts[:skip_validation])
- if opts[:to_system]
- `gem install --ignore-dependencies --no-ri --no-rdoc #{@spec.full_name}.gem`
- else
- FileUtils.mv("#{@spec.full_name}.gem", opts[:path] || _default_path)
+ 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 [:yaml, false].include?(opts[:gemspec])
+ Dir.chdir(lib_path) do
+ Bundler.rubygems.build(@spec, opts[:skip_validation])
end
+ elsif opts[:skip_validation]
+ Dir.chdir(lib_path) { Gem::Package.build(@spec, true) }
+ else
+ Dir.chdir(lib_path) { Gem::Package.build(@spec) }
+ 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
@@ -746,60 +686,63 @@ module Spec
class PluginBuilder < GemBuilder
def _default_files
- @_default_files ||= super.merge("plugins.rb" => "")
+ @_default_files ||= {
+ "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'",
+ "plugins.rb" => "",
+ }
end
end
- TEST_CERT = <<-CERT.gsub(/^\s*/, "")
+ TEST_CERT = <<~CERT
-----BEGIN CERTIFICATE-----
- MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux
+ MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMQwwCgYDVQQDDAN5b3Ux
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
+ ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkupYkg3Nd1oXM3fo0d
+ mVJBWNrni88lKDuIIQXwcKe6XCgiloZG708ecLTOws9+o9MkTl9Wtpf/WGXT98NK
+ EPUYakd2Fv1SuD1jWYlP7iDR6hB3RkWBm5ziujYftVJ4ZrPD42PLjDASvlh75Tvr
+ MeM7yq/qkcgNsd9dQyUvMNPks3tla9je7Dt7Auli2IN3CNXys7gIOfwJH0Bb/M6t
+ y7oUfpoUKAfLzwe61abztgDu1lSNgdFBM1kcxYflyh/FkX5TlAcWeAXzLrnxAXGR
+ UxXrxW4oPC+kZi/pDRBd7X4zQDx7bCmr1+FsS3M05i3w5E08Tt9iKRk4V8nCmE4i
+ k6UCAwEAAaNsMGowCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYE
+ FOOOFw5TNAqt/TcRRZEU3Dg/58XuMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYG
+ A1UdEgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBCwUAA4IBAQAy3xnmobxU
+ 1SyhHvoIXTJmG0wt1DQ/Dqwjy362LpEf1UHt29wtg1Mph58eVtl93z5Vd2t4/O77
+ E2BHpSu9ujc6/Br4+2uA/Qk/xRyLBtZAwty6J4uFvOOg985HonN+RCUZbKSUTmtA
+ TZvNtIDAZFQ8Tu75K4gIBxDcz7biGi4i1VJ3F3GNCNeossr9IQwKvb+UWFq14U5R
+ IzUnGgMIzcjUG2kKQvddRD1CjS+egtcLvShbOfm5bs4w4rfQ2FPF+Aaf9v7fxa/c
+ Jrf3K+cB19eAy7O4nlPG1xurvnZd0QpqRk++werrBuKe1Pgga7YBLePfJhzwqcZv
+ wVOSsB870yeO
-----END CERTIFICATE-----
CERT
- TEST_PKEY = <<-PKEY.gsub(/^\s*/, "")
+ TEST_PKEY = <<~PKEY
-----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
+ MIIEowIBAAKCAQEAyS6liSDc13Whczd+jR2ZUkFY2ueLzyUoO4ghBfBwp7pcKCKW
+ hkbvTx5wtM7Cz36j0yROX1a2l/9YZdP3w0oQ9RhqR3YW/VK4PWNZiU/uINHqEHdG
+ RYGbnOK6Nh+1Unhms8PjY8uMMBK+WHvlO+sx4zvKr+qRyA2x311DJS8w0+Sze2Vr
+ 2N7sO3sC6WLYg3cI1fKzuAg5/AkfQFv8zq3LuhR+mhQoB8vPB7rVpvO2AO7WVI2B
+ 0UEzWRzFh+XKH8WRflOUBxZ4BfMuufEBcZFTFevFbig8L6RmL+kNEF3tfjNAPHts
+ KavX4WxLczTmLfDkTTxO32IpGThXycKYTiKTpQIDAQABAoIBABpyrHEWRed5X7aN
+ kXCBzKSN/LLChT8VNnB6bppLnV501yVbmV2hDlg2EJZkfCMvwIptwnPcKs2uqZ4G
+ u2gMC6X9Bgkg/YK4u4nZJBiIzoMNYEUL48wYGYS1dcokaapO3nQ8M1+XjyAexrFL
+ 5btL1IIisScRTQWiGe6FtzcN43sSNkBISyDF5zG4Kodynqi0ekITmMl2q5XLWcsM
+ KBnmZcRFEmFae2YYczVy8SXNApkZEvN69znvAX1iDNnZ3sJFchXo1nRPt4stOOKw
+ mydgIYqaNQ22aF3OkblvoA4Y4m+X2Qt1sfkryKa5xTT7DSE81GmmazNI64EWqtES
+ 6Xde6P0CgYEA+V1vuSnE5fWX188abWMbVwNMC71WfHbntFmI+qwWYPEpickm+RGX
+ DDfXs5unlVX4KUmjfplgavO29op1GZTuD9TlRnUAV0+0aJnNq4DY6XsHfD84qsBr
+ gQGEHeJ1cMGNDnZR/EV3eudMalj9Qjpx9NoXNzMykb0/SUYZQemiqwcCgYEAzokC
+ s0GoHVJqan4dfU0h0G5QPncrajW9DGG1ySxK/A2eqbVB8W2ZQx39OS26/Gydb31p
+ cR7zm8PZpNbzLqlIMEbD4F6q22xxvYVtDx/HHPjxHMi87yxwQ9uLDUHoMa/LciTO
+ djv3D1xTDDGxbpjmsdmINetunAs3htxku7JY5PMCgYBs3/TVvXzwgmhHm28Ib4sS
+ VKgxP/uw4CGORsFd4SDsNp9SP3c6rAltFjyheMaUlzKApFwz/DdyuvIZdp5mCvZe
+ BzALsS3y8SPtv6lixiDu3/6GqvvM4bKOYuESQzvPfVJfDB4DrTjben2MuUnqTqZO
+ p6IXQc1EgIJPNcH1W1LgpQKBgAKZlPAevngIBpDqn4JpSyititMOevxuSr/yJvCu
+ Xw9HOJ0YTAk3APvoT7y9h6IP1/eEU6R56EUotP+vOQZ4WRFKgsK7TllOxyvElzfe
+ hYom1BoxqLc2Dv+7rsdu8fZWKTB5qCOy44xM9DquEXa79AN/IojTOuQ5++v1sErw
+ ls/jAoGBANneGe9ogN51mYkrLyg1fhU1i24gFRq+sPGEvsCUoE6Vjw/lawQQ80T8
+ v45TFqvhoGpgznqy3qxDJyguquZg6HN2yW6HE2Dvk7uk3XogcjdXgNDmWqb2j0eE
+ z9pKzHCqfwNVPuYf44Znyo2YeyZ2kHn42MU73oXuFshUs3QHcH+P
-----END RSA PRIVATE KEY-----
PKEY
end
diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle
new file mode 100755
index 0000000000..8f8b535295
--- /dev/null
+++ b/spec/bundler/support/bundle
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require_relative "../bundler/support/activate"
+
+load File.expand_path("bundle", Spec::Path.exedir)
diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb
new file mode 100644
index 0000000000..aa7b121706
--- /dev/null
+++ b/spec/bundler/support/bundle.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require_relative "path"
+
+warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead"
+
+load Spec::Path.dev_binstub
diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb
new file mode 100644
index 0000000000..7b69bba668
--- /dev/null
+++ b/spec/bundler/support/checksums.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+module Spec
+ module Checksums
+ class ChecksumsBuilder
+ attr_reader :bundler_registered
+
+ def initialize(enabled = true, &block)
+ @enabled = enabled
+ @checksums = {}
+ yield self if block_given?
+ end
+
+ def initialize_copy(original)
+ super
+ @checksums = @checksums.dup
+ end
+
+ def checksum(repo, name, version, platform = Gem::Platform::RUBY, folder = "gems")
+ @bundler_registered = true if name == "bundler"
+
+ name_tuple = Gem::NameTuple.new(name, version, platform)
+ gem_file = File.join(repo, folder, "#{name_tuple.full_name}.gem")
+ File.open(gem_file, "rb") do |f|
+ register(name_tuple, Bundler::Checksum.from_gem(f, "#{gem_file} (via ChecksumsBuilder#checksum)"))
+ end
+ end
+
+ def no_checksum(name, version, platform = Gem::Platform::RUBY)
+ name_tuple = Gem::NameTuple.new(name, version, platform)
+ register(name_tuple, nil)
+ end
+
+ def delete(name, platform = nil)
+ @checksums.reject! {|k, _| k.name == name && (platform.nil? || k.platform == platform) }
+ end
+
+ def to_s
+ return "" unless @enabled
+
+ locked_checksums = @checksums.map do |name_tuple, checksum|
+ checksum &&= " #{checksum.to_lock}"
+ " #{name_tuple.lock_name}#{checksum}\n"
+ end
+
+ "\nCHECKSUMS\n#{locked_checksums.sort.join}"
+ end
+
+ private
+
+ def register(name_tuple, checksum)
+ delete(name_tuple.name, name_tuple.platform)
+ @checksums[name_tuple] = checksum
+ end
+ end
+
+ def checksums_section(enabled = true, bundler_checksum: true, &block)
+ ChecksumsBuilder.new(enabled, &block).tap do |builder|
+ next if builder.bundler_registered || !bundler_checksum
+
+ next if Bundler::VERSION.to_s.end_with?(".dev")
+ builder.checksum(system_gem_path, "bundler", Bundler::VERSION, Gem::Platform::RUBY, "cache")
+ end
+ end
+
+ def checksums_section_when_enabled(target_lockfile = nil, &block)
+ begin
+ enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/)
+ rescue Errno::ENOENT
+ enabled = true
+ end
+ checksums_section(enabled, &block)
+ end
+
+ def checksum_to_lock(*args)
+ checksums_section(true, bundler_checksum: false) do |c|
+ c.checksum(*args)
+ end.to_s.sub(/^CHECKSUMS\n/, "").strip
+ end
+
+ def checksum_digest(*args)
+ checksum_to_lock(*args).split(Bundler::Checksum::ALGO_SEPARATOR, 2).last
+ end
+
+ # if prefixes is given, removes all checksums where the line
+ # has any of the prefixes on the line before the checksum
+ # otherwise, removes all checksums from the lockfile
+ def remove_checksums_from_lockfile(lockfile, *prefixes)
+ head, remaining = lockfile.split(/^CHECKSUMS$/, 2)
+ return lockfile unless remaining
+ checksums, tail = remaining.split("\n\n", 2)
+
+ prefixes =
+ if prefixes.empty?
+ nil
+ else
+ /(#{prefixes.map {|p| Regexp.escape(p) }.join("|")})/
+ end
+
+ checksums = checksums.each_line.map do |line|
+ if prefixes.nil? || line.match?(prefixes)
+ line.gsub(/ sha256=[a-f0-9]{64}/i, "")
+ else
+ line
+ end
+ end
+
+ head.concat(
+ "CHECKSUMS",
+ checksums.join,
+ "\n\n",
+ tail
+ )
+ end
+
+ def remove_checksums_section_from_lockfile(lockfile)
+ head, remaining = lockfile.split(/^CHECKSUMS$/, 2)
+ return lockfile unless remaining
+ _checksums, tail = remaining.split("\n\n", 2)
+ head.concat(tail)
+ end
+
+ def checksum_from_package(gem_file, name, version)
+ name_tuple = Gem::NameTuple.new(name, version)
+
+ checksum = nil
+
+ File.open(gem_file, "rb") do |f|
+ checksum = Bundler::Checksum.from_gem(f, gemfile)
+ end
+
+ "#{name_tuple.lock_name} #{checksum.to_lock}"
+ end
+ end
+end
diff --git a/spec/bundler/support/code_climate.rb b/spec/bundler/support/code_climate.rb
deleted file mode 100644
index 8f1fb35bcd..0000000000
--- a/spec/bundler/support/code_climate.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-module Spec
- module CodeClimate
- def self.setup
- require "codeclimate-test-reporter"
- ::CodeClimate::TestReporter.start
- configure_exclusions
- rescue LoadError
- # it's fine if CodeClimate isn't set up
- nil
- end
-
- def self.configure_exclusions
- SimpleCov.start do
- add_filter "/bin/"
- add_filter "/lib/bundler/man/"
- add_filter "/lib/bundler/vendor/"
- add_filter "/man/"
- add_filter "/pkg/"
- add_filter "/spec/"
- add_filter "/tmp/"
- end
- end
- end
-end
diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb
new file mode 100644
index 0000000000..e2915b996d
--- /dev/null
+++ b/spec/bundler/support/command_execution.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Spec
+ class CommandExecution
+ def initialize(command, timeout:)
+ @command = command
+ @timeout = timeout
+ @original_stdout = String.new
+ @original_stderr = String.new
+ end
+
+ attr_accessor :exitstatus, :command, :original_stdout, :original_stderr
+ attr_reader :timeout
+ attr_writer :failure_reason
+
+ def raise_error!
+ return unless failure?
+
+ error_header = if failure_reason == :timeout
+ "Invoking `#{command}` was aborted after #{timeout} seconds with output:"
+ else
+ "Invoking `#{command}` failed with output:"
+ end
+
+ raise <<~ERROR
+ #{error_header}
+
+ ----------------------------------------------------------------------
+ #{stdboth}
+ ----------------------------------------------------------------------
+ ERROR
+ end
+
+ def to_s
+ "$ #{command}"
+ end
+ alias_method :inspect, :to_s
+
+ def stdboth
+ @stdboth ||= [stderr, stdout].join("\n").strip
+ end
+
+ def stdout
+ normalize(original_stdout)
+ end
+
+ def stderr
+ normalize(original_stderr)
+ 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
+
+ private
+
+ attr_reader :failure_reason
+
+ def normalize(string)
+ string.dup.force_encoding(Encoding::UTF_8).scrub.strip.gsub("\r\n", "\n")
+ end
+ end
+end
diff --git a/spec/bundler/support/env.rb b/spec/bundler/support/env.rb
new file mode 100644
index 0000000000..0899bd82a3
--- /dev/null
+++ b/spec/bundler/support/env.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Spec
+ module Env
+ def ruby_core?
+ File.exist?(File.expand_path("../../../lib/bundler/bundler.gemspec", __dir__))
+ end
+
+ def rubylib
+ ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR)
+ end
+ end
+end
diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb
new file mode 100644
index 0000000000..2be25b4a78
--- /dev/null
+++ b/spec/bundler/support/filters.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class RequirementChecker < Proc
+ def self.against(provided)
+ new do |required|
+ requirement = Gem::Requirement.new(required)
+
+ !requirement.satisfied_by?(provided)
+ end.tap do |checker|
+ checker.provided = provided
+ end
+ end
+
+ attr_accessor :provided
+
+ def inspect
+ "\"#{provided}\""
+ end
+end
+
+git_version = Gem::Version.new(`git --version`[/(\d+\.\d+\.\d+)/, 1])
+
+RSpec.configure do |config|
+ config.filter_run_excluding realworld: true
+
+ config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version)
+ config.filter_run_excluding git: RequirementChecker.against(git_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_excluding mri_only: RUBY_ENGINE != "ruby"
+
+ config.filter_run_when_matching :focus unless ENV["CI"]
+
+ config.before(:each, :bundler) do |example|
+ bundle_config "simulate_version #{example.metadata[:bundler]}"
+ end
+end
diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb
index 663d3527c5..46718f5fa4 100644
--- a/spec/bundler/support/hax.rb
+++ b/spec/bundler/support/hax.rb
@@ -1,47 +1,74 @@
# frozen_string_literal: true
-require "rubygems"
+
+if ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+ Object.send(:remove_const, :RUBY_PLATFORM)
+ RUBY_PLATFORM = ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+end
module Gem
- class Platform
- @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) if ENV["BUNDLER_SPEC_PLATFORM"]
+ def self.ruby=(ruby)
+ @ruby = ruby
end
- @platforms = [Gem::Platform::RUBY, Gem::Platform.local]
-end
-if ENV["BUNDLER_SPEC_VERSION"]
- module Bundler
- remove_const(:VERSION) if const_defined?(:VERSION)
- VERSION = ENV["BUNDLER_SPEC_VERSION"].dup
+ if ENV["RUBY"]
+ Gem.ruby = ENV["RUBY"]
end
-end
-if ENV["BUNDLER_SPEC_WINDOWS"] == "true"
- require "bundler/constants"
+ if ENV["BUNDLER_GEM_DEFAULT_DIR"]
+ @default_dir = ENV["BUNDLER_GEM_DEFAULT_DIR"]
+ @default_specifications_dir = nil
+ end
- module Bundler
- remove_const :WINDOWS if defined?(WINDOWS)
- WINDOWS = true
+ spec_platform = ENV["BUNDLER_SPEC_PLATFORM"]
+ if spec_platform
+ if /mingw|mswin/.match?(spec_platform)
+ @@win_platform = nil # rubocop:disable Style/ClassVars
+ RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_")
+ end
+
+ RbConfig::CONFIG["arch"] = spec_platform
+
+ class Platform
+ @local = nil
+ end
+ @platforms = []
+ end
+
+ if ENV["BUNDLER_SPEC_GEM_SOURCES"]
+ self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]]
end
-end
-class Object
- if ENV["BUNDLER_SPEC_RUBY_ENGINE"]
- if defined?(RUBY_ENGINE) && RUBY_ENGINE != "jruby" && ENV["BUNDLER_SPEC_RUBY_ENGINE"] == "jruby"
- begin
- # this has to be done up front because psych will try to load a .jar
- # if it thinks its on jruby
- require "psych"
- rescue LoadError
- nil
+ if ENV["BUNDLER_SPEC_READ_ONLY"]
+ module ReadOnly
+ def open(file, mode)
+ if file != IO::NULL && mode == "wb"
+ raise Errno::EROFS
+ else
+ super
+ end
end
end
- remove_const :RUBY_ENGINE if defined?(RUBY_ENGINE)
- RUBY_ENGINE = ENV["BUNDLER_SPEC_RUBY_ENGINE"]
+ File.singleton_class.prepend ReadOnly
+ end
- if RUBY_ENGINE == "jruby"
- remove_const :JRUBY_VERSION if defined?(JRUBY_VERSION)
- JRUBY_VERSION = ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"]
+ if ENV["BUNDLER_SPEC_FAKE_RESOLVE"]
+ module FakeResolv
+ def getaddrinfo(host, port)
+ if host == ENV["BUNDLER_SPEC_FAKE_RESOLVE"]
+ [["AF_INET", port, "127.0.0.1", "127.0.0.1", 2, 2, 17]]
+ else
+ super
+ end
+ end
end
+
+ Socket.singleton_class.prepend FakeResolv
end
end
+
+# mise installed rubygems_plugin.rb to system wide `site_ruby` directory.
+# This empty module avoid to call `mise` command.
+module ReshimInstaller
+ def self.reshim; end
+end
diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb
index 691e7381ff..b0d4b5008b 100644
--- a/spec/bundler/support/helpers.rb
+++ b/spec/bundler/support/helpers.rb
@@ -1,69 +1,61 @@
# frozen_string_literal: true
+require_relative "the_bundle"
+require_relative "path"
+require_relative "options"
+require_relative "subprocess"
+
module Spec
module Helpers
+ include Spec::Path
+ include Spec::Options
+ include Spec::Subprocess
+
+ def self.extended(mod)
+ mod.extend Spec::Path
+ mod.extend Spec::Options
+ mod.extend Spec::Subprocess
+ end
+
def reset!
Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir|
- next if %w(base remote1 gems rubygems . ..).include?(File.basename(dir))
- if ENV["BUNDLER_SUDO_TESTS"]
- `sudo rm -rf "#{dir}"`
- else
- FileUtils.rm_rf(dir)
- end
+ next if %w[base base_system remote1 rubocop standard gems rubygems . ..].include?(File.basename(dir))
+ FileUtils.rm_r(dir)
end
FileUtils.mkdir_p(home)
FileUtils.mkdir_p(tmpdir)
Bundler.reset!
- Bundler.ui = nil
- Bundler.ui # force it to initialize
- end
-
- def self.bang(method)
- define_method("#{method}!") do |*args, &blk|
- send(method, *args, &blk).tap do
- if exitstatus && exitstatus != 0
- error = out + "\n" + err
- error.strip!
- raise RuntimeError,
- "Invoking #{method}!(#{args.map(&:inspect).join(", ")}) failed:\n#{error}",
- caller.drop_while {|bt| bt.start_with?(__FILE__) }
- end
- end
- end
+ Bundler::Source::Git::GitProxy.reset
+ Gem.clear_paths
end
- attr_reader :out, :err, :exitstatus
-
- def the_bundle(*args)
- TheBundle.new(*args)
+ def the_bundle
+ TheBundle.new
end
- def in_app_root(&blk)
- Dir.chdir(bundled_app, &blk)
- end
+ MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/
- def in_app_root2(&blk)
- Dir.chdir(bundled_app2, &blk)
+ def err_without_deprecations
+ err.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "")
end
- def in_app_root_custom(root, &blk)
- Dir.chdir(root, &blk)
+ def deprecations
+ err.split("\n").filter_map {|l| l.sub(MAJOR_DEPRECATION, "") if l.match?(MAJOR_DEPRECATION) }
end
def run(cmd, *args)
opts = args.last.is_a?(Hash) ? args.pop : {}
groups = args.map(&:inspect).join(", ")
- setup = "require 'rubygems' ; require 'bundler' ; Bundler.setup(#{groups})\n"
- @out = ruby(setup + cmd, opts)
+ setup = "require 'bundler' ; Bundler.ui.silence { Bundler.setup(#{groups}) }"
+ ruby([setup, cmd].join(" ; "), opts)
end
- bang :run
def load_error_run(ruby, name, *args)
cmd = <<-RUBY
begin
#{ruby}
rescue LoadError => e
- $stderr.puts "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ warn e.message if e.message.include?("-- #{name}")
end
RUBY
opts = args.last.is_a?(Hash) ? args.pop : {}
@@ -71,166 +63,211 @@ module Spec
run(cmd, *args)
end
- def lib
- root.join("lib")
+ def in_bundled_app(cmd, options = {})
+ sys_exec(cmd, dir: bundled_app, raise_on_error: options[:raise_on_error])
end
- def spec
- spec_dir.to_s
- end
+ def bundle(cmd, options = {}, &block)
+ bundle_bin = options.delete(:bundle_bin)
+ bundle_bin ||= installed_bindir.join("bundle")
- def bundle(cmd, options = {})
- with_sudo = options.delete(:sudo)
- sudo = with_sudo == :preserve_env ? "sudo -E" : "sudo" if with_sudo
+ env = options.delete(:env) || {}
- options["no-color"] = true unless options.key?("no-color") || cmd.to_s =~ /\A(e|ex|exe|exec|conf|confi|config)(\s|\z)/
+ requires = options.delete(:requires) || []
- bundle_bin = options.delete("bundle_bin") || bindir.join("bundle")
+ dir = options.delete(:dir) || bundled_app
+ custom_load_path = options.delete(:load_path)
- if system_bundler = options.delete(:system_bundler)
- bundle_bin = "-S bundle"
- end
+ load_path = []
+ load_path << custom_load_path if custom_load_path
- requires = options.delete(:requires) || []
- if artifice = options.delete(:artifice) { "fail" unless RSpec.current_example.metadata[:realworld] }
- requires << File.expand_path("../artifice/#{artifice}.rb", __FILE__)
- end
- requires << "support/hax"
- requires_str = requires.map {|r| "-r#{r}" }.join(" ")
+ build_env_options = { load_path: load_path, requires: requires, env: env }
+ build_env_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) || cmd.start_with?("exec")
- load_path = []
- load_path << lib unless system_bundler
- load_path << spec
- load_path_str = "-I#{load_path.join(File::PATH_SEPARATOR)}"
+ match_source(cmd)
+
+ env = build_env(build_env_options)
+
+ raise_on_error = options.delete(:raise_on_error)
- env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}'" }.join(" ")
- env["PATH"].gsub!(Path.bindir, "") if env["PATH"] && system_bundler
args = options.map do |k, v|
- v == true ? " --#{k}" : " --#{k} #{v}" if v
+ case v
+ when true
+ " --#{k}"
+ when false
+ " --no-#{k}"
+ else
+ " --#{k} #{v}"
+ end
end.join
- cmd = "#{env} #{sudo} #{Gem.ruby} #{load_path_str} #{requires_str} #{bundle_bin} #{cmd}#{args}"
- sys_exec(cmd) {|i, o, thr| yield i, o, thr if block_given? }
+ cmd = "#{Gem.ruby} #{bundle_bin} #{cmd}#{args}"
+ sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block)
end
- bang :bundle
- def bundler(cmd, options = {})
- options["bundle_bin"] = bindir.join("bundler")
- bundle(cmd, options)
+ def main_source(dir)
+ gemfile = File.expand_path("Gemfile", dir)
+ return unless File.exist?(gemfile)
+
+ match = File.readlines(gemfile).first.match(/source ["'](?<source>[^"']+)["']/)
+ return unless match
+
+ match[:source]
end
- def bundle_ruby(options = {})
- options["bundle_bin"] = bindir.join("bundle_ruby")
- bundle("", options)
+ def bundler(cmd, options = {})
+ options[:bundle_bin] = system_gem_path("bin/bundler")
+ bundle(cmd, options)
end
def ruby(ruby, options = {})
- env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}' " }.join
- ruby = ruby.gsub(/["`\$]/) {|m| "\\#{m}" }
- lib_option = options[:no_lib] ? "" : " -I#{lib}"
- sys_exec(%(#{env}#{Gem.ruby}#{lib_option} -e "#{ruby}"))
+ env = build_env({ artifice: nil }.merge(options))
+ escaped_ruby = ruby.shellescape
+ options[:env] = env if env
+ options[:dir] ||= bundled_app
+ sys_exec(%(#{Gem.ruby} -w -e #{escaped_ruby}), options)
end
- bang :ruby
def load_error_ruby(ruby, name, opts = {})
ruby(<<-R)
begin
#{ruby}
rescue LoadError => e
- $stderr.puts "ZOMG LOAD ERROR"# if e.message.include?("-- #{name}")
+ warn e.message if e.message.include?("-- #{name}")
end
R
end
- def gembin(cmd)
- lib = File.expand_path("../../../lib", __FILE__)
- old = ENV["RUBYOPT"]
- ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -I#{lib}"
- cmd = bundled_app("bin/#{cmd}") unless cmd.to_s.include?("/")
- sys_exec(cmd.to_s)
- ensure
- ENV["RUBYOPT"] = old
- end
+ def build_env(options = {})
+ env = options.delete(:env) || {}
+ libs = options.delete(:load_path) || []
+ env["RUBYOPT"] = opt_add("-I#{libs.join(File::PATH_SEPARATOR)}", env["RUBYOPT"]) if libs.any?
+
+ current_example = RSpec.current_example
+
+ main_source = @gemfile_source if defined?(@gemfile_source)
+ compact_index_main_source = main_source&.start_with?("https://gem.repo", "https://gems.security")
- def gem_command(command, args = "", options = {})
- if command == :exec && !options[:no_quote]
- args = args.gsub(/(?=")/, "\\")
- args = %("#{args}")
+ requires = options.delete(:requires) || []
+ requires << hax
+
+ artifice = options.delete(:artifice) do
+ if current_example && current_example.metadata[:realworld]
+ "vcr"
+ elsif compact_index_main_source
+ env["BUNDLER_SPEC_GEM_REPO"] ||=
+ case main_source
+ when "https://gem.repo1" then gem_repo1.to_s
+ when "https://gem.repo2" then gem_repo2.to_s
+ when "https://gem.repo3" then gem_repo3.to_s
+ when "https://gem.repo4" then gem_repo4.to_s
+ when "https://gems.security" then security_repo.to_s
+ end
+
+ "compact_index"
+ else
+ "fail"
+ end
end
- gem = ENV['BUNDLE_GEM'] || "#{Gem.ruby} -rubygems -S gem --backtrace"
- sys_exec("#{gem} #{command} #{args}")
+ if artifice
+ requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb"
+ end
+
+ requires.each {|r| env["RUBYOPT"] = opt_add("-r#{r}", env["RUBYOPT"]) }
+
+ env
end
- bang :gem_command
- def rake
- if ENV['BUNDLE_RUBY'] && ENV['BUNDLE_GEM']
- "#{ENV['BUNDLE_RUBY']} #{ENV['GEM_PATH']}/bin/rake"
- else
- 'rake'
- end
+ def gembin(cmd, options = {})
+ cmd = bundled_app("bin/#{cmd}") unless cmd.to_s.include?("/")
+ sys_exec(cmd.to_s, options)
end
- def sys_exec(cmd)
- Open3.popen3(cmd.to_s) do |stdin, stdout, stderr, wait_thr|
- yield stdin, stdout, wait_thr if block_given?
- stdin.close
+ def sys_exec(cmd, options = {}, &block)
+ env = options[:env] || {}
+ env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"])
+ options[:env] = env
- @exitstatus = wait_thr && wait_thr.value.exitstatus
- @out = Thread.new { stdout.read }.value.strip
- @err = Thread.new { stderr.read }.value.strip
+ sh(cmd, options, &block)
+ end
+
+ def bundle_config(config = nil, path = bundled_app(".bundle/config"))
+ if config.is_a?(String)
+ key, value = config.split(" ", 2)
+ config = { Bundler::Settings.key_for(key) => value }
end
- (@all_output ||= String.new) << [
- "$ #{cmd.to_s.strip}",
- out,
- err,
- @exitstatus ? "# $? => #{@exitstatus}" : "",
- "\n",
- ].reject(&:empty?).join("\n")
+ current = File.exist?(path) ? Psych.load_file(path) : {}
+ return current unless config
- @out
- end
- bang :sys_exec
+ current = {} if current.empty?
- def config(config = nil, path = bundled_app(".bundle/config"))
- return YAML.load_file(path) unless config
FileUtils.mkdir_p(File.dirname(path))
- File.open(path, "w") do |f|
- f.puts config.to_yaml
+
+ new_config = current.merge(config).compact
+
+ File.open(path, "w+") do |f|
+ f.puts new_config.to_yaml
end
- config
+
+ new_config
end
- def global_config(config = nil)
- config(config, home(".bundle/config"))
+ def bundle_config_global(config = nil)
+ bundle_config(config, home(".bundle/config"))
end
- def create_file(*args)
- path = bundled_app(args.shift)
- path = args.shift if args.first.is_a?(Pathname)
- str = args.shift || ""
+ def create_file(path, contents = "")
+ contents = strip_whitespace(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(str)
+ path.write(contents)
+
+ # if the file is a script, create respective bat file on Windows
+ if contents.start_with?("#!")
+ path.chmod(0o755)
+ if Gem.win_platform?
+ path.sub_ext(".bat").write <<~SCRIPT
+ @ECHO OFF
+ @"ruby.exe" "%~dpn0" %*
+ SCRIPT
+ end
end
end
def gemfile(*args)
- if args.empty?
- File.open("Gemfile", "r", &:read)
+ contents = args.pop
+
+ if contents.nil?
+ read_gemfile
else
- create_file("Gemfile", *args)
+ match_source(contents)
+ create_file(args.pop || "Gemfile", contents)
end
end
def lockfile(*args)
- if args.empty?
- File.open("Gemfile.lock", "r", &:read)
+ contents = args.pop
+
+ if contents.nil?
+ read_lockfile
else
- create_file("Gemfile.lock", *args)
+ create_file(args.pop || "Gemfile.lock", contents)
end
end
+ def read_gemfile(file = "Gemfile")
+ read_bundled_app_file(file)
+ end
+
+ def read_lockfile(file = "Gemfile.lock")
+ read_bundled_app_file(file)
+ end
+
+ def read_bundled_app_file(file)
+ bundled_app(file).read
+ end
+
def strip_whitespace(str)
# Trim the leading spaces
spaces = str[/\A\s+/, 0] || ""
@@ -238,63 +275,193 @@ module Spec
end
def install_gemfile(*args)
+ opts = args.last.is_a?(Hash) ? args.pop : {}
gemfile(*args)
- opts = args.last.is_a?(Hash) ? args.last : {}
- opts[:retry] ||= 0
bundle :install, opts
end
- bang :install_gemfile
def lock_gemfile(*args)
gemfile(*args)
opts = args.last.is_a?(Hash) ? args.last : {}
- opts[:retry] ||= 0
bundle :lock, opts
end
- def install_gems(*gems)
+ def base_system_gems(*names, **options)
+ system_gems names.map {|name| find_base_path(name) }, **options
+ end
+
+ def system_gems(*gems)
+ gems = gems.flatten
options = gems.last.is_a?(Hash) ? gems.pop : {}
- gem_repo = options.fetch(:gem_repo) { gem_repo1 }
+ install_dir = options.fetch(:path, system_gem_path)
+ default = options.fetch(:default, false)
gems.each do |g|
- path = if g == :bundler
- Dir.chdir(root) { gem_command! :build, gemspec.to_s }
- bundler_path = root + "bundler-#{Bundler::VERSION}.gem"
- elsif g.to_s =~ %r{\A/.*\.gem\z}
- g
+ gem_name = g.to_s
+ bundler = gem_name.match(/\Abundler-(?<version>.*)\z/)
+
+ if bundler
+ with_built_bundler(bundler[:version], released: options.fetch(:released, false)) {|gem_path| install_gem(gem_path, install_dir, default) }
+ elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name)
+ install_gem(gem_name, install_dir, default)
else
- "#{gem_repo}/gems/#{g}.gem"
+ gem_repo = options.fetch(:gem_repo, gem_repo1)
+ install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default)
end
+ end
+ end
+
+ def self.install_dev_bundler
+ extend self
+
+ with_built_bundler(nil, build_path: tmp_root) {|gem_path| install_gem(gem_path, pristine_system_gem_path) }
+ end
+
+ def install_gem(path, install_dir, default = false)
+ raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)
- raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ require "rubygems/installer"
- gem_command! :install, "--no-rdoc --no-ri --ignore-dependencies '#{path}'"
- bundler_path && bundler_path.rmtree
+ with_simulated_platform do
+ installer = Gem::Installer.at(
+ path.to_s,
+ install_dir: install_dir.to_s,
+ document: [],
+ ignore_dependencies: true,
+ wrappers: true,
+ env_shebang: true,
+ force: true
+ )
+ installer.install
+ end
+
+ if default
+ gem = Pathname.new(path).basename.to_s.match(/(.*)\.gem/)[1]
+
+ # Revert Gem::Installer#write_spec and apply Gem::Installer#write_default_spec
+ FileUtils.mkdir_p File.join(install_dir, "specifications", "default")
+ File.rename File.join(install_dir, "specifications", gem + ".gemspec"),
+ File.join(install_dir, "specifications", "default", gem + ".gemspec")
+
+ # Revert Gem::Installer#write_cache_file
+ File.delete File.join(install_dir, "cache", gem + ".gem")
end
end
- alias_method :install_gem, :install_gems
+ def uninstall_gem(name, options = {})
+ require "rubygems/uninstaller"
+
+ gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s
+
+ with_env_vars("GEM_HOME" => gem_home) do
+ Gem.clear_paths
+
+ uninstaller = Gem::Uninstaller.new(
+ name,
+ ignore: true,
+ executables: true,
+ all: true
+ )
+ uninstaller.uninstall
+ ensure
+ Gem.clear_paths
+ end
+ end
+
+ def installed_gems_list(options = {})
+ gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s
+
+ # Temporarily set GEM_HOME for the command
+ old_gem_home = ENV["GEM_HOME"]
+ ENV["GEM_HOME"] = gem_home
+ Gem.clear_paths
+
+ begin
+ require "rubygems/commands/list_command"
+
+ # Capture output from the list command
+ require "stringio"
+ output_io = StringIO.new
+ cmd = Gem::Commands::ListCommand.new
+ cmd.ui = Gem::StreamUI.new(StringIO.new, output_io, StringIO.new, false)
+ cmd.invoke
+ output = output_io.string.strip
+ ensure
+ ENV["GEM_HOME"] = old_gem_home
+ Gem.clear_paths
+ end
+
+ # Create a fake command execution so `out` helper works
+ command_execution = Spec::CommandExecution.new("gem list", timeout: 60)
+ command_execution.original_stdout << output
+ command_execution.exitstatus = 0
+ command_executions << command_execution
+
+ output
+ end
+
+ def with_built_bundler(version = nil, opts = {}, &block)
+ require_relative "builders"
+
+ Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block)
+ 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
- ENV["GEM_HOME"] = path.to_s
- ENV["GEM_PATH"] = path.to_s
- ENV["BUNDLER_ORIG_GEM_PATH"] = nil
yield
ensure
ENV.replace(backup)
end
- def with_path_as(path)
- backup = ENV.to_hash
- ENV["PATH"] = path.to_s
- ENV["BUNDLER_ORIG_PATH"] = nil
+ # Simulate the platform set by BUNDLER_SPEC_PLATFORM for in-process
+ # operations, mirroring what hax.rb does for subprocesses.
+ def with_simulated_platform
+ spec_platform = ENV["BUNDLER_SPEC_PLATFORM"]
+ unless spec_platform
+ return yield
+ end
+
+ old_arch = RbConfig::CONFIG["arch"]
+ old_host_os = RbConfig::CONFIG["host_os"]
+
+ if /mingw|mswin/.match?(spec_platform)
+ Gem.class_variable_set(:@@win_platform, nil) # rubocop:disable Style/ClassVars
+ RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_")
+ end
+
+ RbConfig::CONFIG["arch"] = spec_platform
+ Gem::Platform.instance_variable_set(:@local, nil)
+ Gem.instance_variable_set(:@platforms, [])
+
yield
ensure
- ENV.replace(backup)
+ if spec_platform
+ RbConfig::CONFIG["arch"] = old_arch
+ RbConfig::CONFIG["host_os"] = old_host_os
+ Gem::Platform.instance_variable_set(:@local, nil)
+ Gem.instance_variable_set(:@platforms, [])
+ end
end
def with_path_added(path)
- with_path_as(path.to_s + ":" + ENV["PATH"]) do
+ with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do
yield
end
end
@@ -310,134 +477,74 @@ module Spec
def with_fake_man
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
+ create_file(tmp("fake_man/man"), <<~SCRIPT)
+ #!/usr/bin/env ruby
+ puts ARGV.inspect
+ SCRIPT
with_path_added(tmp("fake_man")) { yield }
end
- def system_gems(*gems)
- gems = gems.flatten
-
- FileUtils.rm_rf(system_gem_path)
- FileUtils.mkdir_p(system_gem_path)
-
- Gem.clear_paths
-
- env_backup = ENV.to_hash
- ENV["GEM_HOME"] = system_gem_path.to_s
- ENV["GEM_PATH"] = system_gem_path.to_s
- ENV["BUNDLER_ORIG_GEM_PATH"] = nil
-
- install_gems(*gems)
- return unless block_given?
- begin
- yield
- ensure
- ENV.replace(env_backup)
- end
- end
-
- def realworld_system_gems(*gems)
- gems = gems.flatten
-
- FileUtils.rm_rf(system_gem_path)
- FileUtils.mkdir_p(system_gem_path)
-
- Gem.clear_paths
-
- gem_home = ENV["GEM_HOME"]
- gem_path = ENV["GEM_PATH"]
- path = ENV["PATH"]
- ENV["GEM_HOME"] = system_gem_path.to_s
- ENV["GEM_PATH"] = system_gem_path.to_s
+ def pristine_system_gems(*gems)
+ FileUtils.rm_r(system_gem_path)
- gems.each do |gem|
- gem_command :install, "--no-rdoc --no-ri #{gem}"
- end
- return unless block_given?
- begin
- yield
- ensure
- ENV["GEM_HOME"] = gem_home
- ENV["GEM_PATH"] = gem_path
- ENV["PATH"] = path
+ if gems.any?
+ system_gems(*gems)
+ else
+ default_system_gems
end
end
- def cache_gems(*gems)
+ 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_repo1}/gems/#{g}.gem"
- raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ path = "#{gem_repo}/gems/#{g}.gem"
+ raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)
FileUtils.cp(path, "#{bundled_app}/vendor/cache")
end
end
def simulate_new_machine
- system_gems []
- FileUtils.rm_rf default_bundle_path
- FileUtils.rm_rf bundled_app(".bundle")
+ FileUtils.rm_r bundled_app(".bundle")
+ pristine_system_gems
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?
+ def default_system_gems
+ FileUtils.cp_r pristine_system_gem_path, system_gem_path
end
- def simulate_ruby_version(version)
- return if version == RUBY_VERSION
- old = ENV["BUNDLER_SPEC_RUBY_VERSION"]
- ENV["BUNDLER_SPEC_RUBY_VERSION"] = version
- yield if block_given?
+ 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_VERSION"] = old if block_given?
+ ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = old
end
- def simulate_ruby_engine(engine, version = "1.6.0")
- return if engine == local_ruby_engine
-
- old = ENV["BUNDLER_SPEC_RUBY_ENGINE"]
- ENV["BUNDLER_SPEC_RUBY_ENGINE"] = engine
- old_version = ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"]
- ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] = version
- yield if block_given?
+ def simulate_platform(platform)
+ old = ENV["BUNDLER_SPEC_PLATFORM"]
+ ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s
+ yield
ensure
- ENV["BUNDLER_SPEC_RUBY_ENGINE"] = old if block_given?
- ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] = old_version if block_given?
+ ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given?
end
- def simulate_bundler_version(version)
- old = ENV["BUNDLER_SPEC_VERSION"]
- ENV["BUNDLER_SPEC_VERSION"] = version.to_s
- yield if block_given?
- ensure
- ENV["BUNDLER_SPEC_VERSION"] = old if block_given?
+ def current_ruby_minor
+ Gem.ruby_version.segments.tap {|s| s.delete_at(2) }.join(".")
end
- def simulate_windows
- old = ENV["BUNDLER_SPEC_WINDOWS"]
- ENV["BUNDLER_SPEC_WINDOWS"] = "true"
- simulate_platform mswin do
- yield
- end
- ensure
- ENV["BUNDLER_SPEC_WINDOWS"] = old
+ def next_ruby_minor
+ ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".")
end
- def revision_for(path)
- Dir.chdir(path) { `git rev-parse HEAD`.strip }
+ def ruby_major_minor
+ Gem.ruby_version.segments[0..1]
end
- def capture_output
- capture(:stdout)
+ def revision_for(path)
+ git("rev-parse HEAD", path).strip
end
def with_read_only(pattern)
@@ -454,6 +561,22 @@ module Spec
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
@@ -473,32 +596,36 @@ module Spec
end
end
- def require_rack
- # need to hack, so we can require rack
+ def require_rack_test
+ # need to hack, so we can require rack for testing
old_gem_home = ENV["GEM_HOME"]
- ENV["GEM_HOME"] = Spec::Path.base_system_gems.to_s
- require "rack"
+ ENV["GEM_HOME"] = Spec::Path.scoped_base_system_gem_path.to_s
+ require "rack/test"
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 => e
- raise(e) if tries > (seconds * 2)
- tries += 1
- retry
+ def exit_status_for_signal(signal_number)
+ # For details see: https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts
+ 128 + signal_number
end
- def find_unused_port
- port = 21_453
- begin
- port += 1 while TCPSocket.new("127.0.0.1", port)
- rescue
- false
- end
- port
+ def empty_repo4
+ FileUtils.rm_r gem_repo4
+
+ build_repo4 {}
+ end
+
+ private
+
+ def match_source(contents)
+ match = /source ["']?(?<source>http[^"']+)["']?/.match(contents)
+ return unless match
+
+ @gemfile_source = match[:source]
+ end
+
+ 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
index 29780014fc..1fbdd49abe 100644
--- a/spec/bundler/support/indexes.rb
+++ b/spec/bundler/support/indexes.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Spec
module Indexes
def dep(name, reqs = nil)
@@ -13,15 +14,25 @@ module Spec
alias_method :platforms, :platform
- def resolve(args = [])
+ def resolve(args = [], dependency_api_available: true)
@platforms ||= ["ruby"]
- deps = []
+ default_source = instance_double("Bundler::Source::Rubygems", specs: @index, to_s: "locally install gems", dependency_api_available?: dependency_api_available)
+ 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|
- @platforms.each do |p|
- deps << Bundler::DepProxy.new(d, p)
- end
+ name = d.name
+ source_requirements[name] = d.source = default_source
end
- Bundler::Resolver.resolve(deps, @index, *args)
+ 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)
@@ -30,6 +41,12 @@ module Spec
expect(got).to eq(specs.sort)
end
+ def should_resolve_without_dependency_api(specs)
+ got = resolve(dependency_api_available: false)
+ 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
@@ -38,13 +55,6 @@ module Spec
end
end
- def should_conflict_on(names)
- got = resolve
- flunk "The resolve succeeded with: #{got.map(&:full_name).sort.inspect}"
- rescue Bundler::VersionConflict => e
- expect(Array(names).sort).to eq(e.conflicts.sort)
- end
-
def gem(*args, &blk)
build_spec(*args, &blk).first
end
@@ -56,19 +66,21 @@ module Spec
end
def should_conservative_resolve_and_include(opts, unlock, specs)
- # empty unlock means unlock all
opts = Array(opts)
- search = Bundler::GemVersionPromoter.new(@locked, unlock).tap do |s|
+ search = Bundler::GemVersionPromoter.new.tap do |s|
s.level = opts.first
s.strict = opts.include?(:strict)
end
- should_resolve_and_include specs, [{}, @base, search]
+ 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)
+ gem "myrack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1]
+ gem "myrack-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|
@@ -76,10 +88,10 @@ module Spec
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"
+ dep "myrack", "~> 1.1"
+ dep "myrack-mount", ">= 0.5"
+ elsif version > v("2.3") then dep "myrack", "~> 1.0.0"
+ elsif version > v("2.0.0") then dep "myrack", "~> 0.9.0"
end
end
gem "activerecord", version do
@@ -110,11 +122,11 @@ module Spec
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|
+ platforms "ruby java mswin32 mingw32 x64-mingw-ucrt" 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")
+ dep "weakling", ">= 0.0.3" if platform =~ pl("java") # rubocop:disable Performance/RegexpMatch
end
end
end
@@ -129,6 +141,14 @@ module Spec
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
@@ -136,7 +156,7 @@ module Spec
# 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 "builder", %w[3.0.4 3.1.4]
gem("grape", "0.2.6") do
dep "builder", ">= 0"
end
@@ -156,11 +176,11 @@ module Spec
def a_complex_conflict_index
build_index do
- gem("a", %w(1.0.2 1.1.4 1.2.0 1.4.0)) 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
+ gem("d", %w[1.3.0 1.4.1]) do
dep "x", ">= 0"
end
@@ -203,7 +223,7 @@ module Spec
def index_with_conflict_on_child
build_index do
- gem "json", %w(1.6.5 1.7.7 1.8.0)
+ 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"]
@@ -223,7 +243,7 @@ module Spec
# Issue #3459
def a_complicated_index
build_index do
- gem "foo", %w(3.0.0 3.0.5) 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"]
@@ -262,7 +282,7 @@ module Spec
dep "garply", [">= 0.3.1"]
end
- gem "grault", %w(2.6.3 3.1.1)
+ gem "grault", %w[2.6.3 3.1.1]
gem "garply", "0.5.1" do
dep "waldo", ["~> 0.1.3"]
@@ -272,7 +292,7 @@ module Spec
dep "plugh", ["~> 0.6.0"]
end
- gem "plugh", %w(0.6.3 0.6.11 0.7.0)
+ 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"]
@@ -283,9 +303,9 @@ module Spec
end
end
- def a_unresovable_child_index
+ def a_unresolvable_child_index
build_index do
- gem "json", %w(1.8.0)
+ gem "json", %w[1.8.0]
gem("chef", "10.26") do
dep "json", [">= 1.4.4", "<= 1.7.7"]
@@ -304,10 +324,10 @@ module Spec
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 "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 "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"
@@ -346,7 +366,7 @@ module Spec
def a_circular_index
build_index do
- gem "rack", "1.0.1"
+ gem "myrack", "1.0.1"
gem("foo", "0.2.6") do
dep "bar", ">= 0"
end
@@ -361,5 +381,44 @@ module Spec
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/less_than_proc.rb b/spec/bundler/support/less_than_proc.rb
deleted file mode 100644
index 27966aa6ed..0000000000
--- a/spec/bundler/support/less_than_proc.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-class LessThanProc < Proc
- attr_accessor :present
-
- def self.with(present)
- provided = Gem::Version.new(present.dup)
- new do |required|
- if required =~ /[=><~]/
- !Gem::Requirement.new(required).satisfied_by?(provided)
- else
- provided < Gem::Version.new(required)
- end
- end.tap {|l| l.present = present }
- end
-
- def inspect
- "\"=< #{present}\""
- end
-end
diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb
index 9248360639..5a3c38a4db 100644
--- a/spec/bundler/support/matchers.rb
+++ b/spec/bundler/support/matchers.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
+
require "forwardable"
-require "support/the_bundle"
+require_relative "the_bundle"
+
module Spec
module Matchers
extend RSpec::Matchers
@@ -50,7 +52,7 @@ module Spec
end
def self.define_compound_matcher(matcher, preconditions, &declarations)
- raise "Must have preconditions to define a compound matcher" if preconditions.empty?
+ raise ArgumentError, "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),
@@ -59,52 +61,39 @@ module Spec
end
end
- MAJOR_DEPRECATION = /^\[DEPRECATED FOR 2\.0\]\s*/
+ RSpec::Matchers.define :have_dep do |*args|
+ dep = Bundler::Dependency.new(*args)
- RSpec::Matchers.define :lack_errors do
- diffable
match do |actual|
- actual.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "") == ""
+ actual.length == 1 && actual.all? {|d| d == dep }
end
end
- RSpec::Matchers.define :eq_err do |expected|
- diffable
+ RSpec::Matchers.define :have_gem do |*args|
match do |actual|
- actual.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "") == expected
+ actual.length == args.length && actual.all? {|a| args.include?(a.full_name) }
end
end
- RSpec::Matchers.define :have_major_deprecation do |expected|
+ RSpec::Matchers.define :be_sorted do
diffable
+ attr_reader :expected
match do |actual|
- actual.split(MAJOR_DEPRECATION).any? do |d|
- !d.empty? && values_match?(expected, d.strip)
+ 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 :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_well_formed do
+ match(&:empty?)
- RSpec::Matchers.define :have_rubyopts do |*args|
- args = args.flatten
- args = args.first.split(/\s+/) if args.size == 1
-
- match do |actual|
- actual = actual.split(/\s+/) if actual.is_a?(String)
- args.all? {|arg| actual.include?(arg) } && actual.uniq.size == actual.size
+ failure_message do |actual|
+ actual.join("\n")
end
end
@@ -125,63 +114,87 @@ module Spec
match do
opts = names.last.is_a?(Hash) ? names.pop : {}
source = opts.delete(:source)
- groups = Array(opts[:groups])
- groups << opts
- @errors = names.map do |name|
- name, version, platform = name.split(/\s+/)
+ groups = Array(opts.delete(:groups)).map(&:inspect).join(", ")
+ opts[:raise_on_error] = false
+ @errors = names.filter_map do |full_name|
+ name, version, platform = full_name.split(/\s+/)
+ platform ||= "ruby"
+ require_path = name.tr("-", "/")
version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name)
- begin
- run! "require '#{name}.rb'; puts #{version_const}", *groups
- rescue => e
- next "#{name} is not installed:\n#{indent(e)}"
- end
- out.gsub!(/#{MAJOR_DEPRECATION}.*$/, "")
- actual_version, actual_platform = out.strip.split(/\s+/, 2)
- unless Gem::Version.new(actual_version) == Gem::Version.new(version)
+ 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)
+ actual_platform ||= "ruby"
+ 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
- unless actual_platform == platform
+ if exitstatus == 65
+ actual_platform = out.split("\n").last
next "#{name} was expected to be of platform #{platform} but was #{actual_platform}"
end
- next unless source
- begin
- source_const = "#{Spec::Builders.constantize(name)}_SOURCE"
- run! "require '#{name}/source'; puts #{source_const}", *groups
- rescue
- next "#{name} does not have a source defined:\n#{indent(e)}"
- end
- out.gsub!(/#{MAJOR_DEPRECATION}.*$/, "")
- unless out.strip == source
- next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{out}`"
+ if exitstatus == 66
+ actual_source = out.split("\n").last
+ next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`"
end
- end.compact
+ next "Command to check for inclusion of gem #{full_name} failed"
+ end
@errors.empty?
end
match_when_negated do
opts = names.last.is_a?(Hash) ? names.pop : {}
- groups = Array(opts[:groups]) || []
- @errors = names.map do |name|
+ groups = Array(opts.delete(:groups)).map(&:inspect).join(", ")
+ opts[:raise_on_error] = false
+ @errors = names.filter_map do |name|
name, version = name.split(/\s+/, 2)
- begin
- run <<-R, *(groups + [opts])
- begin
- require '#{name}'
- puts #{Spec::Builders.constantize(name)}
- rescue LoadError, NameError
- puts "WIN"
+ 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
- R
- rescue => e
- next "checking for #{name} failed:\n#{e}"
- end
- next if out == "WIN"
+ 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?
- if Gem::Version.new(out) == Gem::Version.new(version)
- next "expected #{name} (#{version}) not to be installed, but it was"
- end
- end.compact
+ next "expected #{name} (#{version}) not to be installed, but it was"
+ end
@errors.empty?
end
@@ -197,10 +210,6 @@ module Spec
RSpec::Matchers.define_negated_matcher :not_include_gems, :include_gems
RSpec::Matchers.alias_matcher :include_gem, :include_gems
- def have_lockfile(expected)
- read_as(strip_whitespace(expected))
- end
-
def plugin_should_be_installed(*names)
names.each do |name|
expect(Bundler::Plugin).to be_installed(name)
@@ -214,9 +223,5 @@ module Spec
expect(Bundler::Plugin).not_to be_installed(name)
end
end
-
- def lockfile_should_be(expected)
- expect(bundled_app("Gemfile.lock")).to read_as(strip_whitespace(expected))
- end
end
end
diff --git a/spec/bundler/support/options.rb b/spec/bundler/support/options.rb
new file mode 100644
index 0000000000..551fa1acd8
--- /dev/null
+++ b/spec/bundler/support/options.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Spec
+ module Options
+ 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
+ end
+end
diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb
index b58658e71a..2e6486412f 100644
--- a/spec/bundler/support/path.rb
+++ b/spec/bundler/support/path.rb
@@ -1,97 +1,267 @@
# frozen_string_literal: true
-require "pathname"
+
+require "pathname" unless defined?(Pathname)
+require "rbconfig"
+
+require_relative "env"
module Spec
module Path
+ include Spec::Env
+
+ def source_root
+ @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../../bundler").expand_path(__dir__)
+ end
+
def root
- @root ||=
- Pathname.new(for_ruby_core? ? "../../../.." : "../../..").expand_path(__FILE__)
+ @root ||= system_gem_path("gems/bundler-#{Bundler::VERSION}")
end
def gemspec
- @gemspec ||= root.join(for_ruby_core? ? "lib/bundler.gemspec" : "bundler.gemspec")
+ @gemspec ||= source_root.join(relative_gemspec)
+ end
+
+ def relative_gemspec
+ @relative_gemspec ||= ruby_core? ? "lib/bundler/bundler.gemspec" : "bundler.gemspec"
+ end
+
+ def loaded_gemspec
+ @loaded_gemspec ||= Dir.chdir(source_root) { 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 dev_binstub
+ @dev_binstub ||= bindir.join("bundle")
end
def bindir
- @bindir ||= root.join(for_ruby_core? ? "bin" : "exe")
+ @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "../bin")
+ end
+
+ def exedir
+ @exedir ||= 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 ||= ENV["GEM_COMMAND"] || "gem"
+ end
+
+ def path
+ env_path = ENV["PATH"]
+ env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core?
+ env_path
end
def spec_dir
- @spec_dir ||= root.join(for_ruby_core? ? "spec/bundler" : "spec")
+ @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "../spec")
+ end
+
+ def man_dir
+ @man_dir ||= lib_dir.join("bundler/man")
+ end
+
+ def hax
+ @hax ||= spec_dir.join("support/hax.rb")
+ end
+
+ def tracked_files
+ @tracked_files ||= git_ls_files(tracked_files_glob)
+ end
+
+ def shipped_files
+ @shipped_files ||= if ruby_core_tarball?
+ loaded_gemspec.files.map {|f| f.gsub(%r{^exe/}, "libexec/") }
+ elsif ruby_core?
+ tracked_files
+ else
+ loaded_gemspec.files
+ end
+ 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)
- root.join("tmp", *path)
+ tmp_root.join("#{test_env_version}.#{scope}").join(*path)
+ end
+
+ def tmp_root
+ if ruby_core? && (tmpdir = ENV["TMPDIR"])
+ # Use realpath to resolve any symlinks in TMPDIR (e.g., on macOS /var -> /private/var)
+ real = begin
+ File.realpath(tmpdir)
+ rescue Errno::ENOENT, Errno::EACCES
+ tmpdir
+ end
+ Pathname(real)
+ else
+ (ruby_core? ? source_root : source_root.parent).join("tmp")
+ end
+ end
+
+ # Bump this version whenever you make a breaking change to the spec setup
+ # that requires regenerating tmp/.
+
+ def test_env_version
+ 2
+ 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)
+ tmp("home", *path)
end
def default_bundle_path(*path)
system_gem_path(*path)
end
+ def default_cache_path(*path)
+ default_bundle_path("cache/bundler", *path)
+ end
+
+ def compact_index_cache_path
+ home(".bundle/cache/compact_index")
+ end
+
def bundled_app(*path)
- root = tmp.join("bundled_app")
+ root = tmp("bundled_app")
FileUtils.mkdir_p(root)
root.join(*path)
end
- alias_method :bundled_app1, :bundled_app
-
def bundled_app2(*path)
- root = tmp.join("bundled_app2")
+ root = tmp("bundled_app2")
FileUtils.mkdir_p(root)
root.join(*path)
end
def vendored_gems(path = nil)
- bundled_app(*["vendor/bundle", Gem.ruby_engine, Gem::ConfigMap[:ruby_version], path].compact)
+ scoped_gem_path(bundled_app("vendor/bundle")).join(*[path].compact)
end
def cached_gem(path)
bundled_app("vendor/cache/#{path}.gem")
end
- def base_system_gems
- tmp.join("gems/base")
+ def bundled_app_gemfile
+ bundled_app("Gemfile")
+ end
+
+ def bundled_app_lock
+ bundled_app("Gemfile.lock")
+ end
+
+ def scoped_base_system_gem_path
+ scoped_gem_path(base_system_gem_path)
+ end
+
+ def base_system_gem_path
+ tmp_root.join("gems/base")
+ end
+
+ def rubocop_gem_path
+ tmp_root.join("gems/rubocop")
+ end
+
+ def standard_gem_path
+ tmp_root.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)
+ gem_path("remote1", *args)
end
def gem_repo_missing(*args)
- tmp("gems/missing", *args)
+ gem_path("missing", *args)
end
def gem_repo2(*args)
- tmp("gems/remote2", *args)
+ gem_path("remote2", *args)
end
def gem_repo3(*args)
- tmp("gems/remote3", *args)
+ gem_path("remote3", *args)
end
def gem_repo4(*args)
- tmp("gems/remote4", *args)
+ gem_path("remote4", *args)
end
def security_repo(*args)
- tmp("gems/security_repo", *args)
+ gem_path("security_repo", *args)
end
def system_gem_path(*path)
- tmp("gems/system", *path)
+ gem_path("system", *path)
+ end
+
+ def pristine_system_gem_path
+ tmp_root.join("gems/pristine_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 gem_path(*args)
+ tmp("gems", *args)
end
def lib_path(*args)
tmp("libs", *args)
end
- def bundler_path
- Pathname.new(File.expand_path(root.join("lib"), __FILE__))
+ def source_lib_dir
+ source_root.join("lib")
+ end
+
+ def lib_dir
+ root.join("lib")
end
def global_plugin_gem(*args)
@@ -106,15 +276,106 @@ module Spec
tmp "tmpdir", *args
end
- extend self
+ 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*).*$/, %(\\1"#{version}"))
+ File.open(version_file, "w") {|f| f << contents }
+ end
- private
- def for_ruby_core?
- if @for_ruby_core.nil?
- @for_ruby_core = true & (ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"])
+ def replace_required_ruby_version(version, dir:)
+ gemspec_file = File.expand_path("bundler.gemspec", dir)
+ contents = File.read(gemspec_file)
+ contents.sub!(/(^\s+s\.required_ruby_version\s*=\s*)"[^"]+"/, %(\\1"#{version}"))
+ File.open(gemspec_file, "w") {|f| f << contents }
+ end
+
+ def replace_changelog(version, dir:)
+ changelog = File.expand_path("CHANGELOG.md", dir)
+ contents = File.readlines(changelog)
+ contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join
+ File.open(changelog, "w") {|f| f << contents }
+ end
+
+ def git_root
+ ruby_core? ? source_root : source_root.parent
+ end
+
+ def rake_path
+ find_base_path("rake")
+ end
+
+ def rake_version
+ File.basename(rake_path).delete_prefix("rake-").delete_suffix(".gem")
+ end
+
+ def sinatra_dependency_paths
+ deps = %w[
+ mustermann
+ rack
+ rack-protection
+ rack-session
+ tilt
+ sinatra
+ base64
+ logger
+ compact_index
+ ]
+ path = if deps.all? {|dep| !Dir[scoped_base_system_gem_path.join("gems/#{dep}-*")].empty? }
+ scoped_base_system_gem_path
+ elsif ruby_core? && deps.all? {|dep| !Dir[source_root.join(".bundle/gems/#{dep}-*")].empty? }
+ source_root.join(".bundle")
else
- @for_ruby_core
+ scoped_base_system_gem_path
end
+
+ Dir[path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s)
+ end
+
+ private
+
+ def find_base_path(name)
+ Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first
+ end
+
+ def git_ls_files(glob)
+ skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball?
+
+ git("ls-files -z -- #{glob}", source_root).split("\x0")
+ end
+
+ def tracked_files_glob
+ ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec"
+ end
+
+ def lib_tracked_files_glob
+ ruby_core? ? "lib/bundler lib/bundler.rb" : "lib"
+ end
+
+ def man_tracked_files_glob
+ "lib/bundler/man/bundle*.1.ronn lib/bundler/man/gemfile*.5.ronn"
+ 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
index f5636dd70a..b21ce3848d 100644
--- a/spec/bundler/support/permissions.rb
+++ b/spec/bundler/support/permissions.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Spec
module Permissions
def with_umask(new_umask)
diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb
index a2a3afba00..56a0843005 100644
--- a/spec/bundler/support/platforms.rb
+++ b/spec/bundler/support/platforms.rb
@@ -1,86 +1,44 @@
# 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", "linux", nil])
- end
-
- def mswin
- Gem::Platform.new(["x86", "mswin32", nil])
- end
-
- def mingw
- Gem::Platform.new(["x86", "mingw32", nil])
- end
-
- def x64_mingw
- Gem::Platform.new(["x64", "mingw32", nil])
+ def not_local
+ generic_local_platform == Gem::Platform::RUBY ? "java" : Gem::Platform::RUBY
end
- def all_platforms
- [rb, java, linux, mswin, mingw, x64_mingw]
+ def local_platform
+ Bundler.local_platform
end
- def local
- generic_local_platform
- end
-
- def not_local
- all_platforms.find {|p| p != generic_local_platform }
+ def generic_local_platform
+ Gem::Platform.generic(local_platform)
end
def local_tag
- if RUBY_PLATFORM == "java"
+ if Gem.java_platform?
:jruby
+ elsif Gem.win_platform?
+ :windows
else
:ruby
end
end
def not_local_tag
- [:ruby, :jruby].find {|tag| tag != local_tag }
+ [:jruby, :windows, :ruby].find {|tag| tag != local_tag }
end
def local_ruby_engine
- ENV["BUNDLER_SPEC_RUBY_ENGINE"] || (defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby")
+ RUBY_ENGINE
end
def local_engine_version
- return ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] if ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"]
-
- case local_ruby_engine
- when "ruby"
- RUBY_VERSION
- when "rbx"
- Rubinius::VERSION
- when "jruby"
- JRUBY_VERSION
- else
- raise BundlerError, "That RUBY_ENGINE is not recognized"
- end
+ RUBY_ENGINE == "ruby" ? Gem.ruby_version : RUBY_ENGINE_VERSION
end
def not_local_engine_version
case not_local_tag
- when :ruby
+ when :ruby, :windows
not_local_ruby_version
when :jruby
"1.6.1"
@@ -94,5 +52,24 @@ module Spec
def not_local_patchlevel
9999
end
+
+ def default_platform_list(*extra, defaults: default_locked_platforms)
+ defaults.concat(extra).map(&:to_s).uniq
+ end
+
+ def lockfile_platforms(*extra, defaults: default_locked_platforms)
+ platforms = default_platform_list(*extra, defaults: defaults)
+ platforms.sort.join("\n ")
+ end
+
+ def default_locked_platforms
+ [local_platform, generic_default_locked_platform].compact
+ end
+
+ def generic_default_locked_platform
+ return unless Bundler::MatchPlatform.generic_local_platform_is_ruby?
+
+ Gem::Platform::RUBY
+ end
end
end
diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb
index 596e1530d2..812dc4deaa 100644
--- a/spec/bundler/support/rubygems_ext.rb
+++ b/spec/bundler/support/rubygems_ext.rb
@@ -1,64 +1,222 @@
# frozen_string_literal: true
-require "rubygems/user_interaction"
-require "support/path" unless defined?(Spec::Path)
+
+abort "RubyGems only supports Ruby 3.2 or higher" if RUBY_VERSION < "3.2.0"
+
+require_relative "path"
+
+$LOAD_PATH.unshift(Spec::Path.source_lib_dir.to_s)
module Spec
module Rubygems
- DEPS = begin
- deps = {
- # rack 2.x requires Ruby version >= 2.2.2.
- # artifice doesn't support rack 2.x now.
- "rack" => "< 2",
- # rack-test 0.7.0 dropped 1.8.7 support
- # https://github.com/rack-test/rack-test/issues/193#issuecomment-314230318
- "rack-test" => "< 0.7.0",
- "artifice" => "~> 0.6.0",
- "compact_index" => "~> 0.11.0",
- "sinatra" => "~> 1.4.7",
- # Rake version has to be consistent for tests to pass
- "rake" => "10.0.2",
- # 3.0.0 breaks 1.9.2 specs
- "builder" => "2.1.2",
- "bundler" => "1.12.0",
- }
- # ruby-graphviz is used by the viz tests
- deps["ruby-graphviz"] = nil if RUBY_VERSION >= "1.9.3"
- deps
- end
-
- def self.setup
- Gem.clear_paths
+ extend self
- ENV["BUNDLE_PATH"] = nil
- ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gems.to_s
- ENV["PATH"] = [Path.bindir, "#{Path.system_gem_path}/bin", ENV["PATH"]].join(File::PATH_SEPARATOR)
-
- manifest = DEPS.to_a.sort_by(&:first).map {|k, v| "#{k} => #{v}\n" }
- manifest_path = "#{Path.base_system_gems}/manifest.txt"
- # it's OK if there are extra gems
- if !File.exist?(manifest_path) || !(manifest - File.readlines(manifest_path)).empty?
- FileUtils.rm_rf(Path.base_system_gems)
- FileUtils.mkdir_p(Path.base_system_gems)
- puts "installing gems for the tests to use..."
- install_gems(DEPS)
- File.open(manifest_path, "w") {|f| f << manifest.join }
- 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, entrypoint)
+ gem_activate(gem_name)
+ require entrypoint
+ end
+
+ def test_setup
+ # Install test dependencies unless parallel-rspec is being used, since in that case they should be setup already
+ install_test_deps unless ENV["RSPEC_FORMATTER_OUTPUT_ID"]
+
+ 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
+ # Remove "RUBY_CODESIGN", which is used by mkmf-generated Makefile to
+ # sign extension bundles on macOS, to avoid trying to find the specified key
+ # from the fake $HOME/Library/Keychains directory.
+ ENV.delete "RUBY_CODESIGN"
+ if Path.ruby_core?
+ if (tmpdir = ENV["TMPDIR"])
+ tmpdir_real = begin
+ File.realpath(tmpdir)
+ rescue Errno::ENOENT, Errno::EACCES
+ tmpdir
+ end
+ ENV["TMPDIR"] = tmpdir_real if tmpdir_real != tmpdir
+ end
+ else
+ ENV["TMPDIR"] = Path.tmpdir.to_s
+ end
+ require "rubygems/user_interaction"
Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
end
- def self.install_gems(gems)
- reqs, no_reqs = gems.partition {|_, req| !req.nil? && !req.split(" ").empty? }
- reqs = reqs.sort_by {|name, _| name == "rack" ? 0 : 1 } # TODO: remove when we drop ruby 1.8.7 support
- no_reqs.map!(&:first)
- reqs.map! {|name, req| "'#{name}:#{req}'" }
- deps = reqs.concat(no_reqs).join(" ")
- cmd = "#{ENV['BUNDLE_GEM'] || 'gem'} install #{deps} --no-rdoc --no-ri --conservative"
- puts cmd
- system(cmd) || raise("Installing gems #{deps} for the tests to use failed!")
+ def setup_test_paths
+ ENV["BUNDLE_PATH"] = nil
+ ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR)
+ ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core?
+ end
+
+ def install_test_deps
+ dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s)
+ dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s)
+ dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s)
+
+ require_relative "helpers"
+ Helpers.install_dev_bundler
+
+ install_vendored_compact_index
+ end
+
+ # Vendor `rubygems/rubygems.org#lib/compact_index/` under `tmp/compact_index/`
+ # so the artifice can serve compact-index responses without a runtime gem
+ # dependency. Pinned to a reviewed commit; override with COMPACT_INDEX_REF
+ # to refresh against another ref (the existing vendor copy is discarded).
+ def install_vendored_compact_index
+ target_root = Path.tmp_root.join("compact_index")
+ require "fileutils"
+ FileUtils.mkdir_p(Path.tmp_root)
+
+ files = %w[
+ lib/compact_index.rb
+ lib/compact_index/dependency.rb
+ lib/compact_index/gem.rb
+ lib/compact_index/gem_version.rb
+ lib/compact_index/versions_file.rb
+ ]
+
+ # Serialize installs so parallel test setups don't race on the same
+ # vendor tree, and only skip the download when every file is present so
+ # an interrupted run can't leave a partial copy behind.
+ File.open(Path.tmp_root.join("compact_index.lock"), File::CREAT | File::RDWR) do |lock|
+ lock.flock(File::LOCK_EX)
+
+ FileUtils.rm_rf(target_root) if ENV["COMPACT_INDEX_REF"]
+
+ next if files.all? {|path| File.exist?(target_root.join(path)) }
+
+ require "open-uri"
+ ref = ENV["COMPACT_INDEX_REF"] || "7c68a7b39761c61a66f9299f85b889ec39afc02c"
+ files.each do |path|
+ url = "https://raw.githubusercontent.com/rubygems/rubygems.org/#{ref}/#{path}"
+ target = target_root.join(path)
+ FileUtils.mkdir_p(File.dirname(target))
+ tmp = "#{target}.tmp"
+ File.write(tmp, URI.parse(url).open(&:read))
+ File.rename(tmp, target)
+ end
+ end
+ 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 diff")
+
+ puts
+ puts error_message
+ puts
+
+ exit(1)
+ end
+ end
+
+ def dev_bundle(*args, gemfile: dev_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
+
+ require "shellwords"
+ # We don't use `Open3` here because it does not work on JRuby + Windows
+ output = `ruby #{Path.dev_binstub} #{args.shelljoin}`
+ raise output unless $?.success?
+ output
+ 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
+
+ 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_relative "activate"
+ 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 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..c174c461f0
--- /dev/null
+++ b/spec/bundler/support/rubygems_version_manager.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require_relative "options"
+require_relative "env"
+require_relative "subprocess"
+
+class RubygemsVersionManager
+ include Spec::Options
+ include Spec::Env
+ include Spec::Subprocess
+
+ 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"
+
+ 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)
+ 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#{File.join(local_copy_path, "lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"]))
+
+ exec(ENV, *cmd)
+ end
+
+ def switch_local_copy_if_needed
+ return unless local_copy_switch_needed?
+
+ git("checkout #{target_tag}", local_copy_path)
+
+ ENV["RGV"] = local_copy_path
+ end
+
+ def rubygems_unrequire_needed?
+ require "rubygems"
+ !$LOADED_FEATURES.include?(File.join(local_copy_path, "lib/rubygems.rb"))
+ 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
+ git("rev-parse --abbrev-ref HEAD", 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 = File.join(source_root, "tmp/rubygems")
+
+ unless File.directory?(rubygems_path)
+ git("clone .. #{rubygems_path}", source_root)
+ end
+
+ rubygems_path
+ end
+
+ def source_is_path?
+ File.directory?(expanded_source)
+ end
+
+ def expanded_source
+ @expanded_source ||= File.expand_path(@source, source_root)
+ end
+
+ def source_root
+ @source_root ||= File.expand_path(ruby_core? ? "../../.." : "../..", __dir__)
+ end
+
+ def resolve_target_tag
+ return "v#{@source}" if @source.match?(/^\d/)
+
+ @source
+ end
+end
diff --git a/spec/bundler/support/setup.rb b/spec/bundler/support/setup.rb
new file mode 100644
index 0000000000..4ac2e5b472
--- /dev/null
+++ b/spec/bundler/support/setup.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require_relative "switch_rubygems"
+
+require_relative "rubygems_ext"
+Spec::Rubygems.install_test_deps
+
+require_relative "path"
+$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__)) if Spec::Path.ruby_core?
diff --git a/spec/bundler/support/shards.rb b/spec/bundler/support/shards.rb
new file mode 100644
index 0000000000..ce33896539
--- /dev/null
+++ b/spec/bundler/support/shards.rb
@@ -0,0 +1,200 @@
+# frozen_string_literal: true
+
+# This classifies test files into 4 shards by running `bin/rspec --profile 10000`
+# to ensure balanced execution times. When adding new test files, it is recommended to
+# re-aggregate and adjust the shards to keep them balanced.
+# For now, please add new files to shard 'shard_d'.
+
+module Spec
+ module Shards
+ EXAMPLE_MAPPINGS = {
+ shard_a: [
+ "spec/runtime/setup_spec.rb",
+ "spec/commands/install_spec.rb",
+ "spec/commands/add_spec.rb",
+ "spec/install/gems/compact_index_spec.rb",
+ "spec/commands/config_spec.rb",
+ "spec/commands/pristine_spec.rb",
+ "spec/install/gemfile/path_spec.rb",
+ "spec/update/git_spec.rb",
+ "spec/commands/open_spec.rb",
+ "spec/commands/remove_spec.rb",
+ "spec/commands/show_spec.rb",
+ "spec/plugins/source/example_spec.rb",
+ "spec/commands/console_spec.rb",
+ "spec/runtime/require_spec.rb",
+ "spec/runtime/env_helpers_spec.rb",
+ "spec/runtime/gem_tasks_spec.rb",
+ "spec/install/gemfile_spec.rb",
+ "spec/commands/fund_spec.rb",
+ "spec/commands/init_spec.rb",
+ "spec/bundler/ruby_dsl_spec.rb",
+ "spec/bundler/mirror_spec.rb",
+ "spec/bundler/source/git/git_proxy_spec.rb",
+ "spec/bundler/source_list_spec.rb",
+ "spec/bundler/plugin/installer_spec.rb",
+ "spec/bundler/errors_spec.rb",
+ "spec/bundler/friendly_errors_spec.rb",
+ "spec/resolver/platform_spec.rb",
+ "spec/bundler/fetcher/downloader_spec.rb",
+ "spec/update/force_spec.rb",
+ "spec/bundler/env_spec.rb",
+ "spec/install/gems/mirror_spec.rb",
+ "spec/install/failure_spec.rb",
+ "spec/bundler/yaml_serializer_spec.rb",
+ "spec/bundler/environment_preserver_spec.rb",
+ "spec/install/gemfile/install_if_spec.rb",
+ "spec/install/gems/gemfile_source_header_spec.rb",
+ "spec/bundler/fetcher/base_spec.rb",
+ "spec/bundler/rubygems_integration_spec.rb",
+ "spec/bundler/worker_spec.rb",
+ "spec/bundler/dependency_spec.rb",
+ "spec/bundler/ui_spec.rb",
+ "spec/bundler/plugin/source_list_spec.rb",
+ "spec/bundler/source/path_spec.rb",
+ ],
+ shard_b: [
+ "spec/install/gemfile/git_spec.rb",
+ "spec/install/gems/standalone_spec.rb",
+ "spec/commands/lock_spec.rb",
+ "spec/cache/gems_spec.rb",
+ "spec/other/major_deprecation_spec.rb",
+ "spec/install/gems/dependency_api_spec.rb",
+ "spec/install/gemfile/gemspec_spec.rb",
+ "spec/plugins/install_spec.rb",
+ "spec/commands/binstubs_spec.rb",
+ "spec/install/gems/flex_spec.rb",
+ "spec/runtime/inline_spec.rb",
+ "spec/commands/post_bundle_message_spec.rb",
+ "spec/runtime/executable_spec.rb",
+ "spec/lock/git_spec.rb",
+ "spec/plugins/hook_spec.rb",
+ "spec/install/allow_offline_install_spec.rb",
+ "spec/install/gems/post_install_spec.rb",
+ "spec/install/gemfile/ruby_spec.rb",
+ "spec/install/security_policy_spec.rb",
+ "spec/install/yanked_spec.rb",
+ "spec/update/gemfile_spec.rb",
+ "spec/runtime/load_spec.rb",
+ "spec/plugins/command_spec.rb",
+ "spec/commands/version_spec.rb",
+ "spec/install/prereleases_spec.rb",
+ "spec/bundler/uri_credentials_filter_spec.rb",
+ "spec/bundler/plugin_spec.rb",
+ "spec/install/gems/mirror_probe_spec.rb",
+ "spec/plugins/list_spec.rb",
+ "spec/bundler/compact_index_client/parser_spec.rb",
+ "spec/bundler/gem_version_promoter_spec.rb",
+ "spec/other/cli_dispatch_spec.rb",
+ "spec/bundler/source/rubygems_spec.rb",
+ "spec/cache/platform_spec.rb",
+ "spec/update/gems/fund_spec.rb",
+ "spec/bundler/stub_specification_spec.rb",
+ "spec/bundler/retry_spec.rb",
+ "spec/bundler/installer/spec_installation_spec.rb",
+ "spec/bundler/spec_set_spec.rb",
+ "spec/quality_es_spec.rb",
+ "spec/bundler/index_spec.rb",
+ "spec/other/cli_man_pages_spec.rb",
+ ],
+ shard_c: [
+ "spec/commands/newgem_spec.rb",
+ "spec/commands/exec_spec.rb",
+ "spec/commands/clean_spec.rb",
+ "spec/commands/platform_spec.rb",
+ "spec/cache/git_spec.rb",
+ "spec/install/gemfile/groups_spec.rb",
+ "spec/commands/cache_spec.rb",
+ "spec/commands/check_spec.rb",
+ "spec/commands/list_spec.rb",
+ "spec/install/path_spec.rb",
+ "spec/bundler/cli_spec.rb",
+ "spec/install/bundler_spec.rb",
+ "spec/install/git_spec.rb",
+ "spec/commands/doctor_spec.rb",
+ "spec/bundler/dsl_spec.rb",
+ "spec/install/gems/fund_spec.rb",
+ "spec/install/gems/env_spec.rb",
+ "spec/bundler/ruby_version_spec.rb",
+ "spec/bundler/definition_spec.rb",
+ "spec/install/gemfile/eval_gemfile_spec.rb",
+ "spec/plugins/source_spec.rb",
+ "spec/install/gems/dependency_api_fallback_spec.rb",
+ "spec/plugins/uninstall_spec.rb",
+ "spec/bundler/plugin/index_spec.rb",
+ "spec/bundler/bundler_spec.rb",
+ "spec/bundler/fetcher_spec.rb",
+ "spec/bundler/source/rubygems/remote_spec.rb",
+ "spec/bundler/lockfile_parser_spec.rb",
+ "spec/cache/cache_path_spec.rb",
+ "spec/bundler/source/git_spec.rb",
+ "spec/bundler/source_spec.rb",
+ "spec/commands/ssl_spec.rb",
+ "spec/bundler/fetcher/compact_index_spec.rb",
+ "spec/bundler/plugin/api_spec.rb",
+ "spec/bundler/endpoint_specification_spec.rb",
+ "spec/bundler/fetcher/index_spec.rb",
+ "spec/bundler/settings/validator_spec.rb",
+ "spec/bundler/build_metadata_spec.rb",
+ "spec/bundler/current_ruby_spec.rb",
+ "spec/bundler/installer/gem_installer_spec.rb",
+ "spec/bundler/installer/parallel_installer_spec.rb",
+ "spec/bundler/cli_common_spec.rb",
+ "spec/bundler/ci_detector_spec.rb",
+ ],
+ shard_d: [
+ "spec/bundler/rubygems_ext_spec.rb",
+ "spec/bundler/resolver/cooldown_spec.rb",
+ "spec/install/cooldown_spec.rb",
+ "spec/commands/outdated_spec.rb",
+ "spec/commands/update_spec.rb",
+ "spec/lock/lockfile_spec.rb",
+ "spec/install/deploy_spec.rb",
+ "spec/install/gemfile/sources_spec.rb",
+ "spec/runtime/self_management_spec.rb",
+ "spec/install/gemfile/specific_platform_spec.rb",
+ "spec/commands/info_spec.rb",
+ "spec/install/gems/resolving_spec.rb",
+ "spec/install/gemfile/platform_spec.rb",
+ "spec/bundler/gem_helper_spec.rb",
+ "spec/install/global_cache_spec.rb",
+ "spec/runtime/platform_spec.rb",
+ "spec/update/gems/post_install_spec.rb",
+ "spec/install/gems/native_extensions_spec.rb",
+ "spec/install/force_spec.rb",
+ "spec/cache/path_spec.rb",
+ "spec/install/gemspecs_spec.rb",
+ "spec/commands/help_spec.rb",
+ "spec/bundler/shared_helpers_spec.rb",
+ "spec/bundler/settings_spec.rb",
+ "spec/resolver/basic_spec.rb",
+ "spec/install/gemfile/force_ruby_platform_spec.rb",
+ "spec/commands/licenses_spec.rb",
+ "spec/install/gemfile/lockfile_spec.rb",
+ "spec/bundler/fetcher/dependency_spec.rb",
+ "spec/quality_spec.rb",
+ "spec/bundler/remote_specification_spec.rb",
+ "spec/install/process_lock_spec.rb",
+ "spec/install/binstubs_spec.rb",
+ "spec/bundler/compact_index_client/updater_spec.rb",
+ "spec/bundler/ui/shell_spec.rb",
+ "spec/other/ext_spec.rb",
+ "spec/commands/issue_spec.rb",
+ "spec/update/path_spec.rb",
+ "spec/bundler/plugin/api/source_spec.rb",
+ "spec/install/gems/win32_spec.rb",
+ "spec/bundler/plugin/dsl_spec.rb",
+ "spec/runtime/requiring_spec.rb",
+ "spec/bundler/plugin/events_spec.rb",
+ "spec/bundler/resolver/candidate_spec.rb",
+ "spec/bundler/digest_spec.rb",
+ "spec/bundler/fetcher/gem_remote_fetcher_spec.rb",
+ "spec/bundler/uri_normalizer_spec.rb",
+ "spec/install/gems/no_build_extension_spec.rb",
+ "spec/install/gems/no_install_plugin_spec.rb",
+ "spec/bundler/override_spec.rb",
+ "spec/install/gemfile/override_spec.rb",
+ ],
+ }.freeze
+ end
+end
diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb
deleted file mode 100644
index 1a8f91b3ba..0000000000
--- a/spec/bundler/support/silent_logger.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# 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/sometimes.rb b/spec/bundler/support/sometimes.rb
deleted file mode 100644
index 6a50f5ff4c..0000000000
--- a/spec/bundler/support/sometimes.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-module Sometimes
- def run_with_retries(example_to_run, retries)
- example = RSpec.current_example
- example.metadata[:retries] ||= retries
-
- retries.times do |t|
- example.metadata[:retried] = t + 1
- example.instance_variable_set(:@exception, nil)
- example_to_run.run
- break unless example.exception
- end
-
- if e = example.exception
- new_exception = e.exception(e.message + "[Retried #{retries} times]")
- new_exception.set_backtrace e.backtrace
- example.instance_variable_set(:@exception, new_exception)
- end
- end
-end
-
-RSpec.configure do |config|
- config.include Sometimes
- config.alias_example_to :sometimes, :sometimes => true
- config.add_setting :sometimes_retry_count, :default => 5
-
- config.around(:each, :sometimes => true) do |example|
- retries = example.metadata[:retries] || RSpec.configuration.sometimes_retry_count
- run_with_retries(example, retries)
- end
-
- config.after(:suite) do
- message = proc do |color, text|
- colored = RSpec::Core::Formatters::ConsoleCodes.wrap(text, color)
- notification = RSpec::Core::Notifications::MessageNotification.new(colored)
- formatter = RSpec.configuration.formatters.first
- formatter.message(notification) if formatter.respond_to?(:message)
- end
-
- retried_examples = RSpec.world.example_groups.map do |g|
- g.descendants.map do |d|
- d.filtered_examples.select do |e|
- e.metadata[:sometimes] && e.metadata.fetch(:retried, 1) > 1
- end
- end
- end.flatten
-
- message.call(retried_examples.empty? ? :green : :yellow, "\n\nRetried examples: #{retried_examples.count}")
-
- retried_examples.each do |e|
- message.call(:cyan, " #{e.full_description}")
- path = RSpec::Core::Metadata.relative_path(e.location)
- message.call(:cyan, " [#{e.metadata[:retried]}/#{e.metadata[:retries]}] " + path)
- end
- end
-end
diff --git a/spec/bundler/support/streams.rb b/spec/bundler/support/streams.rb
deleted file mode 100644
index 561b29092b..0000000000
--- a/spec/bundler/support/streams.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-require "stringio"
-
-def capture(*streams)
- streams.map!(&:to_s)
- begin
- result = StringIO.new
- streams.each {|stream| eval "$#{stream} = result" }
- yield
- ensure
- streams.each {|stream| eval("$#{stream} = #{stream.upcase}") }
- end
- result.string
-end
diff --git a/spec/bundler/support/subprocess.rb b/spec/bundler/support/subprocess.rb
new file mode 100644
index 0000000000..91db80da48
--- /dev/null
+++ b/spec/bundler/support/subprocess.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+require_relative "command_execution"
+
+module Spec
+ module Subprocess
+ class TimeoutExceeded < StandardError; 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
+
+ def stdboth
+ last_command.stdboth
+ end
+
+ def exitstatus
+ last_command.exitstatus
+ end
+
+ def git(cmd, path = Dir.pwd, options = {})
+ sh("git #{cmd}", options.merge(dir: path))
+ end
+
+ def sh(cmd, options = {})
+ dir = options[:dir]
+ env = options[:env] || {}
+
+ command_execution = CommandExecution.new(cmd.to_s, timeout: options[:timeout] || 60)
+
+ open3_opts = {}
+ open3_opts[:chdir] = dir if dir
+
+ require "open3"
+ require "shellwords"
+ Open3.popen3(env, *cmd.shellsplit, **open3_opts) do |stdin, stdout, stderr, wait_thr|
+ yield stdin, stdout, wait_thr if block_given?
+ stdin.close
+
+ stdout_handler = ->(data) { command_execution.original_stdout << data }
+ stderr_handler = ->(data) { command_execution.original_stderr << data }
+
+ stdout_thread = read_stream(stdout, stdout_handler, timeout: command_execution.timeout)
+ stderr_thread = read_stream(stderr, stderr_handler, timeout: command_execution.timeout)
+
+ stdout_thread.join
+ stderr_thread.join
+
+ status = wait_thr.value
+ command_execution.exitstatus = if status.exited?
+ status.exitstatus
+ elsif status.signaled?
+ exit_status_for_signal(status.termsig)
+ end
+ rescue TimeoutExceeded
+ command_execution.failure_reason = :timeout
+ command_execution.exitstatus = exit_status_for_signal(Signal.list["INT"])
+ end
+
+ unless options[:raise_on_error] == false || command_execution.success?
+ command_execution.raise_error!
+ end
+
+ command_executions << command_execution
+
+ command_execution.stdout
+ end
+
+ # Mostly copied from https://github.com/piotrmurach/tty-command/blob/49c37a895ccea107e8b78d20e4cb29de6a1a53c8/lib/tty/command/process_runner.rb#L165-L193
+ def read_stream(stream, handler, timeout:)
+ Thread.new do
+ Thread.current.report_on_exception = false
+ cmd_start = Time.now
+ readers = [stream]
+
+ while readers.any?
+ ready = IO.select(readers, nil, readers, timeout)
+ raise TimeoutExceeded if ready.nil?
+
+ ready[0].each do |reader|
+ chunk = reader.readpartial(16 * 1024)
+ handler.call(chunk)
+
+ # control total time spent reading
+ runtime = Time.now - cmd_start
+ time_left = timeout - runtime
+ raise TimeoutExceeded if time_left < 0.0
+ rescue Errno::EAGAIN, Errno::EINTR
+ rescue EOFError, Errno::EPIPE, Errno::EIO
+ readers.delete(reader)
+ reader.close
+ end
+ end
+ end
+ end
+
+ def all_commands_output
+ return "" if command_executions.empty?
+
+ "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}"
+ end
+ end
+end
diff --git a/spec/bundler/support/sudo.rb b/spec/bundler/support/sudo.rb
deleted file mode 100644
index 8c82bb8c0f..0000000000
--- a/spec/bundler/support/sudo.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-module Spec
- module Sudo
- def self.present?
- @which_sudo ||= Bundler.which("sudo")
- end
-
- def sudo(cmd)
- raise "sudo not present" unless Sudo.present?
- sys_exec("sudo #{cmd}")
- end
-
- def chown_system_gems_to_root
- sudo "chown -R root #{system_gem_path}"
- 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..640b9f83b7
--- /dev/null
+++ b/spec/bundler/support/switch_rubygems.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative "rubygems_version_manager"
+ENV["RGV"] ||= "."
+RubygemsVersionManager.new(ENV["RGV"]).switch
diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb
index 742d393425..452abd7d41 100644
--- a/spec/bundler/support/the_bundle.rb
+++ b/spec/bundler/support/the_bundle.rb
@@ -1,18 +1,15 @@
# frozen_string_literal: true
-require "support/helpers"
-require "support/path"
+
+require_relative "path"
module Spec
class TheBundle
- include Spec::Helpers
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?
+ def initialize
+ @bundle_dir = Pathname.new(bundled_app)
end
def to_s
@@ -29,8 +26,16 @@ module Spec
end
def locked_gems
- raise "Cannot read lockfile if it doesn't exist" unless locked?
+ raise ArgumentError, "Cannot read lockfile if it doesn't exist" unless locked?
Bundler::LockfileParser.new(lockfile.read)
end
+
+ def locked_specs
+ locked_gems.specs.map(&:full_name)
+ end
+
+ def locked_platforms
+ locked_gems.platforms.map(&:to_s)
+ end
end
end
diff --git a/spec/bundler/support/vendored_net_http.rb b/spec/bundler/support/vendored_net_http.rb
new file mode 100644
index 0000000000..8ff2ccd1fe
--- /dev/null
+++ b/spec/bundler/support/vendored_net_http.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# This defined? guard can be removed once RubyGems 3.4 support is dropped.
+#
+# Bundler specs load this code from `spec/support/vendored_net_http.rb` to avoid
+# activating the Bundler gem too early. Without this guard, we get redefinition
+# warnings once Bundler is actually activated and
+# `lib/bundler/vendored_net_http.rb` is required. This is not an issue in
+# RubyGems versions including `rubygems/vendored_net_http` since `require` takes
+# care of avoiding the double load.
+#
+unless defined?(Gem::Net)
+ begin
+ require "rubygems/vendored_net_http"
+ rescue LoadError
+ begin
+ require "rubygems/net/http"
+ rescue LoadError
+ require "net/http"
+ Gem::Net = Net
+ end
+ end
+end
diff --git a/spec/bundler/update/force_spec.rb b/spec/bundler/update/force_spec.rb
new file mode 100644
index 0000000000..325f58088a
--- /dev/null
+++ b/spec/bundler/update/force_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ before :each do
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack"
+ G
+ end
+
+ it "re-installs installed gems with --force" do
+ myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb")
+ myrack_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle :update, force: true
+
+ expect(out).to include "Installing myrack 1.0.0"
+ expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+
+ it "re-installs installed gems with --redownload" do
+ myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb")
+ myrack_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle :update, redownload: true
+
+ expect(out).to include "Installing myrack 1.0.0"
+ expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n")
+ expect(the_bundle).to include_gems "myrack 1.0.0"
+ end
+end
diff --git a/spec/bundler/update/gemfile_spec.rb b/spec/bundler/update/gemfile_spec.rb
new file mode 100644
index 0000000000..f8849640b6
--- /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 "https://gem.repo1"
+ gem 'myrack'
+ 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 "myrack 1.0.0"
+ end
+ end
+
+ context "with gemfile set via config" do
+ before do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "https://gem.repo1"
+ gem 'myrack'
+ G
+
+ bundle_config "gemfile #{bundled_app("NotGemfile")}"
+ bundle :install
+ end
+
+ it "uses the gemfile to update" do
+ bundle "update", all: true
+ bundle "list"
+
+ expect(out).to include("myrack (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("myrack (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..a5624d3e0a
--- /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 "https://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 "https://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
index 5a4fe7f321..9c71f6e0e3 100644
--- a/spec/bundler/update/gems/post_install_spec.rb
+++ b/spec/bundler/update/gems/post_install_spec.rb
@@ -1,27 +1,26 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle update" do
let(:config) {}
before do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack', "< 1.0"
+ source "https://gem.repo1"
+ gem 'myrack', "< 1.0"
gem 'thin'
G
- bundle! "config #{config}" if config
+ bundle "config set #{config}" if config
- bundle! :install
+ 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" }
+ let(:config) { "ignore_messages.myrack true" }
it "doesn't display gem's post-install message" do
- expect(out).not_to include("Rack's post install message")
+ expect(out).not_to include("Myrack's post install message")
end
end
@@ -36,8 +35,8 @@ RSpec.describe "bundle update" do
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")
+ expect(out).to include("Post-install message from myrack:")
+ expect(out).to include("Myrack's post install message")
end
it "should not display the post-install message for non-updated gems" do
@@ -48,12 +47,12 @@ RSpec.describe "bundle update" do
context "when listed gem is updated" do
before do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack'
+ source "https://gem.repo1"
+ gem 'myrack'
gem 'thin'
G
- bundle! :update
+ bundle :update, all: true
end
it_behaves_like "a post-install message outputter"
@@ -63,12 +62,12 @@ RSpec.describe "bundle update" do
context "when dependency triggers update" do
before do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem 'rack-obama'
+ source "https://gem.repo1"
+ gem 'myrack-obama'
gem 'thin'
G
- bundle! :update
+ bundle :update, all: true
end
it_behaves_like "a post-install message outputter"
diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb
index 021c8c942b..526e988ab7 100644
--- a/spec/bundler/update/git_spec.rb
+++ b/spec/bundler/update/git_spec.rb
@@ -1,53 +1,54 @@
# frozen_string_literal: true
-require "spec_helper"
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"
+ update_git "foo", branch: "omg"
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo-1.0")}", :branch => "omg" do
gem 'foo'
end
G
- update_git "foo", :branch => "omg" do |s|
+ update_git "foo" do |s|
s.write "lib/foo.rb", "FOO = '1.1'"
end
- bundle "update"
+ 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|
+ 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 "https://gem.repo1"
gem "rails", :git => "#{lib_path("rails")}"
G
bundle "update rails"
- expect(out).to include("Using activesupport 3.0 from #{lib_path("rails")} (at master@#{revision_for(lib_path("rails"))[0..6]})")
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")
+ build_git "foo", "1.0", path: lib_path("foo")
+ update_git "foo", branch: "omg", path: lib_path("foo")
install_gemfile <<-G
+ source "https://gem.repo1"
git "#{lib_path("foo")}", :branch => "omg" do
gem 'foo'
end
G
- update_git "foo", :branch => "omg", :path => lib_path("foo") do |s|
+ update_git "foo", path: lib_path("foo") do |s|
s.write "lib/foo.rb", "FOO = '1.1'"
end
@@ -56,18 +57,19 @@ RSpec.describe "bundle update" do
expect(the_bundle).to include_gems "foo 1.1"
end
- it "floats on master 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_system => true do |s|
+ 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 "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo")}"
gem "bar"
G
- update_git "foo", :path => lib_path("foo") do |s|
+ update_git "foo", path: lib_path("foo") do |s|
s.write "lib/foo.rb", "FOO = '1.1'"
end
@@ -77,50 +79,59 @@ RSpec.describe "bundle update" do
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")
+ build_git "foo", path: lib_path("foo_one")
+ build_git "foo", path: lib_path("foo_two")
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", "1.0", :git => "#{lib_path("foo_one")}"
G
- FileUtils.rm_rf lib_path("foo_one")
+ FileUtils.rm_r lib_path("foo_one")
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", "1.0", :git => "#{lib_path("foo_two")}"
G
- expect(err).to lack_errors
+ expect(err).to be_empty
expect(out).to include("Fetching #{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 => @remote.path
- update_git "foo", :push => "master"
+ @remote = build_git("bar", bare: true)
+ update_git "foo", remote: @remote.path
+ update_git "foo", push: "main"
install_gemfile <<-G
+ source "https://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"
+ update_git "foo", tag: "fubar"
+ update_git "foo", push: "fubar"
gemfile <<-G
+ source "https://gem.repo1"
gem 'foo', :git => "#{@remote.path}", :tag => "fubar"
G
- bundle "update"
- expect(exitstatus).to eq(0) if exitstatus
+ bundle "update", all: true
+ expect(err).to be_empty
end
describe "with submodules" do
before :each do
- build_gem "submodule", :to_system => true do |s|
- s.write "lib/submodule.rb", "puts 'GEM'"
+ # 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|
@@ -131,14 +142,13 @@ RSpec.describe "bundle update" do
s.add_dependency "submodule"
end
- Dir.chdir(lib_path("has_submodule-1.0")) do
- sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
- `git commit -m "submodulator"`
- end
+ git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
+ git "commit -m \"submodulator\"", 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 "https://gem.repo4"
git "#{lib_path("has_submodule-1.0")}" do
gem "has_submodule"
end
@@ -148,6 +158,7 @@ RSpec.describe "bundle update" do
expect(out).to eq("GEM")
install_gemfile <<-G
+ source "https://gem.repo4"
git "#{lib_path("has_submodule-1.0")}", :submodules => true do
gem "has_submodule"
end
@@ -157,8 +168,9 @@ RSpec.describe "bundle update" do
expect(out).to eq("GIT")
end
- it "unlocks the source when submodules are removed from git source", :git => ">= 2.9.0" do
+ it "unlocks the source when submodules are removed from git source", git: ">= 2.9.0" do
install_gemfile <<-G
+ source "https://gem.repo4"
git "#{lib_path("has_submodule-1.0")}", :submodules => true do
gem "has_submodule"
end
@@ -168,6 +180,7 @@ RSpec.describe "bundle update" do
expect(out).to eq("GIT")
install_gemfile <<-G
+ source "https://gem.repo4"
git "#{lib_path("has_submodule-1.0")}" do
gem "has_submodule"
end
@@ -182,90 +195,80 @@ RSpec.describe "bundle update" do
build_git "foo", "1.0"
install_gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :git => "#{lib_path("foo-1.0")}"
G
- lib_path("foo-1.0").join(".git").rmtree
+ FileUtils.rm_rf lib_path("foo-1.0").join(".git")
- bundle :update
- expect(out).to include(lib_path("foo-1.0").to_s)
+ 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 "myrack", "0.8"
- build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
- s.write "lib/rack.rb", "puts :LOCAL"
+ build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
+ s.write "lib/myrack.rb", "puts :LOCAL"
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ source "https://gem.repo1"
+ gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
G
- bundle %(config local.rack #{lib_path("local-rack")})
- bundle "update rack"
+ bundle %(config set local.myrack #{lib_path("local-myrack")})
+ bundle "update myrack"
expect(out).to include("Bundle updated!")
end
it "shows the previous version of the gem" do
- build_git "rails", "3.0", :path => lib_path("rails")
+ build_git "rails", "2.3.2", path: lib_path("rails")
install_gemfile <<-G
+ source "https://gem.repo1"
gem "rails", :git => "#{lib_path("rails")}"
G
- lockfile <<-G
- GIT
- remote: #{lib_path("rails")}
- specs:
- rails (2.3.2)
-
- PLATFORMS
- #{generic_local_platform}
-
- DEPENDENCIES
- rails!
- G
+ update_git "rails", "3.0", path: lib_path("rails"), gemspec: true
- bundle "update"
- expect(out).to include("Using rails 3.0 (was 2.3.2) from #{lib_path("rails")} (at master@#{revision_for(lib_path("rails"))[0..6]})")
+ bundle "update", all: true
+ expect(out).to include("Using rails 3.0 (was 2.3.2) from #{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|
+ @git = build_git "foo", path: lib_path("foo") do |s|
s.executables = "foobar"
end
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
git "#{lib_path("foo")}" do
gem 'foo'
end
- gem 'rack'
+ gem 'myrack'
G
end
it "updates the source" do
- update_git "foo", :path => @git.path
+ update_git "foo", path: @git.path
bundle "update --source foo"
- in_app_root do
- run <<-RUBY
- require 'foo'
- puts "WIN" if defined?(FOO_PREV_REF)
- RUBY
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO_PREV_REF)
+ RUBY
- expect(out).to eq("WIN")
- end
+ 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
+ update_git "foo", "2.0", path: @git.path
bundle "update --source foo"
expect(the_bundle).to include_gems "foo 2.0"
@@ -273,24 +276,24 @@ RSpec.describe "bundle update" do
it "leaves all other gems frozen" do
update_repo2
- update_git "foo", :path => @git.path
+ update_git "foo", path: @git.path
bundle "update --source foo"
- expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).to include_gems "myrack 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")
+ @git = build_git "foo", path: lib_path("bar")
install_gemfile <<-G
- source "file://#{gem_repo2}"
+ source "https://gem.repo2"
git "#{lib_path("bar")}" do
gem 'foo'
end
- gem 'rack'
+ gem 'myrack'
G
end
@@ -298,15 +301,20 @@ RSpec.describe "bundle update" 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|
+ update_git "foo", "2.0", path: @git.path do |s|
s.write "foo.gemspec", spec_lines.join("\n")
end
- ref = @git.ref_for "master"
+ ref = @git.ref_for "main"
bundle "update --source bar"
- lockfile_should_be <<-G
+ checksums = checksums_section_when_enabled do |c|
+ c.no_checksum "foo", "2.0"
+ c.checksum gem_repo2, "myrack", "1.0.0"
+ end
+
+ expect(lockfile).to eq <<~G
GIT
remote: #{@git.path}
revision: #{ref}
@@ -314,19 +322,19 @@ RSpec.describe "bundle update" do
foo (2.0)
GEM
- remote: file:#{gem_repo2}/
+ remote: https://gem.repo2/
specs:
- rack (1.0.0)
+ myrack (1.0.0)
PLATFORMS
- ruby
+ #{lockfile_platforms}
DEPENDENCIES
foo!
- rack
-
+ myrack
+ #{checksums}
BUNDLED WITH
- #{Bundler::VERSION}
+ #{Bundler::VERSION}
G
end
end
diff --git a/spec/bundler/update/path_spec.rb b/spec/bundler/update/path_spec.rb
index 5ac4f7b1fe..8c76c94e1a 100644
--- a/spec/bundler/update/path_spec.rb
+++ b/spec/bundler/update/path_spec.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
-require "spec_helper"
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")
+ build_lib "activesupport", "2.3.5", path: lib_path("rails/activesupport")
install_gemfile <<-G
+ source "https://gem.repo1"
gem "activesupport", :path => "#{lib_path("rails/activesupport")}"
G
- build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+ 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")}`")