tdi 0.1.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.
@@ -0,0 +1,58 @@
1
+ require 'net/ssh'
2
+ require 'etc'
3
+
4
+ class TDIPlan < TDI
5
+ def ssh(plan)
6
+ plan.select { |key, val|
7
+ val.is_a?(Hash)
8
+ }.each_pair do |case_name, case_content|
9
+ # Validate.
10
+ unless /^[^@]+@[^@]+$/.match(case_name)
11
+ puts "ERR: Invalid ssh plan format \"#{case_name}\". Case name must match pattern \"user@host\".".light_magenta
12
+ exit 1
13
+ end
14
+
15
+ # Parse.
16
+ remote_user = case_name.split('@').first
17
+ host = case_name.split('@').last
18
+ local_users = [case_content['local_user']].flatten
19
+
20
+ # Users.
21
+ local_users.each do |local_user|
22
+ # Privileged user.
23
+ begin
24
+ Process.euid = 0
25
+ rescue => e
26
+ puts "ERR: Must run as root to change user credentials #{e}).".light_magenta
27
+ exit 1
28
+ end
29
+
30
+ # Change credentials to local user.
31
+ begin
32
+ Process.euid = Etc.getpwnam(local_user).uid
33
+ # SSH needs to know the user homedir in order to use the right
34
+ # private/public key pair to authenticate.
35
+ ENV['HOME'] = Etc.getpwnam(local_user).dir
36
+ rescue => e
37
+ puts "ERR: User \"#{local_user}\" not found (#{e}).".light_magenta
38
+ exit 1
39
+ end
40
+
41
+ begin
42
+ timeout(5) do
43
+ ssh_session = Net::SSH.start(host,
44
+ remote_user,
45
+ :auth_methods => ['publickey'])
46
+ ssh_session.close
47
+ success "SSH (#{local_user}): #{remote_user}@#{host}"
48
+ end
49
+ rescue
50
+ failure "SSH (#{local_user}): #{remote_user}@#{host}"
51
+ end
52
+ end
53
+ end
54
+
55
+ # Change credentials back to privileged user.
56
+ Process.euid = 0
57
+ end
58
+ end
@@ -0,0 +1,298 @@
1
+ require_relative 'rblank'
2
+ require_relative 'rmerge'
3
+
4
+ # Test plan builder.
5
+ def planner(opts, plan)
6
+ # Compila um plano completo.
7
+ # Processa a herança.
8
+ # Compila novamente para gerar um plano intermediário.
9
+ #
10
+ # Pass 1.
11
+ if opts[:verbose] > 1
12
+ puts '* Pass 1...'.cyan
13
+ puts
14
+ end
15
+ compiled_plan1 = plan_compiler(opts, plan)
16
+ inherited_plan1 = plan_inheriter(opts, compiled_plan1)
17
+ recompiled_plan1 = plan_compiler(opts, inherited_plan1)
18
+ if opts[:verbose] > 1
19
+ puts '* Pass 1... done.'.green
20
+ puts
21
+ end
22
+
23
+ # Zera o plano, mantendo apenas a estrutura de hashes. Desta forma é possível
24
+ # gerar um esqueleto de plano com todas as entradas de test cases com valores
25
+ # vazios (originais e herdados).
26
+ # Logo em seguida ocorre um merge recursivo com os valores originais para
27
+ # depois ser processado por completo novamente.
28
+ # O objetivo é dar precedência aos valores globais locais sobre os valores
29
+ # herdados.
30
+ #
31
+ # Blank and repopulate with original values.
32
+ blanked_plan = recompiled_plan1.rblank
33
+ if opts[:verbose] > 2
34
+ puts 'Blanked plan:'
35
+ puts "* #{blanked_plan}".yellow
36
+ end
37
+
38
+ repopulated_plan = blanked_plan.rmerge(compiled_plan1)
39
+ if opts[:verbose] > 2
40
+ puts 'Repopulated plan:'
41
+ puts "* #{repopulated_plan}".yellow
42
+ end
43
+
44
+ # Compila um plano completo.
45
+ # Processa a herança.
46
+ # Compila novamente para gerar um plano final.
47
+ #
48
+ # Pass 2.
49
+ if opts[:verbose] > 1
50
+ puts '* Pass 2...'.cyan
51
+ puts
52
+ end
53
+ compiled_plan2 = plan_compiler(opts, repopulated_plan)
54
+ inherited_plan2 = plan_inheriter(opts, compiled_plan2)
55
+ recompiled_plan2 = plan_compiler(opts, inherited_plan2)
56
+ if opts[:verbose] > 1
57
+ puts '* Pass 2... done.'.green
58
+ puts
59
+ end
60
+
61
+ # Final plan.
62
+ plan_filter(opts, recompiled_plan2)
63
+ end
64
+
65
+ # Test plan compile.
66
+ def plan_compiler(opts, plan)
67
+ # Gera um plano de teste baseado em todos os valores dos hashes, desde o mais
68
+ # global (role) até o mais específico (test case).
69
+ # Ordem de precedência: test case > test plan.
70
+
71
+ puts 'Compiling test plan...'.cyan if opts[:verbose] > 1
72
+
73
+ compiled_plan = {}
74
+
75
+ # Role.
76
+ # Ex: {"admin": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
77
+ plan.select { |key, val|
78
+ val.is_a?(Hash)
79
+ }.each_with_index do |(role_name, role_content), index|
80
+ if opts[:verbose] > 2
81
+ puts '=' * 60
82
+ puts "Role: #{role_name}"
83
+ puts 'Role content:'
84
+ puts "* #{role_content}".yellow
85
+ end
86
+
87
+ # Role (if not already).
88
+ compiled_plan[role_name] ||= role_content
89
+
90
+ # Test plan.
91
+ # Ex: {"acl": {"domain1": {"port": 80}...}...}
92
+ role_content.select { |key, val|
93
+ val.is_a?(Hash)
94
+ }.each_pair do |plan_name, plan_content|
95
+ if opts[:verbose] > 2
96
+ puts "Plan: #{plan_name}"
97
+ puts 'Plan content:'
98
+ puts "* #{plan_content}".yellow
99
+ end
100
+
101
+ # Test plan (if not already).
102
+ compiled_plan[role_name][plan_name] ||= plan_content
103
+
104
+ # Test case.
105
+ # Ex: {"domain1": {"port": 80}...}
106
+ plan_content.select { |key, val|
107
+ val.is_a?(Hash)
108
+ }.each_pair do |case_name, case_content|
109
+ if opts[:verbose] > 2
110
+ puts "Case: #{case_name}"
111
+ puts 'Case content:'
112
+ puts "* #{case_content}".yellow
113
+ end
114
+
115
+ # Test case compile.
116
+ new_case_content = role_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) or val.is_a?(Hash) }
117
+ new_case_content.merge!(plan_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) or val.is_a?(Hash) })
118
+ new_case_content.merge!(case_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) or val.is_a?(Hash) })
119
+
120
+ # Test case (new, merged).
121
+ compiled_plan[role_name][plan_name][case_name] = new_case_content
122
+
123
+ if opts[:verbose] > 2
124
+ puts 'Compiled case content:'
125
+ puts "* #{new_case_content}".yellow
126
+ end
127
+ end
128
+ end
129
+
130
+ if opts[:verbose] > 2
131
+ puts '=' * 60
132
+ puts unless index == plan.size - 1
133
+ end
134
+ end
135
+
136
+ if opts[:verbose] > 1
137
+ puts 'Compiling test plan... done.'.green
138
+ puts
139
+ end
140
+
141
+ compiled_plan
142
+ end
143
+
144
+ # Poor's man test plan inheritance.
145
+ def plan_inheriter(opts, plan)
146
+ # Processa a herança entre roles e test plans.
147
+
148
+ puts 'Inheriting test plan...'.cyan if opts[:verbose] > 1
149
+
150
+ inherited_plan = {}
151
+
152
+ # Role may inherit from role.
153
+ # Ex: {"admin": {"desc": "...", "inherits": "other_role", "acl": {"domain1": {"port": 80}...}...}...}
154
+ plan.select { |key, val|
155
+ val.is_a?(Hash)
156
+ }.each_with_index do |(role_name, role_content), index|
157
+ if opts[:verbose] > 2
158
+ puts '=' * 60
159
+ puts "Role: #{role_name}"
160
+ puts 'Role content:'
161
+ puts "* #{role_content}".yellow
162
+ end
163
+
164
+ # Role (if not already).
165
+ inherited_plan[role_name] ||= role_content
166
+
167
+ # Inheritance present?
168
+ i_role_name = role_content['inherits']
169
+ unless i_role_name.nil?
170
+ puts "Role #{role_name} inherits #{i_role_name}" if opts[:verbose] > 2
171
+ inherited_plan[role_name] = plan[i_role_name].rmerge(role_content)
172
+ end
173
+
174
+ # Plan may inherit from plan.
175
+ # Ex: {"acl": {"inherits": "other_role::other_plan", "domain1": {"port": 80}...}...}
176
+ role_content.select { |key, val|
177
+ val.is_a?(Hash)
178
+ }.each_pair do |plan_name, plan_content|
179
+ if opts[:verbose] > 2
180
+ puts "Plan: #{plan_name}"
181
+ puts 'Plan content:'
182
+ puts "* #{plan_content}".yellow
183
+ end
184
+
185
+ # Test plan (if not already).
186
+ inherited_plan[role_name][plan_name] ||= plan_content
187
+
188
+ # Inheritance present?
189
+ i_plan = plan_content['inherits']
190
+ unless i_plan.nil?
191
+ i_role_name, i_plan_name = role_plan_split(i_plan)
192
+
193
+ if i_role_name.nil? or i_plan_name.nil?
194
+ puts "ERR: Invalid inheritance \"#{i_plan}\". Must match pattern \"role::plan\".".light_magenta
195
+ exit 1
196
+ end
197
+
198
+ # TODO: Tratar quando chave não existe.
199
+ puts "Plan #{plan_name} inherits #{i_plan}" if opts[:verbose] > 2
200
+ inherited_plan[role_name][plan_name] = plan[i_role_name][i_plan_name].rmerge(plan_content)
201
+ end
202
+ end
203
+
204
+ if opts[:verbose] > 2
205
+ puts '=' * 60
206
+ puts unless index == plan.size - 1
207
+ end
208
+ end
209
+
210
+ if opts[:verbose] > 1
211
+ puts 'Inheriting test plan... done.'.green
212
+ puts
213
+ end
214
+
215
+ inherited_plan
216
+ end
217
+
218
+ # Test plan filter.
219
+ def plan_filter(opts, plan)
220
+ # Filtra roles e test plans desejados a partir do plano fornecido.
221
+
222
+ puts 'Filtering test plan...'.cyan if opts[:verbose] > 1
223
+
224
+ filtered_plan = {}
225
+ flag_err = false
226
+
227
+ # Ex: tdi -p admin
228
+ # Ex: tdi -p admin::acl
229
+ # Ex: tdi --plan fe
230
+ # Ex: tdi --plan admin::acl,solr,be
231
+ if opts.plan?
232
+ # Do filter.
233
+ puts 'Filtering following test plan from input file:'.cyan if opts[:verbose] > 0
234
+ opts[:plan].each do |plan_name|
235
+ puts " - #{plan_name}".cyan if opts[:verbose] > 0
236
+
237
+ # Pattern from command line is already validate by validate_args().
238
+ # Does not need to check for nil.
239
+ f_role_name, f_plan_name = role_plan_split(plan_name)
240
+
241
+ if plan.has_key?(f_role_name)
242
+ unless f_plan_name.nil?
243
+ # Test plan only.
244
+ if plan[f_role_name].has_key?(f_plan_name)
245
+ # Initialize hash key if not present.
246
+ filtered_plan[f_role_name] ||= {}
247
+ filtered_plan[f_role_name][f_plan_name] = plan[f_role_name][f_plan_name]
248
+ puts " Test plan \"#{plan_name}\" included.".green if opts[:verbose] > 0
249
+ else
250
+ puts "ERR: Test plan \"#{plan_name}\" not found in input file. This test plan can not be included.".light_magenta
251
+ flag_err = true
252
+ end
253
+ else
254
+ # Role test plan (entire).
255
+ filtered_plan[f_role_name] = plan[f_role_name]
256
+ puts " Role \"#{plan_name}\" included.".green if opts[:verbose] > 0
257
+ end
258
+ else
259
+ puts "ERR: Role \"#{f_role_name}\" not found in input file. Test plan \"#{plan_name}\" can not be included.".light_magenta
260
+ flag_err = true
261
+ end
262
+ end
263
+
264
+ puts if opts[:verbose] > 0
265
+ else
266
+ # No filter.
267
+ filtered_plan = plan
268
+ end
269
+
270
+ if opts[:verbose] > 2
271
+ puts 'Filtered test plan:'.cyan
272
+ puts "* #{filtered_plan}".yellow
273
+ end
274
+
275
+ exit 1 if flag_err
276
+
277
+ if opts[:verbose] > 1
278
+ puts 'Filtering test plan... done.'.green
279
+ puts
280
+ end
281
+
282
+ filtered_plan
283
+ end
284
+
285
+ # Split role_name and plan_name.
286
+ def role_plan_split(name)
287
+ if name.include?('::')
288
+ # Test plan only.
289
+ f_role_name = name.split('::').first
290
+ f_plan_name = name.split('::').last
291
+ else
292
+ # Role test plan (entire).
293
+ f_role_name = name
294
+ f_plan_name = nil
295
+ end
296
+
297
+ return f_role_name, f_plan_name
298
+ end
@@ -0,0 +1,23 @@
1
+ module HashRecursiveBlank
2
+ def rblank
3
+ r = {}
4
+
5
+ each_pair do |key, val|
6
+ r[key] = val.rblank if val.is_a?(Hash)
7
+ end
8
+
9
+ r.keep_if { |key, val| val.is_a?(Hash) }
10
+ end
11
+
12
+ def rblank!
13
+ each_pair do |key, val|
14
+ self[key] = val.rblank! if val.is_a?(Hash)
15
+ end
16
+
17
+ keep_if { |key, val| val.is_a?(Hash) }
18
+ end
19
+ end
20
+
21
+ class Hash
22
+ include HashRecursiveBlank
23
+ end
@@ -0,0 +1,19 @@
1
+ module HashRecursiveMerge
2
+ def rmerge(other_hash)
3
+ r = {}
4
+
5
+ merge(other_hash) do |key, oldval, newval|
6
+ r[key] = oldval.is_a?(Hash) ? oldval.rmerge(newval) : newval
7
+ end
8
+ end
9
+
10
+ def rmerge!(other_hash)
11
+ merge!(other_hash) do |key, oldval, newval|
12
+ oldval.is_a?(Hash) ? oldval.rmerge!(newval) : newval
13
+ end
14
+ end
15
+ end
16
+
17
+ class Hash
18
+ include HashRecursiveMerge
19
+ end
@@ -0,0 +1,99 @@
1
+ require_relative 'tdi'
2
+
3
+ # Run tests.
4
+ def runner(opts, filename, plan)
5
+ puts 'Running tests...'.cyan if opts[:verbose] > 1
6
+
7
+ # Skip reserved roles.
8
+ # Ex: {"global": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
9
+ # Ex: {"common": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
10
+ plan.select { |role_name, role_content|
11
+ if role_content.is_a?(Hash)
12
+ UNTESTABLE_ROLE_LIST.include?(role_name) or role_content['notest'].eql?('true')
13
+ end
14
+ }.each_pair do |role_name, role_content|
15
+ puts "Skipping reserved or disabled role: #{role_name}".yellow if opts[:verbose] > 0
16
+ end
17
+
18
+ # Remove untestable roles.
19
+ plan.reject! { |role_name, role_content|
20
+ if role_content.is_a?(Hash)
21
+ UNTESTABLE_ROLE_LIST.include?(role_name) or role_content['notest'].eql?('true')
22
+ end
23
+ }
24
+ total_roles = plan.select { |key, val| val.is_a?(Hash) }.size
25
+ puts "Total roles to run: #{total_roles}".cyan if opts[:verbose] > 1
26
+
27
+ # Run the rest.
28
+ tdiplan = TDIPlan.new
29
+
30
+ # Role.
31
+ # Ex: {"admin": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
32
+ plan.select { |key, val|
33
+ val.is_a?(Hash)
34
+ }.each_with_index do |(role_name, role_content), index|
35
+ total_plans = role_content.select { |key, val| val.is_a?(Hash) }.size
36
+
37
+ if role_content['desc'].nil?
38
+ puts "* #{role_name.capitalize}".cyan
39
+ else
40
+ puts "* #{role_name.capitalize} - #{role_content['desc']}".cyan
41
+ end
42
+ # puts "Running tests for role: #{role_name}".cyan if opts[:verbose] > 0
43
+ puts "Total test plans to run for this role: #{total_plans}".cyan if opts[:verbose] > 1
44
+
45
+ # Test plan.
46
+ # Ex: {"acl": {"domain1": {"port": 80}...}...}
47
+ role_content.select { |key, val|
48
+ val.is_a?(Hash)
49
+ }.each_pair do |plan_name, plan_content|
50
+ total_cases = plan_content.select { |key, val| val.is_a?(Hash) }.size
51
+
52
+ puts "* #{plan_name.upcase}".cyan
53
+ # puts "Test plan: #{role_name}::#{plan_name}".cyan if opts[:verbose] > 0
54
+ puts "Total test cases to run for this plan: #{total_cases}".cyan if opts[:verbose] > 1
55
+
56
+ if opts[:verbose] > 3
57
+ puts "Plan: #{plan_name}"
58
+ puts 'Plan content:'
59
+ puts "* #{plan_content}".yellow
60
+ end
61
+
62
+ # Test plan content (test cases).
63
+ # Ex: {"domain1": {"port": 80}, "domain2": {"port": 80}...}
64
+ if tdiplan.respond_to?(plan_name)
65
+ tdiplan.send(plan_name, plan_content)
66
+ else
67
+ puts "Skipping not supported test plan type \"#{plan_name}\" for \"#{role_name}::#{plan_name}\".".yellow
68
+ tdiplan.skip = tdiplan.skip + total_cases
69
+ end
70
+ end
71
+
72
+ puts unless index == plan.size - 1
73
+ end
74
+
75
+ # Summary.
76
+ summary(opts, tdiplan)
77
+
78
+ # Shred.
79
+ if opts.shred?
80
+ puts "Shreding and removing test plan file: \"#{filename}\"...".cyan if opts[:verbose] > 2
81
+ if system("shred -f -n 38 -u -z #{filename}")
82
+ puts "Shreding and removing test plan file: \"#{filename}\"... done.".green if opts[:verbose] > 2
83
+ else
84
+ puts "ERR: Shreding and removing test plan file: \"#{filename}\".".light_magenta
85
+ end
86
+ end
87
+
88
+ puts 'Running tests... done.'.green if opts[:verbose] > 1
89
+
90
+ ret = tdiplan.passed? ? 0 : 1
91
+ ret = 0 if opts.nofail?
92
+ ret
93
+ end
94
+
95
+ # Display test summary.
96
+ def summary(opts, tdiplan)
97
+ puts '=' * 79
98
+ puts "Total: #{tdiplan.total} | Skip: #{tdiplan.skip} | Pass: #{tdiplan.pass} | Warn: #{tdiplan.warn} | Fail: #{tdiplan.fail}"
99
+ end