Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ repos:
- id: rubocop
name: Rubocop
language: system
entry: bundle exec rubocop
entry: bundle exec rake rubocop
types: [ruby]
exclude: \.lock
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins:
AllCops:
TargetRubyVersion: 3.1
NewCops: enable
SuggestExtensions: false
Exclude:
- bin/**/*
- vendor/**/*
Expand Down
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.0.2
4.0.6
22 changes: 22 additions & 0 deletions allure-cucumber/lib/allure_cucumber/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def initialize(config)
config.on_event(:test_step_started) { |event| on_test_step_started(event) }
config.on_event(:test_step_finished) { |event| on_test_step_finished(event) }
config.on_event(:test_case_finished) { |event| on_test_case_finished(event) }

register_test_run_hook_finished(config)
end

# Clean test result directory before starting run
Expand All @@ -50,6 +52,17 @@ def on_test_run_started(_event)
# @return [void]
def on_test_run_finished(_event)
lifecycle.write_environment
lifecycle.write_globals
end

# Handle a global hook finishing
# @param [Cucumber::Events::TestRunHookFinished] event
# @return [void]
def on_test_run_hook_finished(event)
return unless event.test_result.failed?

details = Allure::ResultUtils.status_details(event.test_result.exception)
lifecycle.add_global_error(message: details.message, trace: details.trace)
end

# Handle test case started event
Expand Down Expand Up @@ -101,6 +114,15 @@ def on_test_case_finished(event)

attr_reader :lifecycle, :cucumber_model

# Register global hook finished event
# @param [Cucumber::Configuration] config
# @return [void]
def register_test_run_hook_finished(config)
return unless defined?(Cucumber::Events::TestRunHookFinished)

config.on_event(:test_run_hook_finished) { |event| on_test_run_hook_finished(event) }
end

# Is hook fixture like Before, After or Step as AfterStep
# @param [String] text
# @return [boolean]
Expand Down
8 changes: 4 additions & 4 deletions allure-cucumber/spec/cucumber_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def initialize(tmp_dir)
@tmp_dir = tmp_dir
end

def execute(feature)
setup(feature)
def execute(feature, support_code: ENV)
setup(feature, support_code)

