Skip to content
Open
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
19 changes: 11 additions & 8 deletions lib/cucumber/core/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ def done
private

def create_test_case(pickle)
uri = pickle.uri
test_steps = pickle.steps.map { |step| create_test_step(step, uri) }
location = location_from_pickle(pickle)
parent_locations = parent_locations_from_pickle(pickle)
tags = tags_from_pickle(pickle, uri)
test_case = Test::Case.new(id_generator.new_id, pickle.name, test_steps, location, parent_locations, tags, pickle.language)
test_case = Test::Case.new(
id: id_generator.new_id,
name: pickle.name,
test_steps: pickle.steps.map { |step| create_test_step(step, pickle.uri) },
location: location_from_pickle(pickle),
parent_locations: parent_locations_from_pickle(pickle),
tags: tags_from_pickle(pickle),
language: pickle.language
)
@event_bus&.test_case_created(test_case, pickle)
test_case
end
Expand Down Expand Up @@ -80,9 +83,9 @@ def location_from_pickle_step(pickle_step, uri)
Test::Location.new(uri, lines.sort.reverse)
end

def tags_from_pickle(pickle, uri)
def tags_from_pickle(pickle)
pickle.tags.map do |tag|
location = Test::Location.new(uri, source_line(tag.ast_node_id))
location = Test::Location.new(pickle.uri, source_line(tag.ast_node_id))
Test::Tag.new(location, tag.name)
end
end
Expand Down
21 changes: 4 additions & 17 deletions lib/cucumber/core/test/case.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
# frozen_string_literal: true

require 'cucumber/core/value'
require 'cucumber/core/test/result'
require 'cucumber/tag_expressions'

module Cucumber
module Core
module Test
class Case
attr_reader :id, :name, :test_steps, :location, :parent_locations, :tags, :language, :around_hooks

def initialize(id, name, test_steps, location, parent_locations, tags, language, around_hooks = [])
raise ArgumentError.new("test_steps should be an Array but is a #{test_steps.class}") unless test_steps.is_a?(Array)
@id = id
@name = name
@test_steps = test_steps
@location = location
@parent_locations = parent_locations
@tags = tags
@language = language
@around_hooks = around_hooks
end

Case = Value.define(:id, :name, :test_steps, :location, :parent_locations, :tags, :language, around_hooks: []) do
def step_count
test_steps.count
end
Expand All @@ -37,11 +24,11 @@ def describe_to(visitor, *args)
end

def with_steps(test_steps)
self.class.new(id, name, test_steps, location, parent_locations, tags, language, around_hooks)
with(test_steps: test_steps)
end

def with_around_hooks(around_hooks)
self.class.new(id, name, test_steps, location, parent_locations, tags, language, around_hooks)
with(around_hooks: around_hooks)
end

def match_tags?(*expressions)
Expand Down
95 changes: 95 additions & 0 deletions lib/cucumber/core/value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

module Cucumber
module Core
class Value
class << self
def define(*args, **kwargs, &block)
Builder.new(args, kwargs, block).build
end
end

Builder = Struct.new(:args, :kwargs, :block) do
def build
validate_definition!

klass = ::Class.new(Value)

klass.instance_variable_set(:@members, members)

members[:all].each do |arg|
klass.define_method(arg) do
@attributes[arg]
end
end

klass.class_eval(&block) if block

klass
end

private

def validate_definition!
raise ArgumentError if args.any?(/=/)

dup_arg = members[:all].detect { |a| members[:all].count(a) > 1 }
raise ArgumentError, "duplicate member #{dup_arg}" if dup_arg
end

def members
{
all: args + kwargs.keys,
required: args,
optional: kwargs
}
end
end

def members
self.class.instance_variable_get :@members
end

def initialize(**kwargs)
validate_kwargs!(kwargs)

@attributes = {}
members[:required].each do |arg|
@attributes[arg] = kwargs.fetch(arg)
end
members[:optional].each do |arg, default|
@attributes[arg] = kwargs.fetch(arg, default)
end

