From f51882ce2bab88ff06552704cb3297ffbedd084d Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Sun, 5 Jul 2020 15:53:56 -0400 Subject: [PATCH] move to rspec; add ruby tests --- .github/workflows/integration.yml | 3 +- .rspec | 1 + Gemfile | 5 +- Gemfile.lock | 27 +++-- Rakefile | 9 -- integration/commands/cd_test.rb | 10 -- integration/commands/clone_test.rb | 33 ------ integration/commands/doctor_test.rb | 28 ----- integration/dependencies/ruby_test.rb | 32 ----- integration/test_helper.rb | 143 ----------------------- internal/catalog/bindata.go | 2 +- internal/task/root.go | 1 + loon.yml | 5 +- spec/commands/cd_spec.rb | 10 ++ spec/commands/clone_spec.rb | 32 +++++ spec/commands/doctor_spec.rb | 29 +++++ spec/dependencies/ruby_spec.rb | 30 +++++ spec/spec_helper.rb | 162 ++++++++++++++++++++++++++ 18 files changed, 291 insertions(+), 271 deletions(-) create mode 100644 .rspec delete mode 100644 Rakefile delete mode 100644 integration/commands/cd_test.rb delete mode 100644 integration/commands/clone_test.rb delete mode 100644 integration/commands/doctor_test.rb delete mode 100644 integration/dependencies/ruby_test.rb delete mode 100644 integration/test_helper.rb create mode 100644 spec/commands/cd_spec.rb create mode 100644 spec/commands/clone_spec.rb create mode 100644 spec/commands/doctor_spec.rb create mode 100644 spec/dependencies/ruby_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 9ea5cc2..a5c5045 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -5,6 +5,7 @@ jobs: strategy: matrix: platform: [ubuntu-latest, macos-latest] + tag: ["~dependency" "dependency:ruby"] runs-on: ${{ matrix.platform }} steps: - name: Install Go @@ -27,4 +28,4 @@ jobs: - name: Bundle Install run: bundle install - name: Integration Tests - run: rake test + run: bundle exec rspec --tag ${{ matrix.tag }} diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/Gemfile b/Gemfile index dba49fb..3187968 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source 'https://rubygems.org' -gem 'minitest', '~> 5.14.1' -gem 'minitest-hooks', '~> 1.5.0' -gem 'rake', '~> 13.0.1' +gem 'byebug', '~> 11.1.3' +gem 'rspec', '~> 3.9.0' diff --git a/Gemfile.lock b/Gemfile.lock index 6b76807..12ffa60 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,21 +1,28 @@ GEM remote: https://rubygems.org/ specs: - minispec-metadata (3.3.1) - minitest - minitest (5.14.1) - minitest-hooks (1.5.0) - minitest (> 5.3) - rake (13.0.1) + byebug (11.1.3) + diff-lcs (1.4.4) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.3) PLATFORMS ruby DEPENDENCIES - minispec-metadata (~> 3.3.1) - minitest (~> 5.14.1) - minitest-hooks (~> 1.5.0) - rake (~> 13.0.1) + byebug (~> 11.1.3) + rspec (~> 3.9.0) BUNDLED WITH 2.1.4 diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 58adb6d..0000000 --- a/Rakefile +++ /dev/null @@ -1,9 +0,0 @@ -require "rake/testtask" - -Rake::TestTask.new(:test) do |t| - t.libs << "integration" - t.test_files = FileList["integration/**/*_test.rb"] -end - -task :default => :test - diff --git a/integration/commands/cd_test.rb b/integration/commands/cd_test.rb deleted file mode 100644 index 55ab72e..0000000 --- a/integration/commands/cd_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class TestCd < Loon::Test - def test_that_we_get_a_finisher - loon %w(cd andremedeiros/loon) - - assert_status 0 - assert_finalizer 'chdir', "#{ENV['HOME']}/src/github.com/andremedeiros/loon" - end -end diff --git a/integration/commands/clone_test.rb b/integration/commands/clone_test.rb deleted file mode 100644 index 0891792..0000000 --- a/integration/commands/clone_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'test_helper' - -class TestClone < Loon::Test - # Ensure we run these tests in temporary home folders - def around - with_environment(home: Dir.mktmpdir) do - super - end - end - - def test_that_clone_respects_source_tree - with_config(source_tree: '$HOME/{owner}/{name}') do - loon %w(clone andremedeiros/ruby-demo) - - assert_status 0 - assert_path "#{ENV['HOME']}/andremedeiros/ruby-demo/.git" - end - end - - def test_that_repo_gets_checked_out - loon %w(clone andremedeiros/ruby-demo) - - assert_status 0 - assert_path "#{ENV['HOME']}/src/github.com/andremedeiros/ruby-demo/.git" - end - - def test_that_we_get_a_finisher - loon %w(clone andremedeiros/ruby-demo) - - assert_status 0 - assert_finalizer 'chdir', "#{ENV['HOME']}/src/github.com/andremedeiros/ruby-demo" - end -end diff --git a/integration/commands/doctor_test.rb b/integration/commands/doctor_test.rb deleted file mode 100644 index 1f457ab..0000000 --- a/integration/commands/doctor_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class TestDoctor < Loon::Test - def test_that_it_shows_error_on_sudo - with_command_mock('sudo', 'exit 1') do - loon %w(doctor) - end - - assert_status 0 - assert_stderr "sudo not enabled for this user" - end - - def test_that_it_shows_error_on_nix - with_environment(path: '') do - loon %w(doctor) - end - - assert_status 0 - assert_stderr "cannot find nix utility: nix" - end - - def test_it_shows_no_errors_when_all_is_good - loon %(doctor) - - assert_status 0 - assert_stdout "You're all good!" - end -end diff --git a/integration/dependencies/ruby_test.rb b/integration/dependencies/ruby_test.rb deleted file mode 100644 index bb28b3e..0000000 --- a/integration/dependencies/ruby_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class TestRuby < Loon::Test - def test_ruby_versions - dependency_test :ruby - %w(2.6.6 2.7.1).each do |version| - test_ruby_dep version: version, match: version - end - end - - def test_default - dependency_test :ruby - test_ruby_dep match: '2.7.1' - end - - private - - def test_ruby_dep(version: nil, match:) - dep = if version - {'ruby' => version} - else - 'ruby' - end - - with_payload(deps: dep) do - loon %w(exec ruby --version) - - assert_status 0 - assert_stdout match - end - end -end diff --git a/integration/test_helper.rb b/integration/test_helper.rb deleted file mode 100644 index 3d2f1c0..0000000 --- a/integration/test_helper.rb +++ /dev/null @@ -1,143 +0,0 @@ -require 'fileutils' -require 'open3' -require 'pathname' -require 'yaml' - -require 'minitest/autorun' -require 'minitest/hooks/default' -require 'minitest/pride' - -ROOT = Pathname(__FILE__).dirname.dirname -LOON = ROOT.join('loon') - -module Assertions - def assert_status(status) - assert_equal status, @last_status - end - - def refute_status(status) - refute_equal status, @last_status - end - - def assert_stderr(str) - assert @last_stderr.include?(str), "Expected\n\n#{@last_stderr}\n\nto include\n\n#{str}\n" - end - - def assert_stdout(str) - assert @last_stdout.include?(str), "Expected\n\n#{@last_stdout}\n\nto include\n\n#{str}\n" - end - - def assert_finalizer(type, content = nil) - assert @last_finalizer.start_with?("#{type}:"), "Expected finalizer to be #{type}" - assert @last_finalizer.end_with?(":#{content}"), "Expected finalizer to contain #{content} but instead it was #{@last_finalizer}" if content - end - - def assert_path(path) - assert Dir.exist?(path), "Expected #{path} to exist" - end -end - -module Loon - class Test < Minitest::Spec - include Minitest::Hooks - include Assertions - - def dependency_test(dep) - skip "Not running dependency tests for #{dep}" unless ENV['DEPENDENCY'] && ENV["DEPENDENCY_#{lang.to_s.upcase}"] - end - - def with_payload(name: "Test", url: "Test", deps: [], tasks: []) - deps = deps.is_a?(Array) ? deps : [deps] - tasks = tasks.is_a?(Array) ? tasks : [tasks] - - yml = YAML.dump({ - 'name' => name, - 'url' => url, - 'deps' => deps, - 'tasks' => tasks, - }) - - Dir.mktmpdir do |tmpdir| - File.open(File.join(tmpdir, 'loon.yml'), 'w') do |f| - f.write(yml) - end - yield - end - end - - def with_config(cfg) - home = Dir.mktmpdir - with_environment(home: home) do - FileUtils.mkdir_p File.join(home, '.config', 'loon') - File.open(File.join(home, '.config', 'loon', 'config.yml'), 'w') do |f| - cfg.each { |k, v| f.write("#{k}: #{v}\n") } - end - yield - end - ensure - FileUtils.remove_entry home - end - - def with_environment(env) - previous_env = {} - env.each do |k, v| - k = k.to_s.upcase - previous_env[k] = ENV[k] - ENV[k] = v - end - yield - ensure - previous_env.each do |k, v| - ENV[k] = v - end - end - - def with_env_path(dir) - with_environment(path: "#{dir}:#{ENV['PATH']}") do - yield - end - end - - def with_tmpdir - dir = Dir.mktmpdir - yield dir - ensure - FileUtils.remove_entry dir - end - - def with_command_mock(cmd, mock) - with_tmpdir do |dir| - cmd = File.join(dir, cmd) - open(cmd, "w") { |f| f.write(mock) } - FileUtils.chmod("+x", cmd) - with_env_path(dir) { yield } - end - end - - def loon(*args) - cmd = args.prepend(LOON).flatten.map(&:to_s).join(' ') - finalizer = Tempfile.new("finalizer") - script = Tempfile.new("script") - script.write <<~SH - __integration_test() { - exec 9>"#{finalizer.path}" - #{cmd} - ret=$? - exec 9<&- - return "$ret" - } - __integration_test - SH - script.close - FileUtils.chmod("+x", script.path) - out, err, status = Open3.capture3(ENV, script.path) - @last_stdout = out - @last_stderr = err - @last_status = status.exitstatus - @last_finalizer = IO.read(finalizer.path).chomp - ensure - File.unlink(script.path) - File.unlink(finalizer.path) - end - end -end diff --git a/internal/catalog/bindata.go b/internal/catalog/bindata.go index c5198ae..3cd8bbd 100644 --- a/internal/catalog/bindata.go +++ b/internal/catalog/bindata.go @@ -535,7 +535,7 @@ func ruby266Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ruby/2.6.6.json", size: 298, mode: os.FileMode(420), modTime: time.Unix(1593045091, 0)} + info := bindataFileInfo{name: "ruby/2.6.6.json", size: 298, mode: os.FileMode(420), modTime: time.Unix(1593974633, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/internal/task/root.go b/internal/task/root.go index 1659500..737f35e 100644 --- a/internal/task/root.go +++ b/internal/task/root.go @@ -60,6 +60,7 @@ func Run(ctx context.Context, ui ui.UI, p *project.Project, name string, fun fun if err := te.Resolve(ctx, p); err != nil { s.Fail() errs <- err + return } else { done, err := te.Check(ctx, p) if err != nil { diff --git a/loon.yml b/loon.yml index 9bec15b..8fae842 100644 --- a/loon.yml +++ b/loon.yml @@ -25,4 +25,7 @@ tasks: command: go test ./... -v -bench=. $@ integration: description: Runs integration tests - command: rake test + command: bundle exec rspec --tag "~dependency" + integration:dependency: + description: Runs integration tests (with dependencies) + command: bundle exec rspec diff --git a/spec/commands/cd_spec.rb b/spec/commands/cd_spec.rb new file mode 100644 index 0000000..9422bfb --- /dev/null +++ b/spec/commands/cd_spec.rb @@ -0,0 +1,10 @@ +describe 'Commands' do + describe 'cd' do + it 'should emit a finisher' do + loon %w(cd andremedeiros/loon) + + assert_status 0 + assert_finalizer 'chdir', "#{ENV['HOME']}/src/github.com/andremedeiros/loon" + end + end +end diff --git a/spec/commands/clone_spec.rb b/spec/commands/clone_spec.rb new file mode 100644 index 0000000..104703c --- /dev/null +++ b/spec/commands/clone_spec.rb @@ -0,0 +1,32 @@ +describe 'Commands' do + describe 'clone' do + around(:each) do |example| + with_environment(home: Dir.mktmpdir) do + example.run + end + end + + it 'should respect source tree setting' do + with_config(source_tree: '$HOME/{owner}/{name}') do + loon %w(clone andremedeiros/ruby-demo) + + assert_status 0 + assert_path "#{ENV['HOME']}/andremedeiros/ruby-demo/.git" + end + end + + it 'should check out a repo' do + loon %w(clone andremedeiros/ruby-demo) + + assert_status 0 + assert_path "#{ENV['HOME']}/src/github.com/andremedeiros/ruby-demo/.git" + end + + it 'should emit a finisher' do + loon %w(clone andremedeiros/ruby-demo) + + assert_status 0 + assert_finalizer 'chdir', "#{ENV['HOME']}/src/github.com/andremedeiros/ruby-demo" + end + end +end diff --git a/spec/commands/doctor_spec.rb b/spec/commands/doctor_spec.rb new file mode 100644 index 0000000..8bf2b9a --- /dev/null +++ b/spec/commands/doctor_spec.rb @@ -0,0 +1,29 @@ +describe 'Commands' do + describe 'doctor' do + it 'should show error when sudo fails' do + with_command_mock('sudo', 'exit 1') do + loon %w(doctor) + end + + assert_status 0 + assert_stderr 'sudo not enabled for this user' + end + + it 'should show error when nix is not installed' do + with_environment(path: '') do + loon %w(doctor) + end + + assert_status 0 + assert_stderr 'cannot find nix utility: nix' + end + + it 'should show no errors when all is well' do + loon %(doctor) + + assert_status 0 + assert_stdout "You're all good!" + end + end +end + diff --git a/spec/dependencies/ruby_spec.rb b/spec/dependencies/ruby_spec.rb new file mode 100644 index 0000000..6caf15d --- /dev/null +++ b/spec/dependencies/ruby_spec.rb @@ -0,0 +1,30 @@ +describe 'Dependencies' do + describe 'Ruby' do + def test_ruby_dep(version: nil, match:) + dep = if version + {'ruby' => version} + else + 'ruby' + end + + with_payload(deps: dep) do |project| + loon %w(up), dir: project + loon %w(exec ruby --version), dir: project + + assert_stderr_empty + assert_stdout match + assert_status 0 + end + end + + %w(2.6.6 2.7.1).each do |version| + it "installs version #{version} correctly", dependency: 'ruby' do + test_ruby_dep version: version, match: version + end + end + + it 'installs version 2.7.1 as the default', dependency: 'ruby' do + test_ruby_dep match: '2.7.1' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..a9bc8ae --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,162 @@ +require 'byebug' +require 'tempfile' +require 'fileutils' +require 'open3' +require 'pathname' +require 'yaml' + +module Assertions + def assert_status(status) + expect(status).to eq(@last_status) + end + + def refute_status(status) + expect(status).not_to eq(@last_status) + end + + def assert_stderr(str) + expect(@last_stderr).to include(str) + end + + def assert_stderr_empty + expect(@last_stderr).to be_empty + end + + def assert_stdout(str) + expect(@last_stdout).to include(str) + end + + def assert_finalizer(type, content = nil) + expect(@last_finalizer).to start_with(type) + expect(@last_finalizer).to end_with(content) if content + end + + def assert_path(path) + expect(Dir.exist?(path)).to be_truthy + end +end + +module Helpers + ROOT = Pathname(__FILE__).dirname.dirname + LOON = ROOT.join('loon') + + def with_payload(name: "Test", url: "Test", deps: [], tasks: {}) + deps = deps.is_a?(Array) ? deps : [deps] + + yml = YAML.dump({ + 'name' => name, + 'url' => url, + 'deps' => deps, + 'tasks' => tasks, + }) + + Dir.mktmpdir do |tmpdir| + File.open(File.join(tmpdir, 'loon.yml'), 'w') do |f| + f.write(yml) + end + yield tmpdir + end + end + + def with_config(cfg) + home = Dir.mktmpdir + with_environment(home: home) do + FileUtils.mkdir_p File.join(home, '.config', 'loon') + File.open(File.join(home, '.config', 'loon', 'config.yml'), 'w') do |f| + cfg.each { |k, v| f.write("#{k}: #{v}\n") } + end + yield + end + ensure + FileUtils.remove_entry home + end + + def with_environment(env) + previous_env = {} + env.each do |k, v| + k = k.to_s.upcase + previous_env[k] = ENV[k] + ENV[k] = v + end + yield + ensure + previous_env.each do |k, v| + ENV[k] = v + end + end + + def with_env_path(dir) + with_environment(path: "#{dir}:#{ENV['PATH']}") do + yield + end + end + + def with_tmpdir + dir = Dir.mktmpdir + yield dir + ensure + FileUtils.remove_entry dir + end + + def with_command_mock(cmd, mock) + with_tmpdir do |dir| + cmd = File.join(dir, cmd) + open(cmd, "w") { |f| f.write(mock) } + FileUtils.chmod("+x", cmd) + with_env_path(dir) { yield } + end + end + + def loon(*args) + opts = args.last.is_a?(Hash) ? args.pop : {} + cmd = args.prepend(LOON).flatten.map(&:to_s).join(' ') + finalizer = Tempfile.new("finalizer") + script = Tempfile.new("script") + script.write <<~SH + __integration_test() { + exec 9>"#{finalizer.path}" + pushd #{opts[:dir] || Dir.pwd} >/dev/null + #{cmd} + popd >/dev/null + ret=$? + exec 9<&- + return "$ret" + } + __integration_test + SH + script.close + FileUtils.chmod("+x", script.path) + out, err, status = Open3.capture3(ENV, script.path) + @last_stdout = out + @last_stderr = err + @last_status = status.exitstatus + @last_finalizer = IO.read(finalizer.path).chomp + ensure + File.unlink(script.path) + File.unlink(finalizer.path) + end +end + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + config.filter_run_when_matching :focus + config.warnings = true + config.order = :random + + config.include Assertions + config.include Helpers + + # Bundler does... things to the environment, so we want to get the original + # environment before running tests so that the executions aren't tainted. + config.around(:each) do |example| + Bundler.with_original_env { example.run } + end +end