Top Level Namespace

Defined Under Namespace

Modules: CookbookDecompiler Classes: TestbeatContext, TestbeatNode, TestbeatRestRequest, TestbeatShell, TestbeatShellStub, TestbeatShellVagrant

Constant Summary collapse

HTTP_VERBS =
{
  'COPY'      => Net::HTTP::Copy,
  'DELETE'    => Net::HTTP::Delete,
  'GET'       => Net::HTTP::Get,
  'HEAD'      => Net::HTTP::Head,
  'LOCK'      => Net::HTTP::Lock,
  'MKCOL'     => Net::HTTP::Mkcol,
  'MOVE'      => Net::HTTP::Move,
  'OPTIONS'   => Net::HTTP::Options,
  'PATCH'     => Net::HTTP::Patch,
  'POST'      => Net::HTTP::Post,
  'PROPFIND'  => Net::HTTP::Propfind,
  'PROPPATCH' => Net::HTTP::Proppatch,
  'PUT'       => Net::HTTP::Put,
  'TRACE'     => Net::HTTP::Trace,
  'UNLOCK'    => Net::HTTP::Unlock
}
HTTP_VERB_RESOURCE =
/^(COPY|DELETE|GET|HEAD|LOCK|MKCOL|MOVE|OPTIONS|PATCH|POST|PROPFIND|PROPPATCH|PUT|TRACE|UNLOCK)\s+(\S+)/

Instance Method Summary collapse

Instance Method Details

#get_cookbook_name(line) ⇒ Object



22
23
24
25
26
27
28
29
30
# File 'lib/vagrant/cookbook_decompiler.rb', line 22

def get_cookbook_name(line)
  name_match = /include_recipe.?"([^"]*)"/.match(line)
  if name_match
    just_name = name_match[1].split("::")[0]
    return just_name
  else
    raise "Line #{line} does not contain an include_recipe statement"
  end
end

#get_default_recipe(cookbook_name) ⇒ Object



8
9
10
# File 'lib/vagrant/cookbook_decompiler.rb', line 8

def get_default_recipe(cookbook_name)
  return File.open($cookbooks_dir + "/" + cookbook_name + "/recipes/default.rb","r")
end

#get_include_lines(file) ⇒ Object



12
13
14
15
16
17
18
19
20
# File 'lib/vagrant/cookbook_decompiler.rb', line 12

def get_include_lines(file)
  lines_with_include = []
  file.each_line do |line|
    if /include_recipe/.match(line)
      lines_with_include.push(line)
    end
  end
  return lines_with_include
end

#get_included_cookbooks(cookbook_name) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/vagrant/cookbook_decompiler.rb', line 36

def get_included_cookbooks(cookbook_name)
  just_name = remove_recipe_part(cookbook_name)
  recipe_default = get_default_recipe(just_name)
  lines = get_include_lines(recipe_default)
  names = []
  lines.each do |line|
    name = get_cookbook_name(line)
    names.push(name) unless names.include? name
  end
  return names
end

#get_match(pattern, text) ⇒ Object

Get first matching subgroup

stripped of quotes and leading/trailing whitespace


13
14
15
16
17
# File 'lib/vagrant/noderunner.rb', line 13

def get_match(pattern, text)
  matches = pattern.match(text)
  the_match = matches[1]
  the_match.delete("\"").strip()
end

#main(node: "labs01", provider: "virtualbox", retest: false, guestint: true, verbose: true) ⇒ Object

guestint: run on-guest integration tests



162
163
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
236
237
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
# File 'lib/vagrant/noderunner.rb', line 162