Cucumber::Cli::Main.new(
[feature_file, *args],
Expand All @@ -95,11 +95,11 @@ def all_output
[@stdout.string, @stderr.string].reject(&:empty?).join("\n")
end

def setup(feature)
def setup(feature, support_code)
FileUtils.rm_rf(tmp_dir)

write_file(feature_file, feature)
write_file("#{tmp_dir}/features/support/env.rb", ENV)
write_file("#{tmp_dir}/features/support/env.rb", support_code)
write_file("#{tmp_dir}/features/step_definitions/step_defs.rb", STEPS)
end

Expand Down
6 changes: 4 additions & 2 deletions allure-cucumber/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
RSpec.shared_context("cucumber runner") do
let!(:test_tmp_dir) { |e| "tmp/#{e.full_description.tr(' ', '_')}" }

def run_cucumber_cli(feature)
Thread.new { CucumberHelper.new(test_tmp_dir).execute(feature) }.join
def run_cucumber_cli(feature, support_code: CucumberHelper::ENV)
Thread.new do
CucumberHelper.new(test_tmp_dir).execute(feature, support_code: support_code)
end.join
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
FEATURE
end

it "creates environment.properties file" do
it "writes run-level files" do
expect(lifecycle).to have_received(:write_environment).once
expect(lifecycle).to have_received(:write_globals).once
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# frozen_string_literal: true

describe "on_test_run_hook_finished" do
include_context "allure mock"
include_context "cucumber runner"

let(:feature) do
<<~FEATURE
Feature: Global hooks

Scenario: passing scenario
Given a is 5
FEATURE
end

def run_with_global_hooks(hooks)
run_cucumber_cli(feature, support_code: "#{CucumberHelper::ENV}\n#{hooks}")
end

it "adds a failed BeforeAll hook to globals" do
run_with_global_hooks(<<~RUBY)
BeforeAll do
raise "BeforeAll failed"
end
RUBY

expect(lifecycle).to have_received(:add_global_error).with(
message: "BeforeAll failed",
trace: a_string_including("env.rb")
).once
end

it "adds a failed AfterAll hook to globals" do
run_with_global_hooks(<<~RUBY)
AfterAll do
raise "AfterAll failed"
end
RUBY

expect(lifecycle).to have_received(:add_global_error).with(
message: "AfterAll failed",
trace: a_string_including("env.rb")
).once
end

it "ignores passed global hooks" do
run_with_global_hooks(<<~RUBY)
BeforeAll do
end

AfterAll do
end
RUBY

expect(lifecycle).not_to have_received(:add_global_error)
end

it "adds every failed global hook" do
errors = []
allow(lifecycle).to receive(:add_global_error) { |**details| errors << details }

run_with_global_hooks(<<~RUBY)
BeforeAll do
raise "BeforeAll failed"
end

AfterAll do
raise "First AfterAll failed"
end

AfterAll do
raise "Second AfterAll failed"
end
RUBY

expect(errors.map { |error| error[:message] }).to eq(
["BeforeAll failed", "First AfterAll failed", "Second AfterAll failed"]
)
end

it "captures the error before globals are written" do
run_with_global_hooks(<<~RUBY)
AfterAll do
raise "AfterAll failed"
end
RUBY

expect(lifecycle).to have_received(:add_global_error).once.ordered
expect(lifecycle).to have_received(:write_globals).once.ordered
end
end
19 changes: 17 additions & 2 deletions allure-rspec/lib/allure_rspec/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class RSpecFormatter < RSpec::Core::Formatters::BaseFormatter
pending: Allure::Status::SKIPPED,
passed: Allure::Status::PASSED
}.freeze
ANSI_ESCAPE_PATTERN = /\e\[(\d+)(?:;\d+)*m/
SUITE_HOOK_FAILURE_PATTERN = /\AAn error occurred in an? `(?:before|after)\(:suite\)` hook\.\z/

RSpec::Core::Formatters.register(
self,
Expand All @@ -23,7 +25,8 @@ class RSpecFormatter < RSpec::Core::Formatters::BaseFormatter
:example_group_started,
:example_group_finished,
:example_started,
:example_finished
:example_finished,
:message
)

RSpec.configure do |config|
Expand Down Expand Up @@ -58,6 +61,7 @@ def start(_start_notification)
# @return [void]
def stop(_stop_notification)
lifecycle.write_environment
lifecycle.write_globals
end

# Starts example group
Expand Down Expand Up @@ -92,6 +96,17 @@ def example_group_finished(_example_group_notification)
lifecycle.stop_test_container
end

# Capture errors raised by before(:suite) and after(:suite) hooks
# @param [RSpec::Core::Notifications::MessageNotification] notification
# @return [void]
def message(notification)
formatted_error = notification.message.to_s.gsub(ANSI_ESCAPE_PATTERN, "").strip
first_line = formatted_error.lines.first&.chomp
return unless SUITE_HOOK_FAILURE_PATTERN.match?(first_line.to_s)

lifecycle.add_global_error(message: first_line, trace: formatted_error)
end

private

attr_reader :lifecycle, :allure_config
Expand Down Expand Up @@ -125,7 +140,7 @@ def update_test_proc(result)
test_case.stage = Allure::Stage::FINISHED
test_case.status = status(result)
test_case.status_details.message = status_detail.message
test_case.status_details.trace = status_detail.trace&.gsub(/\e\[(\d+)(?:;\d+)*m/, "")
test_case.status_details.trace = status_detail.trace&.gsub(ANSI_ESCAPE_PATTERN, "")
end
end
end
Expand Down
109 changes: 109 additions & 0 deletions allure-rspec/spec/unit/formatter_message_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# frozen_string_literal: true

describe "message" do
include_context "allure mock"
include_context "rspec runner"

let(:passing_example) do
<<~SPEC
describe "Suite" do
it "passes" do
end
end
SPEC
end

def run_with_suite_hooks(hooks)
run_rspec("#{hooks}\n#{passing_example}")
end

it "adds a before(:suite) failure to globals" do
run_with_suite_hooks(<<~SPEC)
RSpec.configure do |config|
config.before(:suite) { raise "before suite failed" }
end
SPEC

expect(lifecycle).to have_received(:add_global_error).with(
message: "An error occurred in a `before(:suite)` hook.",
trace: a_string_including("RuntimeError", "before suite failed")
).once
end

it "adds an after(:suite) failure to globals" do
run_with_suite_hooks(<<~SPEC)
RSpec.configure do |config|
config.after(:suite) { raise "after suite failed" }
end
SPEC

expect(lifecycle).to have_received(:add_global_error).with(
message: "An error occurred in an `after(:suite)` hook.",
trace: a_string_including("RuntimeError", "after suite failed")
).once
end

it "adds every failed suite hook" do
errors = []
allow(lifecycle).to receive(:add_global_error) { |**details| errors << details }

run_with_suite_hooks(<<~SPEC)
RSpec.configure do |config|
config.after(:suite) { raise "first after suite failed" }
config.after(:suite) { raise "second after suite failed" }
end
SPEC

expect(errors.map { |error| error[:trace] }).to contain_exactly(
a_string_including("first after suite failed"),
a_string_including("second after suite failed")
)
end

it "removes ANSI formatting from a suite-hook failure" do
error = nil
allow(lifecycle).to receive(:add_global_error) { |**details| error = details }

run_rspec(<<~SPEC)
RSpec.configuration.reporter.message(
"\e[31mAn error occurred in a `before(:suite)` hook.\e[0m\n" \
"\e[31mRuntimeError: colored failure\e[0m"
)

#{passing_example}
SPEC

expect(error).to include(message: "An error occurred in a `before(:suite)` hook.")
expect(error[:trace]).to include("RuntimeError: colored failure")
expect(error[:trace]).not_to include("\e[")
end

it "ignores an ordinary reporter message" do
run_rspec(<<~SPEC)
RSpec.configuration.reporter.message("ordinary reporter message")

#{passing_example}
SPEC

expect(lifecycle).not_to have_received(:add_global_error)
end

it "ignores a load error" do
run_rspec(<<~SPEC)
raise "spec load failed"
SPEC

expect(lifecycle).not_to have_received(:add_global_error)
end

it "captures the error before globals are written" do
run_with_suite_hooks(<<~SPEC)
RSpec.configure do |config|
config.after(:suite) { raise "after suite failed" }
end
SPEC

expect(lifecycle).to have_received(:add_global_error).once.ordered
expect(lifecycle).to have_received(:write_globals).once.ordered
end
end
3 changes: 2 additions & 1 deletion allure-rspec/spec/unit/formatter_stop_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
SPEC
end

it "creates environment.properties file" do
it "writes run-level files" do
expect(lifecycle).to have_received(:write_environment).once
expect(lifecycle).to have_received(:write_globals).once
end
end
Loading