rouster 0.5
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 +7 -0
- data/.gitignore +6 -0
- data/LICENSE +9 -0
- data/README.md +175 -0
- data/Rakefile +65 -0
- data/Vagrantfile +23 -0
- data/examples/bootstrap.rb +113 -0
- data/examples/demo.rb +71 -0
- data/examples/error.rb +30 -0
- data/lib/rouster.rb +737 -0
- data/lib/rouster/deltas.rb +481 -0
- data/lib/rouster/puppet.rb +398 -0
- data/lib/rouster/testing.rb +743 -0
- data/lib/rouster/tests.rb +596 -0
- data/path_helper.rb +21 -0
- data/rouster.gemspec +30 -0
- data/test/basic.rb +10 -0
- data/test/functional/deltas/test_get_crontab.rb +99 -0
- data/test/functional/deltas/test_get_groups.rb +48 -0
- data/test/functional/deltas/test_get_packages.rb +71 -0
- data/test/functional/deltas/test_get_ports.rb +119 -0
- data/test/functional/deltas/test_get_services.rb +43 -0
- data/test/functional/deltas/test_get_users.rb +45 -0
- data/test/functional/puppet/test_facter.rb +59 -0
- data/test/functional/test_caching.rb +124 -0
- data/test/functional/test_destroy.rb +51 -0
- data/test/functional/test_dirs.rb +88 -0
- data/test/functional/test_files.rb +64 -0
- data/test/functional/test_get.rb +76 -0
- data/test/functional/test_inspect.rb +31 -0
- data/test/functional/test_is_dir.rb +118 -0
- data/test/functional/test_is_file.rb +119 -0
- data/test/functional/test_new.rb +92 -0
- data/test/functional/test_put.rb +81 -0
- data/test/functional/test_rebuild.rb +49 -0
- data/test/functional/test_restart.rb +44 -0
- data/test/functional/test_run.rb +77 -0
- data/test/functional/test_status.rb +38 -0
- data/test/functional/test_suspend.rb +31 -0
- data/test/functional/test_up.rb +27 -0
- data/test/functional/test_validate_file.rb +30 -0
- data/test/puppet/manifests/default.pp +9 -0
- data/test/puppet/manifests/hiera.yaml +12 -0
- data/test/puppet/manifests/hieradata/common.json +3 -0
- data/test/puppet/manifests/hieradata/vagrant.json +3 -0
- data/test/puppet/manifests/manifest.pp +78 -0
- data/test/puppet/modules/role/manifests/ui.pp +5 -0
- data/test/puppet/test_apply.rb +149 -0
- data/test/puppet/test_roles.rb +186 -0
- data/test/tunnel_vs_scp.rb +41 -0
- data/test/unit/puppet/test_get_puppet_star.rb +68 -0
- data/test/unit/test_generate_unique_mac.rb +43 -0
- data/test/unit/test_new.rb +31 -0
- data/test/unit/test_parse_ls_string.rb +334 -0
- data/test/unit/test_traverse_up.rb +43 -0
- data/test/unit/testing/test_meets_constraint.rb +55 -0
- data/test/unit/testing/test_validate_file.rb +112 -0
- data/test/unit/testing/test_validate_group.rb +72 -0
- data/test/unit/testing/test_validate_package.rb +69 -0
- data/test/unit/testing/test_validate_port.rb +98 -0
- data/test/unit/testing/test_validate_service.rb +73 -0
- data/test/unit/testing/test_validate_user.rb +92 -0
- metadata +203 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
require sprintf('%s/../../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'rouster/deltas'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class TestDeltasGetUsers < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
assert_nothing_raised do
|
11
|
+
@app = Rouster.new(:name => 'app')
|
12
|
+
end
|
13
|
+
|
14
|
+
@app.up()
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_happy_path
|
18
|
+
res = nil
|
19
|
+
|
20
|
+
assert_nothing_raised do
|
21
|
+
res = @app.get_users()
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_equal(Hash, res.class)
|
25
|
+
assert_not_nil(@app.deltas[:users])
|
26
|
+
|
27
|
+
res.each_key do |k|
|
28
|
+
assert_not_nil(res[k][:shell])
|
29
|
+
assert_not_nil(res[k][:uid])
|
30
|
+
assert_match(/^\d+$/, res[k][:uid])
|
31
|
+
assert_not_nil(res[k][:gid])
|
32
|
+
assert_match(/^\d+$/, res[k][:gid])
|
33
|
+
assert_not_nil(res[k][:home])
|
34
|
+
assert_not_nil(res[k][:home_exists])
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO add some caching tests
|
40
|
+
|
41
|
+
def teardown
|
42
|
+
@app = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require sprintf('%s/../../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'rouster/puppet'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class TestFacter < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
assert_nothing_raised do
|
11
|
+
@app = Rouster.new(:name => 'app')
|
12
|
+
end
|
13
|
+
|
14
|
+
@app.up()
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_happy_path
|
18
|
+
|
19
|
+
facts = nil
|
20
|
+
|
21
|
+
assert_nothing_raised do
|
22
|
+
facts = @app.facter()
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_equal(true, facts.class.eql?(Hash))
|
26
|
+
assert_equal(facts, @app.facts)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_negative_caching
|
30
|
+
|
31
|
+
facts = nil
|
32
|
+
|
33
|
+
assert_nothing_raised do
|
34
|
+
facts = @app.facter(false)
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_equal(true, facts.class.eql?(Hash))
|
38
|
+
assert_equal(nil, @app.facts)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_custom_facts
|
42
|
+
|
43
|
+
facts = nil
|
44
|
+
|
45
|
+
assert_nothing_raised do
|
46
|
+
facts = @app.facter(false, false)
|
47
|
+
end
|
48
|
+
|
49
|
+
assert_equal(true, facts.class.eql?(Hash))
|
50
|
+
|
51
|
+
# need to come up with definitive test to not include custom facts, unsure how exactly we should do this
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def teardown
|
56
|
+
# noop
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require sprintf('%s/../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class TestCaching < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
# expose private methods
|
10
|
+
Rouster.send(:public, *Rouster.protected_instance_methods)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_status_caching
|
14
|
+
timeout = 5
|
15
|
+
app = Rouster.new(:name => 'app', :cache_timeout => timeout)
|
16
|
+
|
17
|
+
assert_equal(app.cache_timeout, timeout)
|
18
|
+
|
19
|
+
status_orig = app.status()
|
20
|
+
|
21
|
+
assert_not_nil(app.cache[:status])
|
22
|
+
status_orig_time = app.cache[:status][:time]
|
23
|
+
|
24
|
+
assert_equal(status_orig, app.cache[:status][:status])
|
25
|
+
|
26
|
+
status_new = app.status()
|
27
|
+
status_new_time = app.cache[:status][:time]
|
28
|
+
|
29
|
+
assert_equal(status_orig, status_new)
|
30
|
+
assert_equal(status_orig_time, status_new_time)
|
31
|
+
|
32
|
+
app.status()
|
33
|
+
status_orig_time = app.cache[:status][:time]
|
34
|
+
sleep(timeout + 1)
|
35
|
+
|
36
|
+
app.status()
|
37
|
+
status_new_time = app.cache[:status][:time]
|
38
|
+
|
39
|
+
assert_not_equal(status_orig_time, status_new_time)
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_status_caching_functional
|
44
|
+
timeout = 5
|
45
|
+
app = Rouster.new(:name => 'app', :cache_timeout => timeout)
|
46
|
+
|
47
|
+
status_orig = app.status()
|
48
|
+
status_orig_time = app.cache[:status][:time]
|
49
|
+
|
50
|
+
if status_orig.eql?('running')
|
51
|
+
app.suspend()
|
52
|
+
elsif status_orig.eql?('not created')
|
53
|
+
app.up()
|
54
|
+
else
|
55
|
+
# should just be suspended/saved
|
56
|
+
app.destroy()
|
57
|
+
end
|
58
|
+
|
59
|
+
sleep(timeout) # this is almost certainly unnecessary, given the amount of time the above operations take, but just being safe
|
60
|
+
|
61
|
+
status_new = app.status()
|
62
|
+
status_new_time = app.cache[:status][:time]
|
63
|
+
|
64
|
+
assert_not_equal(status_orig, status_new)
|
65
|
+
assert_not_equal(status_orig_time, status_new_time)
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_status_caching_negative
|
70
|
+
app = Rouster.new(:name => 'app')
|
71
|
+
|
72
|
+
app.status()
|
73
|
+
assert_nil(app.cache[:status])
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_ssh_caching
|
77
|
+
|
78
|
+
skip('see comments in rouster.rb line ~84')
|
79
|
+
|
80
|
+
timeout = 100
|
81
|
+
app = Rouster.new(:name => 'app', :sshtunnel => true, :cache_timeout => timeout)
|
82
|
+
app.up()
|
83
|
+
|
84
|
+
assert_equal(app.cache_timeout, timeout)
|
85
|
+
|
86
|
+
avail_orig = app.is_available_via_ssh?
|
87
|
+
assert_not_nil(app.cache[:is_available_via_ssh?])
|
88
|
+
|
89
|
+
avail_orig_time = app.cache[:is_available_via_ssh?][:time]
|
90
|
+
assert_equal(avail_orig, app.cache[:is_available_via_ssh?][:status])
|
91
|
+
|
92
|
+
avail_new = app.is_available_via_ssh?
|
93
|
+
avail_new_time = app.cache[:is_available_via_ssh?][:time]
|
94
|
+
|
95
|
+
assert_equal(avail_orig, avail_new)
|
96
|
+
assert_equal(avail_new_time, avail_orig_time)
|
97
|
+
|
98
|
+
app.is_available_via_ssh?
|
99
|
+
avail_orig_time = app.cache[:is_available_via_ssh?][:time]
|
100
|
+
|
101
|
+
sleep(timeout + 1)
|
102
|
+
|
103
|
+
app.is_available_via_ssh?
|
104
|
+
avail_new_time = app.cache[:is_available_via_ssh?][:time]
|
105
|
+
|
106
|
+
assert_not_equal(avail_orig_time, avail_new_time)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_ssh_caching_functional
|
110
|
+
# noop
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_ssh_caching_negative
|
114
|
+
app = Rouster.new(:name => 'app')
|
115
|
+
|
116
|
+
app.is_available_via_ssh?()
|
117
|
+
assert_nil(app.cache[:is_available_via_ssh?])
|
118
|
+
end
|
119
|
+
|
120
|
+
def teardown
|
121
|
+
# noop
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require sprintf('%s/../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class TestDestroy < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
assert_nothing_raised do
|
10
|
+
@app = Rouster.new(:name => 'app')
|
11
|
+
end
|
12
|
+
|
13
|
+
@app.up()
|
14
|
+
assert_equal(@app.is_available_via_ssh?(), true)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_happy_path
|
18
|
+
|
19
|
+
assert_equal(@app.status(), 'running')
|
20
|
+
|
21
|
+
assert_nothing_raised do
|
22
|
+
@app.destroy()
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_equal(false, @app.is_available_via_ssh?)
|
26
|
+
assert_equal('not created', @app.status())
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_thats_what_we_call_overkill
|
30
|
+
assert_equal(@app.status(), 'running')
|
31
|
+
|
32
|
+
assert_nothing_raised do
|
33
|
+
@app.destroy()
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_equal(false, @app.is_available_via_ssh?)
|
37
|
+
assert_equal('not created', @app.status())
|
38
|
+
|
39
|
+
assert_nothing_raised do
|
40
|
+
@app.destroy()
|
41
|
+
end
|
42
|
+
|
43
|
+
assert_equal('not created', @app.status())
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def teardown
|
48
|
+
@app = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require sprintf('%s/../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'rouster/tests'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class TestDirs < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@app = Rouster.new(:name => 'app', :verbose => 1)
|
11
|
+
@app.up()
|
12
|
+
|
13
|
+
@dir = sprintf('/tmp/rouster.dirs.%s/', Time.now.to_i)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_happy
|
17
|
+
dirs_expected = ['foo', 'bar', 'baz']
|
18
|
+
|
19
|
+
dirs_expected.each do |dir|
|
20
|
+
@app.run(sprintf('mkdir -p %s/%s', @dir, dir))
|
21
|
+
end
|
22
|
+
|
23
|
+
dirs_actual = @app.dirs(@dir)
|
24
|
+
|
25
|
+
# could totally use an is_deeply here..
|
26
|
+
dirs_actual.each do |dir|
|
27
|
+
dir = dir.gsub(/#{@dir}/, '') # remove the path prefix
|
28
|
+
assert(dirs_expected.member?(dir))
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_happy_filter
|
34
|
+
|
35
|
+
dirs_to_create = ['foo', 'bar', 'baz']
|
36
|
+
dirs_expected = ['bar', 'baz']
|
37
|
+
|
38
|
+
dirs_to_create.each do |dir|
|
39
|
+
@app.run(sprintf('mkdir -p %s/%s', @dir, dir))
|
40
|
+
end
|
41
|
+
|
42
|
+
dirs_actual = @app.dirs(@dir, 'b*')
|
43
|
+
|
44
|
+
# would like to do some negative testing here
|
45
|
+
dirs_actual.each do |dir|
|
46
|
+
dir = dir.gsub(/#{@dir}/, '') # remove the path prefix
|
47
|
+
assert(dirs_expected.member?(dir))
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def no_test_happy_recurse
|
54
|
+
|
55
|
+
raise NotImplementedError.new()
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_case_sensitvity
|
60
|
+
dirs_to_create = ['Fizz', 'foo', 'baz']
|
61
|
+
dirs_expected_sensitive = ['foo']
|
62
|
+
dirs_expected_insensitive = ['Fizz', 'foo']
|
63
|
+
|
64
|
+
dirs_to_create.each do |dir|
|
65
|
+
@app.run(sprintf('mkdir -p %s/%s', @dir, dir))
|
66
|
+
end
|
67
|
+
|
68
|
+
dirs_actual_sensitive = @app.dirs(@dir, 'f*', false)
|
69
|
+
|
70
|
+
dirs_actual_sensitive.each do |dir|
|
71
|
+
dir = dir.gsub(/#{@dir}/, '')
|
72
|
+
assert(dirs_expected_sensitive.member?(dir))
|
73
|
+
end
|
74
|
+
|
75
|
+
dirs_actual_insensitive = @app.dirs(@dir, 'f*', true)
|
76
|
+
|
77
|
+
dirs_actual_insensitive.each do |dir|
|
78
|
+
dir = dir.gsub(/#{@dir}/, '')
|
79
|
+
assert(dirs_expected_insensitive.member?(dir))
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
def teardown
|
85
|
+
@app.run(sprintf('rm -rf %s', @dir))
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require sprintf('%s/../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'rouster/tests'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class TestFiles < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@app = Rouster.new(:name => 'app')
|
11
|
+
@app.up()
|
12
|
+
|
13
|
+
@dir = sprintf('/tmp/rouster.files.%s/', Time.now.to_i)
|
14
|
+
@app.run(sprintf('mkdir %s', @dir))
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_happy
|
18
|
+
files_expected = ['foo', 'bar', 'baz']
|
19
|
+
|
20
|
+
files_expected.each do |file|
|
21
|
+
@app.run(sprintf('touch %s/%s', @dir, file))
|
22
|
+
end
|
23
|
+
|
24
|
+
files_actual = @app.files(@dir)
|
25
|
+
|
26
|
+
# could totally use an is_deeply here..
|
27
|
+
files_actual.each do |file|
|
28
|
+
file = file.gsub(/#{@dir}/, '') # remove the path prefix
|
29
|
+
assert(files_expected.member?(file))
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_happy_filter
|
35
|
+
|
36
|
+
files_to_create = ['foo', 'bar', 'baz']
|
37
|
+
files_expected = ['bar', 'baz']
|
38
|
+
|
39
|
+
files_to_create.each do |file|
|
40
|
+
@app.run(sprintf('touch %s/%s', @dir, file))
|
41
|
+
end
|
42
|
+
|
43
|
+
files_actual = @app.files(@dir, 'b*')
|
44
|
+
|
45
|
+
# would like to do some negative testing here
|
46
|
+
files_actual.each do |file|
|
47
|
+
file = file.gsub(/#{@dir}/, '') # remove the path prefix
|
48
|
+
assert(files_expected.member?(file))
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def no_test_happy_recurse
|
54
|
+
|
55
|
+
raise NotImplementedError.new()
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def teardown
|
60
|
+
# noop
|
61
|
+
@app.run(sprintf('rm -r %s', @dir))
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require sprintf('%s/../../path_helper', File.dirname(File.expand_path(__FILE__)))
|
2
|
+
|
3
|
+
require 'rouster'
|
4
|
+
require 'rouster/tests'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class TestGet < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@app = Rouster.new(:name => 'app')
|
11
|
+
|
12
|
+
@app.up()
|
13
|
+
|
14
|
+
@kg_local_location = sprintf('/tmp/rouster-test_get_local.%s.%s', $$, Time.now.to_i)
|
15
|
+
@kg_local_location.freeze
|
16
|
+
@kg_remote_location = '/etc/hosts'
|
17
|
+
@kb_dne_location = '/tmp/this-doesnt_exist/and/never/will.txt'
|
18
|
+
|
19
|
+
File.delete(@kg_local_location) if File.file?(@kg_local_location).true?
|
20
|
+
|
21
|
+
assert_equal(@app.is_available_via_ssh?, true, 'app is available via SSH')
|
22
|
+
assert_equal(File.file?(@kg_local_location), false, 'test KG file not present')
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_happy_path
|
26
|
+
|
27
|
+
assert_nothing_raised do
|
28
|
+
@app.get(@kg_remote_location, @kg_local_location)
|
29
|
+
end
|
30
|
+
|
31
|
+
assert(File.file?(@kg_local_location), 'downloaded file exists')
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_local_path_dne
|
35
|
+
|
36
|
+
assert_raise Rouster::FileTransferError do
|
37
|
+
@app.get(@kg_remote_location, @kb_dne_location)
|
38
|
+
|
39
|
+
# TODO how can we check the contents of the expception message?
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_equal(false, File.file?(@kg_local_location), 'known bad local path DNE')
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_remote_path_dne
|
46
|
+
|
47
|
+
assert_raise Rouster::FileTransferError do
|
48
|
+
res = @app.get(@kb_dne_location, @kg_local_location)
|
49
|
+
assert_equal(false, res)
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal(false, File.file?(@kg_local_location), 'known bad remote file path DNE')
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_with_suspended_machine
|
56
|
+
@app.suspend()
|
57
|
+
|
58
|
+
#assert_raise Rouster::SSHConnectionError do <-- this is what we want when the connection is bad, as opposed to permission/disk space issues
|
59
|
+
assert_raise Rouster::FileTransferError do
|
60
|
+
@app.get(@kg_remote_location, @kg_local_location)
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_equal(false, File.file?(@kg_local_location), 'when machine is suspended, unable to get from it')
|
64
|
+
end
|
65
|
+
|
66
|
+
def teardown
|
67
|
+
File.delete(@kg_local_location) if File.file?(@kg_local_location).true?
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.shutdown
|
71
|
+
# TODO we should suspend instead if any test failed for triage
|
72
|
+
#@app.suspend()
|
73
|
+
#@ppm.suspend()
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|