Top Level Namespace

Defined Under Namespace

Modules: HashRecursiveBlank, HashRecursiveMerge, Tdi Classes: Hash, TDI

Instance Method Summary collapse

Instance Method Details

#a_p(obj) ⇒ Object

Awesome Print config.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/util.rb', line 25

def a_p(obj)
  ap obj, {
    indent: 2,
    index: false,
    color: {
      args:       :pale,
      array:      :white,
      bigdecimal: :blue,
      class:      :yellow,
      date:       :greenish,
      falseclass: :red,
      fixnum:     :blue,
      float:      :blue,
      hash:       :blue,
      keyword:    :cyan,
      method:     :purpleish,
      nilclass:   :red,
      rational:   :blue,
      string:     :green,
      struct:     :pale,
      symbol:     :cyanish,
      time:       :greenish,
      trueclass:  :green,
      variable:   :cyanish,
    }
  }
end

#getaddress(host, raise_exception: true) ⇒ Object

Return IP address.



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/util.rb', line 107

def getaddress(host, raise_exception: true)
  if IPAddress.valid?(host)
    return host # use address (already an IP)
  else
    if raise_exception
      return Socket::getaddrinfo(host, nil, :AF_INET, :STREAM).first[2]
    else
      return Socket::getaddrinfo(host, nil, :AF_INET, :STREAM).first[2] rescue nil
    end
  end
end

#local_networksObject

Return a list of local networks and it’s details.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/util.rb', line 54

def local_networks
  Socket.getifaddrs.each.select { |ifaddr| ifaddr.addr.ipv4? && !ifaddr.name.start_with?('lo') }.
    map do |ifaddr|
      ip = IPAddress::IPv4.new("#{ifaddr.addr.ip_address}/#{ifaddr.netmask.ip_address}")
      {
        interface: ifaddr.name,
        network: ip.network.address,
        netmask: ip.netmask,
        prefix: ip.prefix,
        broadcast: ip.broadcast.address,
        ipv4: ip.address,
      }
    end
end

#origin_network(remote) ⇒ Object

Return the origin network to be used when trying to connect to a remote service.

Stolen from:

coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/