def main(node: "labs01", provider: "virtualbox", retest: false, guestint: true, verbose: true)
  [node, provider, retest, guestint, verbose]
  $node = node
  options = {}

  $chef_dir = ""
  $vagrant_dir = $chef_dir + "nodes/"
  $bats_test_tmp = $vagrant_dir + "bats_tmp/"

  $vagrant_file = $vagrant_dir + $node + "/Vagrantfile"
  #$vagrant_chef_dir = %x[ cd #{$vagrant_dir}#{node}; vagrant ssh -c "find /tmp/vagrant-chef/ -maxdepth 2 -type d -name cookbooks ]
  #"/tmp/vagrant-chef/chef-solo-1/"

  puts "### node: #{$node} (#{$vagrant_dir + $node}) ###"
  recipes = []


  if not (Dir.exists?($vagrant_dir + $node) and File.exists?($vagrant_file))
    $stderr.puts "No such Vagrant node #{ $node }"
    exit 1
  end

  # ----------------------------------------------------------------------------------
  # Start Vagrant or run provision on an already running node

  cwd_to_node = "cd #{ $vagrant_dir + $node}; "

  v_status = %x[ cd #{ $vagrant_dir + $node}; vagrant status ]
  runlist_file = "/tmp/#{$node}_testbeat.runlist";
  if /poweroff/.match(v_status) or /not created/.match(v_status)
    puts "Vagrant node not running, start and provision..."
    if File.exists?(runlist_file)
      File.delete(runlist_file)
    end
    vagrant_cmd = cwd_to_node + "vagrant up --provider=#{provider}"
  elsif /running/.match(v_status)
    # Add "if runlist file older than 1 h, assume force_long"
    if retest and File.exists?(runlist_file)
      old_run = File.read(runlist_file)
      #run_match = /Run List expands to \[(.*?)\]/.match(old_run)
      recipes = old_run.split(", ")
      print "Recipes (rerun based on #{runlist_file}): "
      puts recipes
      all_cookbooks = CookbookDecompiler.resolve_dependencies(recipes).to_a
      puts "All cookbooks included: " + all_cookbooks.join(", ")
      # code duplicated from uncached runlist below
      rspec_ok = true
      if guestint
        rspec_ok = rspec_ok && run_integration_tests(all_cookbooks)
      end
      rspec_ok = rspec_ok && run_tests(all_cookbooks)
      if not rspec_ok
        puts "There were test failures!"
        exit 1
      end
      puts "All tests for cached runlist passed"
      exit 0
    else
      puts "Vagrant node running, provision..."
      vagrant_cmd = cwd_to_node + "vagrant provision"
    end
  else
    $stderr.puts "Unknown Vagrant state: #{v_status}"
  end

  # ----------------------------------------------------------------------------------
  # Build an array consisting of custom tests to be compared with Vagrant provision
  # output

  # First we look up tests for our custom Vagrant output checker
  test_collection = []

  if options[:tests]
    tests = options[:tests].split(",")
    tests.each do |opt|
        test_file_path = opt
        if File.exists?(test_file_path)
        contents = File.read(test_file_path)
        obj = JSON.parse(contents)
        obj["tests"].each do |test|
          test_collection.push(test)
        end
      end
    end
  end

  #vagrant_run_output = %x[ export LANG=en_US.UTF-8; #{vagrant_cmd} ]
  vagrant_run_output = ''
  IO.popen(vagrant_cmd, :err=>[:child, :out], :external_encoding=>"UTF-8") do |io|
    io.each do |line|
      if verbose then puts line end
      vagrant_run_output << line + "\n"
    end
  end
  result = $?.success?

  if not result
    $stderr.puts "Vagrant run failed! See output below"
    $stderr.puts vagrant_run_output
    exit 1
  else
    puts "Vagrant provision completed."
    # Run List expands to [repos-channel::haproxy, cms-base::folderstructure, repos-apache2, repos-subversion, repos-rweb, repos-trac, repos-liveserver, repos-indexing, repos-snapshot, repos-vagrant-labs]
    run_match = /Run List expands to \[(.*?)\]/.match(vagrant_run_output)
    if run_match
      dump_file = File.new("/tmp/#{$node}_testbeat.runlist","w+",0755)
      dump_file.write(run_match[1]) # should be run_match[1] but role-leanserver edit above...
      dump_file.close()

      recipes = run_match[1].split(", ")
      puts "Run list extracted from Vagrant: " + recipes.join(", ")
      all_cookbooks = CookbookDecompiler.resolve_dependencies(recipes).to_a
      puts "All cookbooks included: " + all_cookbooks.join(", ")
      puts "test_collection (presumably not used anymore): " + test_collection.join(", ");
      # the run code has been duplicated for cached runlist above
      rspec_ok = true
      if guestint
        rspec_ok = rspec_ok && run_integration_tests(all_cookbooks)
      end
      rspec_ok = rspec_ok && run_tests(all_cookbooks)
      if not rspec_ok
        exit 1
      end
    else
      puts "Unable to find text 'Run List expands to' in Vagrant output :("
    end
  end

end

#remove_recipe_part(name) ⇒ Object



32
33
34
# File 'lib/vagrant/cookbook_decompiler.rb', line 32

def remove_recipe_part(name)
  return name.split("::")[0]
end

#run_integration_tests(recipes) ⇒ Object



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
# File 'lib/vagrant/noderunner.rb', line 29

def run_integration_tests(recipes)
  # Now its time for the bats tests.
  integration_tests =  []
  repos_backup_testing = false
  # Next we reduce recipes to cookbooks
  if recipes.include?("repos-backup")
    recipes.delete("repos-backup")
    repos_backup_testing = true
  end

  integration_result_format = "--format RspecJunitFormatter"
  node_path_to_cookbooks = ""
  sync_data = File.read($vagrant_dir + $node + "/.vagrant/machines/default/virtualbox/synced_folders")
  sync_info = JSON.parse(sync_data)
  sync_info['virtualbox'].each do |key,value|
    if /cookbooks/.match(value['hostpath'])
      node_path_to_cookbooks = value['guestpath'].split("/")[0..-2].join("/")
      break
    end
  end
  recipes.each do |recipe|
    # break off sub-recipe name from cookbook name
    #  repos-channel::haproxy -> repos-channel
    puts "Recipe: #{recipe}"
    cookbook_name = recipe.split("::")[0]
    path_to_cookbook_integration_tests = "cookbooks/#{cookbook_name}/test/integration/default"
    if Dir.exists?(path_to_cookbook_integration_tests)
      puts "Found: #{path_to_cookbook_integration_tests}"
      Dir.glob("#{path_to_cookbook_integration_tests}/*.rb").each do |path_to_file|
        # /tmp/vagrant-chef-?/chef-solo-1/
        # cd #{$vagrant_dir}#{node}; vagrant ssh -c
        puts "node_path_to_cookbooks: #{node_path_to_cookbooks}. path_to_file: #{path_to_file}"
        integration_tests.push("rspec #{integration_result_format} #{node_path_to_cookbooks}/#{path_to_file} > /vagrant/generated_integration_results_#{cookbook_name}.xml")
      end
    else
      puts "Couldn't find #{path_to_cookbook_integration_tests}"
    end
  end
  path_to_node_integration_tests = "#{$vagrant_dir}#{$node}/integration/default"
  if Dir.exists?(path_to_node_integration_tests)
    Dir.glob("#{path_to_node_integration_tests}/*.rb").each do |path_to_file|
      just_filename = path_to_file.split("/")[-1]
      integration_tests.push("rspec #{integration_result_format} /vagrant/integration/default/#{just_filename} > /vagrant/generated_integration_results_#{$node}.xml")
    end
  else
    puts "No node-specific integration tests available for #{$node} in #{path_to_node_integration_tests}"
  end

  if integration_tests.empty?()
    puts "No integration tests for node #{$node}"
  else
    run_integration_tests_bats(integration_tests)
  end
  if repos_backup_testing
    system("cd #{ $vagrant_dir + $node }; vagrant ssh -c \"sudo chef-solo -j /tmp/vagrant-chef/dna.json -c /tmp/vagrant-chef/solo.rb -o 'recipe[cms-base],recipe[repos-backup]'\"")
  end

  #backup_test_cmd = "rspec -f j /tmp/vagrant-chef-?/chef-solo-1/cookbooks/repos-backup/test/integration/default/repos-backup_spec.rb > integration_repos-backup.txt"
  #run_integration_tests_bats([backup_test_cmd])
end

#run_integration_tests_bats(tests) ⇒ Object

Prepared commands from run_tests are fed in here.



20
21
22
23
24
25
26
27
# File 'lib/vagrant/noderunner.rb', line 20

def run_integration_tests_bats(tests)
  puts "Running bats tests for cookbooks."

  tests.sort().each do |cmd|
    #print "Running bats test #{cmd}"
    system("cd #{ $vagrant_dir + $node }; vagrant ssh -c \"#{cmd}\"")
  end
end

#run_rspec(node, path, outf, verbose) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/vagrant/noderunner.rb', line 90

def run_rspec(node, path, outf, verbose)
  puts "Starting test: #{path}"
  rspec_cmd = "NODE=#{node} rspec #{path} --format documentation --out #{outf}.txt --format html --out #{outf}.html --format RspecJunitFormatter --out #{outf}.xml --format progress"
  IO.popen(rspec_cmd, :err=>[:child, :out], :external_encoding=>"UTF-8") do |io|
    io.each_char do |c|
      if verbose then
        $stdout.print c
        if c == '.' or c == 'F' or c == "\n" then $stdout.flush end
      end
    end
  end
  return $?.success?
end

#run_tests(recipes, out: "#{$vagrant_dir}#{$node}/generated/", testglob: "test/acceptance/*.rb", verbose: true) ⇒ Object



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
# File 'lib/vagrant/noderunner.rb', line 104

def run_tests(recipes, out: "#{$vagrant_dir}#{$node}/generated/", testglob: "test/acceptance/*.rb", verbose: true)
  [recipes, out, testglob, verbose]
  rspec_ok = true # if no tests => no failure

  if Dir.exists?(out)
    puts "Using existing output directory #{out}"
    Dir.glob("#{out}acceptance*").each do |previous|
      File.delete(previous);
    end
  else
    puts "Creating output directory #{out}"
  end

  run_history = Set.new()
  recipes.each do |recipe|
    # break off sub-recipe name from cookbook name
    #  repos-channel::haproxy -> repos-channel
    #puts "Recipe: " + recipe
    cookbook_name = recipe.split("::")[0]
    cookbook_specs = Dir.glob("cookbooks/#{cookbook_name}/#{testglob}")
    if cookbook_specs.length == 0
      puts "No generic acceptance tests available for #{recipe} in #{$chef_dir}cookbooks/#{cookbook_name}/test/acceptance"
    end
    cookbook_specs.each do |path_to_file|
      if not run_history.include?(path_to_file)
        run_history.add(path_to_file)
        just_filename = path_to_file.split("/")[-1]
        rspec_ok = run_rspec($node, path_to_file, "#{out}acceptance_#{cookbook_name}_#{just_filename}", verbose) && rspec_ok
      end
    end
  end

  path_to_node_acceptance_tests = "#{$vagrant_dir}#{$node}/acceptance"
  if Dir.exists?(path_to_node_acceptance_tests)
    rspec_ok = run_rspec($node, "#{path_to_node_acceptance_tests}/*.rb", "#{out}acceptance_node", verbose) && rspec_ok
  else
    puts "No node-specific acceptance tests available for #{$node} in #{path_to_node_acceptance_tests}"
  end

  # Print a summary in the end
  concat = File.open("#{out}acceptance.txt", "w")
  Dir.glob("#{out}acceptance_*.txt").each do |generated_docs|
    puts generated_docs
    concat.write("#" + generated_docs)
    File.readlines(generated_docs).each do |line|
      concat.write(line)
      results = /^\d+ examples, (\d+) failure.?/.match(line)
      if results
        puts line
      end
    end
  end
  concat.close unless concat.nil?

  return rspec_ok
end