diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2d43edaa..e02a9068 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,15 +17,19 @@ jobs: fail-fast: false matrix: ruby: + - '3.4' - '3.3' - '3.2' - '3.1' - '3.0' + - '2.7' - 'head' - jruby - jruby-head - truffleruby - - truffleruby-head + # truffleruby-head is failing. + # TODO: Turn this back on once more important MRI Rubies are validated. + # - truffleruby-head steps: - uses: actions/checkout@v4 @@ -38,10 +42,23 @@ jobs: - name: Update bundler env: RUBY_VERSION: ${{ matrix.ruby }} + # See: https://dev.to/galtzo/matrix-ruby-gem-bundler-etc-4kk7 run: | case ${RUBY_VERSION} in - truffleruby|truffleruby-head) - gem install bundler -v 2.5.18 + 1.8|1.9|2.0|2.1|2.2) + gem install bundler -v 1.17.3 + ;; + + 2.3|2.4|2.5) + gem install bundler -v 2.3.27 + ;; + + 2.6|2.7) + gem install bundler -v 2.4.22 + ;; + + 3.0) + gem install bundler -v 2.5.23 ;; *) diff --git a/.gitignore b/.gitignore index acc12f06..46fa9b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ pkg Gemfile.lock .bundle .DS_Store + +spec/tmp diff --git a/.rspec b/.rspec new file mode 100644 index 00000000..78b7894f --- /dev/null +++ b/.rspec @@ -0,0 +1,4 @@ +--format progress +--color +--require spec_helper +--warnings diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..4f80d90f --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,20 @@ +inherit_gem: + rubocop-lts: config/rubygem_rspec.yml + +RSpec/ExampleLength: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/DescribeClass: + Enabled: false + +# TODO: We would need to implement Mutexes in order to make violations thread safe. +# But even then they would still trigger the violation. +# See: https://coderscat.com/ruby-change-current-working-directory/ +ThreadSafety/DirChdir: + Enabled: false + +Style/SymbolArray: + EnforcedStyle: brackets diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock new file mode 100644 index 00000000..cffcf805 --- /dev/null +++ b/.rubocop_gradual.lock @@ -0,0 +1,112 @@ +{ + "Gemfile:3872962921": [ + [26, 3, 4, "Security/Eval: The use of `eval` is a serious security risk.", 2087429787], + [28, 3, 4, "Security/Eval: The use of `eval` is a serious security risk.", 2087429787], + [30, 3, 4, "Security/Eval: The use of `eval` is a serious security risk.", 2087429787] + ], + "bin/bundle:41466308": [ + [66, 5, 20, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2485198147] + ], + "lib/appraisal/appraisal_file.rb:3950440874": [ + [13, 5, 52, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 946226050] + ], + "lib/appraisal/cli.rb:435288507": [ + [116, 5, 410, "Style/MissingRespondToMissing: When using `method_missing`, define `respond_to_missing?`.", 1969529734] + ], + "lib/appraisal/customize.rb:473220891": [ + [12, 5, 138, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2682968747], + [19, 5, 62, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 4082816720], + [23, 5, 516, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2780620929] + ], + "lib/appraisal/utils.rb:3706894031": [ + [6, 5, 129, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 733338411], + [10, 5, 124, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 222857520], + [17, 5, 354, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 119295155], + [39, 5, 152, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1306460482], + [46, 5, 151, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1120325158], + [52, 5, 98, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 714993131], + [56, 5, 244, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1461524090], + [65, 5, 111, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1909626476] + ], + "spec/acceptance/cli/clean_spec.rb:1564995421": [ + [3, 23, 17, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 1606893221] + ], + "spec/acceptance/cli/generate_spec.rb:245350726": [ + [3, 23, 20, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 3905826443] + ], + "spec/acceptance/cli/help_spec.rb:3569358941": [ + [3, 23, 16, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 2148351217] + ], + "spec/acceptance/cli/install_spec.rb:3249736824": [ + [3, 23, 19, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 3284642881] + ], + "spec/acceptance/cli/list_spec.rb:36921834": [ + [3, 23, 16, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 2152687586] + ], + "spec/acceptance/cli/update_spec.rb:3754424074": [ + [3, 23, 18, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 3752509585] + ], + "spec/acceptance/cli/version_spec.rb:2850459497": [ + [3, 23, 19, "RSpec/DescribeMethod: The second argument to describe should be the method being tested. '#instance' or '.class'.", 316256858] + ], + "spec/appraisal/appraisal_file_spec.rb:1896479557": [ + [28, 18, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441], + [33, 7, 57, "RSpec/NestedGroups: Maximum example group nesting exceeded [4/3].", 511689843], + [41, 20, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441], + [41, 33, 39, "RSpec/ExpectChange: Prefer `change(Appraisal::Customize, :heading)`.", 513325635], + [45, 7, 63, "RSpec/NestedGroups: Maximum example group nesting exceeded [4/3].", 1527582647], + [53, 20, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441], + [53, 33, 45, "RSpec/ExpectChange: Prefer `change(Appraisal::Customize, :single_quotes)`.", 627072007], + [57, 7, 77, "RSpec/NestedGroups: Maximum example group nesting exceeded [4/3].", 1960750461], + [65, 11, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441] + ], + "spec/appraisal/appraisal_spec.rb:3939376245": [ + [33, 11, 23, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2511581329], + [90, 11, 23, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1120511218], + [95, 7, 83, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [96].", 1453890366], + [95, 13, 10, "RSpec/InstanceVariable: Avoid instance variables - use let, a method call, or a local variable (if possible).", 4097172192], + [96, 7, 82, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [95].", 1614459968], + [96, 13, 10, "RSpec/InstanceVariable: Avoid instance variables - use let, a method call, or a local variable (if possible).", 4097172192], + [97, 61, 20, "RSpec/VerifiedDoubles: Prefer using verifying doubles over normal doubles.", 2281802167], + [104, 9, 10, "RSpec/InstanceVariable: Avoid instance variables - use let, a method call, or a local variable (if possible).", 4097172192], + [114, 7, 10, "RSpec/InstanceVariable: Avoid instance variables - use let, a method call, or a local variable (if possible).", 4097172192], + [120, 7, 10, "RSpec/InstanceVariable: Avoid instance variables - use let, a method call, or a local variable (if possible).", 4097172192], + [126, 7, 10, "RSpec/InstanceVariable: Avoid instance variables - use let, a method call, or a local variable (if possible).", 4097172192] + ], + "spec/appraisal/customize_spec.rb:3636242792": [ + [7, 1, 4472, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 194570577], + [17, 3, 13, "RSpec/SubjectDeclaration: Use subject explicitly rather than using let", 3282510975], + [25, 3, 564, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3336288078], + [27, 7, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441], + [43, 3, 286, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 4222920637], + [45, 7, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441], + [55, 3, 2985, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [13/5]", 1576524985], + [65, 7, 62, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [66, 67, 68].", 123979604], + [66, 7, 72, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [65, 67, 68].", 3240119264], + [67, 7, 74, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [65, 66, 68].", 3701550880], + [68, 7, 85, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [65, 66, 67].", 3874694796], + [72, 7, 7, "RSpec/NamedSubject: Name your test subject if you need to reference it explicitly.", 1892732441] + ], + "spec/appraisal/gemfile_spec.rb:3969538335": [ + [228, 11, 17, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3608128140], + [229, 13, 13, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 420381022], + [236, 13, 15, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3511037132], + [246, 13, 12, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3100155342], + [256, 11, 24, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3242693747], + [259, 13, 17, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 105800534], + [282, 13, 16, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 179066369], + [313, 13, 15, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3912615366], + [357, 13, 14, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2832187185], + [415, 13, 22, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 772581599], + [425, 11, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1076107314] + ], + "spec/appraisal/utils_spec.rb:2286225770": [ + [62, 17, 58, "RSpec/VerifiedDoubles: Prefer using verifying doubles over normal doubles.", 1573223692] + ], + "spec/support/acceptance_test_helpers.rb:3072478660": [ + [149, 5, 32, "Style/InvertibleUnlessCondition: Prefer `if $?.exitstatus == 0` over `unless $?.exitstatus != 0`.", 4187517264] + ], + "spec/support/stream_helpers.rb:3654818709": [ + [10, 17, 4, "Security/Eval: The use of `eval` is a serious security risk.", 2087429787] + ] +} diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..aba173c5 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby truffleruby-24.1.2 diff --git a/Gemfile b/Gemfile index a0dda8f5..eecd1d1c 100644 --- a/Gemfile +++ b/Gemfile @@ -2,10 +2,47 @@ source "https://rubygems.org" gemspec +# For Ruby version specific dependencies +ruby_version = Gem::Version.new(RUBY_VERSION) + +platform :mri do + # Debugging - Ensure ENV["DEBUG"] == "true" to use debuggers within spec suite + if ruby_version < Gem::Version.new("2.7") + # Use byebug in code + gem "byebug", ">= 11" + else + # Use binding.break, binding.b, or debugger in code + gem "debug", ">= 1.0.0" + end + + # Dev Console - Binding.pry - Irb replacement + gem "pry", "~> 0.14" # ruby >= 2.0 +end + # This here to make sure appraisal works with Rails 3.0.0. gem "thor", "~> 0.14.0" -group :development, :test do - gem "activesupport", ">= 3.2.21" - gem "rspec", "~> 3.0" +if ruby_version < Gem::Version.new("1.9") + eval File.read("Gemfile-1.8") +elsif ruby_version < Gem::Version.new("2.1") + eval File.read("Gemfile-2.0") +elsif ruby_version < Gem::Version.new("2.2") + eval File.read("Gemfile-2.1") +elsif ruby_version < Gem::Version.new("2.7") + # Std Lib extractions + gem "benchmark", "~> 0.4" # Removed from Std Lib in Ruby 3.5 +else + # Ruby >= 2.7 we can run style / lint checks via rubocop-gradual with rubocop-lts rules for Ruby 1.8+. + # This means we can develop on modern Ruby but remain compatible with ancient Ruby. + # TODO: Replace individual style gems below with modular gemfile, once eval_gemfile support is added to appraisal. + # eval_gemfile "gemfiles/modular/style.gemfile" + # We run rubocop on the latest version of Ruby, + # but in support of the oldest supported version of Ruby + gem "rubocop-lts", "~> 0.1", ">= 0.1.1" # Style and Linting support for Ruby >= 1.8 + gem "rubocop-packaging", "~> 0.5", ">= 0.5.2" + gem "rubocop-rspec", "~> 3.2" + gem "standard", ">= 1.35.1", "!= 1.41.1", "!= 1.42.0" + + # Std Lib extractions + gem "benchmark", "~> 0.4" # Removed from Std Lib in Ruby 3.5 end diff --git a/Gemfile-1.8 b/Gemfile-1.8 new file mode 100644 index 00000000..976c8496 --- /dev/null +++ b/Gemfile-1.8 @@ -0,0 +1,6 @@ +# These gems are locked for Ruby 1.8.7 compatibility +gem "i18n", "~> 0.6.0" +gem "activesupport", "~> 3.2.21" +gem "rake", "~> 10.5" +gem "rack", "~> 1.6.5" +gem "benchmark", "~> 0.3" # Removed from Std Lib in Ruby 3.5 diff --git a/Gemfile-2.0 b/Gemfile-2.0 new file mode 100644 index 00000000..75ff4f5d --- /dev/null +++ b/Gemfile-2.0 @@ -0,0 +1,4 @@ +# These gems are locked for Ruby 1.9 & 2.0 compatibility +gem "activesupport", "~> 4.2.7" +gem "rack", "~> 1.6.5" +gem "benchmark", "~> 0.3" # Removed from Std Lib in Ruby 3.5 diff --git a/Gemfile-2.1 b/Gemfile-2.1 new file mode 100644 index 00000000..71901a8d --- /dev/null +++ b/Gemfile-2.1 @@ -0,0 +1,4 @@ +# These gems are locked for Ruby 2.1 compatibility +gem "activesupport", "~> 4.2.7" +gem "rack", "~> 1.6.5" +gem "benchmark", "~> 0.4" # Removed from Std Lib in Ruby 3.5 diff --git a/README.md b/README.md index 464f9165..aa2cfda0 100644 --- a/README.md +++ b/README.md @@ -122,31 +122,31 @@ To do this, use the `remove_gem` declaration within the necessary `appraise` blo **Gemfile** ```ruby -gem 'rails', '~> 4.2' +gem "rails", "~> 4.2" group :test do - gem 'rspec', '~> 4.0' - gem 'test_after_commit' + gem "rspec", "~> 4.0" + gem "test_after_commit" end ``` **Appraisals** ```ruby -appraise 'rails-5' do - gem 'rails', '~> 5.2' +appraise "rails-5" do + gem "rails", "~> 5.2" group :test do - remove_gem 'test_after_commit' + remove_gem "test_after_commit" end end ``` Using the `Appraisals` file defined above, this is what the resulting `Gemfile` will look like: ```ruby -gem 'rails', '~> 5.2' +gem "rails", "~> 5.2" group :test do - gem 'rspec', '~> 4.0' + gem "rspec", "~> 4.0" end ``` @@ -174,12 +174,12 @@ You can also provide variables for substitution in the heading, based on each ap ```ruby customize_gemfiles do { - single_quotes: true, - heading: <<~HEADING - frozen_string_literal: true + :single_quotes => true, + :heading => <<-HEADING, +frozen_string_literal: true - `%{gemfile}` has been generated by Appraisal, do NOT modify it or `%{lockfile}` directly! - Make the changes to the "%{appraisal}" block in `Appraisals` instead. See the conventions at https://example.com/ +`%{gemfile}` has been generated by Appraisal, do NOT modify it or `%{lockfile}` directly! +Make the changes to the "%{appraisal}" block in `Appraisals` instead. See the conventions at https://example.com/ HEADING } end @@ -196,7 +196,7 @@ Using the `Appraisals` file defined above, this is what the resulting `Gemfile` # `rails-3.gemfile` has been generated by Appraisal, do NOT modify it or `rails-3.gemfile.lock` directly! # Make the changes to the "rails-3" block in `Appraisals` instead. See the conventions at https://example.com/ -gem 'rails', '3.2.14' +gem "rails", "3.2.14" ``` Version Control diff --git a/Rakefile b/Rakefile index f8d79dbc..44f0463b 100644 --- a/Rakefile +++ b/Rakefile @@ -10,5 +10,14 @@ RSpec::Core::RakeTask.new do |t| t.verbose = false end -desc "Default: run the rspec examples" -task default: [:spec] +begin + require "rubocop/lts" + Rubocop::Lts.install_tasks +rescue LoadError + task(:rubocop_gradual) do + warn("RuboCop (Gradual) is disabled") + end +end + +desc "Default: rubocop_gradual's autocorrect and run the rspec examples" +task :default => ["rubocop_gradual:autocorrect", :spec] diff --git a/appraisal.gemspec b/appraisal.gemspec index d136a37f..533b12e0 100644 --- a/appraisal.gemspec +++ b/appraisal.gemspec @@ -1,26 +1,53 @@ # frozen_string_literal: true -require_relative "lib/appraisal/version" +# TODO: Switch to require_relative once support for Ruby < 2 is dropped. +# require_relative "lib/appraisal/version" + +lib = File.expand_path("../lib", __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "appraisal/version" Gem::Specification.new do |s| - s.name = "appraisal" - s.version = Appraisal::VERSION.dup - s.platform = Gem::Platform::RUBY - s.authors = ["Joe Ferris", "Prem Sichanugrist"] - s.email = ["jferris@thoughtbot.com", "prem@thoughtbot.com"] - s.homepage = "http://github.com/thoughtbot/appraisal" - s.summary = "Find out what your Ruby gems are worth" + s.name = "appraisal" + s.version = Appraisal::VERSION.dup + s.platform = Gem::Platform::RUBY + s.authors = ["Joe Ferris", "Prem Sichanugrist"] + s.email = ["jferris@thoughtbot.com", "prem@thoughtbot.com"] + s.homepage = "http://github.com/thoughtbot/appraisal" + s.summary = "Find out what your Ruby gems are worth" s.description = 'Appraisal integrates with bundler and rake to test your library against different versions of dependencies in repeatable scenarios called "appraisals."' - s.license = "MIT" + s.license = "MIT" + + # specify which files should be added to the gem when it is released. + s.files = Dir[ + # Splats (keep alphabetical) + "lib/**/*.rb", + ] + + # automatically included with gem package, no need to list twice (i.e. do not list in files above). + s.extra_rdoc_files = Dir[ + # Files (keep alphabetical) + "CONTRIBUTING.md", + "MIT-LICENSE", + "README.md", + "SECURITY.md", + ] + + # bin/ is scripts, in any available language, for development of this specific gem + # exe/ is for ruby scripts that will ship with this gem to be used by other tools + s.bindir = "exe" + # files listed are relative paths from bindir above. + s.executables = [ + "appraisal", + ] - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = `git ls-files -- exe/*`.split("\n").map { |f| File.basename(f) } - s.bindir = "exe" + s.required_ruby_version = ">= 1.8.7" - s.required_ruby_version = ">= 2.3.0" + s.add_runtime_dependency("bundler", ">= 1.17.3") # Last version supporting Ruby 1.8.7 + s.add_runtime_dependency("rake", ">= 10") # Last version supporting Ruby 1.8.7 + s.add_runtime_dependency("thor", ">= 0.14") # Last version supporting Ruby 1.8.7 && Rails 3 - s.add_dependency("rake") - s.add_dependency("bundler") - s.add_dependency("thor", ">= 0.14.0") + s.add_development_dependency("activesupport", ">= 3.2.21") + s.add_development_dependency("rspec", "~> 3.13") + s.add_development_dependency("rspec-pending_for", "~> 0.1", ">= 0.1.17") end diff --git a/bin/bundle b/bin/bundle index 50da5fdf..fcf376ea 100755 --- a/bin/bundle +++ b/bin/bundle @@ -24,14 +24,14 @@ m = Module.new do def cli_arg_version return unless invoked_as_script? # don't want to hijack other binstubs return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil update_index = nil ARGV.each_with_index do |a, i| - if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) - bundler_version = a - end - next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ - bundler_version = $1 + bundler_version = a if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/o + + bundler_version = Regexp.last_match(1) update_index = i end bundler_version @@ -41,7 +41,7 @@ m = Module.new do gemfile = ENV["BUNDLE_GEMFILE"] return gemfile if gemfile && !gemfile.empty? - File.expand_path("../Gemfile", __dir__) + File.expand_path("../Gemfile", File.dirname(__FILE__)) end def lockfile @@ -55,8 +55,10 @@ m = Module.new do def lockfile_version return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) - return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/o + Regexp.last_match(1) end @@ -83,15 +85,19 @@ m = Module.new do def activate_bundler gem_error = activation_error_handling do - gem "bundler", bundler_requirement + gem("bundler", bundler_requirement) end return if gem_error.nil? + require_error = activation_error_handling do require "bundler/version" end - return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) - warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" - exit 42 + if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + return + end + + warn("Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`") + exit(42) end def activation_error_handling @@ -104,6 +110,4 @@ end m.load_bundler! -if m.invoked_as_script? - load Gem.bin_path("bundler", "bundle") -end +load Gem.bin_path("bundler", "bundle") if m.invoked_as_script? diff --git a/bin/rspec b/bin/rspec index cb53ebe5..d6b5fc6e 100755 --- a/bin/rspec +++ b/bin/rspec @@ -8,9 +8,9 @@ # this file is here to facilitate running it. # -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", File.dirname(__FILE__)) -bundle_binstub = File.expand_path("bundle", __dir__) +bundle_binstub = File.expand_path("bundle", File.dirname(__FILE__)) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") diff --git a/exe/appraisal b/exe/appraisal index 4e963840..16e4087a 100755 --- a/exe/appraisal +++ b/exe/appraisal @@ -10,5 +10,5 @@ begin Appraisal::CLI.start rescue Appraisal::AppraisalsNotFound => e puts e.message - exit 127 + exit(127) end diff --git a/gemfiles/modular/style.gemfile b/gemfiles/modular/style.gemfile new file mode 100644 index 00000000..c9cf8473 --- /dev/null +++ b/gemfiles/modular/style.gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# We run rubocop on the latest version of Ruby, +# but in support of the oldest supported version of Ruby + +gem "rubocop-lts", "~> 0.1", ">= 0.1.1" # Style and Linting support for Ruby >= 1.8 +gem "rubocop-packaging", "~> 0.5", ">= 0.5.2" +gem "rubocop-rspec", "~> 3.2" +gem "standard", ">= 1.35.1", "!= 1.41.1", "!= 1.42.0" + +# Std Lib extractions +gem "benchmark", "~> 0.4" # Removed from Std Lib in Ruby 3.5 diff --git a/lib/appraisal/appraisal.rb b/lib/appraisal/appraisal.rb index 86f2c3eb..3d76c5a4 100644 --- a/lib/appraisal/appraisal.rb +++ b/lib/appraisal/appraisal.rb @@ -10,7 +10,7 @@ module Appraisal # Represents one appraisal and its dependencies class Appraisal - DEFAULT_INSTALL_OPTIONS = { "jobs" => 1 }.freeze + DEFAULT_INSTALL_OPTIONS = {"jobs" => 1}.freeze attr_reader :name, :gemfile @@ -74,28 +74,24 @@ def write_gemfile def install(options = {}) commands = [install_command(options).join(" ")] - if options["without"].nil? || options["without"].empty? - commands.unshift(check_command.join(" ")) - end + commands.unshift(check_command.join(" ")) if options["without"].nil? || options["without"].empty? command = commands.join(" || ") if Bundler.settings[:path] - env = { "BUNDLE_DISABLE_SHARED_GEMS" => "1" } - Command.new(command, env: env).run + env = {"BUNDLE_DISABLE_SHARED_GEMS" => "1"} + Command.new(command, :env => env).run else Command.new(command).run end end def update(gems = []) - Command.new(update_command(gems), gemfile: gemfile_path).run + Command.new(update_command(gems), :gemfile => gemfile_path).run end def gemfile_path - unless gemfile_root.exist? - gemfile_root.mkdir - end + gemfile_root.mkdir unless gemfile_root.exist? gemfile_root.join(gemfile_name).to_s end @@ -110,10 +106,10 @@ def relativize lockfile_content = File.read(lockfile_path) File.open(lockfile_path, "w") do |file| - file.write lockfile_content.gsub( + file.write(lockfile_content.gsub( / #{current_directory}/, - " #{relative_path}" - ) + " #{relative_path}", + )) end end @@ -150,7 +146,7 @@ def lockfile_path end def clean_name - name.gsub(/[^\w\.]/, "_") + name.gsub(/[^\w.]/, "_") end def bundle_options(options) @@ -161,8 +157,8 @@ def bundle_options(options) if Utils.support_parallel_installation? options_strings << "--jobs=#{jobs}" else - warn "Your current version of Bundler does not support parallel installation. Please " + - "upgrade Bundler to version >= 1.4.0, or invoke `appraisal` without `--jobs` option." + warn("Your current version of Bundler does not support parallel installation. Please " \ + "upgrade Bundler to version >= 1.4.0, or invoke `appraisal` without `--jobs` option.") end end diff --git a/lib/appraisal/appraisal_file.rb b/lib/appraisal/appraisal_file.rb index 2d182038..1d7b9ec6 100644 --- a/lib/appraisal/appraisal_file.rb +++ b/lib/appraisal/appraisal_file.rb @@ -19,11 +19,9 @@ def initialize @gemfile = Gemfile.new @gemfile.load(ENV["BUNDLE_GEMFILE"] || "Gemfile") - if File.exist? path - run IO.read(path) - else - raise AppraisalsNotFound - end + raise AppraisalsNotFound unless File.exist?(path) + + run(File.read(path)) end def each(&block) @@ -37,7 +35,8 @@ def appraise(name, &block) end def customize_gemfiles(&_block) - Customize.new(**yield) + args = yield + Customize.new(args) end private diff --git a/lib/appraisal/bundler_dsl.rb b/lib/appraisal/bundler_dsl.rb index e102f4cd..8f474b5e 100644 --- a/lib/appraisal/bundler_dsl.rb +++ b/lib/appraisal/bundler_dsl.rb @@ -1,24 +1,35 @@ # frozen_string_literal: true require "appraisal/dependency_list" +require "appraisal/ordered_hash" module Appraisal class BundlerDSL attr_reader :dependencies - PARTS = %w[source ruby_version gits paths dependencies groups - platforms source_blocks install_if gemspec] + PARTS = %w[ + source + ruby_version + gits + paths + dependencies + groups + platforms + source_blocks + install_if + gemspec + ] def initialize @sources = [] @ruby_version = nil @dependencies = DependencyList.new @gemspecs = [] - @groups = {} - @platforms = {} - @gits = {} - @paths = {} - @source_blocks = {} + @groups = OrderedHash.new + @platforms = OrderedHash.new + @gits = OrderedHash.new + @paths = OrderedHash.new + @source_blocks = OrderedHash.new @git_sources = {} @install_if = {} end @@ -82,11 +93,11 @@ def path(source, options = {}, &block) end def to_s - Utils.join_parts(PARTS.map { |part| send("#{part}_entry") }) + Utils.join_parts(PARTS.map { |part| send(:"#{part}_entry") }) end def for_dup - Utils.join_parts(PARTS.map { |part| send("#{part}_entry_for_dup") }) + Utils.join_parts(PARTS.map { |part| send(:"#{part}_entry_for_dup") }) end def gemspec(options = {}) @@ -114,7 +125,7 @@ def ruby_version_entry case @ruby_version when String then "ruby #{@ruby_version.inspect}" - else "ruby(#{@ruby_version.inspect})" + else "ruby(#{Utils.format_string(@ruby_version)})" end end @@ -136,8 +147,8 @@ def dependencies_entry_for_dup @dependencies.for_dup end - %i[gits paths platforms groups source_blocks install_if].each do |method_name| - class_eval <<-METHODS, __FILE__, __LINE__ + [:gits, :paths, :platforms, :groups, :source_blocks, :install_if].each do |method_name| + class_eval <<-METHODS, __FILE__, __LINE__ + 1 private def #{method_name}_entry @@ -151,16 +162,34 @@ def #{method_name}_entry_for_dup end def indent(string) - string.strip.gsub(/^(.+)$/, ' \1') + indent_by = ENV.fetch("APPRAISAL_INDENTER", "lookaround") + if indent_by == "lookaround" + # Default indenter for Appraisal v3 + # Uses a "look-around" of the "look-behind" variety to indent lines that are more than just empty space. + # In other words, retain existing indentation, and indent the line again, but not on empty lines. + string. + # NOTES: + # (?![\r\n]) - Negative Look Behind which requires that the following pattern, + # which is (\s*) in this case, *not* be followed by a new line character + # (\s*) - Captures whitespace at beginning of the line + # Learn more here: https://learnbyexample.github.io/Ruby_Regexp/lookarounds.html + gsub(/^(?![\r\n])(\s*)/, ' \0'). + rstrip + elsif indent_by == "capture" + # Original indentation regex for Appraisal < v3 + string.gsub(/^(.+)$/, ' \1').rstrip + else + string + end end def substitute_git_source(requirements) requirements.each do |requirement| - if requirement.is_a?(Hash) - (requirement.keys & @git_sources.keys).each do |matching_source| - value = requirement.delete(matching_source) - requirement[:git] = @git_sources[matching_source].call(value) - end + next unless requirement.is_a?(Hash) + + (requirement.keys & @git_sources.keys).each do |matching_source| + value = requirement.delete(matching_source) + requirement[:git] = @git_sources[matching_source].call(value) end end end diff --git a/lib/appraisal/cli.rb b/lib/appraisal/cli.rb index 3c8a56f1..f5a966a2 100644 --- a/lib/appraisal/cli.rb +++ b/lib/appraisal/cli.rb @@ -11,7 +11,7 @@ class CLI < Thor class << self # Override help command to print out usage def help(shell, subcommand = false) - shell.say strip_heredoc(<<-HELP) + shell.say(strip_heredoc(<<-HELP)) Appraisal: Find out what your Ruby gems are worth. Usage: @@ -23,10 +23,10 @@ def help(shell, subcommand = false) if File.exist?("Appraisals") shell.say - shell.say "Available Appraisal(s):" + shell.say("Available Appraisal(s):") AppraisalFile.each do |appraisal| - shell.say " - #{appraisal.name}" + shell.say(" - #{appraisal.name}") end end @@ -48,23 +48,31 @@ def strip_heredoc(string) end desc "install", "Resolve and install dependencies for each appraisal" - method_option "jobs", aliases: "j", type: :numeric, default: 1, - banner: "SIZE", - desc: "Install gems in parallel using the given number of workers." - method_option "retry", type: :numeric, default: 1, - desc: "Retry network and git requests that have failed" - method_option "without", banner: "GROUP_NAMES", - desc: "A space-separated list of groups referencing gems to skip " + + method_option "jobs", + :aliases => "j", + :type => :numeric, + :default => 1, + :banner => "SIZE", + :desc => "Install gems in parallel using the given number of workers." + method_option "retry", + :type => :numeric, + :default => 1, + :desc => "Retry network and git requests that have failed" + method_option "without", + :banner => "GROUP_NAMES", + :desc => "A space-separated list of groups referencing gems to skip " \ "during installation. Bundler will remember this option." - method_option "full-index", type: :boolean, - desc: "Run bundle install with the " \ - "full-index argument." - method_option "path", type: :string, - desc: "Install gems in the specified directory. " \ - "Bundler will remember this option." + method_option "full-index", + :type => :boolean, + :desc => "Run bundle install with the " \ + "full-index argument." + method_option "path", + :type => :string, + :desc => "Install gems in the specified directory. " \ + "Bundler will remember this option." def install - invoke :generate, [], {} + invoke(:generate, [], {}) AppraisalFile.each do |appraisal| appraisal.install(options) @@ -81,12 +89,12 @@ def generate desc "clean", "Remove all generated gemfiles and lockfiles from gemfiles folder" def clean - FileUtils.rm_f Dir["gemfiles/*.{gemfile,gemfile.lock}"] + FileUtils.rm_f(Dir["gemfiles/*.{gemfile,gemfile.lock}"]) end desc "update [LIST_OF_GEMS]", "Remove all generated gemfiles and lockfiles, resolve, and install dependencies again" def update(*gems) - invoke :generate, [] + invoke(:generate, []) AppraisalFile.each do |appraisal| appraisal.update(gems) @@ -105,16 +113,16 @@ def version private - def method_missing(name, *args, &block) + def method_missing(name, *args) matching_appraisal = AppraisalFile.new.appraisals.detect do |appraisal| appraisal.name == name.to_s end if matching_appraisal - Command.new(args, gemfile: matching_appraisal.gemfile_path).run + Command.new(args, :gemfile => matching_appraisal.gemfile_path).run else AppraisalFile.each do |appraisal| - Command.new(ARGV, gemfile: appraisal.gemfile_path).run + Command.new(ARGV, :gemfile => appraisal.gemfile_path).run end end end diff --git a/lib/appraisal/command.rb b/lib/appraisal/command.rb index facadfd9..14c2718e 100644 --- a/lib/appraisal/command.rb +++ b/lib/appraisal/command.rb @@ -26,9 +26,7 @@ def run ENV[key] = value end - unless Kernel.system(command_as_string) - exit(1) - end + exit(1) unless Kernel.system(command_as_string) end end @@ -36,20 +34,20 @@ def run def ensure_bundler_is_available version = Utils.bundler_version - unless system %(gem list --silent -i bundler -v #{version}) - puts ">> Reinstall Bundler into #{ENV["GEM_HOME"]}" - - unless system "gem install bundler --version #{version}" - puts - puts <<-ERROR.strip.gsub(/\s+/, " ") - Bundler installation failed. - Please try running: - `GEM_HOME="#{ENV["GEM_HOME"]}" gem install bundler --version #{version}` - manually. - ERROR - exit(1) - end - end + return if system(%(gem list --silent -i bundler -v #{version})) + + puts ">> Reinstall Bundler into #{ENV["GEM_HOME"]}" + + return if system("gem install bundler --version #{version}") + + puts + puts <<-ERROR.rstrip +Bundler installation failed. +Please try running: + `GEM_HOME="#{ENV["GEM_HOME"]}" gem install bundler --version #{version}` +manually. + ERROR + exit(1) end def announce diff --git a/lib/appraisal/conditional.rb b/lib/appraisal/conditional.rb index ad2a8e1a..1056cded 100644 --- a/lib/appraisal/conditional.rb +++ b/lib/appraisal/conditional.rb @@ -11,14 +11,22 @@ def initialize(condition) end def to_s - "install_if #{@condition} do\n#{indent(super)}\nend" + <<-OUTPUT.rstrip +install_if #{@condition} do +#{indent(super)} +end + OUTPUT end # :nodoc: def for_dup return unless @condition.is_a?(String) - "install_if #{@condition} do\n#{indent(super)}\nend" + <<-OUTPUT.rstrip +install_if #{@condition} do +#{indent(super)} +end + OUTPUT end end end diff --git a/lib/appraisal/customize.rb b/lib/appraisal/customize.rb index 20cb99ac..cc8e08de 100644 --- a/lib/appraisal/customize.rb +++ b/lib/appraisal/customize.rb @@ -2,13 +2,17 @@ module Appraisal class Customize - def initialize(heading: nil, single_quotes: false) - @@heading = heading&.chomp + def initialize(options = {}) + heading = options.fetch(:heading, nil) + single_quotes = options.fetch(:single_quotes, false) + @@heading = !heading.nil? && heading.chomp @@single_quotes = single_quotes end - def self.heading(gemfile) + def self.heading(gemfile = nil) @@heading ||= nil + return @@heading unless gemfile + customize(@@heading, gemfile) end @@ -16,18 +20,18 @@ def self.single_quotes @@single_quotes ||= false end - def self.customize(heading, gemfile) - return nil unless heading + def self.customize(topper, gemfile) + return unless topper format( - heading.to_s, - appraisal: gemfile.send("clean_name"), - gemfile: gemfile.send("gemfile_name"), - gemfile_path: gemfile.gemfile_path, - lockfile: "#{gemfile.send('gemfile_name')}.lock", - lockfile_path: gemfile.send("lockfile_path"), - relative_gemfile_path: gemfile.relative_gemfile_path, - relative_lockfile_path: "#{gemfile.relative_gemfile_path}.lock" + topper.to_s, + :appraisal => gemfile.send(:clean_name), + :gemfile => gemfile.send(:gemfile_name), + :gemfile_path => gemfile.gemfile_path, + :lockfile => "#{gemfile.send(:gemfile_name)}.lock", + :lockfile_path => gemfile.send(:lockfile_path), + :relative_gemfile_path => gemfile.relative_gemfile_path, + :relative_lockfile_path => "#{gemfile.relative_gemfile_path}.lock", ) end diff --git a/lib/appraisal/dependency.rb b/lib/appraisal/dependency.rb index 37302ab8..367749df 100644 --- a/lib/appraisal/dependency.rb +++ b/lib/appraisal/dependency.rb @@ -14,12 +14,12 @@ def initialize(name, requirements) end def to_s - formatted_output Utils.format_arguments(path_prefixed_requirements) + formatted_output(Utils.format_arguments(path_prefixed_requirements)) end # :nodoc: def for_dup - formatted_output Utils.format_arguments(requirements) + formatted_output(Utils.format_arguments(requirements)) end private @@ -27,13 +27,9 @@ def for_dup def path_prefixed_requirements requirements.map do |requirement| if requirement.is_a?(Hash) - if requirement[:path] - requirement[:path] = Utils.prefix_path(requirement[:path]) - end + requirement[:path] = Utils.prefix_path(requirement[:path]) if requirement[:path] - if requirement[:git] - requirement[:git] = Utils.prefix_path(requirement[:git]) - end + requirement[:git] = Utils.prefix_path(requirement[:git]) if requirement[:git] end requirement diff --git a/lib/appraisal/dependency_list.rb b/lib/appraisal/dependency_list.rb index 9b6fb8d9..8a42c938 100644 --- a/lib/appraisal/dependency_list.rb +++ b/lib/appraisal/dependency_list.rb @@ -1,25 +1,26 @@ # frozen_string_literal: true require "appraisal/dependency" +require "appraisal/ordered_hash" require "set" module Appraisal class DependencyList def initialize - @dependencies = {} + @dependencies = OrderedHash.new @removed_dependencies = Set.new end def add(name, requirements) - unless @removed_dependencies.include?(name) - @dependencies[name] = Dependency.new(name, requirements) - end + return if @removed_dependencies.include?(name) + + @dependencies[name] = Dependency.new(name, requirements) end def remove(name) - if @removed_dependencies.add?(name) - @dependencies.delete(name) - end + return unless @removed_dependencies.add?(name) + + @dependencies.delete(name) end def to_s diff --git a/lib/appraisal/gemfile.rb b/lib/appraisal/gemfile.rb index bfbb4580..93911b57 100644 --- a/lib/appraisal/gemfile.rb +++ b/lib/appraisal/gemfile.rb @@ -14,7 +14,7 @@ module Appraisal # Load bundler Gemfiles and merge dependencies class Gemfile < BundlerDSL def load(path) - run(IO.read(path), path) if File.exist?(path) + run(File.read(path), path) if File.exist?(path) end def run(definitions, path, line = 1) diff --git a/lib/appraisal/gemspec.rb b/lib/appraisal/gemspec.rb index 8c4d6a0a..dfdef6de 100644 --- a/lib/appraisal/gemspec.rb +++ b/lib/appraisal/gemspec.rb @@ -24,7 +24,7 @@ def for_dup def exported_options @options.merge( - path: Utils.prefix_path(@options[:path]) + :path => Utils.prefix_path(@options[:path]), ) end end diff --git a/lib/appraisal/git.rb b/lib/appraisal/git.rb index 5849cca1..e7dd6aae 100644 --- a/lib/appraisal/git.rb +++ b/lib/appraisal/git.rb @@ -13,20 +13,34 @@ def initialize(source, options = {}) def to_s if @options.empty? - "git #{Utils.prefix_path(@source).inspect} do\n#{indent(super)}\nend" + <<-OUTPUT.rstrip +git #{Utils.prefix_path(@source).inspect} do +#{indent(super)} +end + OUTPUT else - "git #{Utils.prefix_path(@source).inspect}, #{Utils.format_string(@options)} do\n" + - "#{indent(super)}\nend" + <<-OUTPUT.rstrip +git #{Utils.prefix_path(@source).inspect}, #{Utils.format_string(@options)} do +#{indent(super)} +end + OUTPUT end end # :nodoc: def for_dup if @options.empty? - "git #{@source.inspect} do\n#{indent(super)}\nend" + <<-OUTPUT.rstrip +git #{@source.inspect} do +#{indent(super)} +end + OUTPUT else - "git #{@source.inspect}, #{Utils.format_string(@options)} do\n" + - "#{indent(super)}\nend" + <<-OUTPUT.rstrip +git #{@source.inspect}, #{Utils.format_string(@options)} do +#{indent(super)} +end + OUTPUT end end end diff --git a/lib/appraisal/group.rb b/lib/appraisal/group.rb index 2327298c..6517d914 100644 --- a/lib/appraisal/group.rb +++ b/lib/appraisal/group.rb @@ -11,21 +11,21 @@ def initialize(group_names) end def to_s - formatted_output indent(super) + formatted_output(super) end # :nodoc: def for_dup - formatted_output indent(super) + formatted_output(super) end private def formatted_output(output_dependencies) - <<~OUTPUT.strip - group #{Utils.format_arguments(@group_names)} do - #{output_dependencies} - end + <<-OUTPUT.rstrip +group #{Utils.format_arguments(@group_names)} do +#{indent(output_dependencies)} +end OUTPUT end end diff --git a/lib/appraisal/ordered_hash.rb b/lib/appraisal/ordered_hash.rb new file mode 100644 index 00000000..b460f9d5 --- /dev/null +++ b/lib/appraisal/ordered_hash.rb @@ -0,0 +1,22 @@ +module Appraisal + # An ordered hash implementation for Ruby 1.8.7 compatibility. This is not + # a complete implementation, but it covers Appraisal's specific needs. + class OrderedHash < ::Hash + # Hashes are ordered in Ruby 1.9. + if RUBY_VERSION < "1.9" + def initialize(*args, &block) + super + @keys = [] + end + + def []=(key, value) + @keys << key unless has_key?(key) + super + end + + def values + @keys.collect { |key| self[key] } + end + end + end +end diff --git a/lib/appraisal/path.rb b/lib/appraisal/path.rb index f06d2737..42be91b7 100644 --- a/lib/appraisal/path.rb +++ b/lib/appraisal/path.rb @@ -13,20 +13,34 @@ def initialize(source, options = {}) def to_s if @options.empty? - "path #{Utils.prefix_path(@source).inspect} do\n#{indent(super)}\nend" + <<-OUTPUT.rstrip +path #{Utils.prefix_path(@source).inspect} do +#{indent(super)} +end + OUTPUT else - "path #{Utils.prefix_path(@source).inspect}, #{Utils.format_string(@options)} do\n" + - "#{indent(super)}\nend" + <<-OUTPUT.rstrip +path #{Utils.prefix_path(@source).inspect}, #{Utils.format_string(@options)} do +#{indent(super)} +end" + OUTPUT end end # :nodoc: def for_dup if @options.empty? - "path #{@source.inspect} do\n#{indent(super)}\nend" + <<-OUTPUT.rstrip +path #{@source.inspect} do +#{indent(super)} +end + OUTPUT else - "path #{@source.inspect}, #{Utils.format_string(@options)} do\n" + - "#{indent(super)}\nend" + <<-OUTPUT.rstrip +path #{@source.inspect}, #{Utils.format_string(@options)} do +#{indent(super)} +end + OUTPUT end end end diff --git a/lib/appraisal/platform.rb b/lib/appraisal/platform.rb index ad98ce1f..9b8cc553 100644 --- a/lib/appraisal/platform.rb +++ b/lib/appraisal/platform.rb @@ -11,21 +11,21 @@ def initialize(platform_names) end def to_s - formatted_output indent(super) + formatted_output(super) end # :nodoc: def for_dup - formatted_output indent(super) + formatted_output(super) end private def formatted_output(output_dependencies) - <<~OUTPUT.strip - platforms #{Utils.format_arguments(@platform_names)} do - #{output_dependencies} - end + <<-OUTPUT.rstrip +platforms #{Utils.format_arguments(@platform_names)} do +#{indent(output_dependencies)} +end OUTPUT end end diff --git a/lib/appraisal/source.rb b/lib/appraisal/source.rb index c5905617..6c926a2d 100644 --- a/lib/appraisal/source.rb +++ b/lib/appraisal/source.rb @@ -11,21 +11,21 @@ def initialize(source) end def to_s - formatted_output indent(super) + formatted_output(super) end # :nodoc: def for_dup - formatted_output indent(super) + formatted_output(super) end private def formatted_output(output_dependencies) - <<~OUTPUT.strip - source #{@source.inspect} do - #{output_dependencies} - end + <<-OUTPUT.rstrip +source #{@source.inspect} do +#{indent(output_dependencies)} +end OUTPUT end end diff --git a/lib/appraisal/task.rb b/lib/appraisal/task.rb index 5c649925..cdca7b3d 100644 --- a/lib/appraisal/task.rb +++ b/lib/appraisal/task.rb @@ -8,49 +8,49 @@ module Appraisal # for a given appraisal. class Task < Rake::TaskLib def initialize - namespace :appraisal do - desc "DEPRECATED: Generate a Gemfile for each appraisal" - task :gemfiles do - warn "`rake appraisal:gemfile` task is deprecated and will be removed soon. " + - "Please use `appraisal generate`." - exec "bundle exec appraisal generate" + namespace(:appraisal) do + desc("DEPRECATED: Generate a Gemfile for each appraisal") + task(:gemfiles) do + warn("`rake appraisal:gemfile` task is deprecated and will be removed soon. " \ + "Please use `appraisal generate`.") + exec("bundle exec appraisal generate") end - desc "DEPRECATED: Resolve and install dependencies for each appraisal" - task :install do - warn "`rake appraisal:install` task is deprecated and will be removed soon. " + - "Please use `appraisal install`." - exec "bundle exec appraisal install" + desc("DEPRECATED: Resolve and install dependencies for each appraisal") + task(:install) do + warn("`rake appraisal:install` task is deprecated and will be removed soon. " \ + "Please use `appraisal install`.") + exec("bundle exec appraisal install") end - desc "DEPRECATED: Remove all generated gemfiles from gemfiles/ folder" - task :cleanup do - warn "`rake appraisal:cleanup` task is deprecated and will be removed soon. " + - "Please use `appraisal clean`." - exec "bundle exec appraisal clean" + desc("DEPRECATED: Remove all generated gemfiles from gemfiles/ folder") + task(:cleanup) do + warn("`rake appraisal:cleanup` task is deprecated and will be removed soon. " \ + "Please use `appraisal clean`.") + exec("bundle exec appraisal clean") end begin AppraisalFile.each do |appraisal| - desc "DEPRECATED: Run the given task for appraisal #{appraisal.name}" - task appraisal.name do + desc("DEPRECATED: Run the given task for appraisal #{appraisal.name}") + task(appraisal.name) do ARGV.shift - warn "`rake appraisal:#{appraisal.name}` task is deprecated and will be removed soon. " + - "Please use `appraisal #{appraisal.name} rake #{ARGV.join(' ')}`." - exec "bundle exec appraisal #{appraisal.name} rake #{ARGV.join(' ')}" + warn("`rake appraisal:#{appraisal.name}` task is deprecated and will be removed soon. " \ + "Please use `appraisal #{appraisal.name} rake #{ARGV.join(" ")}`.") + exec("bundle exec appraisal #{appraisal.name} rake #{ARGV.join(" ")}") end end rescue AppraisalsNotFound end - task :all do + task(:all) do ARGV.shift - exec "bundle exec appraisal rake #{ARGV.join(' ')}" + exec("bundle exec appraisal rake #{ARGV.join(" ")}") end end - desc "Run the given task for all appraisals" - task appraisal: "appraisal:all" + desc("Run the given task for all appraisals") + task(:appraisal => "appraisal:all") end end end diff --git a/lib/appraisal/utils.rb b/lib/appraisal/utils.rb index 40e6c31e..e0c455aa 100644 --- a/lib/appraisal/utils.rb +++ b/lib/appraisal/utils.rb @@ -7,6 +7,13 @@ def self.support_parallel_installation? Gem::Version.create(Bundler::VERSION) >= Gem::Version.create("1.4.0.pre.1") end + def self.support_git_local_installation? + Gem::Version.create(Bundler::VERSION) > Gem::Version.create("2.4.22") + end + + # Appraisal needs to print Gemfiles in the oldest Ruby syntax that is supported by Appraisal. + # Otherwise, a project would not be able to use Appraisal to test compatibility + # with older versions of Ruby, which is a core use case for Appraisal. def self.format_string(object, enclosing_object = false) case object when Hash @@ -15,7 +22,7 @@ def self.format_string(object, enclosing_object = false) end if enclosing_object - "{ #{items.join(', ')} }" + "{ #{items.join(", ")} }" else items.join(", ") end @@ -24,30 +31,31 @@ def self.format_string(object, enclosing_object = false) end end + # Appraisal needs to print Gemfiles in the oldest Ruby syntax that is supported by Appraisal. + # This means formatting Hashes as Rockets, until support for Ruby 1.8 is dropped. + # Regardless of what Ruby is used to generate appraisals, + # generated appraisals may need to run on a different Ruby version. + # Generated appraisals should use a syntax compliant with the oldest supported Ruby version. def self.format_hash_value(key, value) key = format_string(key, true) value = format_string(value, true) - if key.start_with?(":") - "#{key.sub(/^:/, "")}: #{value}" - else - "#{key} => #{value}" - end + "#{key} => #{value}" end def self.format_arguments(arguments) - unless arguments.empty? - arguments.map { |object| format_string(object, false) }.join(", ") - end + return if arguments.empty? + + arguments.map { |object| format_string(object, false) }.join(", ") end def self.join_parts(parts) - parts.reject(&:nil?).reject(&:empty?).join("\n\n").strip + parts.reject(&:nil?).reject(&:empty?).join("\n\n").rstrip end def self.prefix_path(path) - if path !~ /^(?:\/|\S:)/ && path !~ /^\S+:\/\// && path !~ /^\S+@\S+:/ - cleaned_path = path.gsub(/(^|\/)\.(?:\/|$)/, "\\1") + if path !~ %r{^(?:/|\S:)} && path !~ %r{^\S+://} && path !~ /^\S+@\S+:/ + cleaned_path = path.gsub(%r{(^|/)\.(?:/|$)}, '\\1') File.join("..", cleaned_path) else path diff --git a/spec/acceptance/appraisals_file_bundler_dsl_compatibility_spec.rb b/spec/acceptance/appraisals_file_bundler_dsl_compatibility_spec.rb index 41e6cdd6..084b9984 100644 --- a/spec/acceptance/appraisals_file_bundler_dsl_compatibility_spec.rb +++ b/spec/acceptance/appraisals_file_bundler_dsl_compatibility_spec.rb @@ -1,17 +1,25 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "Appraisals file Bundler DSL compatibility" do - it "supports all Bundler DSL in Appraisals file" do - build_gems %w[bagel orange_juice milk waffle coffee ham - sausage pancake rotten_egg mayonnaise] + it "supports all Bundler DSL in Appraisals file", :git_local do + build_gems %w[ + bagel + orange_juice + milk + waffle + coffee + ham + sausage + pancake + rotten_egg + mayonnaise + ] build_git_gems %w[egg croissant pain_au_chocolat omelette] - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source 'https://rubygems.org' git_source(:custom_git_source) { |repo| "../build/\#{repo}" } - ruby RUBY_VERSION + ruby "#{RUBY_VERSION}#{ruby_dev_append}" gem 'bagel' gem "croissant", :custom_git_source => "croissant" @@ -49,10 +57,10 @@ gem 'appraisal', :path => #{PROJECT_ROOT.inspect} GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise 'breakfast' do source 'http://some-other-source.com' - ruby "2.3.0" + ruby "1.8.7" gem 'bread' gem "pain_au_chocolat", :custom_git_source => "pain_au_chocolat" @@ -94,13 +102,13 @@ run "bundle install --local" run "appraisal generate" - expect(content_of("gemfiles/breakfast.gemfile")).to eq <<-GEMFILE.strip_heredoc + expect(content_of("gemfiles/breakfast.gemfile")).to eq <<-GEMFILE.strip_heredoc.rstrip # This file was generated by Appraisal source "https://rubygems.org" source "http://some-other-source.com" - ruby "2.3.0" + ruby "1.8.7" git "../../build/egg" do gem "egg" @@ -153,29 +161,29 @@ end it 'supports ruby file: ".ruby-version" DSL' do - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source 'https://rubygems.org' - ruby RUBY_VERSION + ruby "#{RUBY_VERSION}#{ruby_dev_append}" gem 'appraisal', :path => #{PROJECT_ROOT.inspect} GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise 'ruby-version' do - ruby file: ".ruby-version" + ruby({:file => ".ruby-version"}) end APPRAISALS run "bundle install --local" run "appraisal generate" - expect(content_of("gemfiles/ruby_version.gemfile")).to eq <<-GEMFILE.strip_heredoc + expect(content_of("gemfiles/ruby_version.gemfile")).to eq <<-GEMFILE.strip_heredoc.rstrip # This file was generated by Appraisal source "https://rubygems.org" - ruby({:file=>".ruby-version"}) + ruby(:file => ".ruby-version") gem "appraisal", :path => #{PROJECT_ROOT.inspect} GEMFILE diff --git a/spec/acceptance/bundle_with_custom_path_spec.rb b/spec/acceptance/bundle_with_custom_path_spec.rb index b0d2157b..f459723c 100644 --- a/spec/acceptance/bundle_with_custom_path_spec.rb +++ b/spec/acceptance/bundle_with_custom_path_spec.rb @@ -1,20 +1,24 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "Bundle with custom path" do let(:gem_name) { "rack" } let(:path) { "vendor/bundle" } - shared_examples :gemfile_dependencies_are_satisfied do + shared_examples "gemfile dependencies are satisfied" do it "installs gems in the --path directory" do - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem 'appraisal', :path => #{PROJECT_ROOT.inspect} + + if RUBY_VERSION < "1.9" + #{File.read(File.join(PROJECT_ROOT, "Gemfile-1.8"))} + elsif RUBY_VERSION < "2.2" + #{File.read(File.join(PROJECT_ROOT, "Gemfile-2.1"))} + end GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise "#{gem_name}" do gem '#{gem_name}' end @@ -25,8 +29,8 @@ run "bundle exec appraisal install" installed_gem = Dir.glob("tmp/stage/#{path}/#{Gem.ruby_engine}/*/gems/*") - .map { |path| path.split("/").last } - .select { |gem| gem.include?(gem_name) } + .map { |path| path.split("/").last } + .select { |gem| gem.include?(gem_name) } expect(installed_gem).not_to be_empty bundle_output = run "bundle check" @@ -39,14 +43,18 @@ end end - include_examples :gemfile_dependencies_are_satisfied + include_examples "gemfile dependencies are satisfied" context "when already installed in vendor/another" do before do - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" - gem '#{gem_name}' + if RUBY_VERSION <= "1.9" + gem '#{gem_name}', '~> 1.6.5' + else + gem '#{gem_name}' + end GEMFILE run "bundle config set --local path vendor/another" @@ -54,6 +62,6 @@ run "bundle config unset --local path" end - include_examples :gemfile_dependencies_are_satisfied + include_examples "gemfile dependencies are satisfied" end end diff --git a/spec/acceptance/bundle_without_spec.rb b/spec/acceptance/bundle_without_spec.rb index 7eb1b71f..b454adf1 100644 --- a/spec/acceptance/bundle_without_spec.rb +++ b/spec/acceptance/bundle_without_spec.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -require "spec_helper" - -RSpec.describe "Bundler without flag" do - it "passes --without flag to Bundler on install" do +RSpec.describe "Bundle without group" do + it "config set --local without group is honored by Bundler" do + reason = "config set --local without group support seems broken, see: https://github.com/rubygems/rubygems/issues/8518" + # Somehow this spec passes on truffleruby *only*!! + pending_for(:engine => "ruby", :reason => reason) + pending_for(:engine => "jruby", :reason => reason) build_gems %w[pancake orange_juice waffle coffee sausage soda] - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem "pancake" @@ -19,7 +21,7 @@ gem "appraisal", :path => #{PROJECT_ROOT.inspect} GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise "breakfast" do gem "waffle" diff --git a/spec/acceptance/cli/clean_spec.rb b/spec/acceptance/cli/clean_spec.rb index 7eed158c..45742a1e 100644 --- a/spec/acceptance/cli/clean_spec.rb +++ b/spec/acceptance/cli/clean_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal clean" do it "remove all gemfiles from gemfiles directory" do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end diff --git a/spec/acceptance/cli/generate_spec.rb b/spec/acceptance/cli/generate_spec.rb index 285fc215..9c6fdb50 100644 --- a/spec/acceptance/cli/generate_spec.rb +++ b/spec/acceptance/cli/generate_spec.rb @@ -1,16 +1,14 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal generate" do it "generates the gemfiles" do - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem "appraisal", :path => "#{PROJECT_ROOT}" GEMFILE - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -24,7 +22,7 @@ expect(file("gemfiles/1.0.0.gemfile")).to be_exists expect(file("gemfiles/1.1.0.gemfile")).to be_exists - expect(content_of("gemfiles/1.0.0.gemfile")).to eq <<-GEMFILE.strip_heredoc + expect(content_of("gemfiles/1.0.0.gemfile")).to eq <<-GEMFILE.strip_heredoc.rstrip # This file was generated by Appraisal source "https://rubygems.org" diff --git a/spec/acceptance/cli/help_spec.rb b/spec/acceptance/cli/help_spec.rb index da99f7a3..96524545 100644 --- a/spec/acceptance/cli/help_spec.rb +++ b/spec/acceptance/cli/help_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal help" do it "prints usage along with commands, and list of appraisals" do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end diff --git a/spec/acceptance/cli/install_spec.rb b/spec/acceptance/cli/install_spec.rb index 09dcc106..3fad1102 100644 --- a/spec/acceptance/cli/install_spec.rb +++ b/spec/acceptance/cli/install_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal install" do it "raises error when there is no Appraisals file" do output = run "appraisal install 2>&1", false @@ -10,7 +8,7 @@ end it "installs the dependencies" do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -30,7 +28,7 @@ build_gemspec add_gemspec_to_gemfile - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -41,7 +39,7 @@ expect(content_of("gemfiles/1.0.0.gemfile.lock")).not_to include(current_directory) end - it "does not relativize directory of uris in gemfile.lock" do + it "does not relativize directory of uris in gemfile.lock", :git_local do build_gemspec add_gemspec_to_gemfile @@ -49,7 +47,7 @@ uri_dummy_path = "#{current_directory}/uri_dummy" FileUtils.symlink(File.absolute_path("tmp/build/uri_dummy"), uri_dummy_path) - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'uri_dummy', git: 'file://#{uri_dummy_path}' end @@ -60,9 +58,9 @@ expect(content_of("gemfiles/1.0.0.gemfile.lock")).to include("file://#{uri_dummy_path}") end - context "with job size", parallel: true do + context "with job size", :parallel do before do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -72,21 +70,21 @@ it "accepts --jobs option to set job size" do output = run "appraisal install --jobs=2" - expect(output).to include("bundle install --gemfile='#{file('gemfiles/1.0.0.gemfile')}' --jobs=2") + expect(output).to include("bundle install --gemfile='#{file("gemfiles/1.0.0.gemfile")}' --jobs=2") end it "ignores --jobs option if the job size is less than or equal to 1" do output = run "appraisal install --jobs=0" - expect(output).to include("bundle install --gemfile='#{file('gemfiles/1.0.0.gemfile')}'") - expect(output).not_to include("bundle install --gemfile='#{file('gemfiles/1.0.0.gemfile')}' --jobs=0") - expect(output).not_to include("bundle install --gemfile='#{file('gemfiles/1.0.0.gemfile')}' --jobs=1") + expect(output).to include("bundle install --gemfile='#{file("gemfiles/1.0.0.gemfile")}'") + expect(output).not_to include("bundle install --gemfile='#{file("gemfiles/1.0.0.gemfile")}' --jobs=0") + expect(output).not_to include("bundle install --gemfile='#{file("gemfiles/1.0.0.gemfile")}' --jobs=1") end end context "with full-index", :parallel do before do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -96,13 +94,13 @@ it "accepts --full-index option to pull the full RubyGems index" do output = run("appraisal install --full-index") - expect(output).to include("bundle install --gemfile='#{file('gemfiles/1.0.0.gemfile')}' --retry 1 --full-index true") + expect(output).to include("bundle install --gemfile='#{file("gemfiles/1.0.0.gemfile")}' --retry 1 --full-index true") end end context "with path", :parallel do before do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -112,7 +110,7 @@ it "accepts --path option to specify the location to install gems into" do output = run("appraisal install --path vendor/appraisal") - expect(output).to include("bundle install --gemfile='#{file('gemfiles/1.0.0.gemfile')}' --path #{file('vendor/appraisal')} --retry 1") + expect(output).to include("bundle install --gemfile='#{file("gemfiles/1.0.0.gemfile")}' --path #{file("vendor/appraisal")} --retry 1") end end end diff --git a/spec/acceptance/cli/list_spec.rb b/spec/acceptance/cli/list_spec.rb index 917712ee..7bf1379b 100644 --- a/spec/acceptance/cli/list_spec.rb +++ b/spec/acceptance/cli/list_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal list" do it "prints list of appraisals" do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end diff --git a/spec/acceptance/cli/run_spec.rb b/spec/acceptance/cli/run_spec.rb index 6bfcfd8c..19825462 100644 --- a/spec/acceptance/cli/run_spec.rb +++ b/spec/acceptance/cli/run_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI appraisal (with arguments)" do before do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end @@ -20,7 +18,7 @@ end it "sets APPRAISAL_INITIALIZED environment variable" do - write_file "test.rb", <<-TEST_FILE.strip_heredoc + write_file "test.rb", <<-TEST_FILE.strip_heredoc.rstrip if ENV['APPRAISAL_INITIALIZED'] puts "Appraisal initialized!" end diff --git a/spec/acceptance/cli/update_spec.rb b/spec/acceptance/cli/update_spec.rb index 8929c89c..61fe1611 100644 --- a/spec/acceptance/cli/update_spec.rb +++ b/spec/acceptance/cli/update_spec.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal update" do before do build_gem "dummy2", "1.0.0" - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise 'dummy' do gem 'dummy', '~> 1.0.0' gem 'dummy2', '~> 1.0.0' @@ -20,8 +18,8 @@ after do in_test_directory do - `gem uninstall dummy -v 1.0.1` - `gem uninstall dummy2 -a` + %x(gem uninstall dummy -v 1.0.1) + %x(gem uninstall dummy2 -a) end end diff --git a/spec/acceptance/cli/version_spec.rb b/spec/acceptance/cli/version_spec.rb index e47a6548..ab313df9 100644 --- a/spec/acceptance/cli/version_spec.rb +++ b/spec/acceptance/cli/version_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI", "appraisal version" do context "with version subcommand" do it "prints out version string" do diff --git a/spec/acceptance/cli/with_no_arguments_spec.rb b/spec/acceptance/cli/with_no_arguments_spec.rb index 4a04e64a..5a5e9f4d 100644 --- a/spec/acceptance/cli/with_no_arguments_spec.rb +++ b/spec/acceptance/cli/with_no_arguments_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "CLI appraisal (with no arguments)" do it "runs install command" do - build_appraisal_file <<-APPRAISAL + build_appraisal_file <<-APPRAISAL.strip_heredoc.rstrip appraise '1.0.0' do gem 'dummy', '1.0.0' end diff --git a/spec/acceptance/gemfile_dsl_compatibility_spec.rb b/spec/acceptance/gemfile_dsl_compatibility_spec.rb index 2a9dcac3..6e802218 100644 --- a/spec/acceptance/gemfile_dsl_compatibility_spec.rb +++ b/spec/acceptance/gemfile_dsl_compatibility_spec.rb @@ -1,16 +1,14 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "Gemfile DSL compatibility" do - it "supports all Bundler DSL in Gemfile" do + it "supports all Bundler DSL in Gemfile", :git_local do build_gems %w[bacon orange_juice waffle] build_git_gem "egg" build_gemspec - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" - ruby RUBY_VERSION + ruby "#{RUBY_VERSION}#{ruby_dev_append}" git "../build/egg" do gem "egg" @@ -33,7 +31,7 @@ gemspec GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise "japanese" do gem "rice" gem "miso_soup" @@ -47,12 +45,12 @@ run "bundle install --local" run "appraisal generate" - expect(content_of("gemfiles/japanese.gemfile")).to eq <<-GEMFILE.strip_heredoc + expect(content_of("gemfiles/japanese.gemfile")).to eq <<-GEMFILE.strip_heredoc.rstrip # This file was generated by Appraisal source "https://rubygems.org" - ruby "#{RUBY_VERSION}" + ruby "#{RUBY_VERSION}#{ruby_dev_append}" git "../../build/egg" do gem "egg" @@ -77,12 +75,12 @@ gemspec :path => "../" GEMFILE - expect(content_of("gemfiles/english.gemfile")).to eq <<-GEMFILE.strip_heredoc + expect(content_of("gemfiles/english.gemfile")).to eq <<-GEMFILE.strip_heredoc.rstrip # This file was generated by Appraisal source "https://rubygems.org" - ruby "#{RUBY_VERSION}" + ruby "#{RUBY_VERSION}#{ruby_dev_append}" git "../../build/egg" do gem "egg" @@ -112,14 +110,14 @@ build_gem "bacon", "1.1.0" build_gem "bacon", "1.2.0" - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem "appraisal", :path => #{PROJECT_ROOT.inspect} gem "bacon", "1.2.0" GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise "1.0.0" do gem "bacon", "1.0.0" end @@ -145,7 +143,7 @@ build_gem "bacon", "1.0.0" build_gemspec - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem "appraisal", :path => #{PROJECT_ROOT.inspect} @@ -155,7 +153,7 @@ end GEMFILE - build_appraisal_file <<-APPRAISALS + build_appraisal_file <<-APPRAISALS.strip_heredoc.rstrip appraise "1.0.0" do gem "bacon", "1.0.0" end @@ -164,7 +162,7 @@ run "bundle install --local" run "appraisal generate" - expect(content_of("gemfiles/1.0.0.gemfile")).to eq <<-GEMFILE.strip_heredoc + expect(content_of("gemfiles/1.0.0.gemfile")).to eq <<-GEMFILE.strip_heredoc.rstrip # This file was generated by Appraisal source "https://rubygems.org" diff --git a/spec/acceptance/gemspec_spec.rb b/spec/acceptance/gemspec_spec.rb index d85fdf08..1db139cc 100644 --- a/spec/acceptance/gemspec_spec.rb +++ b/spec/acceptance/gemspec_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "spec_helper" - RSpec.describe "Gemspec" do before do build_appraisal_file @@ -11,7 +9,7 @@ it "supports gemspec syntax with default options" do build_gemspec - write_file "Gemfile", <<-GEMFILE + write_file "Gemfile", <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem 'appraisal', :path => #{PROJECT_ROOT.inspect} @@ -29,7 +27,7 @@ it "supports gemspec syntax with path option" do build_gemspec "specdir" - write_file "Gemfile", <<-GEMFILE + write_file "Gemfile", <<-GEMFILE.strip_heredoc.rstrip source "https://rubygems.org" gem 'appraisal', :path => #{PROJECT_ROOT.inspect} @@ -45,15 +43,16 @@ end def build_appraisal_file - super <<-APPRAISALS + appraisals = <<-APPRAISALS.strip_heredoc.rstrip appraise 'stock' do gem 'rake' end APPRAISALS + super(appraisals) end def build_rakefile - write_file "Rakefile", <<-RAKEFILE + write_file "Rakefile", <<-RAKEFILE.strip_heredoc.rstrip require 'rubygems' require 'bundler/setup' require 'appraisal' @@ -66,9 +65,13 @@ def build_rakefile end def build_gemspec(path = ".") - Dir.mkdir("tmp/stage/#{path}") rescue nil + begin + Dir.mkdir("tmp/stage/#{path}") + rescue StandardError + nil + end - write_file File.join(path, "gemspec_project.gemspec"), <<-GEMSPEC + write_file File.join(path, "gemspec_project.gemspec"), <<-GEMSPEC.strip_heredoc.rstrip Gem::Specification.new do |s| s.name = 'gemspec_project' s.version = '0.1' diff --git a/spec/appraisal/appraisal_file_spec.rb b/spec/appraisal/appraisal_file_spec.rb index 8b583ea8..c90a390a 100644 --- a/spec/appraisal/appraisal_file_spec.rb +++ b/spec/appraisal/appraisal_file_spec.rb @@ -16,7 +16,7 @@ end describe "#customize_gemfiles" do - before(:each) do + before do allow(File).to receive(:exist?).with(anything).and_return(true) allow(IO).to receive(:read).with(anything).and_return("") end @@ -33,13 +33,11 @@ context "when the block returns a hash with :heading key" do subject do described_class.new.customize_gemfiles do - { heading: "foo" } + {:heading => "foo"} end end it "sets the heading" do - pending("test is broken: wrong number of arguments (given 0, expected 1)") - expect { subject }.to change { Appraisal::Customize.heading }.to("foo") end end @@ -47,7 +45,7 @@ context "when the block returns a hash with :single_quotes key" do subject do described_class.new.customize_gemfiles do - { single_quotes: true } + {:single_quotes => true} end end @@ -59,16 +57,14 @@ context "when the block returns a hash with :heading and :single_quotes keys" do subject do described_class.new.customize_gemfiles do - { heading: "foo", single_quotes: true } + {:heading => "foo", :single_quotes => true} end end it "sets the heading and single_quotes" do - pending("test is broken: wrong number of arguments (given 0, expected 1)") - subject expect(Appraisal::Customize.heading).to eq("foo") - expect(Appraisal::Customize.single_quotes).to eq(true) + expect(Appraisal::Customize.single_quotes).to be(true) end end end diff --git a/spec/appraisal/appraisal_spec.rb b/spec/appraisal/appraisal_spec.rb index d68bfd57..4a694411 100644 --- a/spec/appraisal/appraisal_spec.rb +++ b/spec/appraisal/appraisal_spec.rb @@ -6,23 +6,23 @@ RSpec.describe Appraisal::Appraisal do it "converts spaces to underscores in the gemfile path" do - appraisal = Appraisal::Appraisal.new("one two", "Gemfile") + appraisal = described_class.new("one two", "Gemfile") expect(appraisal.gemfile_path).to match(/one_two\.gemfile$/) end - it "converts punctuation to underscores in the gemfile path" do - appraisal = Appraisal::Appraisal.new("o&ne!", "Gemfile") + it "converts punctuation to underscores in the gemfile path" do + appraisal = described_class.new("o&ne!", "Gemfile") expect(appraisal.gemfile_path).to match(/o_ne_\.gemfile$/) end it "keeps dots in the gemfile path" do - appraisal = Appraisal::Appraisal.new("rails3.0", "Gemfile") + appraisal = described_class.new("rails3.0", "Gemfile") expect(appraisal.gemfile_path).to match(/rails3\.0\.gemfile$/) end it "generates a gemfile with a newline at the end of file" do output_file = Tempfile.new("gemfile") - appraisal = Appraisal::Appraisal.new("fake", "fake") + appraisal = described_class.new("fake", "fake") allow(appraisal).to receive(:gemfile_path).and_return(output_file.path) appraisal.write_gemfile @@ -33,9 +33,9 @@ context "gemfile customization" do it "generates a gemfile with a custom heading" do heading = "This file was generated with a custom heading!" - Appraisal::Customize.new(heading: heading) + Appraisal::Customize.new(:heading => heading) output_file = Tempfile.new("gemfile") - appraisal = Appraisal::Appraisal.new("custom", "Gemfile") + appraisal = described_class.new("custom", "Gemfile") allow(appraisal).to receive(:gemfile_path).and_return(output_file.path) appraisal.write_gemfile @@ -45,18 +45,18 @@ end it "generates a gemfile with multiple lines of custom heading" do - heading = <<~HEADING + heading = <<-HEADING.strip_heredoc.rstrip frozen_string_literal: true\n This file was generated with a custom heading! HEADING - Appraisal::Customize.new(heading: heading) + Appraisal::Customize.new(:heading => heading) output_file = Tempfile.new("gemfile") - appraisal = Appraisal::Appraisal.new("custom", "Gemfile") + appraisal = described_class.new("custom", "Gemfile") allow(appraisal).to receive(:gemfile_path).and_return(output_file.path) appraisal.write_gemfile - expected_output = <<~HEADING + expected_output = <<-HEADING.strip_heredoc.rstrip # frozen_string_literal: true\n # This file was generated with a custom heading! HEADING @@ -64,9 +64,9 @@ end it "generates a gemfile with single quotes rather than doubles" do - Appraisal::Customize.new(single_quotes: true) + Appraisal::Customize.new(:single_quotes => true) output_file = Tempfile.new("gemfile") - appraisal = Appraisal::Appraisal.new("quotes", 'gem "foo"') + appraisal = described_class.new("quotes", 'gem "foo"') allow(appraisal).to receive(:gemfile_path).and_return(output_file.path) appraisal.write_gemfile @@ -77,7 +77,7 @@ it "does not customize anything by default" do Appraisal::Customize.new output_file = Tempfile.new("gemfile") - appraisal = Appraisal::Appraisal.new("fake", 'gem "foo"') + appraisal = described_class.new("fake", 'gem "foo"') allow(appraisal).to receive(:gemfile_path).and_return(output_file.path) appraisal.write_gemfile @@ -91,10 +91,10 @@ include StreamHelpers before do - @appraisal = Appraisal::Appraisal.new("fake", "fake") + @appraisal = described_class.new("fake", "fake") allow(@appraisal).to receive(:gemfile_path).and_return("/home/test/test directory") allow(@appraisal).to receive(:project_root).and_return(Pathname.new("/home/test")) - allow(Appraisal::Command).to receive(:new).and_return(double(run: true)) + allow(Appraisal::Command).to receive(:new).and_return(double(:run => true)) end it "runs single install command on Bundler < 1.4.0" do diff --git a/spec/appraisal/customize_spec.rb b/spec/appraisal/customize_spec.rb index 98e77f36..d833f8c4 100644 --- a/spec/appraisal/customize_spec.rb +++ b/spec/appraisal/customize_spec.rb @@ -8,7 +8,7 @@ let(:appraisal) { Appraisal::Appraisal.new("test", "Gemfile") } let(:single_line_heading) { "This file was generated with a custom heading!" } let(:multi_line_heading) do - <<~HEADING + <<-HEADING.strip_heredoc.rstrip frozen_string_literal: true This file was generated with a custom heading! @@ -16,16 +16,16 @@ end let(:subject) { described_class.new } let(:single_line_subject) do - described_class.new(heading: single_line_heading) + described_class.new(:heading => single_line_heading) end let(:multi_line_single_quotes_subject) do - described_class.new(heading: multi_line_heading, single_quotes: true) + described_class.new(:heading => multi_line_heading, :single_quotes => true) end describe ".heading" do it "returns nil if no heading is set" do subject - expect(described_class.heading(appraisal)).to eq(nil) + expect(described_class.heading(appraisal)).to be_nil end it "returns the heading if set" do @@ -36,19 +36,19 @@ it "returns the heading without an trailing newline" do multi_line_single_quotes_subject expect(described_class.heading(appraisal)).to eq(multi_line_heading.chomp) - expect(described_class.heading(appraisal)).to_not end_with("\n") + expect(described_class.heading(appraisal)).not_to end_with("\n") end end describe ".single_quotes" do it "returns false if not set" do subject - expect(described_class.single_quotes).to eq(false) + expect(described_class.single_quotes).to be(false) end it "returns true if set" do multi_line_single_quotes_subject - expect(described_class.single_quotes).to eq(true) + expect(described_class.single_quotes).to be(true) end end @@ -60,6 +60,7 @@ let(:lockfile_relative_path) { "gemfiles/#{lockfile}" } let(:gemfile_full_path) { "/path/to/project/#{gemfile_relative_path}" } let(:lockfile_full_path) { "/path/to/project/#{lockfile_relative_path}" } + before do allow(appraisal).to receive(:gemfile_name).and_return(gemfile) allow(appraisal).to receive(:gemfile_path).and_return(gemfile_full_path) @@ -69,72 +70,72 @@ it "returns nil if no heading is set" do subject - expect(described_class.send(:customize, nil, appraisal)).to eq(nil) + expect(described_class.send(:customize, nil, appraisal)).to be_nil end it "returns the heading unchanged" do single_line_subject expect(described_class.send( - :customize, - single_line_heading, - appraisal - )).to eq(single_line_heading) + :customize, + single_line_heading, + appraisal, + )).to eq(single_line_heading) end it "returns the heading with the appraisal name" do expect(described_class.send( - :customize, - "Appraisal: %{appraisal}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Appraisal: #{appraisal_name}") + :customize, + "Appraisal: %{appraisal}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Appraisal: #{appraisal_name}") end it "returns the heading with the gemfile name" do expect(described_class.send( - :customize, - "Gemfile: %{gemfile}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Gemfile: #{gemfile}") + :customize, + "Gemfile: %{gemfile}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Gemfile: #{gemfile}") end it "returns the heading with the gemfile path" do expect(described_class.send( - :customize, - "Gemfile: %{gemfile_path}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Gemfile: #{gemfile_full_path}") + :customize, + "Gemfile: %{gemfile_path}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Gemfile: #{gemfile_full_path}") end it "returns the heading with the lockfile name" do expect(described_class.send( - :customize, - "Lockfile: %{lockfile}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Lockfile: #{lockfile}") + :customize, + "Lockfile: %{lockfile}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Lockfile: #{lockfile}") end it "returns the heading with the lockfile path" do expect(described_class.send( - :customize, - "Lockfile: %{lockfile_path}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Lockfile: #{lockfile_full_path}") + :customize, + "Lockfile: %{lockfile_path}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Lockfile: #{lockfile_full_path}") end it "returns the heading with the relative gemfile path" do expect(described_class.send( - :customize, - "Gemfile: %{relative_gemfile_path}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Gemfile: #{gemfile_relative_path}") + :customize, + "Gemfile: %{relative_gemfile_path}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Gemfile: #{gemfile_relative_path}") end it "returns the heading with the relative lockfile path" do expect(described_class.send( - :customize, - "Gemfile: %{relative_lockfile_path}", # rubocop:disable Style/FormatStringToken - appraisal - )).to eq("Gemfile: #{lockfile_relative_path}") + :customize, + "Gemfile: %{relative_lockfile_path}", # rubocop:disable Style/FormatStringToken + appraisal, + )).to eq("Gemfile: #{lockfile_relative_path}") end end end diff --git a/spec/appraisal/dependency_list_spec.rb b/spec/appraisal/dependency_list_spec.rb index 8b322984..a9fac337 100644 --- a/spec/appraisal/dependency_list_spec.rb +++ b/spec/appraisal/dependency_list_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Appraisal::DependencyList do describe "#add" do - let(:dependency_list) { Appraisal::DependencyList.new } + let(:dependency_list) { described_class.new } it "adds dependency to the list" do dependency_list.add("rails", ["4.1.4"]) @@ -17,7 +17,7 @@ dependency_list.add("rails", ["4.1.4"]) dependency_list.add("bundler", ["1.7.2"]) - expect(dependency_list.to_s).to eq <<-GEMS.strip_heredoc.strip + expect(dependency_list.to_s).to eq <<-GEMS.strip_heredoc.rstrip gem "rails", "4.1.4" gem "bundler", "1.7.2" GEMS @@ -32,7 +32,7 @@ end describe "#remove" do - let(:dependency_list) { Appraisal::DependencyList.new } + let(:dependency_list) { described_class.new } before do dependency_list.add("rails", ["4.1.4"]) diff --git a/spec/appraisal/gemfile_spec.rb b/spec/appraisal/gemfile_spec.rb index 666c07ba..07c02f6f 100644 --- a/spec/appraisal/gemfile_spec.rb +++ b/spec/appraisal/gemfile_spec.rb @@ -8,26 +8,26 @@ include StreamHelpers it "supports gemfiles without sources" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new expect(gemfile.to_s.strip).to eq "" end it "supports multiple sources" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.source "one" gemfile.source "two" expect(gemfile.to_s.strip).to eq %(source "one"\nsource "two") end it "ignores duplicate sources" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.source "one" gemfile.source "one" expect(gemfile.to_s.strip).to eq %(source "one") end it "preserves dependency order" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.gem "one" gemfile.gem "two" gemfile.gem "three" @@ -35,19 +35,19 @@ end it "supports symbol sources" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.source :one expect(gemfile.to_s.strip).to eq %(source :one) end it "supports group syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.group :development, :test do gem "one" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip group :development, :test do gem "one" end @@ -55,7 +55,7 @@ end it "supports nested DSL within group syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.group :development, :test do platforms :jruby do @@ -69,7 +69,7 @@ end end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip group :development, :test do git "git://example.com/repo.git" do gem "two" @@ -87,13 +87,13 @@ end it "supports platform syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.platform :jruby do gem "one" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip platforms :jruby do gem "one" end @@ -101,7 +101,7 @@ end it "supports nested DSL within platform syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.platform :jruby do group :development, :test do @@ -115,7 +115,7 @@ end end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip platforms :jruby do git "git://example.com/repo.git" do gem "two" @@ -133,13 +133,13 @@ end it "supports git syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git "git://example.com/repo.git" do gem "one" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip git "git://example.com/repo.git" do gem "one" end @@ -147,7 +147,7 @@ end it "supports nested DSL within git syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git "git://example.com/repo.git" do group :development, :test do @@ -161,7 +161,7 @@ end end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip git "git://example.com/repo.git" do path "../.." do gem "three" @@ -179,13 +179,13 @@ end it "supports path syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.path "../path" do gem "one" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip path "../../path" do gem "one" end @@ -193,8 +193,9 @@ end it "supports nested DSL within path syntax" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new + # TODO: Small bug: order in a Gemfile can be important for certain gems, but order is not preserved. gemfile.path "../path" do group :development, :test do gem "one" @@ -206,8 +207,7 @@ gem "three" end end - - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + fixture_gemfile = <<-GEMFILE.strip_heredoc.rstrip path "../../path" do git "git://example.com/repo.git" do gem "three" @@ -222,19 +222,20 @@ end end GEMFILE + expect(gemfile.to_s).to eq fixture_gemfile end context "excess new line" do context "no contents" do it "shows empty string" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new expect(gemfile.to_s).to eq "" end end context "full contents" do it "does not show newline at end" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.source "source" gemfile.gem "gem" gemfile.gemspec @@ -244,7 +245,7 @@ context "no gemspec" do it "does not show newline at end" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.source "source" gemfile.gem "gem" expect(gemfile.to_s).to match(/[^\n]\z/m) @@ -253,70 +254,71 @@ end context "relative path handling" do - before { stub_const("RUBY_VERSION", "2.3.0") } + before { stub_const("RUBY_VERSION", "1.8.7") } context "in :path option" do it "handles dot path" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", path: "." + gemfile = described_class.new + gemfile.gem "bacon", :path => "." - expect(gemfile.to_s).to eq %(gem "bacon", path: "../") + expect(gemfile.to_s).to eq %(gem "bacon", :path => "../") end it "handles relative path" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", path: "../bacon" + gemfile = described_class.new + gemfile.gem "bacon", :path => "../bacon" - expect(gemfile.to_s).to eq %(gem "bacon", path: "../../bacon") + expect(gemfile.to_s).to eq %(gem "bacon", :path => "../../bacon") end it "handles absolute path" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", path: "/tmp" + gemfile = described_class.new + gemfile.gem "bacon", :path => "/tmp" - expect(gemfile.to_s).to eq %(gem "bacon", path: "/tmp") + expect(gemfile.to_s).to eq %(gem "bacon", :path => "/tmp") end end context "in :git option" do it "handles dot git path" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", git: "." + gemfile = described_class.new + gemfile.gem "bacon", :git => "." - expect(gemfile.to_s).to eq %(gem "bacon", git: "../") + expect(gemfile.to_s).to eq %(gem "bacon", :git => "../") end it "handles relative git path" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", git: "../bacon" + gemfile = described_class.new + gemfile.gem "bacon", :git => "../bacon" - expect(gemfile.to_s).to eq %(gem "bacon", git: "../../bacon") + expect(gemfile.to_s).to eq %(gem "bacon", :git => "../../bacon") end it "handles absolute git path" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", git: "/tmp" + gemfile = described_class.new + gemfile.gem "bacon", :git => "/tmp" - expect(gemfile.to_s).to eq %(gem "bacon", git: "/tmp") + expect(gemfile.to_s).to eq %(gem "bacon", :git => "/tmp") end it "handles git uri" do - gemfile = Appraisal::Gemfile.new - gemfile.gem "bacon", git: "git@github.com:bacon/bacon.git" + gemfile = described_class.new + gemfile.gem "bacon", :git => "git@github.com:bacon/bacon.git" - expect(gemfile.to_s).to eq %(gem "bacon", git: "git@github.com:bacon/bacon.git") + expect(gemfile.to_s) + .to eq %(gem "bacon", :git => "git@github.com:bacon/bacon.git") end end context "in path block" do it "handles dot path" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.path "." do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip path "../" do gem "bacon" end @@ -324,13 +326,13 @@ end it "handles relative path" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.path "../bacon" do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip path "../../bacon" do gem "bacon" end @@ -338,13 +340,13 @@ end it "handles absolute path" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.path "/tmp" do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip path "/tmp" do gem "bacon" end @@ -354,13 +356,13 @@ context "in git block" do it "handles dot git path" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git "." do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip git "../" do gem "bacon" end @@ -368,13 +370,13 @@ end it "handles relative git path" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git "../bacon" do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip git "../../bacon" do gem "bacon" end @@ -382,13 +384,13 @@ end it "handles absolute git path" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git "/tmp" do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip git "/tmp" do gem "bacon" end @@ -396,13 +398,13 @@ end it "handles git uri" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git "git@github.com:bacon/bacon.git" do gem "bacon" end - expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.strip + expect(gemfile.to_s).to eq <<-GEMFILE.strip_heredoc.rstrip git "git@github.com:bacon/bacon.git" do gem "bacon" end @@ -412,28 +414,28 @@ context "in gemspec directive" do it "handles gemspec path" do - gemfile = Appraisal::Gemfile.new - gemfile.gemspec path: "." + gemfile = described_class.new + gemfile.gemspec :path => "." - expect(gemfile.to_s).to eq %(gemspec path: "../") + expect(gemfile.to_s).to eq %(gemspec :path => "../") end end end context "git_source support" do - before { stub_const("RUBY_VERSION", "2.3.0") } + before { stub_const("RUBY_VERSION", "1.8.7") } it "stores git_source declaration and apply it as git option" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new gemfile.git_source(:custom_source) { |repo| "path/#{repo}" } - gemfile.gem "bacon", custom_source: "bacon_pancake" + gemfile.gem "bacon", :custom_source => "bacon_pancake" - expect(gemfile.to_s).to eq %(gem "bacon", git: "../path/bacon_pancake") + expect(gemfile.to_s).to eq %(gem "bacon", :git => "../path/bacon_pancake") end end it "preserves the Gemfile's __FILE__" do - gemfile = Appraisal::Gemfile.new + gemfile = described_class.new Tempfile.open do |tmpfile| tmpfile.write "__FILE__" tmpfile.rewind diff --git a/spec/appraisal/utils_spec.rb b/spec/appraisal/utils_spec.rb index 04dfe910..743f8683 100644 --- a/spec/appraisal/utils_spec.rb +++ b/spec/appraisal/utils_spec.rb @@ -5,61 +5,64 @@ RSpec.describe Appraisal::Utils do describe ".format_string" do - it "prints out a nice looking hash without brackets with new syntax" do - hash = { foo: "bar" } - expect(Appraisal::Utils.format_string(hash)).to eq('foo: "bar"') + it "prints out a nice looking hash without brackets with old syntax" do + hash = {:foo => "bar"} + expect(described_class.format_string(hash)).to eq(':foo => "bar"') - hash = { "baz" => { ball: "boo" } } - expect(Appraisal::Utils.format_string(hash)).to eq('"baz" => { ball: "boo" }') + hash = {"baz" => {:ball => "boo"}} + expect(described_class.format_string(hash)) + .to eq('"baz" => { :ball => "boo" }') end end describe ".format_arguments" do - before { stub_const("RUBY_VERSION", "2.3.0") } + before { stub_const("RUBY_VERSION", "1.8.7") } it "prints out arguments without enclosing square brackets" do - arguments = [:foo, { bar: { baz: "ball" } }] + arguments = [:foo, {:bar => {:baz => "ball"}}] - expect(Appraisal::Utils.format_arguments(arguments)).to eq(':foo, bar: { baz: "ball" }') + expect(described_class.format_arguments(arguments)).to eq( + ':foo, :bar => { :baz => "ball" }', + ) end it "returns nil if arguments is empty" do arguments = [] - expect(Appraisal::Utils.format_arguments(arguments)).to eq(nil) + expect(described_class.format_arguments(arguments)).to be_nil end end describe ".prefix_path" do it "prepends two dots in front of relative path" do - expect(Appraisal::Utils.prefix_path("test")).to eq "../test" + expect(described_class.prefix_path("test")).to eq "../test" end it "replaces single dot with two dots" do - expect(Appraisal::Utils.prefix_path(".")).to eq "../" + expect(described_class.prefix_path(".")).to eq "../" end it "ignores absolute path" do - expect(Appraisal::Utils.prefix_path("/tmp")).to eq "/tmp" + expect(described_class.prefix_path("/tmp")).to eq "/tmp" end it "strips out './' from path" do - expect(Appraisal::Utils.prefix_path("./tmp/./appraisal././")).to eq "../tmp/appraisal./" + expect(described_class.prefix_path("./tmp/./appraisal././")).to eq "../tmp/appraisal./" end it "does not prefix Git uri" do - expect(Appraisal::Utils.prefix_path("git@github.com:bacon/bacon.git")).to eq "git@github.com:bacon/bacon.git" - expect(Appraisal::Utils.prefix_path("git://github.com/bacon/bacon.git")).to eq "git://github.com/bacon/bacon.git" - expect(Appraisal::Utils.prefix_path("https://github.com/bacon/bacon.git")).to eq("https://github.com/bacon/bacon.git") + expect(described_class.prefix_path("git@github.com:bacon/bacon.git")).to eq "git@github.com:bacon/bacon.git" + expect(described_class.prefix_path("git://github.com/bacon/bacon.git")).to eq "git://github.com/bacon/bacon.git" + expect(described_class.prefix_path("https://github.com/bacon/bacon.git")).to eq("https://github.com/bacon/bacon.git") end end describe ".bundler_version" do it "returns the bundler version" do - bundler = double("Bundler", name: "bundler", version: "a.b.c") + bundler = double("Bundler", :name => "bundler", :version => "a.b.c") allow(Gem::Specification).to receive(:detect).and_return(bundler) - version = Appraisal::Utils.bundler_version + version = described_class.bundler_version expect(version).to eq "a.b.c" expect(Gem::Specification).to have_received(:detect) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 20b036de..1d9f7366 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,27 @@ # frozen_string_literal: true require "rubygems" -require "bundler/setup" -require "./spec/support/acceptance_test_helpers" -require "./spec/support/stream_helpers" + +if ENV["CI"].nil? && ENV["DEBUG"] == "true" + ENV["VERBOSE"] = "true" + ruby_version = Gem::Version.new(RUBY_VERSION) + if ruby_version < Gem::Version.new("2.7") + # Use byebug in code + require "byebug" + else + # Use binding.break, binding.b, or debugger in code + require "debug" + end +end + +# External Libraries +require "active_support/core_ext/string/strip" +require "rspec/pending_for" + +# This library +require "support/dependency_helpers" +require "support/acceptance_test_helpers" +require "support/stream_helpers" PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")).freeze TMP_GEM_ROOT = File.join(PROJECT_ROOT, "tmp", "bundler") @@ -13,11 +31,11 @@ RSpec.configure do |config| config.raise_errors_for_deprecations! - config.define_derived_metadata(file_path: %r{spec\/acceptance\/}) do |metadata| + config.define_derived_metadata(:file_path => %r{spec/acceptance/}) do |metadata| metadata[:type] = :acceptance end - config.include AcceptanceTestHelpers, type: :acceptance + config.include AcceptanceTestHelpers, :type => :acceptance # disable monkey patching # see: https://relishapp.com/rspec/rspec-core/v/3-8/docs/configuration/zero-monkey-patching-mode diff --git a/spec/support/acceptance_test_helpers.rb b/spec/support/acceptance_test_helpers.rb index 52e0c60a..78ce6751 100644 --- a/spec/support/acceptance_test_helpers.rb +++ b/spec/support/acceptance_test_helpers.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true +# External Libraries require "rspec/expectations/expectation_target" -require "active_support/core_ext/string/strip" require "active_support/core_ext/string/filters" require "active_support/concern" + +# This library require "appraisal/utils" -require "./spec/support/dependency_helpers" module AcceptanceTestHelpers extend ActiveSupport::Concern @@ -22,9 +23,15 @@ module AcceptanceTestHelpers included do metadata[:type] = :acceptance - before parallel: true do + before :parallel => true do unless Appraisal::Utils.support_parallel_installation? - pending "This Bundler version does not support --jobs flag." + skip "This Bundler version does not support --jobs flag." + end + end + + before :git_local => true do + unless Appraisal::Utils.support_git_local_installation? + skip "This Bundler version does not support sourcing gems from git repos on local filesystem." end end @@ -43,6 +50,15 @@ module AcceptanceTestHelpers end end + # A head version of MRI ruby will require the -dev appended to the ruby string in the gemfile. + # Unfortunately, there is no ENV variable that has access to the "dev" concept directly. + # Perhaps the best we can do is parse RUBY_DESCRIPTION? + # > RUBY_DESCRIPTION + # "ruby 3.5.0dev (2025-02-20T18:14:37Z ... etc) + def ruby_dev_append + RUBY_DESCRIPTION.include?("dev") ? "-dev" : "" + end + def save_environment_variables @original_environment_variables = {} @@ -58,7 +74,7 @@ def unset_bundler_environment_variables end def add_binstub_path - ENV["PATH"] = "bin:#{ENV['PATH']}" + ENV["PATH"] = "bin:#{ENV["PATH"]}" end def restore_environment_variables @@ -82,7 +98,7 @@ def add_gemspec_to_gemfile end def build_gemspec - write_file "stage.gemspec", <<-GEMSPEC + write_file "stage.gemspec", <<-GEMSPEC.strip_heredoc.rstrip Gem::Specification.new do |s| s.name = 'stage' s.version = '0.1' @@ -94,8 +110,8 @@ def build_gemspec def content_of(path) file(path).read.tap do |content| - content.gsub!(/(\S+): /, ":\\1 => ") - end + content.gsub!(/(\S+): /, ':\\1 => ') + end.strip end def file(path) @@ -130,25 +146,30 @@ def build_default_dummy_gems def ensure_bundler_is_available run "bundle -v 2>&1", false - if $?.exitstatus != 0 - puts <<-WARNING.squish.strip_heredoc - Reinstall Bundler to #{TMP_GEM_ROOT} as `BUNDLE_DISABLE_SHARED_GEMS` - is enabled. - WARNING - version = Utils.bundler_version + return unless $?.exitstatus != 0 - run "gem install bundler --version #{version} --install-dir '#{TMP_GEM_ROOT}'" - end + puts <<-WARNING.strip_heredoc.rstrip + Reinstall Bundler to #{TMP_GEM_ROOT} as `BUNDLE_DISABLE_SHARED_GEMS` + is enabled. + WARNING + version = Utils.bundler_version + + run "gem install bundler --version #{version} --install-dir '#{TMP_GEM_ROOT}'" end def build_default_gemfile - build_gemfile <<-GEMFILE + build_gemfile <<-GEMFILE.strip_heredoc.rstrip source 'https://rubygems.org' gem 'appraisal', :path => '#{PROJECT_ROOT}' GEMFILE run "bundle install --local" + # Support for binstubs --all was added to bundler's 1-17-stable branch + # and released with bundler v1.17.0.pre.2 (2018-10-13) + # See: + # - https://github.com/rubygems/bundler/pull/6450 + # - https://github.com/rubygems/bundler/commit/9d59fa41ef43aaccc6cf867a69a49648510c4df7#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4edR10 run "bundle binstubs --all" end @@ -159,17 +180,15 @@ def in_test_directory(&block) def run(command, raise_on_error = true) in_test_directory do - `#{command}`.tap do |output| + %x(#{command}).tap do |output| exitstatus = $?.exitstatus - if ENV["VERBOSE"] - puts output - end + puts output if ENV["VERBOSE"] if raise_on_error && exitstatus != 0 - raise RuntimeError, <<-ERROR_MESSAGE.strip_heredoc + raise <<-ERROR_MESSAGE.strip_heredoc.rstrip Command #{command.inspect} exited with status #{exitstatus}. Output: - #{output.gsub(/^/, ' ')} + #{output.gsub(/^/, " ")} ERROR_MESSAGE end end diff --git a/spec/support/dependency_helpers.rb b/spec/support/dependency_helpers.rb index 54752d20..c3ff63bc 100644 --- a/spec/support/dependency_helpers.rb +++ b/spec/support/dependency_helpers.rb @@ -1,46 +1,81 @@ # frozen_string_literal: true module DependencyHelpers - def build_gem(gem_name, version = "1.0.0") + DEFAULT_OPTS = { + :skip_build => false, + :skip_install => false, + }.freeze + # @param opts [String, Hash] version string, or options hash + # @option :skip_build [Boolean, nil] skip running gem build, default is false + # @option :skip_install [Boolean, nil] skip running gem install, default is value of skip_build + # @option :version [String, nil] version for the gem, default is "1.0.0" + # @return void + def build_gem(gem_name, opts = {}) ENV["GEM_HOME"] = TMP_GEM_ROOT + version = "1.0.0" # default - unless File.exist? "#{TMP_GEM_ROOT}/gems/#{gem_name}-#{version}" - FileUtils.mkdir_p "#{TMP_GEM_BUILD}/#{gem_name}/lib" - - FileUtils.cd "#{TMP_GEM_BUILD}/#{gem_name}" do - gemspec = "#{gem_name}.gemspec" - lib_file = "lib/#{gem_name}.rb" - - File.open gemspec, "w" do |file| - file.puts <<-GEMSPEC - Gem::Specification.new do |s| - s.name = #{gem_name.inspect} - s.version = #{version.inspect} - s.authors = 'Mr. Smith' - s.summary = 'summary' - s.files = #{lib_file.inspect} - s.license = 'MIT' - s.homepage = 'http://github.com/thoughtbot/#{gem_name}' - s.required_ruby_version = '>= 2.3.0' - end - GEMSPEC - end - - File.open lib_file, "w" do |file| - file.puts "$#{gem_name}_version = '#{version}'" - end - - redirect = ENV["VERBOSE"] ? "" : "2>&1" + if opts.respond_to?(:has_key?) + version = opts[:version] if opts.has_key?(:version) + opts[:skip_build] = false unless opts.has_key?(:skip_build) + opts[:skip_install] = opts[:skip_build] unless opts.has_key?(:skip_install) + else + version = case opts + when String then opts + when nil then "1.0.0" + else + raise ArgumentError, "Unexpected value for opts (must be version string or options Hash): #{opts}" + end + opts = DEFAULT_OPTS.dup + end + skip_build = opts[:skip_build] + skip_install = opts[:skip_install] + return if File.exist? "#{TMP_GEM_ROOT}/gems/#{gem_name}-#{version}" + + build_dir = "#{TMP_GEM_BUILD}/#{gem_name}" + FileUtils.mkdir_p "#{build_dir}/lib" + + FileUtils.cd build_dir do + gemspec = "#{gem_name}.gemspec" + lib_file = "lib/#{gem_name}.rb" + + File.open gemspec, "w" do |file| + file.puts <<-GEMSPEC.strip_heredoc.rstrip + Gem::Specification.new do |s| + s.name = #{gem_name.inspect} + s.version = #{version.inspect} + s.authors = 'Mr. Smith' + s.summary = 'summary' + s.files = #{lib_file.inspect} + s.license = 'MIT' + s.homepage = 'http://github.com/thoughtbot/#{gem_name}' + s.required_ruby_version = '>= 1.8.7' + end + GEMSPEC + end + + File.open lib_file, "w" do |file| + file.puts "$#{gem_name}_version = '#{version}'" + end + + redirect = ENV["VERBOSE"] ? "" : "2>&1" + # Caller may turn it into a git repo here + yield build_dir, redirect if block_given? + + unless skip_build puts "building gem: #{gem_name} #{version}" if ENV["VERBOSE"] - `gem build #{gemspec} #{redirect}` + %x(gem build #{gemspec} #{redirect}) + end + unless skip_install puts "installing gem: #{gem_name} #{version}" if ENV["VERBOSE"] - `gem install -lN #{gem_name}-#{version}.gem -v #{version} #{redirect}` - - puts "" if ENV["VERBOSE"] + %x(gem install -lN #{gem_name}-#{version}.gem -v #{version} #{redirect}) end + + puts "" if ENV["VERBOSE"] end + + nil end def build_gems(gems) @@ -48,22 +83,24 @@ def build_gems(gems) end def build_git_gem(gem_name, version = "1.0.0") - puts "building git gem: #{gem_name} #{version}" if ENV["VERBOSE"] - build_gem gem_name, version - - Dir.chdir "#{TMP_GEM_BUILD}/#{gem_name}" do - `git init . --initial-branch=master` - `git config user.email "appraisal@thoughtbot.com"` - `git config user.name "Appraisal"` - `git add .` - `git commit --all --no-verify --message "initial commit"` + build_gem(gem_name, {:version => version, :skip_build => true, :skip_install => true}) do |_gem_dir, redirect| + # At this point we have a gem file structure on disk inside `_gem_dir`. + # Since we are already inside _gem_dir, we do not need to chdir. + puts "initializing git repo for gem: #{gem_name} #{version}" if ENV["VERBOSE"] + # Set up our clone of the bare git repository, and push our gem into it + %x(git init . --initial-branch=main #{redirect}) + %x(git config user.email "appraisal@thoughtbot.com" #{redirect}) + %x(git config user.name "Appraisal" #{redirect}) + %x(git config commit.gpgsign false #{redirect}) + %x(git add . #{redirect}) + %x(git commit --all --no-verify --message "initial commit" #{redirect}) end # Cleanup Bundler cache path manually for now git_cache_path = File.join(ENV["GEM_HOME"], "cache", "bundler", "git") Dir[File.join(git_cache_path, "#{gem_name}-*")].each do |path| - puts "deleting: #{path}" if ENV["VERBOSE"] + puts "cleaning up bundler git cache: #{path}" if ENV["VERBOSE"] FileUtils.rm_r(path) end end diff --git a/spec/support/stream_helpers.rb b/spec/support/stream_helpers.rb index ca0eb436..021dcc9f 100644 --- a/spec/support/stream_helpers.rb +++ b/spec/support/stream_helpers.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# External Libraries require "tempfile" module StreamHelpers @@ -13,7 +14,7 @@ def capture(stream) yield stream_io.rewind - return captured_stream.read + captured_stream.read ensure captured_stream.close captured_stream.unlink