The above code does NOT make a connection or send any packets. Since UDP is a stateless protocol connect() merely makes a system call which figures out how to route the packets based on the address and what interface (and therefore IP address) it should bind to. addr() returns an array containing the family (AF_INET), local port, and local address (which is what we want) of the socket. This is a good alternative to ‘ifconfig`/`ipconfig` solutions because it doesn’t spawn a shell and it works the same on all systems.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/util.rb', line 83

def origin_network(remote)
  orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true

  host = remote # host (name or IP)
  addr = getaddress(remote, raise_exception: false)

  UDPSocket.open do |s|
    begin
      s.connect(remote, 1)
      res = {from: local_networks.each.select { |locnet| locnet[:ipv4].eql?(s.addr.last) }.first}
    rescue
      res = {from: nil}
    end

    res[:to] = {host: host, addr: addr}

    return res
  end

ensure
  Socket.do_not_reverse_lookup = orig
end

#plan_compiler(opts, plan) ⇒ Object

Test plan compile.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/planner.rb', line 85

def plan_compiler(opts, plan)
  # Gera um plano de teste baseado em todos os valores dos hashes, desde o mais
  # global (role) até o mais específico (test case).
  # Ordem de precedência: test case > test plan.

  puts 'Compiling test plan...'.cyan if opts[:verbose] > 1

  compiled_plan = {}

  # Role.
  # Ex: {"app": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
  plan.select { |key, val|
    val.is_a?(Hash)
  }.each_with_index do |(role_name, role_content), index|
    if opts[:verbose] > 2
      puts '=' * 60
      puts "Role: #{role_name}"
      puts 'Role content:'
      puts "* #{role_content}".yellow
    end

    # Role (if not already).
    compiled_plan[role_name] ||= role_content

    # Test plan.
    # Ex: {"acl": {"domain1": {"port": 80}...}...}
    role_content.select { |key, val|
      val.is_a?(Hash)
    }.each_pair do |plan_name, plan_content|
      if opts[:verbose] > 2
        puts "Plan: #{plan_name}"
        puts 'Plan content:'
        puts "* #{plan_content}".yellow
      end

      # Test plan (if not already).
      compiled_plan[role_name][plan_name] ||= plan_content

      # Test case.
      # Ex: {"domain1": {"port": 80}...}
      plan_content.select { |key, val|
        val.is_a?(Hash)
      }.each_pair do |case_name, case_content|
        if opts[:verbose] > 2
          puts "Case: #{case_name}"
          puts 'Case content:'
          puts "* #{case_content}".yellow
        end

        # Test case compile.
        new_case_content = role_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) || val.is_a?(Hash) }
        new_case_content.merge!(plan_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) || val.is_a?(Hash) })
        new_case_content.merge!(case_content.reject { |key, val| UNMERGEABLE_KEY_LIST.include?(key) || val.is_a?(Hash) })

        # Test case (new, merged).
        compiled_plan[role_name][plan_name][case_name] = new_case_content

        if opts[:verbose] > 2
          puts 'Compiled case content:'
          puts "* #{new_case_content}".yellow
        end
      end
    end

    if opts[:verbose] > 2
      puts '=' * 60
      puts unless index == plan.size - 1
    end
  end

  if opts[:verbose] > 1
    puts 'Compiling test plan... done.'.green
    puts
  end

  compiled_plan
end

#plan_filter(opts, plan) ⇒ Object

Test plan filter.



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/planner.rb', line 238

def plan_filter(opts, plan)
  # Filtra roles e test plans desejados a partir do plano fornecido.

  puts 'Filtering test plan...'.cyan if opts[:verbose] > 1

  filtered_plan = {}
  flag_err = false

  # Ex: -p admin
  # Ex: -p admin::acl
  # Ex: --plan app
  # Ex: --plan app::acl
  # Ex: --plan fe
  # Ex: --plan admin::acl,app,fe
  if opts.plan?
    # Do filter.
    puts 'Filtering following test plan from input file:'.cyan if opts[:verbose] > 0
    opts[:plan].each do |plan_name|
      puts "  - #{plan_name}".cyan if opts[:verbose] > 0

      # Pattern from command line is already validate by validate_args().
      # Does not need to check for nil.
      f_role_name, f_plan_name = role_plan_split(plan_name)

      if plan.has_key?(f_role_name)
        unless f_plan_name.nil?
          # Test plan only.
          if plan[f_role_name].has_key?(f_plan_name)
            # Initialize hash key if not present.
            filtered_plan[f_role_name] ||= {}
            filtered_plan[f_role_name][f_plan_name] = plan[f_role_name][f_plan_name]
            puts "    Test plan \"#{plan_name}\" included.".green if opts[:verbose] > 0
          else
            puts "ERR: Test plan \"#{plan_name}\" not found in input file. This test plan can not be included.".light_magenta
            flag_err = true
          end
        else
          # Role test plan (entire).
          filtered_plan[f_role_name] = plan[f_role_name]
          puts "    Role \"#{plan_name}\" included.".green if opts[:verbose] > 0
        end
      else
        puts "ERR: Role \"#{f_role_name}\" not found in input file. Test plan \"#{plan_name}\" can not be included.".light_magenta
        flag_err = true
      end
    end

    puts if opts[:verbose] > 0
  else
    # No filter.
    filtered_plan = plan
  end

  if opts[:verbose] > 2
    puts 'Filtered test plan:'.cyan
    puts "* #{filtered_plan}".yellow
  end

  exit 1 if flag_err

  if opts[:verbose] > 1
    puts 'Filtering test plan... done.'.green
    puts
  end

  filtered_plan
end

#plan_inheriter(opts, plan) ⇒ Object

Poor’s man test plan inheritance.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/planner.rb', line 164

def plan_inheriter(opts, plan)
  # Processa a herança entre roles e test plans.

  puts 'Inheriting test plan...'.cyan if opts[:verbose] > 1

  inherited_plan = {}

  # Role may inherit from role.
  # Ex: {"app": {"desc": "...", "inherits": "other_role", "acl": {"domain1": {"port": 80}...}...}...}
  plan.select { |key, val|
    val.is_a?(Hash)
  }.each_with_index do |(role_name, role_content), index|
    if opts[:verbose] > 2
      puts '=' * 60
      puts "Role: #{role_name}"
      puts 'Role content:'
      puts "* #{role_content}".yellow
    end

    # Role (if not already).
    inherited_plan[role_name] ||= role_content

    # Inheritance present?
    i_role_name = role_content['inherits']
    unless i_role_name.nil?
      puts "Role #{role_name} inherits #{i_role_name}" if opts[:verbose] > 2
      inherited_plan[role_name] = plan[i_role_name].rmerge(role_content)
    end

    # Plan may inherit from plan.
    # Ex: {"acl": {"inherits": "other_role::other_plan", "domain1": {"port": 80}...}...}
    role_content.select { |key, val|
      val.is_a?(Hash)
    }.each_pair do |plan_name, plan_content|
      if opts[:verbose] > 2
        puts "Plan: #{plan_name}"
        puts 'Plan content:'
        puts "* #{plan_content}".yellow
      end

      # Test plan (if not already).
      inherited_plan[role_name][plan_name] ||= plan_content

      # Inheritance present?
      i_plan = plan_content['inherits']
      unless i_plan.nil?
        i_role_name, i_plan_name = role_plan_split(i_plan)

        if i_role_name.nil? || i_plan_name.nil?
          puts "ERR: Invalid inheritance \"#{i_plan}\". Must match pattern \"role::plan\".".light_magenta
          exit 1
        end

        # TODO: Tratar quando chave não existe.
        puts "Plan #{plan_name} inherits #{i_plan}" if opts[:verbose] > 2
        inherited_plan[role_name][plan_name] = plan[i_role_name][i_plan_name].rmerge(plan_content)
      end
    end

    if opts[:verbose] > 2
      puts '=' * 60
      puts unless index == plan.size - 1
    end
  end

  if opts[:verbose] > 1
    puts 'Inheriting test plan... done.'.green
    puts
  end

  inherited_plan
end

#planner(opts, plan) ⇒ Object

Test plan builder.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/planner.rb', line 24

def planner(opts, plan)
  # Compila um plano completo.
  # Processa a herança.
  # Compila novamente para gerar um plano intermediário.
  #
  # Pass 1.
  if opts[:verbose] > 1
    puts '* Pass 1...'.cyan
    puts
  end
  compiled_plan1 = plan_compiler(opts, plan)
  inherited_plan1 = plan_inheriter(opts, compiled_plan1)
  recompiled_plan1 = plan_compiler(opts, inherited_plan1)
  if opts[:verbose] > 1
    puts '* Pass 1... done.'.green
    puts
  end

  # Zera o plano, mantendo apenas a estrutura de hashes. Desta forma é possível
  # gerar um esqueleto de plano com todas as entradas de test cases com valores
  # vazios (originais e herdados).
  # Logo em seguida ocorre um merge recursivo com os valores originais para
  # depois ser processado por completo novamente.
  # O objetivo é dar precedência aos valores globais locais sobre os valores
  # herdados.
  #
  # Blank and repopulate with original values.
  blanked_plan = recompiled_plan1.rblank
  if opts[:verbose] > 2
    puts 'Blanked plan:'
    puts "* #{blanked_plan}".yellow
  end

  repopulated_plan = blanked_plan.rmerge(compiled_plan1)
  if opts[:verbose] > 2
    puts 'Repopulated plan:'
    puts "* #{repopulated_plan}".yellow
  end

  # Compila um plano completo.
  # Processa a herança.
  # Compila novamente para gerar um plano final.
  #
  # Pass 2.
  if opts[:verbose] > 1
    puts '* Pass 2...'.cyan
    puts
  end
  compiled_plan2 = plan_compiler(opts, repopulated_plan)
  inherited_plan2 = plan_inheriter(opts, compiled_plan2)
  recompiled_plan2 = plan_compiler(opts, inherited_plan2)
  if opts[:verbose] > 1
    puts '* Pass 2... done.'.green
    puts
  end

  # Final plan.
  plan_filter(opts, recompiled_plan2)
end

#report(opts, tdiplan) ⇒ Object

Generate report file.



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/runner.rb', line 123

def report(opts, tdiplan)
  if opts[:verbose] > 2
    puts 'Report:'.cyan
    a_p tdiplan.report
  end

  if opts.reportfile?
    puts "Generating report file: \"#{opts[:reportfile]}\"".cyan if opts[:verbose] > 1
    File.open(opts[:reportfile], 'w') { |file| file.write(JSON.pretty_generate(tdiplan.report)) }
  end
end

#role_plan_split(name) ⇒ Object

Split role_name and plan_name.



307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/planner.rb', line 307

def role_plan_split(name)
  if name.include?('::')
    # Test plan only.
    f_role_name = name.split('::').first
    f_plan_name = name.split('::').last
  else
    # Role test plan (entire).
    f_role_name = name
    f_plan_name = nil
  end

  return f_role_name, f_plan_name
end

#runner(opts, filename, plan) ⇒ Object

Run tests.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/runner.rb', line 25

def runner(opts, filename, plan)
  puts 'Running tests...'.cyan if opts[:verbose] > 1

  # Skip reserved roles.
  # Ex: {"global": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
  # Ex: {"common": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
  plan.select { |role_name, role_content|
    if role_content.is_a?(Hash)
      UNTESTABLE_ROLE_LIST.include?(role_name) || role_content['notest'].eql?('true')
    end
  }.each_pair do |role_name, role_content|
    puts "Skipping reserved or disabled role: #{role_name}".yellow if opts[:verbose] > 0
  end

  # Remove untestable roles.
  plan.reject! { |role_name, role_content|
    if role_content.is_a?(Hash)
      UNTESTABLE_ROLE_LIST.include?(role_name) || role_content['notest'].eql?('true')
    end
  }
  total_roles = plan.select { |key, val| val.is_a?(Hash) }.size
  puts "Total roles to run: #{total_roles}".cyan if opts[:verbose] > 1

  # Run the rest.
  tdiplan = TDIPlan.new

  # Role.
  # Ex: {"admin": {"desc": "...", "acl": {"domain1": {"port": 80}...}...}...}
  plan.select { |key, val|
    val.is_a?(Hash)
  }.each_with_index do |(role_name, role_content), index|
    total_plans = role_content.select { |key, val| val.is_a?(Hash) }.size

    if role_content['desc'].nil?
      puts "* #{role_name.capitalize}".cyan
    else
      puts "* #{role_name.capitalize} - #{role_content['desc']}".cyan
    end
    puts "Total test plans to run for this role: #{total_plans}".cyan if opts[:verbose] > 1

    # Test plan.
    # Ex: {"acl": {"domain1": {"port": 80}...}...}
    role_content.select { |key, val|
      val.is_a?(Hash)
    }.each_pair do |plan_name, plan_content|
      total_cases = plan_content.select { |key, val| val.is_a?(Hash) }.size

      puts "* #{plan_name.upcase}".cyan
      puts "Total test cases to run for this plan: #{total_cases}".cyan if opts[:verbose] > 1

      if opts[:verbose] > 3
        puts "Plan: #{plan_name}"
        puts 'Plan content:'
        puts "* #{plan_content}".yellow
      end

      # Test plan content (test cases).
      # Ex: {"domain1": {"port": 80}, "domain2": {"port": 80}...}
      if tdiplan.respond_to?(plan_name)
        tdiplan.send(plan_name, role_name, plan_name, plan_content)
      else
        puts "Skipping not supported test plan type \"#{plan_name}\" for \"#{role_name}::#{plan_name}\".".yellow
        tdiplan.skip = tdiplan.skip + total_cases
      end
    end

    puts unless index == plan.size - 1
  end

  # Summary.
  summary(opts, tdiplan)

  # Report file.
  report(opts, tdiplan)

  # Shred.
  shred(opts, filename, tdiplan)

  puts 'Running tests... done.'.green if opts[:verbose] > 1

  ret = 0
  ret += 1 if tdiplan.fail > 0
  ret += 2 if opts.warnfail? && tdiplan.warn > 0
  ret = 0 if opts.nofail?
  # 1 if failures
  # 2 if warnings (only if -w/--warnfail is active)
  # 3 if both (only if -w/--warnfail is active)
  # 0 if none or if -n/--nofail is active
  ret
end

#shred(opts, filename, tdiplan) ⇒ Object

Remove tdi plan file.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/runner.rb', line 136

def shred(opts, filename, tdiplan)
  if opts.shred?
    puts "Shreding and removing test plan file: \"#{filename}\"...".cyan if opts[:verbose] > 2
    if OS.linux?
      shred_cmd = "shred -f -n 38 -u -z #{filename}"
    elsif OS.mac?
      shred_cmd = "srm -f -z #{filename}"
    else
      shred_cmd = "rm -f #{filename}"
    end
    puts "Shreding with command \"#{shred_cmd}\"...".cyan if opts[:verbose] > 2
    if system(shred_cmd)
      puts "Shreding and removing test plan file: \"#{filename}\"... done.".green if opts[:verbose] > 2
    else
      puts "ERR: Shreding and removing test plan file: \"#{filename}\".".light_magenta
    end
  end
end

#summary(opts, tdiplan) ⇒ Object

Display test summary.



117
118
119
120
# File 'lib/runner.rb', line 117

def summary(opts, tdiplan)
  puts '=' * 79
  puts "Total: #{tdiplan.total}  |  Skip: #{tdiplan.skip}  |  Pass: #{tdiplan.pass}  |  Warn: #{tdiplan.warn}  |  Fail: #{tdiplan.fail}"
end