1#!/usr/bin/env python3 2 3"""Runs ./ninja and checks if the output is correct. 4 5In order to simulate a smart terminal it uses the 'script' command. 6""" 7 8import os 9import platform 10import subprocess 11import sys 12import tempfile 13import unittest 14 15default_env = dict(os.environ) 16default_env.pop('NINJA_STATUS', None) 17default_env.pop('CLICOLOR_FORCE', None) 18default_env['TERM'] = '' 19NINJA_PATH = os.path.abspath('./ninja') 20 21def run(build_ninja, flags='', pipe=False, env=default_env): 22 with tempfile.TemporaryDirectory() as d: 23 with open(os.path.join(d, 'build.ninja'), 'w') as f: 24 f.write(build_ninja) 25 f.flush() 26 ninja_cmd = '{} {}'.format(NINJA_PATH, flags) 27 try: 28 if pipe: 29 output = subprocess.check_output([ninja_cmd], shell=True, cwd=d, env=env) 30 elif platform.system() == 'Darwin': 31 output = subprocess.check_output(['script', '-q', '/dev/null', 'bash', '-c', ninja_cmd], 32 cwd=d, env=env) 33 else: 34 output = subprocess.check_output(['script', '-qfec', ninja_cmd, '/dev/null'], 35 cwd=d, env=env) 36 except subprocess.CalledProcessError as err: 37 sys.stdout.buffer.write(err.output) 38 raise err 39 final_output = '' 40 for line in output.decode('utf-8').splitlines(True): 41 if len(line) > 0 and line[-1] == '\r': 42 continue 43 final_output += line.replace('\r', '') 44 return final_output 45 46@unittest.skipIf(platform.system() == 'Windows', 'These test methods do not work on Windows') 47class Output(unittest.TestCase): 48 BUILD_SIMPLE_ECHO = '\n'.join(( 49 'rule echo', 50 ' command = printf "do thing"', 51 ' description = echo $out', 52 '', 53 'build a: echo', 54 '', 55 )) 56 57 def test_issue_1418(self): 58 self.assertEqual(run( 59'''rule echo 60 command = sleep $delay && echo $out 61 description = echo $out 62 63build a: echo 64 delay = 3 65build b: echo 66 delay = 2 67build c: echo 68 delay = 1 69''', '-j3'), 70'''[1/3] echo c\x1b[K 71c 72[2/3] echo b\x1b[K 73b 74[3/3] echo a\x1b[K 75a 76''') 77 78 def test_issue_1214(self): 79 print_red = '''rule echo 80 command = printf '\x1b[31mred\x1b[0m' 81 description = echo $out 82 83build a: echo 84''' 85 # Only strip color when ninja's output is piped. 86 self.assertEqual(run(print_red), 87'''[1/1] echo a\x1b[K 88\x1b[31mred\x1b[0m 89''') 90 self.assertEqual(run(print_red, pipe=True), 91'''[1/1] echo a 92red 93''') 94 # Even in verbose mode, colors should still only be stripped when piped. 95 self.assertEqual(run(print_red, flags='-v'), 96'''[1/1] printf '\x1b[31mred\x1b[0m' 97\x1b[31mred\x1b[0m 98''') 99 self.assertEqual(run(print_red, flags='-v', pipe=True), 100'''[1/1] printf '\x1b[31mred\x1b[0m' 101red 102''') 103 104 # CLICOLOR_FORCE=1 can be used to disable escape code stripping. 105 env = default_env.copy() 106 env['CLICOLOR_FORCE'] = '1' 107 self.assertEqual(run(print_red, pipe=True, env=env), 108'''[1/1] echo a 109\x1b[31mred\x1b[0m 110''') 111 112 def test_issue_1966(self): 113 self.assertEqual(run( 114'''rule cat 115 command = cat $rspfile $rspfile > $out 116 rspfile = cat.rsp 117 rspfile_content = a b c 118 119build a: cat 120''', '-j3'), 121'''[1/1] cat cat.rsp cat.rsp > a\x1b[K 122''') 123 124 125 def test_pr_1685(self): 126 # Running those tools without .ninja_deps and .ninja_log shouldn't fail. 127 self.assertEqual(run('', flags='-t recompact'), '') 128 self.assertEqual(run('', flags='-t restat'), '') 129 130 def test_issue_2048(self): 131 with tempfile.TemporaryDirectory() as d: 132 with open(os.path.join(d, 'build.ninja'), 'w'): 133 pass 134 135 with open(os.path.join(d, '.ninja_log'), 'w') as f: 136 f.write('# ninja log v4\n') 137 138 try: 139 output = subprocess.check_output([NINJA_PATH, '-t', 'recompact'], 140 cwd=d, 141 env=default_env, 142 stderr=subprocess.STDOUT, 143 text=True 144 ) 145 146 self.assertEqual( 147 output.strip(), 148 "ninja: warning: build log version is too old; starting over" 149 ) 150 except subprocess.CalledProcessError as err: 151 self.fail("non-zero exit code with: " + err.output) 152 153 def test_status(self): 154 self.assertEqual(run(''), 'ninja: no work to do.\n') 155 self.assertEqual(run('', pipe=True), 'ninja: no work to do.\n') 156 self.assertEqual(run('', flags='--quiet'), '') 157 158 def test_ninja_status_default(self): 159 'Do we show the default status by default?' 160 self.assertEqual(run(Output.BUILD_SIMPLE_ECHO), '[1/1] echo a\x1b[K\ndo thing\n') 161 162 def test_ninja_status_quiet(self): 163 'Do we suppress the status information when --quiet is specified?' 164 output = run(Output.BUILD_SIMPLE_ECHO, flags='--quiet') 165 self.assertEqual(output, 'do thing\n') 166 167 def test_entering_directory_on_stdout(self): 168 output = run(Output.BUILD_SIMPLE_ECHO, flags='-C$PWD', pipe=True) 169 self.assertEqual(output.splitlines()[0][:25], "ninja: Entering directory") 170 171 def test_tool_inputs(self): 172 plan = ''' 173rule cat 174 command = cat $in $out 175build out1 : cat in1 176build out2 : cat in2 out1 177build out3 : cat out2 out1 | implicit || order_only 178''' 179 self.assertEqual(run(plan, flags='-t inputs out3'), 180'''implicit 181in1 182in2 183order_only 184out1 185out2 186''') 187 188 189if __name__ == '__main__': 190 unittest.main() 191