beaker 1.9.1 → 1.10.0
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.
- checksums.yaml +8 -8
- data/.simplecov +1 -0
- data/beaker.gemspec +2 -0
- data/lib/beaker/dsl/helpers.rb +15 -6
- data/lib/beaker/dsl/install_utils.rb +1 -1
- data/lib/beaker/host.rb +21 -2
- data/lib/beaker/host/aix/exec.rb +7 -0
- data/lib/beaker/host/unix.rb +4 -0
- data/lib/beaker/host/unix/exec.rb +8 -0
- data/lib/beaker/host/windows.rb +2 -0
- data/lib/beaker/host/windows/exec.rb +11 -0
- data/lib/beaker/host_prebuilt_steps.rb +7 -6
- data/lib/beaker/hypervisor.rb +6 -2
- data/lib/beaker/hypervisor/aws_sdk.rb +389 -0
- data/lib/beaker/hypervisor/blimper.rb +8 -23
- data/lib/beaker/hypervisor/ec2_helper.rb +28 -0
- data/lib/beaker/hypervisor/google_compute.rb +10 -8
- data/lib/beaker/hypervisor/google_compute_helper.rb +1 -1
- data/lib/beaker/hypervisor/vagrant.rb +8 -0
- data/lib/beaker/options/command_line_parser.rb +6 -0
- data/lib/beaker/options/parser.rb +1 -1
- data/lib/beaker/options/presets.rb +3 -0
- data/lib/beaker/ssh_connection.rb +2 -3
- data/lib/beaker/test_case.rb +2 -2
- data/lib/beaker/version.rb +1 -1
- data/spec/beaker/dsl/helpers_spec.rb +27 -5
- data/spec/beaker/dsl/install_utils_spec.rb +2 -2
- data/spec/beaker/host_prebuilt_steps_spec.rb +2 -2
- data/spec/beaker/hypervisor/aws_sdk_spec.rb +81 -0
- data/spec/beaker/hypervisor/blimper_spec.rb +0 -35
- data/spec/beaker/hypervisor/ec2_helper_spec.rb +23 -0
- data/spec/beaker/hypervisor/vagrant_spec.rb +22 -1
- data/spec/beaker/options/parser_spec.rb +4 -1
- data/spec/beaker/test_case_spec.rb +85 -1
- data/spec/mocks.rb +4 -0
- metadata +37 -2
@@ -1,29 +1,10 @@
|
|
1
1
|
require 'blimpy'
|
2
2
|
require 'yaml' unless defined?(YAML)
|
3
|
+
require 'beaker/hypervisor/ec2_helper'
|
3
4
|
|
4
|
-
module Beaker
|
5
|
+
module Beaker
|
5
6
|
class Blimper < Beaker::Hypervisor
|
6
7
|
|
7
|
-
def amiports(host)
|
8
|
-
roles = host['roles']
|
9
|
-
ports = [22]
|
10
|
-
|
11
|
-
if roles.include? 'database'
|
12
|
-
ports << 8080
|
13
|
-
ports << 8081
|
14
|
-
end
|
15
|
-
|
16
|
-
if roles.include? 'master'
|
17
|
-
ports << 8140
|
18
|
-
end
|
19
|
-
|
20
|
-
if roles.include? 'dashboard'
|
21
|
-
ports << 443
|
22
|
-
end
|
23
|
-
|
24
|
-
ports
|
25
|
-
end
|
26
|
-
|
27
8
|
def initialize(blimpy_hosts, options)
|
28
9
|
@options = options
|
29
10
|
@logger = options[:logger]
|
@@ -46,7 +27,7 @@ module Beaker
|
|
46
27
|
ami = ami_spec[amitype]
|
47
28
|
fleet.add(:aws) do |ship|
|
48
29
|
ship.name = host.name
|
49
|
-
ship.ports = amiports(host)
|
30
|
+
ship.ports = Beaker::EC2Helper.amiports(host['roles'])
|
50
31
|
ship.image_id = ami[:image][image_type.to_sym]
|
51
32
|
if not ship.image_id
|
52
33
|
raise "No image_id found for host #{ship.name} (#{amitype}:#{amisize}) using snapshot/image_type #{image_type}"
|
@@ -54,7 +35,11 @@ module Beaker
|
|
54
35
|
ship.flavor = amisize
|
55
36
|
ship.region = ami[:region]
|
56
37
|
ship.username = 'root'
|
57
|
-
ship.tags = {
|
38
|
+
ship.tags = {
|
39
|
+
:department => @options[:department],
|
40
|
+
:project => @options[:project],
|
41
|
+
:jenkins_build_url => @options[:jenkins_build_url],
|
42
|
+
}
|
58
43
|
end
|
59
44
|
@logger.debug "Added #{host.name} (#{amitype}:#{amisize}) using snapshot/image_type #{image_type} to blimpy fleet"
|
60
45
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Beaker
|
2
|
+
class EC2Helper
|
3
|
+
# Return a list of open ports for testing based on a hosts role
|
4
|
+
#
|
5
|
+
# @todo horribly hard-coded
|
6
|
+
# @param [Array<String>] an array of roles
|
7
|
+
# @return [Array<Number>] array of port numbers
|
8
|
+
# @api private
|
9
|
+
def self.amiports(roles)
|
10
|
+
ports = [22]
|
11
|
+
|
12
|
+
if roles.include? 'database'
|
13
|
+
ports << 8080
|
14
|
+
ports << 8081
|
15
|
+
end
|
16
|
+
|
17
|
+
if roles.include? 'master'
|
18
|
+
ports << 8140
|
19
|
+
end
|
20
|
+
|
21
|
+
if roles.include? 'dashboard'
|
22
|
+
ports << 443
|
23
|
+
end
|
24
|
+
|
25
|
+
ports
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -45,7 +45,8 @@ module Beaker
|
|
45
45
|
|
46
46
|
|
47
47
|
@hosts.each do |host|
|
48
|
-
|
48
|
+
gplatform = Platform.new(host[:image] || host[:platform])
|
49
|
+
img = @gce_helper.get_latest_image(gplatform, start, attempts)
|
49
50
|
host['diskname'] = generate_host_name
|
50
51
|
disk = @gce_helper.create_disk(host['diskname'], img, start, attempts)
|
51
52
|
@logger.debug("Created Google Compute disk for #{host.name}: #{host['diskname']}")
|
@@ -57,8 +58,9 @@ module Beaker
|
|
57
58
|
@logger.debug("Created Google Compute instance for #{host.name}: #{host['vmhostname']}")
|
58
59
|
#add metadata to instance
|
59
60
|
@gce_helper.setMetadata_on_instance(host['vmhostname'], instance['metadata']['fingerprint'],
|
60
|
-
[ {:key => :department, :value => @options[:department]},
|
61
|
-
{:key => :project, :value => @options[:project]}
|
61
|
+
[ {:key => :department, :value => @options[:department]},
|
62
|
+
{:key => :project, :value => @options[:project]},
|
63
|
+
{:key => :jenkins_build_url, :value => @options[:jenkins_build_url]} ],
|
62
64
|
start, attempts)
|
63
65
|
@logger.debug("Added tags to Google Compute instance #{host.name}: #{host['vmhostname']}")
|
64
66
|
|
@@ -87,7 +89,7 @@ module Beaker
|
|
87
89
|
attempts = @options[:timeout].to_i / SLEEPWAIT
|
88
90
|
start = Time.now
|
89
91
|
|
90
|
-
@gce_helper.delete_firewall(@firewall, start, attempts)
|
92
|
+
@gce_helper.delete_firewall(@firewall, start, attempts)
|
91
93
|
|
92
94
|
@hosts.each do |host|
|
93
95
|
@gce_helper.delete_instance(host['vmhostname'], start, attempts)
|
@@ -97,14 +99,14 @@ module Beaker
|
|
97
99
|
end
|
98
100
|
|
99
101
|
end
|
100
|
-
|
101
|
-
#Shutdown and destroy Google Compute instances (including their associated disks and firewall rules)
|
102
|
+
|
103
|
+
#Shutdown and destroy Google Compute instances (including their associated disks and firewall rules)
|
102
104
|
#that have been alive longer than ZOMBIE hours.
|
103
105
|
def kill_zombies(max_age = ZOMBIE)
|
104
106
|
now = start = Time.now
|
105
107
|
attempts = @options[:timeout].to_i / SLEEPWAIT
|
106
108
|
|
107
|
-
#get rid of old instances
|
109
|
+
#get rid of old instances
|
108
110
|
instances = @gce_helper.list_instances(start, attempts)
|
109
111
|
if instances
|
110
112
|
instances.each do |instance|
|
@@ -116,7 +118,7 @@ module Beaker
|
|
116
118
|
@gce_helper.delete_instance( instance['name'], start, attempts )
|
117
119
|
end
|
118
120
|
end
|
119
|
-
else
|
121
|
+
else
|
120
122
|
@logger.debug("No zombie instances found")
|
121
123
|
end
|
122
124
|
#get rid of old disks
|
@@ -477,7 +477,7 @@ module Beaker
|
|
477
477
|
{ :api_method => @compute.firewalls.insert,
|
478
478
|
:parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
|
479
479
|
:body_object => { 'name' => name,
|
480
|
-
'allowed'=> [ { 'IPProtocol' => 'tcp', "ports" => [ '443', '8140', '61613' ]} ],
|
480
|
+
'allowed'=> [ { 'IPProtocol' => 'tcp', "ports" => [ '443', '8140', '61613', '8080', '8081' ]} ],
|
481
481
|
'network'=> network,
|
482
482
|
'sourceRanges' => [ "0.0.0.0/0" ] } }
|
483
483
|
end
|
@@ -30,6 +30,11 @@ module Beaker
|
|
30
30
|
v_file << " v.vm.box_url = '#{host['box_url']}'\n" unless host['box_url'].nil?
|
31
31
|
v_file << " v.vm.base_mac = '#{randmac}'\n"
|
32
32
|
v_file << " v.vm.network :private_network, ip: \"#{host['ip'].to_s}\", :netmask => \"#{host['netmask'] ||= "255.255.0.0"}\"\n"
|
33
|
+
if /windows/i.match(host['platform'])
|
34
|
+
v_file << " v.vm.network :forwarded_port, guest: 3389, host: 3389\n"
|
35
|
+
v_file << " v.vm.network :forwarded_port, guest: 5985, host: 5985, id: 'winrm', auto_correct: true\n"
|
36
|
+
v_file << " v.vm.guest = :windows"
|
37
|
+
end
|
33
38
|
v_file << " end\n"
|
34
39
|
@logger.debug "created Vagrantfile for VagrantHost #{host.name}"
|
35
40
|
end
|
@@ -92,6 +97,9 @@ module Beaker
|
|
92
97
|
end
|
93
98
|
|
94
99
|
def provision
|
100
|
+
if !@options[:provision] and !File.file?(@vagrant_file)
|
101
|
+
raise "Beaker is configured with provision = false but no vagrant file was found at #{@vagrant_file}. You need to enable provision"
|
102
|
+
end
|
95
103
|
if @options[:provision]
|
96
104
|
#setting up new vagrant hosts
|
97
105
|
#make sure that any old boxes are dead dead dead
|
@@ -170,6 +170,12 @@ module Beaker
|
|
170
170
|
@cmd_options[:add_el_extras] = true
|
171
171
|
end
|
172
172
|
|
173
|
+
opts.on '--[no-]validate',
|
174
|
+
'Validate that SUTs are correctly configured before running tests',
|
175
|
+
'(default: true)' do |bool|
|
176
|
+
@cmd_options[:validate] = bool
|
177
|
+
end
|
178
|
+
|
173
179
|
opts.on('--version', 'Report currently running version of beaker' ) do
|
174
180
|
@cmd_options[:version] = true
|
175
181
|
end
|
@@ -78,7 +78,7 @@ module Beaker
|
|
78
78
|
if discover_files.empty?
|
79
79
|
parser_error "empty directory used as an option (#{root})!"
|
80
80
|
end
|
81
|
-
files += discover_files.
|
81
|
+
files += discover_files.sort_by {|file| [file.count("/"), file]}
|
82
82
|
else #not a file, not a directory, not nothin'
|
83
83
|
parser_error "#{root} used as a file option but is not a file or directory!"
|
84
84
|
end
|
@@ -23,6 +23,7 @@ module Beaker
|
|
23
23
|
:pe_ver => ENV['pe_ver'],
|
24
24
|
:project => ENV['BEAKER_project'],
|
25
25
|
:department => ENV['BEAKER_department'],
|
26
|
+
:jenkins_build_url => ENV['BUILD_URL'],
|
26
27
|
}.delete_if {|key, value| value.nil? or value.empty? })
|
27
28
|
end
|
28
29
|
|
@@ -34,6 +35,8 @@ module Beaker
|
|
34
35
|
h.merge({
|
35
36
|
:project => 'Beaker',
|
36
37
|
:department => ENV['USER'] || ENV['USERNAME'] || 'unknown',
|
38
|
+
:validate => true,
|
39
|
+
:jenkins_build_url => nil,
|
37
40
|
:log_level => 'verbose',
|
38
41
|
:trace_limit => 10,
|
39
42
|
:hosts_file => 'sample.cfg',
|
@@ -14,7 +14,8 @@ module Beaker
|
|
14
14
|
Errno::ECONNREFUSED,
|
15
15
|
Errno::ECONNRESET,
|
16
16
|
Errno::ENETUNREACH,
|
17
|
-
Net::SSH::Disconnect
|
17
|
+
Net::SSH::Disconnect,
|
18
|
+
Net::SSH::AuthenticationFailed,
|
18
19
|
]
|
19
20
|
|
20
21
|
def initialize hostname, user = nil, options = {}
|
@@ -48,8 +49,6 @@ module Beaker
|
|
48
49
|
puts "Failed to connect to #{@hostname}"
|
49
50
|
raise
|
50
51
|
end
|
51
|
-
rescue Net::SSH::AuthenticationFailed => e
|
52
|
-
raise "Unable to authenticate to #{@hostname} with user #{e.message.inspect}"
|
53
52
|
end
|
54
53
|
self
|
55
54
|
end
|
data/lib/beaker/test_case.rb
CHANGED
@@ -131,13 +131,13 @@ module Beaker
|
|
131
131
|
@test_status = :pending
|
132
132
|
rescue SkipTest
|
133
133
|
@test_status = :skip
|
134
|
-
rescue StandardError, ScriptError => e
|
134
|
+
rescue StandardError, ScriptError, SignalException => e
|
135
135
|
log_and_fail_test(e)
|
136
136
|
ensure
|
137
137
|
@teardown_procs.each do |teardown|
|
138
138
|
begin
|
139
139
|
teardown.call
|
140
|
-
rescue StandardError => e
|
140
|
+
rescue StandardError, SignalException => e
|
141
141
|
log_and_fail_test(e)
|
142
142
|
end
|
143
143
|
end
|
data/lib/beaker/version.rb
CHANGED
@@ -447,6 +447,26 @@ describe ClassMixedWithDSLHelpers do
|
|
447
447
|
:expect_failures => true
|
448
448
|
)
|
449
449
|
end
|
450
|
+
|
451
|
+
it 'can set the --parser future flag' do
|
452
|
+
subject.should_receive( :create_remote_file ).and_return( true )
|
453
|
+
subject.should_receive( :puppet ).
|
454
|
+
with( 'apply', '--verbose', '--parser future', '--detailed-exitcodes', 'agent' ).
|
455
|
+
and_return( 'puppet_command' )
|
456
|
+
subject.should_receive( :on ).with(
|
457
|
+
agent,
|
458
|
+
'puppet_command',
|
459
|
+
:acceptable_exit_codes => [1,2,3,4,5,6]
|
460
|
+
)
|
461
|
+
|
462
|
+
subject.apply_manifest_on(
|
463
|
+
agent,
|
464
|
+
'class { "boo": }',
|
465
|
+
:acceptable_exit_codes => (1..5),
|
466
|
+
:future_parser => true,
|
467
|
+
:expect_failures => true
|
468
|
+
)
|
469
|
+
end
|
450
470
|
end
|
451
471
|
|
452
472
|
describe "#apply_manifest" do
|
@@ -654,11 +674,13 @@ describe ClassMixedWithDSLHelpers do
|
|
654
674
|
let(:test_case_path) { 'testcase/path' }
|
655
675
|
let(:tmpdir_path) { '/tmp/tmpdir' }
|
656
676
|
let(:puppet_path) { '/puppet/path' }
|
677
|
+
let(:puppetservice) { @ps || nil }
|
657
678
|
let(:is_pe) { false }
|
658
679
|
let(:host) do
|
659
680
|
FakeHost.new(:pe => is_pe,
|
660
681
|
:options => {
|
661
682
|
'puppetpath' => puppet_path,
|
683
|
+
'puppetservice' => puppetservice,
|
662
684
|
'platform' => 'el'
|
663
685
|
})
|
664
686
|
end
|
@@ -689,23 +711,23 @@ describe ClassMixedWithDSLHelpers do
|
|
689
711
|
Tempfile.should_receive(:open).with('beaker')
|
690
712
|
end
|
691
713
|
|
692
|
-
context '
|
693
|
-
let(:
|
714
|
+
context 'with puppetservice and service-path defined' do
|
715
|
+
let(:puppetservice) { 'whatever' }
|
694
716
|
|
695
717
|
it 'bounces puppet twice' do
|
696
718
|
subject.with_puppet_running_on(host, {})
|
697
|
-
expect(host).to execute_commands_matching(
|
719
|
+
expect(host).to execute_commands_matching(/#{@ps} restart/).exactly(2).times
|
698
720
|
end
|
699
721
|
|
700
722
|
it 'yield to a block after bouncing service' do
|
701
723
|
execution = 0
|
702
724
|
expect do
|
703
725
|
subject.with_puppet_running_on(host, {}) do
|
704
|
-
expect(host).to execute_commands_matching(
|
726
|
+
expect(host).to execute_commands_matching(/#{@ps} restart/).once
|
705
727
|
execution += 1
|
706
728
|
end
|
707
729
|
end.to change { execution }.by(1)
|
708
|
-
expect(host).to execute_commands_matching(
|
730
|
+
expect(host).to execute_commands_matching(/#{@ps} restart/).exactly(2).times
|
709
731
|
end
|
710
732
|
end
|
711
733
|
|
@@ -299,7 +299,7 @@ describe ClassMixedWithDSLInstallUtils do
|
|
299
299
|
it 'installs' do
|
300
300
|
expect(subject).to receive(:on).with(hosts[0], /puppetlabs-release-\$\(lsb_release -c -s\)\.deb/)
|
301
301
|
expect(subject).to receive(:on).with(hosts[0], 'dpkg -i puppetlabs-release-$(lsb_release -c -s).deb')
|
302
|
-
expect(subject).to receive(:on).with(hosts[0], 'apt-get
|
302
|
+
expect(subject).to receive(:on).with(hosts[0], 'apt-get update')
|
303
303
|
expect(subject).to receive(:on).with(hosts[0], 'apt-get install -y puppet')
|
304
304
|
subject.install_puppet
|
305
305
|
end
|
@@ -309,7 +309,7 @@ describe ClassMixedWithDSLInstallUtils do
|
|
309
309
|
it 'installs' do
|
310
310
|
expect(subject).to receive(:on).with(hosts[0], /puppetlabs-release-\$\(lsb_release -c -s\)\.deb/)
|
311
311
|
expect(subject).to receive(:on).with(hosts[0], 'dpkg -i puppetlabs-release-$(lsb_release -c -s).deb')
|
312
|
-
expect(subject).to receive(:on).with(hosts[0], 'apt-get
|
312
|
+
expect(subject).to receive(:on).with(hosts[0], 'apt-get update')
|
313
313
|
expect(subject).to receive(:on).with(hosts[0], 'apt-get install -y puppet')
|
314
314
|
subject.install_puppet
|
315
315
|
end
|
@@ -111,7 +111,7 @@ describe Beaker do
|
|
111
111
|
it "can perform apt-get on ubuntu hosts" do
|
112
112
|
host = make_host( 'testhost', { :platform => 'ubuntu' } )
|
113
113
|
|
114
|
-
Beaker::Command.should_receive( :new ).with("apt-get
|
114
|
+
Beaker::Command.should_receive( :new ).with("apt-get update").once
|
115
115
|
|
116
116
|
subject.apt_get_update( host )
|
117
117
|
|
@@ -120,7 +120,7 @@ describe Beaker do
|
|
120
120
|
it "can perform apt-get on debian hosts" do
|
121
121
|
host = make_host( 'testhost', { :platform => 'debian' } )
|
122
122
|
|
123
|
-
Beaker::Command.should_receive( :new ).with("apt-get
|
123
|
+
Beaker::Command.should_receive( :new ).with("apt-get update").once
|
124
124
|
|
125
125
|
subject.apt_get_update( host )
|
126
126
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Beaker
|
4
|
+
describe AwsSdk do
|
5
|
+
let(:aws) {
|
6
|
+
# Mock out the call to load_fog_credentials
|
7
|
+
Beaker::AwsSdk.any_instance.stub(:load_fog_credentials).and_return(fog_file_contents)
|
8
|
+
|
9
|
+
# This is needed because the EC2 api looks up a local endpoints.json file
|
10
|
+
FakeFS.deactivate!
|
11
|
+
aws = Beaker::AwsSdk.new(@hosts, make_opts)
|
12
|
+
FakeFS.activate!
|
13
|
+
|
14
|
+
aws
|
15
|
+
}
|
16
|
+
let(:amispec) {{
|
17
|
+
"centos-5-x86-64-west" => {
|
18
|
+
:image => {:pe => "ami-sekrit1"},
|
19
|
+
:region => "us-west-2",
|
20
|
+
},
|
21
|
+
"centos-6-x86-64-west" => {
|
22
|
+
:image => {:pe => "ami-sekrit2"},
|
23
|
+
:region => "us-west-2",
|
24
|
+
},
|
25
|
+
"centos-7-x86-64-west" => {
|
26
|
+
:image => {:pe => "ami-sekrit3"},
|
27
|
+
:region => "us-west-2",
|
28
|
+
},
|
29
|
+
}}
|
30
|
+
|
31
|
+
before :each do
|
32
|
+
@hosts = make_hosts({:snapshot => :pe})
|
33
|
+
@hosts[0][:platform] = "centos-5-x86-64-west"
|
34
|
+
@hosts[1][:platform] = "centos-6-x86-64-west"
|
35
|
+
@hosts[2][:platform] = "centos-7-x86-64-west"
|
36
|
+
end
|
37
|
+
|
38
|
+
context '#backoff_sleep' do
|
39
|
+
it "should call sleep 1024 times at attempt 10" do
|
40
|
+
Object.any_instance.should_receive(:sleep).with(1024)
|
41
|
+
aws.backoff_sleep(10)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context '#public_key' do
|
46
|
+
it "retrieves contents from local ~/.ssh/ssh_rsa.pub file" do
|
47
|
+
# Stub calls to file read/exists
|
48
|
+
allow(File).to receive(:exists?).with(/id_rsa.pub/) { true }
|
49
|
+
allow(File).to receive(:read).with(/id_rsa.pub/) { "foobar" }
|
50
|
+
|
51
|
+
# Should return contents of previously stubbed id_rsa.pub
|
52
|
+
expect(aws.public_key).to eq("foobar")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return an error if the files do not exist" do
|
56
|
+
expect { aws.public_key }.to raise_error(RuntimeError, /Expected either/)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context '#key_name' do
|
61
|
+
it 'returns a key name from the local hostname' do
|
62
|
+
# Mock out the hostname and local user calls
|
63
|
+
Socket.should_receive(:gethostname) { "foobar" }
|
64
|
+
aws.should_receive(:local_user) { "bob" }
|
65
|
+
|
66
|
+
# Should match the expected composite key name
|
67
|
+
expect(aws.key_name).to eq("Beaker-bob-foobar")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context '#group_id' do
|
72
|
+
it 'should return a predicatable group_id from a port list' do
|
73
|
+
expect(aws.group_id([22, 1024])).to eq("Beaker-2799478787")
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should return a predicatable group_id from an empty list' do
|
77
|
+
expect { aws.group_id([]) }.to raise_error(ArgumentError, "Ports list cannot be nil or empty")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|