freeze
end

def inspect
attribute_markers = @attributes.map do |key, value|
"#{key}=#{value}"
end.join(', ')

display = ['value', self.class.name, attribute_markers].compact.join(' ')

"#<#{display}>"
end
alias to_s inspect

def with(**kwargs)
return self if kwargs.empty?

self.class.new(**@attributes.merge(kwargs))
end

private

def validate_kwargs!(kwargs)
extras = kwargs.keys - members[:all]
raise ArgumentError, "unknown arguments #{extras.join(', ')}" if extras.any?

missing = members[:required] - kwargs.keys
raise ArgumentError, "missing arguments #{missing.map(&:inspect).join(', ')}" if missing.any?
end
end
end
end
18 changes: 13 additions & 5 deletions spec/cucumber/core/test/case_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@
include Cucumber::Core
include Cucumber::Core::Gherkin::Writer

let(:id) { double }
let(:name) { double }
let(:test_steps) { [double, double] }
let(:location) { double }
let(:parent_locations) { double }
let(:tags) { double }
let(:language) { double }
let(:test_case) { described_class.new(id, name, test_steps, location, parent_locations, tags, language) }
let(:test_steps) { [double, double] }
let(:test_case) do
described_class.new(
id: double,
name: name,
test_steps: test_steps,
location: location,
parent_locations: double,
tags: tags,
language: language
)
end

describe '#describe_to' do
let(:visitor) { double }
Expand All @@ -42,7 +50,7 @@
expect(first_hook).to receive(:describe_to).ordered.and_yield
expect(second_hook).to receive(:describe_to).ordered.and_yield
around_hooks = [first_hook, second_hook]
described_class.new(id, name, [], location, parent_locations, tags, language, around_hooks).describe_to(visitor, double)
test_case.with(test_steps: [], around_hooks: around_hooks).describe_to(visitor, double)
end
end

Expand Down
53 changes: 29 additions & 24 deletions spec/cucumber/core/test/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
require 'cucumber/core/test/duration_matcher'

describe Cucumber::Core::Test::Runner do
let(:test_case) { Cucumber::Core::Test::Case.new(double, double, test_steps, double, double, double, double) }
let(:test_steps) { double }
let(:test_case) { Cucumber::Core::Test::Case.new(id: double, name: double, test_steps: test_steps, location: double, parent_locations: double, tags: double, language: double) }
let(:text) { double }
let(:location) { double }
let(:runner) { described_class.new(event_bus) }
let(:event_bus) { double.as_null_object }
let(:passing) { Cucumber::Core::Test::Step.new(double, text, double, double).with_action { :no_op } }
Expand Down Expand Up @@ -221,26 +223,29 @@

context 'with multiple test cases' do
context 'when the first test case fails' do
let(:first_test_case) { Cucumber::Core::Test::Case.new(double, double, [failing], double, double, double, double) }
let(:last_test_case) { Cucumber::Core::Test::Case.new(double, double, [passing], double, double, double, double) }
let(:test_cases) { [first_test_case, last_test_case] }
let(:first_test_case) { test_case.with(test_steps: [failing]) }
let(:last_test_case) { test_case.with(test_steps: [passing]) }

it 'reports the results correctly for the following test case' do
expect(event_bus).to receive(:test_case_finished) { |reported_test_case, result|
expect(result).to be_failed if reported_test_case.equal?(first_test_case)
expect(result).to be_passed if reported_test_case.equal?(last_test_case)
}.twice

test_cases.each { |test_case| test_case.describe_to(runner) }
[first_test_case, last_test_case].each do |test_case|
test_case.describe_to(runner)
end
end
end
end

