#!/usr/bin/env python3
import ninja
import os
import unittest
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
TEST_DATA_DIR = os.path.join(TEST_DIR, 'testdata')
ENCODING = 'utf-8'
class MockedParser(ninja.Parser):
def __init__(self, *args, **kwargs):
super(MockedParser, self).__init__(*args, **kwargs)
self.mocked_env = []
def _push_context(self, lexer, env):
super(MockedParser, self)._push_context(lexer, env)
self.mocked_env.append(env)
class EvalStringTest(unittest.TestCase):
def test_empty(self):
s = ninja.EvalStringBuilder().getvalue()
self.assertFalse(s)
self.assertEqual('', ninja.eval_string(s, ninja.EvalEnv()))
def test_append_raw(self):
s = ninja.EvalStringBuilder().append_raw('a').getvalue()
self.assertTrue(s)
self.assertEqual('a', ninja.eval_string(s, ninja.EvalEnv()))
def test_append_raw_concat(self):
sb = ninja.EvalStringBuilder()
sb.append_raw('a')
sb.append_raw('b')
s = sb.getvalue()
self.assertTrue(s)
self.assertEqual('ab', ninja.eval_string(s, ninja.EvalEnv()))
def test_append_var(self):
s = ninja.EvalStringBuilder().append_var('key').getvalue()
self.assertTrue(s)
def test_var_eval(self):
env = ninja.EvalEnv()
env['key'] = ninja.EvalStringBuilder().append_raw('value').getvalue()
s = ninja.EvalStringBuilder().append_var('key').getvalue()
self.assertEqual('value', ninja.eval_string(s, env))
def test_var_concat_eval(self):
env = ninja.EvalEnv()
env['key1'] = ninja.EvalStringBuilder().append_raw('a').getvalue()
env['key2'] = ninja.EvalStringBuilder().append_raw('b').getvalue()
sb = ninja.EvalStringBuilder()
sb.append_var('key1')
sb.append_var('key2')
s = sb.getvalue()
self.assertEqual('ab', ninja.eval_string(s, env))
def test_var_repeat_eval(self):
env = ninja.EvalEnv()
env['key1'] = ninja.EvalStringBuilder().append_raw('a').getvalue()
env['key2'] = ninja.EvalStringBuilder().append_raw('b').getvalue()
sb = ninja.EvalStringBuilder()
sb.append_var('key1')
sb.append_var('key1')
sb.append_var('key2')
sb.append_var('key1')
sb.append_var('key2')
s = sb.getvalue()
self.assertEqual('aabab', ninja.eval_string(s, env))
def test_var_recursive_eval(self):
env = ninja.EvalEnv()
env['a'] = ninja.EvalStringBuilder().append_var('b').getvalue()
env['c'] = ninja.EvalStringBuilder().append_raw('d').getvalue()
sb = ninja.EvalStringBuilder()
sb.append_var('c')
sb.append_var('c')
env['b'] = sb.getvalue()
sb = ninja.EvalStringBuilder()
sb.append_var('a')
sb.append_var('a')
s = sb.getvalue()
self.assertEqual('dddd', ninja.eval_string(s, env))
def test_unknown_variable_eval_error(self):
s = ninja.EvalStringBuilder().append_var('a').getvalue()
self.assertEqual('', ninja.eval_string(s, ninja.EvalEnv()))
def test_circular_eval_eval_error(self):
env = ninja.EvalEnv()
env['a'] = ninja.EvalStringBuilder().append_var('b').getvalue()
env['b'] = ninja.EvalStringBuilder().append_var('b').getvalue()
s = ninja.EvalStringBuilder().append_var('a').getvalue()
with self.assertRaises(ninja.EvalCircularError):
ninja.eval_string(s, env)
def test_raw_and_var_eval(self):
env = ninja.EvalEnv()
env['b'] = ninja.EvalStringBuilder().append_raw('d').getvalue()
sb = ninja.EvalStringBuilder()
sb.append_raw('a')
sb.append_var('b')
sb.append_raw('c')
s = sb.getvalue()
self.assertEqual('adc', ninja.eval_string(s, env))
class ParseErrorTest(unittest.TestCase):
def test_repr(self):
ex = ninja.ParseError('build.ninja', 5, 1)
self.assertEqual('ParseError: build.ninja:5:1', repr(ex))
ex = ninja.ParseError('build.ninja', 5, 1, 'invalid char')
self.assertEqual('ParseError: build.ninja:5:1: invalid char', repr(ex))
class LexerTest(unittest.TestCase):
def test_peek_skip_comment(self):
lexer = ninja.Lexer(['#comment'])
tok = lexer.peek()
self.assertEqual(ninja.TK.EOF, tok.kind)
def test_peek_skip_comment_line(self):
lexer = ninja.Lexer(['#comment\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
lexer = ninja.Lexer([' #comment\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
lexer = ninja.Lexer(['\t#comment\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
lexer = ninja.Lexer([' \t#comment\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
def test_peek_skip_empty_line(self):
lexer = ninja.Lexer([' \n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
lexer = ninja.Lexer(['\t\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
lexer = ninja.Lexer([' \t\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
def test_peek_newline(self):
lexer = ninja.Lexer(['\n'])
tok = lexer.peek()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
def test_peek_space(self):
lexer = ninja.Lexer([' a'])
tok = lexer.peek()
self.assertEqual(ninja.TK.SPACE, tok.kind)
tok = lexer.peek() # Again
self.assertEqual(ninja.TK.SPACE, tok.kind) # Not changed
tok = lexer.lex() # Consume
self.assertEqual(ninja.TK.SPACE, tok.kind) # Not changed
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
def test_lex_space(self):
lexer = ninja.Lexer([' '])
tok = lexer.lex()
self.assertEqual(ninja.TK.SPACE, tok.kind)
lexer = ninja.Lexer(['\t'])
tok = lexer.lex()
self.assertEqual(ninja.TK.SPACE, tok.kind)
lexer = ninja.Lexer(['\t '])
tok = lexer.lex()
self.assertEqual(ninja.TK.SPACE, tok.kind)
lexer = ninja.Lexer([' \t'])
tok = lexer.lex()
self.assertEqual(ninja.TK.SPACE, tok.kind)
lexer = ninja.Lexer([' a'])
tok = lexer.lex()
self.assertEqual(ninja.TK.SPACE, tok.kind)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
def test_lex_skip_space(self):
lexer = ninja.Lexer(['a b'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(3, tok.column)
def test_lex_skip_space_newline_escape(self):
lexer = ninja.Lexer(['build $\n', ' \texample'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(2, tok.line)
self.assertEqual(3, tok.column)
lexer = ninja.Lexer(['build $\n', 'example'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(2, tok.line)
self.assertEqual(1, tok.column)
lexer = ninja.Lexer(['build a:$\n', 'example'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(7, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.COLON, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(8, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(2, tok.line)
self.assertEqual(1, tok.column)
# Multiple newline escapes.
lexer = ninja.Lexer(['build $\n', '$\n', '$\n', 'example'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(4, tok.line)
self.assertEqual(1, tok.column)
def test_peek_space_after_newline(self):
lexer = ninja.Lexer(['a b\n', ' c'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(3, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.NEWLINE, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(4, tok.column)
# A space token must be emitted.
tok = lexer.lex()
self.assertEqual(ninja.TK.SPACE, tok.kind)
self.assertEqual(2, tok.line)
self.assertEqual(1, tok.column)
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
self.assertEqual(2, tok.line)
self.assertEqual(2, tok.column)
def test_lex_ident(self):
lexer = ninja.Lexer(['abcdefghijklmnopqrstuvwxyz'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
lexer = ninja.Lexer(['ABCDEFGHIJKLMNOPQRSTUVWXYZ'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
lexer = ninja.Lexer(['0123456789'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
lexer = ninja.Lexer(['.'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
lexer = ninja.Lexer(['-'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
lexer = ninja.Lexer(['_'])
tok = lexer.lex()
self.assertEqual(ninja.TK.IDENT, tok.kind)
def test_lex_assign(self):
lexer = ninja.Lexer(['='])
tok = lexer.lex()
self.assertEqual(ninja.TK.ASSIGN, tok.kind)
def test_lex_colon(self):
lexer = ninja.Lexer([':'])
tok = lexer.lex()
self.assertEqual(ninja.TK.COLON, tok.kind)
def test_lex_pipe(self):
lexer = ninja.Lexer(['|'])
tok = lexer.lex()
self.assertEqual(ninja.TK.PIPE, tok.kind)
def test_lex_pipe2(self):
lexer = ninja.Lexer(['||'])
tok = lexer.lex()
self.assertEqual(ninja.TK.PIPE2, tok.kind)
def test_lex_non_trivial(self):
lexer = ninja.Lexer(['$name'])
with self.assertRaises(ninja.ParseError):
lexer.lex()
lexer = ninja.Lexer(['${name}'])
with self.assertRaises(ninja.ParseError):
lexer.lex()
def test_lex_match(self):
lexer = ninja.Lexer(['ident'])
with self.assertRaises(ninja.ParseError):
lexer.lex_match({ninja.TK.PIPE})
def test_lex_path_char(self):
lexer = ninja.Lexer(['path1 path2'])
tok = lexer.lex_path()
self.assertEqual(ninja.TK.PATH, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
self.assertEqual(('t', 'path1'), tok.value)
tok = lexer.lex_path()
self.assertEqual(ninja.TK.PATH, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(7, tok.column)
self.assertEqual(('t', 'path2'), tok.value)
def test_lex_str_char(self):
lexer = ninja.Lexer(['string with spaces'])
tok = lexer.lex_string()
self.assertEqual(ninja.TK.STRING, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
self.assertEqual(('t', 'string with spaces'), tok.value)
def test_lex_path_escape_char(self):
for char in ' \t$:':
lexer = ninja.Lexer(['$' + char])
tok = lexer.lex_path()
self.assertEqual(ninja.TK.PATH, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
self.assertEqual(('t', char), tok.value)
def test_lex_str_escape_char(self):
for char in ' \t$:':
lexer = ninja.Lexer(['$' + char])
tok = lexer.lex_string()
self.assertEqual(ninja.TK.STRING, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
self.assertEqual(('t', char), tok.value)
def test_lex_path_escape_char_bad(self):
lexer = ninja.Lexer(['$'])
with self.assertRaises(ninja.ParseError):
lexer.lex_path()
lexer = ninja.Lexer(['$%'])
with self.assertRaises(ninja.ParseError):
lexer.lex_path()
def test_lex_str_escape_char_bad(self):
lexer = ninja.Lexer(['$'])
with self.assertRaises(ninja.ParseError):
lexer.lex_string()
lexer = ninja.Lexer(['$%'])
with self.assertRaises(ninja.ParseError):
lexer.lex_string()
def test_lex_path_end_char(self):
for char in ' \t\n:|':
lexer = ninja.Lexer(['path' + char])
tok = lexer.lex_path()
self.assertEqual(ninja.TK.PATH, tok.kind)
self.assertEqual(1, tok.line)
self.assertEqual(1, tok.column)
self.assertEqual(('t', 'path'), tok.value)
def test_lex_path_var(self):
lexer = ninja.Lexer(['$a'])
tok = lexer.lex_path()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('v', 'a',), tok.value)
lexer = ninja.Lexer(['${a}'])
tok = lexer.lex_path()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('v', 'a',), tok.value)
lexer = ninja.Lexer(['path/${a}'])
tok = lexer.lex_path()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('tv' ,'path/', 'a'), tok.value)
def test_lex_str_var(self):
lexer = ninja.Lexer(['$a'])
tok = lexer.lex_string()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('v', 'a'), tok.value)
lexer = ninja.Lexer(['${a}'])
tok = lexer.lex_string()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('v', 'a'), tok.value)
lexer = ninja.Lexer(['path/${a}'])
tok = lexer.lex_string()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('tv', 'path/', 'a'), tok.value)
lexer = ninja.Lexer(['path/${a} with space'])
tok = lexer.lex_string()
self.assertIs(type(tok.value), ninja.EvalString)
self.assertEqual(('tvt', 'path/', 'a', ' with space'), tok.value)
class ParserTest(unittest.TestCase):
def test_init_base_dir(self):
parser = ninja.Parser()
self.assertEqual(os.getcwd(), parser._base_dir)
parser = ninja.Parser('/path/to/a/dir')
self.assertEqual('/path/to/a/dir', parser._base_dir)
def test_global_binding_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'global_binding.ninja')
parser = MockedParser()
parser.parse(input_path, ENCODING)
env = parser.mocked_env[0]
self.assertEqual('1', env['a'])
self.assertEqual('2', env['b'])
self.assertEqual('3', env['c'])
self.assertEqual('1 2 3', env['d'])
self.assertEqual('mixed 1 and 2', env['e'])
def test_rule_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'rule.ninja')
parser = ninja.Parser()
manifest = parser.parse(input_path, ENCODING)
self.assertEqual(2, len(manifest.rules))
rule_cc = manifest.rules[0]
self.assertEqual('cc', rule_cc.name)
self.assertEqual(1, len(rule_cc.bindings))
sb = ninja.EvalStringBuilder()
sb.append_raw('gcc -c -o ')
sb.append_var('outs')
sb.append_raw(' ')
sb.append_var('ins')
self.assertEqual(sb.getvalue(), rule_cc.bindings['command'])
rule_ld = manifest.rules[1]
self.assertEqual('ld', rule_ld.name)
self.assertEqual(1, len(rule_ld.bindings))
sb = ninja.EvalStringBuilder()
sb.append_raw('gcc -o ')
sb.append_var('outs')
sb.append_raw(' ')
sb.append_var('ins')
self.assertEqual(sb.getvalue(), rule_ld.bindings['command'])
def test_build_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'build.ninja')
parser = ninja.Parser()
manifest = parser.parse(input_path, ENCODING)
self.assertEqual(1, len(manifest.builds))
build = manifest.builds[0]
self.assertEqual('explicit_out1', build.explicit_outs[0])
self.assertEqual('explicit_out2', build.explicit_outs[1])
self.assertEqual('implicit_out1', build.implicit_outs[0])
self.assertEqual('implicit_out2', build.implicit_outs[1])
self.assertEqual('phony', build.rule)
self.assertEqual('explicit_in1', build.explicit_ins[0])
self.assertEqual('explicit_in2', build.explicit_ins[1])
self.assertEqual('implicit_in1', build.implicit_ins[0])
self.assertEqual('implicit_in2', build.implicit_ins[1])
self.assertEqual('order_only1', build.prerequisites[0])
self.assertEqual('order_only2', build.prerequisites[1])
self.assertEqual(('t', '1',), build.bindings['a'])
self.assertEqual(('t', '2',), build.bindings['b'])
def test_default_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'default.ninja')
parser = ninja.Parser()
manifest = parser.parse(input_path, ENCODING)
self.assertEqual(1, len(manifest.defaults))
default = manifest.defaults[0]
self.assertEqual('foo.o', default.outs[0])
self.assertEqual('bar.o', default.outs[1])
def test_pool_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'pool.ninja')
parser = ninja.Parser()
manifest = parser.parse(input_path, ENCODING)
self.assertEqual(1, len(manifest.pools))
pool = manifest.pools[0]
self.assertEqual('example', pool.name)
self.assertEqual(('t', '5',), pool.bindings['depth'])
def test_subninja_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'subninja.ninja')
parser = MockedParser(TEST_DATA_DIR)
manifest = parser.parse(input_path, ENCODING)
env = parser.mocked_env[0]
self.assertEqual('original', env['a'])
self.assertEqual(2, len(manifest.builds))
env = parser.mocked_env[1]
self.assertEqual('changed', env['a'])
def test_include_stmt(self):
input_path = os.path.join(TEST_DATA_DIR, 'include.ninja')
parser = MockedParser(TEST_DATA_DIR)
manifest = parser.parse(input_path, ENCODING)
env = parser.mocked_env[0]
self.assertEqual('changed', env['a'])
self.assertEqual(2, len(manifest.builds))
class ParserTestWithBadInput(unittest.TestCase):
def test_unexpected_trivial_token(self):
input_path = os.path.join(TEST_DATA_DIR, 'bad_trivial.ninja')
with self.assertRaises(ninja.ParseError) as ctx:
MockedParser().parse(input_path, ENCODING)
self.assertEqual(input_path, ctx.exception.path)
self.assertEqual(1, ctx.exception.line)
self.assertEqual(1, ctx.exception.column)
def test_unexpected_non_trivial_token(self):
input_path = os.path.join(TEST_DATA_DIR, 'bad_non_trivial.ninja')
with self.assertRaises(ninja.ParseError) as ctx:
MockedParser().parse(input_path, ENCODING)
self.assertEqual(input_path, ctx.exception.path)
self.assertEqual(1, ctx.exception.line)
self.assertEqual(1, ctx.exception.column)
def test_bad_after_good(self):
input_path = os.path.join(TEST_DATA_DIR, 'bad_after_good.ninja')
with self.assertRaises(ninja.ParseError) as ctx:
MockedParser().parse(input_path, ENCODING)
self.assertEqual(input_path, ctx.exception.path)
self.assertEqual(4, ctx.exception.line)
self.assertEqual(1, ctx.exception.column)
def test_bad_path(self):
input_path = os.path.join(TEST_DATA_DIR, 'bad_path.ninja')
with self.assertRaises(ninja.ParseError) as ctx:
MockedParser().parse(input_path, ENCODING)
self.assertEqual(input_path, ctx.exception.path)
self.assertEqual(1, ctx.exception.line)
self.assertEqual(9, ctx.exception.column)
if __name__ == '__main__':
unittest.main()