hypercuke 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ =begin
4
+
5
+ All commands should begin with "cucumber".
6
+ (Assume that the --require business is handled in cucumber.yml.)
7
+
8
+ Some sample HCu commands and their expected outputs:
9
+ $ hcu core # cucumber --tags @core_ok
10
+ $ hcu model # cucumber --tags @model_ok
11
+ $ hcu core wip # cucumber --tags @model_wip --profile wip
12
+ $ hcu core ok # cucumber --tags @core_ok
13
+
14
+ If the user specifies a --profile tag, assume they know what they're doing...
15
+ $ hcu core --profile emperor_penguin # cucumber --tags @core_ok --profile emperor_penguin
16
+ $ hcu core wip --profile emperor_penguin # cucumber --tags @core_wip --profile emperor_penguin
17
+ ...even if they use the "-p" tag instead...
18
+ $ hcu core -p emperor_penguin # cucumber --tags @core_ok --profile emperor_penguin
19
+ $ hcu core wip -p emperor_penguin # cucumber --tags @core_wip --profile emperor_penguin
20
+
21
+ Everything else should just get passed through to Cucumber unmangled.
22
+ $ hcu core --wibble # cucumber --tags @core_ok --wibble
23
+
24
+ Also, the '-h' flag should display HCu help (TBD)
25
+ ( TODO: WRITE THIS EXAMPLE? )
26
+
27
+ =end
28
+
29
+ describe Hypercuke::CLI do
30
+
31
+ def cli_for(hcu_command, *args)
32
+ described_class.new(hcu_command, *args)
33
+ end
34
+
35
+ describe "cucumber command line generation" do
36
+ shared_examples_for "command line builder" do |shared_example_opts|
37
+ let(:cmd_base) { shared_example_opts[:cmd_base] }
38
+
39
+ def expect_command_line(hcu_command, expected_output)
40
+ argv = hcu_command.split(/\s+/)
41
+ actual_output = cli_for(argv).cucumber_command
42
+
43
+ expect( actual_output ).to eq( expected_output ), <<-EOF
44
+ Transforming command '#{hcu_command}':
45
+ expected: #{expected_output.inspect}
46
+ got: #{actual_output.inspect}
47
+ EOF
48
+ end
49
+
50
+ it "ignores the 0th 'hcu' argument in its various forms (does this even happen?)" do
51
+ expect_command_line 'hcu core', "#{cmd_base} --tags @core_ok"
52
+ expect_command_line 'bin/hcu core', "#{cmd_base} --tags @core_ok"
53
+ end
54
+
55
+ it "treats the first argument as a layer name and adds the appropriate --tags flag" do
56
+ expect_command_line 'core', "#{cmd_base} --tags @core_ok"
57
+ expect_command_line 'model', "#{cmd_base} --tags @model_ok"
58
+ end
59
+
60
+ it "barfs if the layer name is not given" do
61
+ expect{ cli_for('hcu').cucumber_command }.to raise_error( "Layer name is required" )
62
+ expect{ cli_for('').cucumber_command }.to raise_error( "Layer name is required" )
63
+ end
64
+
65
+ it "treats the second argument as a mode (assuming it doesn't start with a dash)" do
66
+ expect_command_line 'core ok', "#{cmd_base} --tags @core_ok"
67
+ end
68
+
69
+ it "adds '--profile wip' when the mode is 'wip'" do
70
+ expect_command_line 'core wip', "#{cmd_base} --tags @core_wip --profile wip"
71
+ end
72
+
73
+ it "ignores most other arguments and just hands them off to Cucumber" do
74
+ expect_command_line 'core --wibble', "#{cmd_base} --tags @core_ok --wibble"
75
+ expect_command_line 'core ok --wibble', "#{cmd_base} --tags @core_ok --wibble"
76
+ end
77
+
78
+ it "doesn't override a profile if the user explicitly specifies one (using either -p or --profile)" do
79
+ expect_command_line 'core --dingbat --profile emperor_penguin', "#{cmd_base} --tags @core_ok --profile emperor_penguin --dingbat"
80
+ expect_command_line 'core --dingbat -p emperor_penguin', "#{cmd_base} --tags @core_ok --profile emperor_penguin --dingbat"
81
+ end
82
+
83
+ it "doesn't override a user-specified profile, even in wip mode when it would normally use the wip profile" do
84
+ expect_command_line 'core wip --profile emperor_penguin', "#{cmd_base} --tags @core_wip --profile emperor_penguin"
85
+ expect_command_line 'core wip -p emperor_penguin', "#{cmd_base} --tags @core_wip --profile emperor_penguin"
86
+ end
87
+ end
88
+
89
+ context "when Bundler IS NOT present" do
90
+ before do
91
+ allow( described_class ).to receive(:bundler_present?).and_return(false)
92
+ end
93
+
94
+ it_behaves_like "command line builder", cmd_base: 'cucumber'
95
+ end
96
+
97
+ context "when Bundler IS present" do
98
+ before do
99
+ allow( described_class ).to receive(:bundler_present?).and_return(true)
100
+ end
101
+
102
+ it_behaves_like "command line builder", cmd_base: 'bundle exec cucumber'
103
+ end
104
+ end
105
+
106
+ describe "layer_name" do
107
+ it "matches the layer argument to 'hcu'" do
108
+ expect( cli_for('hcu core') .layer_name ).to eq( 'core' )
109
+ expect( cli_for('hcu model').layer_name ).to eq( 'model' )
110
+ expect( cli_for('hcu ui') .layer_name ).to eq( 'ui' )
111
+ end
112
+ end
113
+
114
+ describe "#run!" do
115
+ let(:cli) { cli_for('fudge_ripple', output, environment, kernel) }
116
+ let(:output) { double('output', puts: nil) }
117
+ let(:kernel) { double('Kernel', exec: nil) }
118
+ let(:environment) { { 'foo' => 'bar' } }
119
+
120
+ it "prints the generated Cucumber command to output" do
121
+ expect(output).to receive(:puts).with(cli.cucumber_command_for_display)
122
+ cli.run!
123
+ end
124
+
125
+ it "uses exec to run the Cucumber command, passing in the layer name to the environment" do
126
+ layer = Hypercuke::LAYER_NAME_ENV_VAR
127
+ expected_env = {
128
+ 'foo' => 'bar',
129
+ layer => 'fudge_ripple'
130
+ }
131
+ expect(kernel).to receive(:exec).with(expected_env, cli.cucumber_command)
132
+ cli.run!
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hypercuke::Context do
4
+ subject { Hypercuke::Context.new }
5
+
6
+ it "allows square-bracket setting and getting, same as a Hash" do
7
+ expect( subject[:wibble] ).to be nil
8
+ subject[:wibble] = 'wibble'
9
+ expect( subject[:wibble] ).to eq( 'wibble' )
10
+ end
11
+
12
+ let(:ransom_1960s) { "one MILLION dollars" }
13
+ let(:ransom_1990s) { "one hundred billion dollars" }
14
+
15
+ it "behaves like a Hash with regard to #fetch" do
16
+ subject[:demand] = ransom_1960s
17
+ expect( subject.fetch(:demand) ).to eq( ransom_1960s )
18
+
19
+ expect{ subject.fetch(:updated_demand) }
20
+ .to raise_error( KeyError )
21
+ result = subject.fetch(:updated_demand) { ransom_1990s }
22
+ expect( result ).to eq( ransom_1990s )
23
+ end
24
+
25
+ describe "#fetch_or_default" do
26
+ before do
27
+ subject[:demand] = ransom_1960s
28
+ end
29
+
30
+ describe "when asked for a key that exists" do
31
+ it "returns the value without calling the block" do
32
+ result = subject.fetch_or_default(:demand) { fail "this block should not be called" }
33
+ expect( result ).to eq( ransom_1960s )
34
+ end
35
+ end
36
+
37
+ describe "when asked for a key that does not exist" do
38
+ it "calls the block, sets the key, and returns the value" do
39
+ result = subject.fetch_or_default(:updated_demand) { ransom_1990s }
40
+ expect( result ).to eq( ransom_1990s )
41
+ result = subject.fetch_or_default(:updated_demand) { fail "this block should not be called" }
42
+ expect( result ).to eq( ransom_1990s )
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hypercuke do
4
+ around do |example|
5
+ layer = Hypercuke.current_layer
6
+ begin
7
+ example.run
8
+ ensure
9
+ Hypercuke.current_layer = layer
10
+ end
11
+ end
12
+
13
+ describe "current layer" do
14
+ it "can be set" do
15
+ Hypercuke.current_layer = 'wibble'
16
+ expect( Hypercuke.current_layer ).to eq( :wibble )
17
+ end
18
+
19
+ it "defaults to an environment variable" do
20
+ expect( Hypercuke.current_layer ).to be nil
21
+ begin
22
+ ENV['HYPERCUKE_LAYER'] = 'flapjack_adjustment_station'
23
+ expect( Hypercuke.current_layer ).to eq( :flapjack_adjustment_station )
24
+ ensure
25
+ ENV['HYPERCUKE_LAYER'] = nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ require_relative '../lib/hypercuke'
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |c|
5
+ c.syntax = :expect # I hate Kernel#should with the fire of a thousand suns
6
+ end
7
+
8
+ config.after do
9
+ Hypercuke.reset!
10
+ end
11
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ describe "step adapter definition API" do
4
+ subject { Hypercuke }
5
+
6
+ # NAMING THINGS
7
+ #
8
+ # Top-level object available as #driver: Step Driver
9
+ # Area of domain: Topic
10
+ # Layer of application: Layer
11
+ # Intersection of the two: StepAdapter
12
+ # Name of generated step adapter: Hypercuke::StepAdapters::<Topic>::<Layer>
13
+ #
14
+ # L T O P I C S
15
+ # A | | Cheese | Wine | Bread |
16
+ # Y | Core | SA* | SA* | SA* |
17
+ # E | Model | SA* | SA* | SA* |
18
+ # R | UI | SA* | SA* | SA* |
19
+ # S
20
+ # * SA = StepAdapter
21
+
22
+ context "when empty" do
23
+ it "has no layers when it starts" do
24
+ expect( subject.layers ).to be_empty
25
+ end
26
+ it "has no topics when it starts" do
27
+ expect( subject.topics ).to be_empty
28
+ end
29
+ it "has no step adapters when it starts" do
30
+ expect( subject::StepAdapters.constants ).to be_empty
31
+ end
32
+ end
33
+
34
+ context "with a single adapter in a single layer" do
35
+ before do
36
+ subject.topic :cheese do
37
+ layer :core do
38
+ def select_variety
39
+ "It's hard to go wrong with cheddar."
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ let(:sd_class) { subject.step_adapter_class( :cheese, :core ) }
46
+
47
+ it "defines a cheese topic" do
48
+ expect( subject.topic_names ).to eq( [:cheese] )
49
+ end
50
+
51
+ it "defines a core layer" do
52
+ expect( subject.layer_names ).to eq( [:core] )
53
+ end
54
+
55
+ it "defines a step adapter class for the cheese/core combo" do
56
+ expect( sd_class ).to_not be_nil
57
+ expect( sd_class.superclass ).to be( Hypercuke::StepAdapter )
58
+ expect( sd_class.instance_methods(false) ).to eq( [ :select_variety ] )
59
+ end
60
+
61
+ it "allows the step adapter to be reopened" do
62
+ subject.topic :cheese do
63
+ layer :core do
64
+ def pair_with(wine)
65
+ "I probably should pick an example I know more about"
66
+ end
67
+ end
68
+ end
69
+
70
+ expect( sd_class.instance_methods(false).sort ).to eq( [ :select_variety, :pair_with ].sort )
71
+ end
72
+
73
+ it "gives the step adapter class a reasonable name" do
74
+ expect( sd_class.name ).to eq( "Hypercuke::StepAdapters::Cheese::Core" )
75
+ end
76
+ end
77
+
78
+ context "with two topics and three layers" do
79
+ before do
80
+ subject.topic :wibble do
81
+ layer :spam do ; def wibble ; "wibble spam" ; end ; end
82
+ layer :eggs do ; def wibble ; "wibble eggs" ; end ; end
83
+ layer :bacon do ; def wibble ; "wibble bacon" ; end ; end
84
+ end
85
+ subject.topic :yak do
86
+ layer :spam do ; def shave ; "s*MOO*th spam" ; end ; end
87
+ layer :eggs do ; def shave ; "s*MOO*th eggs" ; end ; end
88
+ end
89
+ end
90
+
91
+ it "defines topic names" do
92
+ expect( subject.topic_names ).to eq( [:wibble, :yak] )
93
+ end
94
+
95
+ it "defines layer names" do
96
+ expect( subject.layer_names ).to eq( [:spam, :eggs, :bacon] )
97
+ end
98
+
99
+ it "defines step adapter classes with reasonable class names" do
100
+ expect( Hypercuke::StepAdapters::Wibble::Spam .superclass ).to be( Hypercuke::StepAdapter )
101
+ expect( Hypercuke::StepAdapters::Wibble::Eggs .superclass ).to be( Hypercuke::StepAdapter )
102
+ expect( Hypercuke::StepAdapters::Wibble::Bacon.superclass ).to be( Hypercuke::StepAdapter )
103
+
104
+ expect( Hypercuke::StepAdapters::Yak::Spam .superclass ).to be( Hypercuke::StepAdapter )
105
+ expect( Hypercuke::StepAdapters::Yak::Eggs .superclass ).to be( Hypercuke::StepAdapter )
106
+ expect{ Hypercuke::StepAdapters::Yak::Bacon }.to raise_error(NameError)
107
+ end
108
+ end
109
+
110
+ context "naming conflicts" do
111
+ it "works when a topic name resolves to something outside the Hypercuke namespace" do
112
+ Hypercuke.topic :array do
113
+ layer :core do
114
+ def metasyntactic_variables ; %w[ foo bar yak shed ] ; end
115
+ end
116
+ end
117
+
118
+ expect( Hypercuke::StepAdapters::Array::Core .superclass ).to be( Hypercuke::StepAdapter )
119
+ end
120
+
121
+ it "works when a layer name resolves to something outside the Hypercuke namespace" do
122
+ Hypercuke.topic :absurdity do
123
+ layer :array do
124
+ def metasyntactic_variables ; %w[ foo bar yak shed ] ; end
125
+ end
126
+ end
127
+
128
+ expect( Hypercuke::StepAdapters::Absurdity::Array .superclass ).to be( Hypercuke::StepAdapter )
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hypercuke::StepDriver do
4
+ before do
5
+ Hypercuke.topic :wibble do
6
+ layer :spam do ; def wibble ; "wibble spam" ; end ; end
7
+ layer :eggs do ; def wibble ; "wibble eggs" ; end ; end
8
+ layer :bacon do ; def wibble ; "wibble bacon" ; end ; end
9
+ end
10
+ Hypercuke.topic :yak do
11
+ layer :spam do ; def shave ; "s*MOO*th spam" ; end ; end
12
+ layer :eggs do ; def shave ; "s*MOO*th eggs" ; end ; end
13
+ # Is yak bacon even a thing? ...wait, don't answer that.
14
+ end
15
+ Hypercuke.current_layer = :spam
16
+ end
17
+
18
+
19
+ subject(:step_driver) { described_class.new }
20
+
21
+ describe "step adapter retrieval" do
22
+
23
+ it "asks Hypercuke for the current layer" do
24
+ Hypercuke.current_layer = :bacon
25
+ expect( step_driver.layer_name ).to eq( :bacon )
26
+ end
27
+
28
+ it "can return a step adapter for each layer" do
29
+ expect( step_driver.wibble.wibble ).to eq( "wibble spam" )
30
+ expect( step_driver.yak.shave ).to eq( "s*MOO*th spam" )
31
+ end
32
+
33
+ it "explodes when asked for a topic that isn't defined" do
34
+ expect{ step_driver.heffalump }.to raise_error( Hypercuke::TopicNotDefinedError, "Topic not defined: heffalump" )
35
+ end
36
+
37
+ it "explodes when asked for a layer that isn't defined" do
38
+ Hypercuke.current_layer = :booOOoogus
39
+ expect{ step_driver.wibble }.to raise_error( Hypercuke::LayerNotDefinedError, "Layer not defined: booOOoogus" )
40
+ end
41
+
42
+ it "explodes when asked for a step adapter that isn't defined at the current layer" do
43
+ Hypercuke.current_layer = :bacon
44
+ expect( step_driver.wibble ).to be_kind_of( Hypercuke::StepAdapters::Wibble::Bacon )
45
+ expect{ step_driver.yak }.to raise_error( Hypercuke::StepAdapterNotDefinedError, "Step adapter not defined: 'Hypercuke::StepAdapters::Yak::Bacon'" )
46
+ end
47
+
48
+ it "can return a step adapter for each layer (even when the layer is changed)" do
49
+ [ :spam, :eggs, :bacon ].each do |layer|
50
+ Hypercuke.current_layer = layer
51
+ expect( step_driver.wibble.wibble ).to eq( "wibble #{layer}" )
52
+ expect( step_driver.yak.shave ).to eq( "s*MOO*th #{layer}" ) unless :bacon == layer
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ describe "context management" do
59
+ let(:sd_context) { step_driver.send(:__context__) }
60
+
61
+ specify "step driver has a context" do
62
+ expect( sd_context ).to be_kind_of( Hypercuke::Context )
63
+ end
64
+
65
+ specify "step driver passes its context to any step adapter it creates" do
66
+ step_adapter_context = step_driver.wibble.__send__(:context)
67
+ expect( step_adapter_context ).to be( sd_context )
68
+ end
69
+ end
70
+
71
+ describe "inter-adapter dependencies" do
72
+ before do
73
+ Hypercuke.reset!
74
+ Hypercuke.topic :bikeshed do
75
+ layer :distraction do
76
+ def color ; "blue" ; end
77
+ end
78
+ layer :planet_ruby do
79
+ def color ; "ruby red" ; end # HINT: the herring is red, too...
80
+ end
81
+ end
82
+ Hypercuke.topic :yak do
83
+ layer :distraction do
84
+ def bikeshed_color
85
+ step_driver.bikeshed.color
86
+ end
87
+ end
88
+ end
89
+ Hypercuke.current_layer = :distraction
90
+ end
91
+
92
+ let(:step_driver) { described_class.new }
93
+
94
+ specify "step adapters within the same layer may access one another through the step driver" do
95
+ expect( step_driver.yak.bikeshed_color ).to eq( "blue" )
96
+ end
97
+ end
98
+
99
+ end