diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a520758..57dce2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: working-directory: test - run: test/await_healthy.sh - uses: ./ - name: Run test + name: Run smoke-test (running the action) with: GL_SERVER_URL: http://172.17.0.1:8080 GL_PROJECT_ID: '1000' @@ -32,6 +32,10 @@ jobs: GLPA_SOME_VARIABLE: some value for the variable - run: 'curl --silent --header "Private-Token: TEST1234567890123456" "http://127.17.0.1:8080/api/v4/projects/1000/jobs/1/trace"' if: always() + - uses: ruby/setup-ruby@v1 + - run: bundle install + - run: bundle exec rspec + name: Run tests (running rspec) - run: docker compose down if: always() working-directory: test diff --git a/.rubocop.yml b/.rubocop.yml index f0b527c..9443c5b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,6 @@ +require: + - rubocop-rspec + AllCops: NewCops: enable @@ -13,6 +16,17 @@ Naming/BlockForwarding: Naming/MethodParameterName: Enabled: false +RSpec/ExampleLength: + Enabled: false + +# the action folder does not match gitlab_pipeline_action +RSpec/FilePath: + Enabled: false + +# the action folder does not match gitlab_pipeline_action +RSpec/SpecFilePathFormat: + Enabled: false + Style/Documentation: Enabled: false diff --git a/Gemfile b/Gemfile index b208a48..7dca5a6 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,12 @@ gem 'gitlab', '~> 4.19' gem 'git', '~> 1.18' -gem 'rubocop', '~> 1.57' - gem 'docker-api', '~> 2.2' + +group :development, :test do + gem 'rubocop', '~> 1.57' + + gem 'rubocop-rspec', '~> 2.25' + + gem 'rspec', '~> 3.12' +end diff --git a/Gemfile.lock b/Gemfile.lock index 288feb6..c475371 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,7 @@ GEM addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) + diff-lcs (1.5.0) docker-api (2.2.0) excon (>= 0.47.0) multi_json @@ -32,6 +33,19 @@ GEM rchardet (1.8.0) regexp_parser (2.8.2) rexml (3.2.6) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) rubocop (1.57.2) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -45,6 +59,14 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) parser (>= 3.2.1.0) + rubocop-capybara (2.19.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.24.0) + rubocop (~> 1.33) + rubocop-rspec (2.25.0) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -57,7 +79,9 @@ DEPENDENCIES docker-api (~> 2.2) git (~> 1.18) gitlab (~> 4.19) + rspec (~> 3.12) rubocop (~> 1.57) + rubocop-rspec (~> 2.25) BUNDLED WITH 2.4.12 diff --git a/lib/action/context.rb b/lib/action/context.rb index 472d29e..52374d1 100644 --- a/lib/action/context.rb +++ b/lib/action/context.rb @@ -3,7 +3,7 @@ module GitlabPipelineAction class Context attr_accessor :gh_project, :gh_sha, :gh_ref, :gh_server_url, - :gl_server_url, :gl_project_id, :gl_project_path, :gl_runner_token, + :gl_server_url, :gl_server_url_for_runner, :gl_project_id, :gl_project_path, :gl_runner_token, :gl_api_token, :gl_pipeline, :gl_branch_name, :gl_pipeline_variables, :gl_show_job_logs, :git_repository, :git_path, diff --git a/lib/action/entrypoint.rb b/lib/action/entrypoint.rb index d71d6b4..bec8bb2 100644 --- a/lib/action/entrypoint.rb +++ b/lib/action/entrypoint.rb @@ -15,8 +15,7 @@ class Entrypoint GitlabPipelineAction::Step::ShowJobLogs ].freeze - def execute - context = Context.new + def execute(context = Context.new) STEPS.each do |step_class| step = step_class.new(context) print "#{step_class}: " diff --git a/lib/action/step/prepare_context.rb b/lib/action/step/prepare_context.rb index 45d6bcd..12f3c96 100644 --- a/lib/action/step/prepare_context.rb +++ b/lib/action/step/prepare_context.rb @@ -12,6 +12,10 @@ def execute context.gl_branch_name = "glpa/#{ENV.fetch('GITHUB_REF_NAME', nil)}" context.gl_server_url = ENV.fetch('INPUT_GL_SERVER_URL', nil) + context.gl_server_url_for_runner = ENV.fetch( + 'INPUT_GL_SERVER_URL_FOR_RUNNER', # intentionally undocumented + context.gl_server_url + ) context.gl_project_id = ENV.fetch('INPUT_GL_PROJECT_ID', nil) context.gl_runner_token = ENV.fetch('INPUT_GL_RUNNER_TOKEN', nil) context.gl_api_token = ENV.fetch('INPUT_GL_API_TOKEN', nil) diff --git a/lib/action/step/start_runner.rb b/lib/action/step/start_runner.rb index fa7f5e3..692fb17 100644 --- a/lib/action/step/start_runner.rb +++ b/lib/action/step/start_runner.rb @@ -20,7 +20,7 @@ def execute container.exec([ 'gitlab-runner', 'register', '--non-interactive', - '--url', context.gl_server_url, + '--url', context.gl_server_url_for_runner, '--token', context.gl_runner_token, '--executor', 'docker', '--docker-image', 'alpine:latest' diff --git a/spec/action/full_run_spec.rb b/spec/action/full_run_spec.rb new file mode 100644 index 0000000..23927d4 --- /dev/null +++ b/spec/action/full_run_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Full run', :require_gitlab do # rubocop:disable RSpec/DescribeClass + subject(:execute) { GitlabPipelineAction::Entrypoint.new.execute(context) } + + let(:context) { GitlabPipelineAction::Context.new } + + it 'runs without error', :disable_console do + expect { execute }.not_to raise_error + end + + it 'does not output job traces by default' do + expect { execute }.to output(include('GitlabPipelineAction::Step::ShowJobLogs: skipped')).to_stdout + end + + context 'when SHOW_JOB_LOGS is set to failures' do + stub_env('INPUT_SHOW_JOB_LOGS', 'failures') + + it 'only outputs job traces of failed jobs' do + expect { execute }.to output( + include("'failing-job' in stage 'test' (failed)") + .and(not_include("'job' in stage 'test' (success)")) + ).to_stdout + end + end + + context 'when SHOW_JOB_LOGS is set to all' do + stub_env('INPUT_SHOW_JOB_LOGS', 'all') + + it 'outputs job traces of all jobs' do + expect { execute }.to output( + include( + "'failing-job' in stage 'test' (failed)", + "'job' in stage 'test' (success)", + "'job-with-commands' in stage 'test' (success)" + ) + ).to_stdout + end + end + + context 'when setting a variable for the pipeline' do + stub_env('GLPA_SOME_VARIABLE', 'A test value for the variable') + stub_env('INPUT_SHOW_JOB_LOGS', 'all') + + it 'passes the variable to the pipeline' do + expect { execute }.to output( + include('A test value for the variable') + ).to_stdout + end + end +end diff --git a/spec/action/helper/github_spec.rb b/spec/action/helper/github_spec.rb new file mode 100644 index 0000000..d7857aa --- /dev/null +++ b/spec/action/helper/github_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabPipelineAction::Helper::Github do + it '#warning writes the correct format' do + expect do + described_class.warning('Some warning message') + end.to output( + "::warning::Some warning message\n" + ).to_stdout + end + + it '#with_group writes the correct format' do + expect do + described_class.with_group('Some group name') do + puts 'Some message' + end + end.to output( + "::group::Some group name\n" \ + "Some message\n" \ + "::endgroup::\n" + ).to_stdout + end + + it '#stop_commands writes the correct format' do + allow(SecureRandom).to receive(:hex).and_return('Some random value') + + expect do + described_class.stop_commands do + puts 'Some message' + end + end.to output( + "::stop-commands::Some random value\n" \ + "Some message\n" \ + "::Some random value::\n" + ).to_stdout + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..cf1fd3a --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration + +require_relative '../lib/gitlab_pipeline_action' + +Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require f } + +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # The action produces console output, using doc formatter to separate + # the output from different runs better + config.default_formatter = 'doc' + + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + # config.disable_monkey_patching! + # + # # This setting enables warnings. It's recommended, but in some cases may + # # be too noisy due to issues in dependencies. + # config.warnings = true + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed +end diff --git a/spec/support/environment_variables.rb b/spec/support/environment_variables.rb new file mode 100644 index 0000000..43705e5 --- /dev/null +++ b/spec/support/environment_variables.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +RSpec.configure do + ENV['GITHUB_REPOSITORY'] = 'Taucher2003/GitLab-Pipeline-Action' + ENV['GITHUB_SHA'] = 'master' + ENV['GITHUB_REF'] = 'refs/heads/master' + ENV['GITHUB_REF_NAME'] = 'master' + ENV['GITHUB_SERVER_URL'] = 'https://github.com' + + ENV['INPUT_GL_SERVER_URL'] = ENV.fetch('GITLAB_BASE_URL', 'http://127.0.0.1:8080') + ENV['INPUT_GL_SERVER_URL_FOR_RUNNER'] = ENV.fetch('GITLAB_BASE_URL_FOR_RUNNER', ENV.fetch('INPUT_GL_SERVER_URL', nil)) + ENV['INPUT_GL_PROJECT_ID'] = ENV.fetch('GITLAB_PROJECT_ID', '1000') + ENV['INPUT_GL_RUNNER_TOKEN'] = ENV.fetch('GITLAB_RUNNER_TOKEN', 'some_long_runner_token') + ENV['INPUT_GL_API_TOKEN'] = ENV.fetch('GITLAB_TOKEN', 'TEST1234567890123456') +end diff --git a/spec/support/gitlab_instance.rb b/spec/support/gitlab_instance.rb new file mode 100644 index 0000000..d2df02d --- /dev/null +++ b/spec/support/gitlab_instance.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +gitlab_not_running = false + +RSpec.configure do |config| + Gitlab.configure do |c| + c.endpoint = ENV.fetch('GITLAB_BASE_URL', 'http://127.0.0.1:8080/api/v4') + c.private_token = ENV.fetch('GITLAB_TOKEN', 'TEST1234567890123456') + end + + begin + puts Gitlab.version.to_h + rescue Errno::ECONNREFUSED + gitlab_not_running = true + end + + config.around(:each, :require_gitlab) do |example| + if gitlab_not_running + skip 'GitLab is not running' + next + end + + example.run + end +end diff --git a/spec/support/negated_matchers.rb b/spec/support/negated_matchers.rb new file mode 100644 index 0000000..2e29df0 --- /dev/null +++ b/spec/support/negated_matchers.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +RSpec.configure do + RSpec::Matchers.define_negated_matcher :not_include, :include +end diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb new file mode 100644 index 0000000..26b9176 --- /dev/null +++ b/spec/support/stub_env.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module StubEnv + def stub_env(key, value) + around do |example| + original_value = ENV.fetch(key, nil) + ENV[key] = value + example.run + ensure + ENV[key] = original_value + end + end +end + +RSpec.configure do |config| + config.extend StubEnv +end diff --git a/spec/support/suppress_console_output.rb b/spec/support/suppress_console_output.rb new file mode 100644 index 0000000..2bd236f --- /dev/null +++ b/spec/support/suppress_console_output.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.around(:each, :disable_console) do |example| + original_stdout = $stdout + original_stderr = $stderr + + example_description = example.metadata[:full_description] + example_log_path = example_description.gsub(' ', '_').downcase + + tmp_dir = File.expand_path("../../tmp/spec/logs/#{example_log_path}", __dir__) + FileUtils.mkdir_p tmp_dir + + $stdout = File.open("#{tmp_dir}/stdout", 'w') + $stderr = File.open("#{tmp_dir}/stderr", 'w') + + example.run + + begin + $stdout.flush + $stderr.flush + ensure + $stdout.close + $stderr.close + end + + $stdout = original_stdout + $stderr = original_stderr + + if ENV['CI'] + GitlabPipelineAction::Helper::Github.with_group "#{example_description} (stdout)" do + puts File.read "#{tmp_dir}/stdout" + end + GitlabPipelineAction::Helper::Github.with_group "#{example_description} (stderr)" do + puts File.read "#{tmp_dir}/stderr" + end + end + end +end