| #!/usr/bin/env ruby |
| # coding: binary |
| # |
| # Copyright 2015 Google Inc. All rights reserved |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| require 'fileutils' |
| |
| # suppress GNU make jobserver magic when calling "make" |
| ENV.delete('MAKEFLAGS') |
| ENV.delete('MAKELEVEL') |
| |
| while true |
| if ARGV[0] == '-s' |
| test_serialization = true |
| ARGV.shift |
| elsif ARGV[0] == '-c' |
| ckati = true |
| ARGV.shift |
| ENV['KATI_VARIANT'] = 'c' |
| elsif ARGV[0] == '-n' |
| via_ninja = true |
| ARGV.shift |
| ENV['NINJA_STATUS'] = 'NINJACMD: ' |
| elsif ARGV[0] == '-a' |
| gen_all_targets = true |
| ARGV.shift |
| elsif ARGV[0] == '-v' |
| show_failing = true |
| ARGV.shift |
| else |
| break |
| end |
| end |
| |
| def get_output_filenames |
| files = Dir.glob('*') |
| files.delete('Makefile') |
| files.delete('build.ninja') |
| files.delete('env.sh') |
| files.delete('ninja.sh') |
| files.delete('gmon.out') |
| files.delete('submake') |
| files.reject!{|f|f =~ /\.json$/} |
| files.reject!{|f|f =~ /^kati\.*/} |
| files |
| end |
| |
| def cleanup |
| (get_output_filenames + Dir.glob('.*')).each do |fname| |
| next if fname == '.' || fname == '..' |
| FileUtils.rm_rf fname |
| end |
| end |
| |
| def move_circular_dep(l) |
| # We don't care when circular dependency detection happens. |
| circ = '' |
| while l.sub!(/Circular .* dropped\.\n/, '') do |
| circ += $& |
| end |
| circ + l |
| end |
| |
| expected_failures = [] |
| unexpected_passes = [] |
| failures = [] |
| passes = [] |
| |
| if !ARGV.empty? |
| test_files = ARGV.map do |test| |
| "testcase/#{File.basename(test)}" |
| end |
| else |
| test_files = Dir.glob('testcase/*.mk').sort |
| test_files += Dir.glob('testcase/*.sh').sort |
| end |
| |
| def run_in_testdir(test_filename) |
| c = File.read(test_filename) |
| name = File.basename(test_filename) |
| dir = "out/#{name}" |
| |
| FileUtils.mkdir_p(dir) |
| Dir.glob("#{dir}/*").each do |fname| |
| FileUtils.rm_rf(fname) |
| end |
| |
| Dir.chdir(dir) do |
| yield name |
| end |
| end |
| |
| def normalize_ninja_log(log, mk) |
| log.gsub!(/^NINJACMD: .*\n/, '') |
| log.gsub!(/^ninja: no work to do\.\n/, '') |
| log.gsub!(/^ninja: error: (.*, needed by .*),.*/, |
| '*** No rule to make target \\1.') |
| log.gsub!(/^ninja: warning: multiple rules generate (.*)\. builds involving this target will not be correct.*$/, |
| 'ninja: warning: multiple rules generate \\1.') |
| |
| if mk =~ /err_error_in_recipe.mk/ |
| # This test expects ninja fails. Strip ninja specific error logs. |
| ninja_failed_subst = '' |
| elsif mk =~ /\/fail_/ |
| # Recipes in these tests fail. |
| ninja_failed_subst = "*** [test] Error 1\n" |
| end |
| if ninja_failed_subst |
| log.gsub!(/^FAILED: (.*\n\/bin\/bash)?.*\n/, ninja_failed_subst) |
| log.gsub!(/^ninja: .*\n/, '') |
| end |
| log |
| end |
| |
| def normalize_quotes(log) |
| log.gsub!(/[`'"]/, '"') |
| # For recent GNU find, which uses Unicode characters. |
| log.gsub!(/(\xe2\x80\x98|\xe2\x80\x99)/, '"') |
| log |
| end |
| |
| def normalize_make_log(expected, mk, via_ninja) |
| expected = normalize_quotes(expected) |
| expected.gsub!(/^make(?:\[\d+\])?: (Entering|Leaving) directory.*\n/, '') |
| expected.gsub!(/^make(?:\[\d+\])?: /, '') |
| expected = move_circular_dep(expected) |
| |
| # Normalizations for old/new GNU make. |
| expected.gsub!(' recipe for target ', ' commands for target ') |
| expected.gsub!(' recipe commences ', ' commands commence ') |
| expected.gsub!('missing rule before recipe.', 'missing rule before commands.') |
| expected.gsub!(' (did you mean TAB instead of 8 spaces?)', '') |
| expected.gsub!('Extraneous text after', 'extraneous text after') |
| # Not sure if this is useful. |
| expected.gsub!(/\s+Stop\.$/, '') |
| # GNU make 4.0 has this output. |
| expected.gsub!(/Makefile:\d+: commands for target ".*?" failed\n/, '') |
| # We treat some warnings as errors. |
| expected.gsub!(/^\/bin\/(ba)?sh: line 0: /, '') |
| # We print out some ninja warnings in some tests to match what we expect |
| # ninja to produce. Remove them if we're not testing ninja. |
| if !via_ninja |
| expected.gsub!(/^ninja: warning: .*\n/, '') |
| end |
| # Normalization for "include foo" with C++ kati. |
| expected.gsub!(/(: )(\S+): (No such file or directory)\n\*\*\* No rule to make target "\2"./, '\1\2: \3') |
| |
| expected |
| end |
| |
| def normalize_kati_log(output) |
| output = normalize_quotes(output) |
| output = move_circular_dep(output) |
| |
| # kati specific log messages. |
| output.gsub!(/^\*kati\*.*\n/, '') |
| output.gsub!(/^c?kati: /, '') |
| output.gsub!(/\/bin\/sh: ([^:]*): command not found/, |
| "\\1: Command not found") |
| output.gsub!(/.*: warning for parse error in an unevaluated line: .*\n/, '') |
| output.gsub!(/^([^ ]+: )?FindEmulator: /, '') |
| output.gsub!(/^\/bin\/sh: line 0: /, '') |
| output.gsub!(/ (\.\/+)+kati\.\S+/, '') # kati log files in find_command.mk |
| output.gsub!(/ (\.\/+)+test\S+.json/, '') # json files in find_command.mk |
| # Normalization for "include foo" with Go kati. |
| output.gsub!(/(: )open (\S+): n(o such file or directory)\nNOTE:.*/, |
| "\\1\\2: N\\3") |
| output |
| end |
| |
| bash_var = ' SHELL=/bin/bash' |
| |
| run_make_test = proc do |mk| |
| c = File.read(mk) |
| expected_failure = false |
| if c =~ /\A# TODO(?:\(([-a-z|]+)\))?/ |
| if $1 |
| todos = $1.split('|') |
| if todos.include?('go') && !ckati |
| expected_failure = true |
| end |
| if todos.include?('c') && ckati |
| expected_failure = true |
| end |
| if todos.include?('go-ninja') && !ckati && via_ninja |
| expected_failure = true |
| end |
| if todos.include?('c-ninja') && ckati && via_ninja |
| expected_failure = true |
| end |
| if todos.include?('c-exec') && ckati && !via_ninja |
| expected_failure = true |
| end |
| if todos.include?('ninja') && via_ninja |
| expected_failure = true |
| end |
| else |
| expected_failure = true |
| end |
| end |
| |
| run_in_testdir(mk) do |name| |
| File.open("Makefile", 'w') do |ofile| |
| ofile.print(c) |
| end |
| File.symlink('../../testcase/submake', 'submake') |
| |
| expected = '' |
| output = '' |
| |
| testcases = c.scan(/^test\d*/).sort.uniq |
| if testcases.empty? |
| testcases = [''] |
| end |
| |
| is_silent_test = mk =~ /\/submake_/ |
| |
| cleanup |
| testcases.each do |tc| |
| cmd = 'make' |
| if via_ninja || is_silent_test |
| cmd += ' -s' |
| end |
| cmd += bash_var |
| cmd += " #{tc} 2>&1" |
| res = IO.popen(cmd, 'r:binary', &:read) |
| res = normalize_make_log(res, mk, via_ninja) |
| expected += "=== #{tc} ===\n" + res |
| expected_files = get_output_filenames |
| expected += "\n=== FILES ===\n#{expected_files * "\n"}\n" |
| end |
| |
| cleanup |
| testcases.each do |tc| |
| json = "#{tc.empty? ? 'test' : tc}" |
| cmd = "../../kati -save_json=#{json}.json -log_dir=. --use_find_emulator" |
| if ckati |
| cmd = "../../ckati --use_find_emulator" |
| end |
| if via_ninja |
| cmd += ' --ninja' |
| end |
| if gen_all_targets |
| if !ckati || !via_ninja |
| raise "-a should be used with -c -n" |
| end |
| cmd += ' --gen_all_targets' |
| end |
| if is_silent_test |
| cmd += ' -s' |
| end |
| cmd += bash_var |
| if !gen_all_targets || mk =~ /makecmdgoals/ |
| cmd += " #{tc}" |
| end |
| cmd += " 2>&1" |
| res = IO.popen(cmd, 'r:binary', &:read) |
| if via_ninja && File.exist?('build.ninja') && File.exists?('ninja.sh') |
| cmd = './ninja.sh -j1 -v' |
| if gen_all_targets |
| cmd += " #{tc}" |
| end |
| cmd += ' 2>&1' |
| log = IO.popen(cmd, 'r:binary', &:read) |
| res += normalize_ninja_log(log, mk) |
| end |
| res = normalize_kati_log(res) |
| output += "=== #{tc} ===\n" + res |
| output_files = get_output_filenames |
| output += "\n=== FILES ===\n#{output_files * "\n"}\n" |
| end |
| |
| File.open('out.make', 'w'){|ofile|ofile.print(expected)} |
| File.open('out.kati', 'w'){|ofile|ofile.print(output)} |
| |
| if expected =~ /FAIL/ |
| puts %Q(#{name} has a string "FAIL" in its expectation) |
| exit 1 |
| end |
| |
| if expected != output |
| if expected_failure |
| puts "#{name}: FAIL (expected)" |
| expected_failures << name |
| else |
| puts "#{name}: FAIL" |
| failures << name |
| end |
| if !expected_failure || show_failing |
| puts `diff -u out.make out.kati` |
| end |
| else |
| if expected_failure |
| puts "#{name}: PASS (unexpected)" |
| unexpected_passes << name |
| else |
| puts "#{name}: PASS" |
| passes << name |
| end |
| end |
| |
| if name !~ /^err_/ && test_serialization && !expected_failure |
| testcases.each do |tc| |
| json = "#{tc.empty? ? 'test' : tc}" |
| cmd = "../../kati -save_json=#{json}_2.json -load_json=#{json}.json -n -log_dir=. #{tc} 2>&1" |
| res = IO.popen(cmd, 'r:binary', &:read) |
| if !File.exist?("#{json}.json") || !File.exist?("#{json}_2.json") |
| puts "#{name}##{json}: Serialize failure (not exist)" |
| puts res |
| else |
| json1 = File.read("#{json}.json") |
| json2 = File.read("#{json}_2.json") |
| if json1 != json2 |
| puts "#{name}##{json}: Serialize failure" |
| puts res |
| end |
| end |
| end |
| end |
| end |
| end |
| |
| run_shell_test = proc do |sh| |
| is_ninja_test = sh =~ /\/ninja_/ |
| if is_ninja_test && (!ckati || !via_ninja) |
| next |
| end |
| |
| run_in_testdir(sh) do |name| |
| cleanup |
| cmd = "sh ../../#{sh} make" |
| if is_ninja_test |
| cmd += ' -s' |
| end |
| cmd += bash_var |
| expected = IO.popen(cmd, 'r:binary', &:read) |
| cleanup |
| |
| if is_ninja_test |
| if ckati |
| cmd = "sh ../../#{sh} ../../ckati --ninja --regen" |
| else |
| next |
| end |
| else |
| if ckati |
| cmd = "sh ../../#{sh} ../../ckati" |
| else |
| cmd = "sh ../../#{sh} ../../kati --use_cache -log_dir=." |
| end |
| end |
| cmd += bash_var |
| |
| output = IO.popen(cmd, 'r:binary', &:read) |
| |
| expected = normalize_make_log(expected, sh, is_ninja_test) |
| output = normalize_kati_log(output) |
| if is_ninja_test |
| output = normalize_ninja_log(output, sh) |
| end |
| File.open('out.make', 'w'){|ofile|ofile.print(expected)} |
| File.open('out.kati', 'w'){|ofile|ofile.print(output)} |
| |
| if expected != output |
| puts "#{name}: FAIL" |
| puts `diff -u out.make out.kati` |
| failures << name |
| else |
| puts "#{name}: PASS" |
| passes << name |
| end |
| end |
| end |
| |
| test_files.each do |test| |
| if /\.mk$/ =~ test |
| run_make_test.call(test) |
| elsif /\.sh$/ =~ test |
| run_shell_test.call(test) |
| else |
| raise "Unknown test type: #{test}" |
| end |
| end |
| |
| puts |
| |
| if !expected_failures.empty? |
| puts "=== Expected failures ===" |
| expected_failures.each do |n| |
| puts n |
| end |
| end |
| |
| if !unexpected_passes.empty? |
| puts "=== Unexpected passes ===" |
| unexpected_passes.each do |n| |
| puts n |
| end |
| end |
| |
| if !failures.empty? |
| puts "=== Failures ===" |
| failures.each do |n| |
| puts n |
| end |
| end |
| |
| puts |
| |
| if !unexpected_passes.empty? || !failures.empty? |
| puts "FAIL! (#{failures.size + unexpected_passes.size} fails #{passes.size} passes)" |
| exit 1 |
| else |
| puts 'PASS!' |
| end |