• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env ruby
2# coding: binary
3#
4# Copyright 2015 Google Inc. All rights reserved
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18require 'fileutils'
19
20# suppress GNU make jobserver magic when calling "make"
21ENV.delete('MAKEFLAGS')
22ENV.delete('MAKELEVEL')
23
24while true
25  if ARGV[0] == '-s'
26    test_serialization = true
27    ARGV.shift
28  elsif ARGV[0] == '-c'
29    ckati = true
30    ARGV.shift
31    ENV['KATI_VARIANT'] = 'c'
32  elsif ARGV[0] == '-n'
33    via_ninja = true
34    ARGV.shift
35    ENV['NINJA_STATUS'] = 'NINJACMD: '
36  elsif ARGV[0] == '-a'
37    gen_all_targets = true
38    ARGV.shift
39  elsif ARGV[0] == '-v'
40    show_failing = true
41    ARGV.shift
42  elsif ARGV[0] == "-q"
43    hide_passing = true
44    ARGV.shift
45  else
46    break
47  end
48end
49
50def get_output_filenames
51  files = Dir.glob('*')
52  files.delete('Makefile')
53  files.delete('build.ninja')
54  files.delete('env.sh')
55  files.delete('ninja.sh')
56  files.delete('gmon.out')
57  files.delete('submake')
58  files.reject!{|f|f =~ /\.json$/}
59  files.reject!{|f|f =~ /^kati\.*/}
60  files
61end
62
63def cleanup
64  (get_output_filenames + Dir.glob('.*')).each do |fname|
65    next if fname == '.' || fname == '..'
66    FileUtils.rm_rf fname
67  end
68end
69
70def move_circular_dep(l)
71  # We don't care when circular dependency detection happens.
72  circ = ''
73  while l.sub!(/Circular .* dropped\.\n/, '') do
74    circ += $&
75  end
76  circ + l
77end
78
79expected_failures = []
80unexpected_passes = []
81failures = []
82passes = []
83
84if !ARGV.empty?
85  test_files = ARGV.map do |test|
86    "testcase/#{File.basename(test)}"
87  end
88else
89  test_files = Dir.glob('testcase/*.mk').sort
90  test_files += Dir.glob('testcase/*.sh').sort
91end
92
93def run_in_testdir(test_filename)
94  c = File.read(test_filename)
95  name = File.basename(test_filename)
96  dir = "out/#{name}"
97
98  FileUtils.mkdir_p(dir)
99  Dir.glob("#{dir}/*").each do |fname|
100    FileUtils.rm_rf(fname)
101  end
102
103  Dir.chdir(dir) do
104    yield name
105  end
106end
107
108def normalize_ninja_log(log, mk)
109  log.gsub!(/^NINJACMD: .*\n/, '')
110  log.gsub!(/^ninja: no work to do\.\n/, '')
111  log.gsub!(/^ninja: error: (.*, needed by .*),.*/,
112            '*** No rule to make target \\1.')
113  log.gsub!(/^ninja: warning: multiple rules generate (.*)\. builds involving this target will not be correct.*$/,
114            'ninja: warning: multiple rules generate \\1.')
115
116  if mk =~ /err_error_in_recipe.mk/
117    # This test expects ninja fails. Strip ninja specific error logs.
118    ninja_failed_subst = ''
119  elsif mk =~ /\/fail_/
120    # Recipes in these tests fail.
121    ninja_failed_subst = "*** [test] Error 1\n"
122  end
123  if ninja_failed_subst
124    log.gsub!(/^FAILED: (.*\n\/bin\/bash)?.*\n/, ninja_failed_subst)
125    log.gsub!(/^ninja: .*\n/, '')
126  end
127  log
128end
129
130def normalize_quotes(log)
131  log.gsub!(/[`'"]/, '"')
132  # For recent GNU find, which uses Unicode characters.
133  log.gsub!(/(\xe2\x80\x98|\xe2\x80\x99)/, '"')
134  log
135end
136
137def normalize_make_log(expected, mk, via_ninja)
138  expected = normalize_quotes(expected)
139  expected.gsub!(/^make(?:\[\d+\])?: (Entering|Leaving) directory.*\n/, '')
140  expected.gsub!(/^make(?:\[\d+\])?: /, '')
141  expected = move_circular_dep(expected)
142
143  # Normalizations for old/new GNU make.
144  expected.gsub!(' recipe for target ', ' commands for target ')
145  expected.gsub!(' recipe commences ', ' commands commence ')
146  expected.gsub!('missing rule before recipe.', 'missing rule before commands.')
147  expected.gsub!(' (did you mean TAB instead of 8 spaces?)', '')
148  expected.gsub!('Extraneous text after', 'extraneous text after')
149  # Not sure if this is useful.
150  expected.gsub!(/\s+Stop\.$/, '')
151  # GNU make 4.0 has this output.
152  expected.gsub!(/Makefile:\d+: commands for target ".*?" failed\n/, '')
153  # We treat some warnings as errors.
154  expected.gsub!(/^\/bin\/(ba)?sh: line 0: /, '')
155  # We print out some ninja warnings in some tests to match what we expect
156  # ninja to produce. Remove them if we're not testing ninja.
157  if !via_ninja
158    expected.gsub!(/^ninja: warning: .*\n/, '')
159  end
160  # Normalization for "include foo" with C++ kati.
161  expected.gsub!(/(: )(\S+): (No such file or directory)\n\*\*\* No rule to make target "\2"./, '\1\2: \3')
162
163  expected
164end
165
166def normalize_kati_log(output)
167  output = normalize_quotes(output)
168  output = move_circular_dep(output)
169
170  # kati specific log messages.
171  output.gsub!(/^\*kati\*.*\n/, '')
172  output.gsub!(/^c?kati: /, '')
173  output.gsub!(/\/bin\/sh: ([^:]*): command not found/,
174               "\\1: Command not found")
175  output.gsub!(/.*: warning for parse error in an unevaluated line: .*\n/, '')
176  output.gsub!(/^([^ ]+: )?FindEmulator: /, '')
177  output.gsub!(/^\/bin\/sh: line 0: /, '')
178  output.gsub!(/ (\.\/+)+kati\.\S+/, '') # kati log files in find_command.mk
179  output.gsub!(/ (\.\/+)+test\S+.json/, '') # json files in find_command.mk
180  # Normalization for "include foo" with Go kati.
181  output.gsub!(/(: )open (\S+): n(o such file or directory)\nNOTE:.*/,
182               "\\1\\2: N\\3")
183  # Bionic libc has different error messages than glibc
184  output.gsub!(/Too many symbolic links encountered/, 'Too many levels of symbolic links')
185  output
186end
187
188bash_var = ' SHELL=/bin/bash'
189
190run_make_test = proc do |mk|
191  c = File.read(mk)
192  expected_failure = false
193  if c =~ /\A# TODO(?:\(([-a-z|]+)\))?/
194    if $1
195      todos = $1.split('|')
196      if todos.include?('go') && !ckati
197        expected_failure = true
198      end
199      if todos.include?('c') && ckati
200        expected_failure = true
201      end
202      if todos.include?('go-ninja') && !ckati && via_ninja
203        expected_failure = true
204      end
205      if todos.include?('c-ninja') && ckati && via_ninja
206        expected_failure = true
207      end
208      if todos.include?('c-exec') && ckati && !via_ninja
209        expected_failure = true
210      end
211      if todos.include?('ninja') && via_ninja
212        expected_failure = true
213      end
214    else
215      expected_failure = true
216    end
217  end
218
219  run_in_testdir(mk) do |name|
220    File.open("Makefile", 'w') do |ofile|
221      ofile.print(c)
222    end
223    File.symlink('../../testcase/submake', 'submake')
224
225    expected = ''
226    output = ''
227
228    testcases = c.scan(/^test\d*/).sort.uniq
229    if testcases.empty?
230      testcases = ['']
231    end
232
233    is_silent_test = mk =~ /\/submake_/
234
235    cleanup
236    testcases.each do |tc|
237      cmd = 'make'
238      if via_ninja || is_silent_test
239        cmd += ' -s'
240      end
241      cmd += bash_var
242      cmd += " #{tc} 2>&1"
243      res = IO.popen(cmd, 'r:binary', &:read)
244      res = normalize_make_log(res, mk, via_ninja)
245      expected += "=== #{tc} ===\n" + res
246      expected_files = get_output_filenames
247      expected += "\n=== FILES ===\n#{expected_files * "\n"}\n"
248    end
249
250    cleanup
251    testcases.each do |tc|
252      json = "#{tc.empty? ? 'test' : tc}"
253      cmd = "../../kati -save_json=#{json}.json -log_dir=. --use_find_emulator"
254      if ckati
255        cmd = "../../ckati --use_find_emulator"
256      end
257      if via_ninja
258        cmd += ' --ninja'
259      end
260      if gen_all_targets
261        if !ckati || !via_ninja
262          raise "-a should be used with -c -n"
263        end
264        cmd += ' --gen_all_targets'
265      end
266      if is_silent_test
267        cmd += ' -s'
268      end
269      cmd += bash_var
270      if !gen_all_targets || mk =~ /makecmdgoals/
271        cmd += " #{tc}"
272      end
273      cmd += " 2>&1"
274      res = IO.popen(cmd, 'r:binary', &:read)
275      if via_ninja && File.exist?('build.ninja') && File.exists?('ninja.sh')
276        cmd = './ninja.sh -j1 -v'
277        if gen_all_targets
278          cmd += " #{tc}"
279        end
280        cmd += ' 2>&1'
281        log = IO.popen(cmd, 'r:binary', &:read)
282        res += normalize_ninja_log(log, mk)
283      end
284      res = normalize_kati_log(res)
285      output += "=== #{tc} ===\n" + res
286      output_files = get_output_filenames
287      output += "\n=== FILES ===\n#{output_files * "\n"}\n"
288    end
289
290    File.open('out.make', 'w'){|ofile|ofile.print(expected)}
291    File.open('out.kati', 'w'){|ofile|ofile.print(output)}
292
293    if expected =~ /FAIL/
294      puts %Q(#{name} has a string "FAIL" in its expectation)
295      exit 1
296    end
297
298    if expected != output
299      if expected_failure
300        if !hide_passing
301          puts "#{name}: FAIL (expected)"
302        end
303        expected_failures << name
304      else
305        puts "#{name}: FAIL"
306        failures << name
307      end
308      if !expected_failure || show_failing
309        puts `diff -u out.make out.kati`
310      end
311    else
312      if expected_failure
313        puts "#{name}: PASS (unexpected)"
314        unexpected_passes << name
315      else
316        if !hide_passing
317          puts "#{name}: PASS"
318        end
319        passes << name
320      end
321    end
322
323    if name !~ /^err_/ && test_serialization && !expected_failure
324      testcases.each do |tc|
325        json = "#{tc.empty? ? 'test' : tc}"
326        cmd = "../../kati -save_json=#{json}_2.json -load_json=#{json}.json -n -log_dir=. #{tc} 2>&1"
327        res = IO.popen(cmd, 'r:binary', &:read)
328        if !File.exist?("#{json}.json") || !File.exist?("#{json}_2.json")
329          puts "#{name}##{json}: Serialize failure (not exist)"
330          puts res
331        else
332          json1 = File.read("#{json}.json")
333          json2 = File.read("#{json}_2.json")
334          if json1 != json2
335            puts "#{name}##{json}: Serialize failure"
336            puts res
337          end
338        end
339      end
340    end
341  end
342end
343
344run_shell_test = proc do |sh|
345  is_ninja_test = sh =~ /\/ninja_/
346  if is_ninja_test && (!ckati || !via_ninja)
347    next
348  end
349
350  run_in_testdir(sh) do |name|
351    cleanup
352    cmd = "bash ../../#{sh} make"
353    if is_ninja_test
354      cmd += ' -s'
355    end
356    cmd += bash_var
357    expected = IO.popen(cmd, 'r:binary', &:read)
358    cleanup
359
360    if is_ninja_test
361      if ckati
362        cmd = "bash ../../#{sh} ../../ckati --ninja --regen"
363      else
364        next
365      end
366    else
367      if ckati
368        cmd = "bash ../../#{sh} ../../ckati"
369      else
370        cmd = "bash ../../#{sh} ../../kati --use_cache -log_dir=."
371      end
372    end
373    cmd += bash_var
374
375    output = IO.popen(cmd, 'r:binary', &:read)
376
377    expected = normalize_make_log(expected, sh, is_ninja_test)
378    output = normalize_kati_log(output)
379    if is_ninja_test
380      output = normalize_ninja_log(output, sh)
381    end
382    File.open('out.make', 'w'){|ofile|ofile.print(expected)}
383    File.open('out.kati', 'w'){|ofile|ofile.print(output)}
384
385    if expected != output
386      puts "#{name}: FAIL"
387      puts `diff -u out.make out.kati`
388      failures << name
389    else
390      if !hide_passing
391        puts "#{name}: PASS"
392      end
393      passes << name
394    end
395  end
396end
397
398test_files.each do |test|
399  if /\.mk$/ =~ test
400    run_make_test.call(test)
401  elsif /\.sh$/ =~ test
402    run_shell_test.call(test)
403  else
404    raise "Unknown test type: #{test}"
405  end
406end
407
408puts
409
410if !expected_failures.empty? && !hide_passing
411  puts "=== Expected failures ==="
412  expected_failures.each do |n|
413    puts n
414  end
415end
416
417if !unexpected_passes.empty?
418  puts "=== Unexpected passes ==="
419  unexpected_passes.each do |n|
420    puts n
421  end
422end
423
424if !failures.empty?
425  puts "=== Failures ==="
426  failures.each do |n|
427    puts n
428  end
429end
430
431puts
432
433if !unexpected_passes.empty? || !failures.empty?
434  puts "FAIL! (#{failures.size + unexpected_passes.size} fails #{passes.size} passes)"
435  exit 1
436else
437  puts 'PASS!'
438end
439