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