context 'when passing the latest result to a mapping' do
let(:hook_mapping) { Cucumber::Core::Test::UnskippableAction.new { |last_result| @result_spy = last_result } }
let(:after_hook) { Cucumber::Core::Test::HookStep.new(double, text, double, hook_mapping) }
let(:failing_step) { Cucumber::Core::Test::Step.new(double, text, double).with_action { fail } }
let(:test_steps) { [failing_step, after_hook] }
let(:test_steps) do
hook_mapping = Cucumber::Core::Test::UnskippableAction.new { |last_result| @result_spy = last_result }
after_hook = Cucumber::Core::Test::HookStep.new(double, text, double, hook_mapping)
failing_step = Cucumber::Core::Test::Step.new(double, text, double).with_action { fail }
[failing_step, after_hook]
end

it 'passes a Failed result when the scenario is failing' do
test_case.describe_to(runner)
Expand All @@ -254,47 +259,47 @@

it "passes normally when around hooks don't fail" do
around_hook = Cucumber::Core::Test::AroundHook.new { |block| block.call }
test_case = Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double, [around_hook])
expect(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result|
passing_test_case = test_case.with(test_steps: [passing_step], around_hooks: [around_hook])
expect(event_bus).to receive(:test_case_finished).with(passing_test_case, anything) do |_reported_test_case, result|
expect(result).to be_passed
end
test_case.describe_to runner
passing_test_case.describe_to runner
end

it 'gets a failed result if the Around hook fails before the test case is run' do
around_hook = Cucumber::Core::Test::AroundHook.new { |_block| raise exception }
test_case = Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double, [around_hook])
expect(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result|
failing_test_case = test_case.with(test_steps: [passing_step], around_hooks: [around_hook])
expect(event_bus).to receive(:test_case_finished).with(failing_test_case, anything) do |_reported_test_case, result|
expect(result).to be_failed
expect(result.exception).to eq exception
end
test_case.describe_to runner
failing_test_case.describe_to runner
end

it 'gets a failed result if the Around hook fails after the test case is run' do
around_hook = Cucumber::Core::Test::AroundHook.new { |block| block.call; raise exception }
test_case = Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double, [around_hook])
expect(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result|
failing_test_case = test_case.with(test_steps: [passing_step], around_hooks: [around_hook])
expect(event_bus).to receive(:test_case_finished).with(failing_test_case, anything) do |_reported_test_case, result|
expect(result).to be_failed
expect(result.exception).to eq exception
end
test_case.describe_to runner
failing_test_case.describe_to runner
end

it 'fails when a step fails if the around hook works' do
around_hook = Cucumber::Core::Test::AroundHook.new { |block| block.call }
failing_step = Cucumber::Core::Test::Step.new(double, text, double, double).with_action { raise exception }
test_case = Cucumber::Core::Test::Case.new(double, double, [failing_step], double, double, double, double, [around_hook])
expect(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result|
failing_step = Cucumber::Core::Test::Step.new(double, text, location, location).with_action { raise exception }
failing_test_case = test_case.with(test_steps: [failing_step], around_hooks: [around_hook])
expect(event_bus).to receive(:test_case_finished).with(failing_test_case, anything) do |_reported_test_case, result|
expect(result).to be_failed
expect(result.exception).to eq exception
end
test_case.describe_to runner
failing_test_case.describe_to runner
end

it 'sends after_test_step for a step interrupted by (a timeout in) the around hook' do
around_hook = Cucumber::Core::Test::AroundHook.new { |block| block.call; raise exception }
test_case = Cucumber::Core::Test::Case.new(double, double, [], double, double, double, double, [around_hook])
failing_test_case = test_case.with(test_steps: [], around_hooks: [around_hook])
allow(runner).to receive(:running_test_step).and_return(passing_step)
expect(event_bus).to receive(:test_step_finished).with(passing_step, anything) do |_reported_test_case, result|
expect(result).to be_failed
Expand All @@ -304,7 +309,7 @@
expect(result).to be_failed
expect(result.exception).to eq(exception)
end
test_case.describe_to(runner)
failing_test_case.describe_to(runner)
end
end
end