blob: b018bd96784346343f77f569ad4fe50517e69e59 [file] [log] [blame]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This ninja_wrapper.py code is copied from
# https://cs.chromium.org/chromium/build/scripts/slave/recipe_modules/chromium/resources/ninja_wrapper.py?g=0
import textwrap
import unittest
import mock
import ninja_wrapper
_NINJA_STDOUT_CXX_RULE = """[35792/53672] CXX successful/a.o
[35793/53672] CXX a.o b.o
FAILED: a.o b.o
failed edge output line 1
failed edge output line 2
failed edge output line 3
[35794/53672] CXX successful/b.o
[35795/53672] CXX c.o
FAILED: c.o
failed edge output line 1
failed edge output line 2
ninja: build stopped: subcommand failed."""
_NINJA_STDOUT_NON_CXX_RULE = """[2893/2900] LINK ./a.exe
FAILED: a.exe
failed edge output line 1
[2894/2900] AR ./b.o
FAILED: b.0
failed edge output line 1
failed edge output line 2
[2895/2900] STAMP ./c.o
FAILED: c.o
failed edge output line 1
failed edge output line 2
failed edge output line 3
[2898/2900] ACTION ./d.o
FAILED: d.o
failed edge output line 1
failed edge output line 2
failed edge output line 3
failed edge output line 4
ninja: build stopped: subcommand failed."""
_NINJA_STDOUT_MIXED_RULE = """[2893/2900] CXX ./a.o
FAILED: a.o
failed edge output line 1
[2894/2900] CXX ./b.o
FAILED: b.o
failed edge output line 1
failed edge output line 2
[2898/2900] ACTION ./c.o
FAILED: c.o
failed edge output line 1
failed edge output line 2
failed edge output line 3
ninja: build stopped: subcommand failed."""
_GRAPH_RETURN = """digraph ninja {
rankdir="LR"
node [fontsize=10, shape=box, height=0.25]
edge [fontsize=10]
"node_id1" [label="gen/b.cc"]
"edge_id1" [label="edge_rule1", shape=ellipse]
"edge_id1" -> "node_id1"
"edge_id1" -> "node_id2"
"node_id3" -> "edge_id1" [arrowhead=none]
"node_id6" -> "edge_id1" [arrowhead=none style=dotted]
"node_id2" [label="flag.h"]
"node_id4" -> "node_id2" [label="edge_rule2"]
"node_id4" [label="../../flag.py"]
"node_id3" [label="b.o"]
"node_id5" -> "node_id3" [label="edge_rule3"]
"node_id5" [label="../../b.cc"]
"node_id6" [label="../../order.h"]
}"""
_DEPS_RETURN = """a.o: #deps 4, deps mtime 1 (STALE)
../../base/a.cc
../../base/a.h
gen/b.cc
../../build/bd.h
b.o: #deps 3, deps mtime 1 (VALID)
../../base/b.cc
../../base/b.h
gen/b.cc
c.o: #deps 1, deps mtime 1 (STALE)
../../base/b.cc
"""
_DEPS_NOT_FOUND_RETURN = """a.o: deps not found
b.o: #deps 3, deps mtime 1 (VALID)
../../base/b.cc
../../base/b.h
gen/b.cc
"""
_DEPS_ERROR_RETURN = """ninja: error: unknown target 'obj/e.o'"""
class NinjaWrapperTestCase(unittest.TestCase):
def testParseNinjaStdoutCXX(self):
warning_collector = ninja_wrapper.WarningCollector()
ninja_parser = ninja_wrapper.NinjaBuildOutputStreamingParser(
warning_collector)
for line in _NINJA_STDOUT_CXX_RULE.splitlines():
ninja_parser.parse(line)
expected_list = [{
'output_nodes': ['a.o', 'b.o'],
'rule':
'CXX',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
failed edge output line 3
"""),
'dependencies': []
}, {
'output_nodes': ['c.o'],
'rule':
'CXX',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
"""),
'dependencies': []
}]
self.assertListEqual(ninja_parser.failed_target_list, expected_list)
self.assertListEqual(warning_collector.get(), [])
def testParseMalformattedNinjaStdout(self):
warning_collector = ninja_wrapper.WarningCollector()
ninja_parser = ninja_wrapper.NinjaBuildOutputStreamingParser(
warning_collector)
malformatted_stdout = textwrap.dedent("""\
[] CXX node1
FAILED: node1
output line
[1/1] CXX node2""")
for line in malformatted_stdout.splitlines():
ninja_parser.parse(line)
self.assertListEqual(ninja_parser.failed_target_list, [])
expected_warning = [
'Unknown line when parsing '
"ninja stdout: '[] CXX node1'"
]
self.assertListEqual(warning_collector.get(), expected_warning)
def testParseDeps(self):
warning_collector = ninja_wrapper.WarningCollector()
target_dict = ninja_wrapper.parse_ninja_deps(_DEPS_RETURN,
warning_collector)
source_deps_a = ['../../base/a.cc', '../../base/a.h', '../../build/bd.h']
auto_generated_deps_a = ['gen/b.cc']
self.assertListEqual(target_dict['a.o'].source_deps, source_deps_a)
self.assertListEqual(target_dict['a.o'].auto_generated_deps,
auto_generated_deps_a)
source_deps_b = ['../../base/b.cc', '../../base/b.h']
auto_generated_deps_b = ['gen/b.cc']
self.assertListEqual(target_dict['b.o'].source_deps, source_deps_b)
self.assertListEqual(target_dict['b.o'].auto_generated_deps,
auto_generated_deps_b)
self.assertListEqual(warning_collector.get(), [])
def testParseEmptyDeps(self):
warning_collector = ninja_wrapper.WarningCollector()
malformatted_deps = textwrap.dedent("""\
a.o: #deps 2, deps mtime 1 (VALID)
../../a.cc
""")
target_dict = ninja_wrapper.parse_ninja_deps(malformatted_deps,
warning_collector)
self.assertListEqual(target_dict['a.o'].source_deps, ['../../a.cc'])
expected_warning = ['Unexpected empty deps line']
self.assertListEqual(warning_collector.get(), expected_warning)
def testParseExceedLimitDeps(self):
warning_collector = ninja_wrapper.WarningCollector()
malformatted_deps = textwrap.dedent("""\
a.o: #deps 2, deps mtime 1 (VALID)
../../a.cc
""")
target_dict = ninja_wrapper.parse_ninja_deps(malformatted_deps,
warning_collector)
self.assertListEqual(target_dict['a.o'].source_deps, ['../../a.cc'])
expected_warning = ['Expect 2 deps, but 1 line(s) left.']
self.assertListEqual(warning_collector.get(), expected_warning)
def testParseUnknownDeps(self):
warning_collector = ninja_wrapper.WarningCollector()
malformatted_deps = 'malformatted deps'
target_dict = ninja_wrapper.parse_ninja_deps(malformatted_deps,
warning_collector)
self.assertDictEqual(target_dict, {})
expected_warning = [
'Unknown line when parsing deps output: %r' % malformatted_deps
]
self.assertListEqual(warning_collector.get(), expected_warning)
def testParseNinjaDepsNotFound(self):
warning_collector = ninja_wrapper.WarningCollector()
target_dict = ninja_wrapper.parse_ninja_deps(_DEPS_NOT_FOUND_RETURN,
warning_collector)
source_deps_b = ['../../base/b.cc', '../../base/b.h']
auto_generated_deps_b = ['gen/b.cc']
self.assertListEqual(target_dict['a.o'].source_deps, [])
self.assertListEqual(target_dict['a.o'].auto_generated_deps, [])
self.assertListEqual(target_dict['b.o'].source_deps, source_deps_b)
self.assertListEqual(target_dict['b.o'].auto_generated_deps,
auto_generated_deps_b)
self.assertListEqual(warning_collector.get(), [])
def testParseMalformattedGraph(self):
warning_collector = ninja_wrapper.WarningCollector()
malformatted_graph = 'malformatted'
ninja_wrapper.Graph.build_graph(malformatted_graph, warning_collector)
self.assertListEqual(
warning_collector.get(),
['Unknown line when parsing graph output: %r' % malformatted_graph])
def testParseNinjaGraph(self):
warning_collector = ninja_wrapper.WarningCollector()
graph = ninja_wrapper.Graph.build_graph(_GRAPH_RETURN, warning_collector)
graph_dict = graph.get_root_deps(['gen/b.cc'])
expected_dict = {'gen/b.cc': ['../../b.cc']}
self.assertDictEqual(graph_dict, expected_dict)
self.assertListEqual(warning_collector.get(), [])
def testCheckAutoGeneratedTrue(self):
file_name = 'gen/123123'
self.assertTrue(ninja_wrapper.is_auto_generated(file_name))
def testCheckAutoGeneratedFalse(self):
file_name = '../../123123'
self.assertFalse(ninja_wrapper.is_auto_generated(file_name))
@mock.patch(
'ninja_wrapper.run_ninja_tool', side_effect=[_DEPS_RETURN, _GRAPH_RETURN])
def testGetDetailedInfo(self, _):
failed_target_list = [{
'output_nodes': ['a.o', 'b.o'],
'rule':
'CXX',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
failed edge output line 3
"""),
'dependencies': []
}, {
'output_nodes': ['c.o'],
'rule':
'CXX',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
"""),
'dependencies': []
}, {
'output_nodes': ['d.o'],
'rule':
'LINK',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
"""),
'dependencies': []
}]
expected_deps1 = set([
'../../base/a.cc', '../../base/a.h', '../../build/bd.h',
'../../base/b.cc', '../../base/b.h', '../../b.cc'
])
expected_deps2 = set(['../../base/b.cc'])
expected_deps3 = set([])
warning_collector = ninja_wrapper.WarningCollector()
target_dict = ninja_wrapper.get_detailed_info('', '', failed_target_list,
warning_collector)
self.assertSetEqual(
set(target_dict['failures'][0]['dependencies']), expected_deps1)
self.assertSetEqual(
set(target_dict['failures'][1]['dependencies']), expected_deps2)
self.assertSetEqual(
set(target_dict['failures'][2]['dependencies']), expected_deps3)
self.assertListEqual(warning_collector.get(), [])
@mock.patch('ninja_wrapper.run_ninja_tool', side_effect=['', _GRAPH_RETURN])
def testGetDetailedInfoErrorDeps(self, _):
failed_target_list = [{
'output_nodes': ['a.o', 'b.o'],
'rule':
'CXX',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
failed edge output line 3
"""),
'dependencies': []
}, {
'output_nodes': ['c.o'],
'rule':
'CXX',
'output':
textwrap.dedent("""\
failed edge output line 1
failed edge output line 2
"""),
'dependencies': []
}]
expected_deps1 = set([])
expected_deps2 = set([])
warning_collector = ninja_wrapper.WarningCollector()
target_dict = ninja_wrapper.get_detailed_info('', '', failed_target_list,
warning_collector)
self.assertSetEqual(
set(target_dict['failures'][0]['dependencies']), expected_deps1)
self.assertSetEqual(
set(target_dict['failures'][1]['dependencies']), expected_deps2)
self.assertListEqual(warning_collector.get(), [])
def testGetDetailedInfoNonCXXRules(self):
warning_collector = ninja_wrapper.WarningCollector()
ninja_parser = ninja_wrapper.NinjaBuildOutputStreamingParser(
warning_collector)
for line in _NINJA_STDOUT_NON_CXX_RULE.splitlines():
ninja_parser.parse(line)
failed_target_list = ninja_parser.failed_target_list
target_dict = ninja_wrapper.get_detailed_info('', '', failed_target_list,
warning_collector)
expected_deps1 = set([])
expected_deps2 = set([])
expected_deps3 = set([])
expected_deps4 = set([])
self.assertSetEqual(
set(target_dict['failures'][0]['dependencies']), expected_deps1)
self.assertSetEqual(
set(target_dict['failures'][1]['dependencies']), expected_deps2)
self.assertSetEqual(
set(target_dict['failures'][2]['dependencies']), expected_deps3)
self.assertSetEqual(
set(target_dict['failures'][3]['dependencies']), expected_deps4)
self.assertListEqual(warning_collector.get(), [])
@mock.patch(
'ninja_wrapper.run_ninja_tool',
side_effect=[_DEPS_NOT_FOUND_RETURN, _GRAPH_RETURN])
def testGetDetailedInfoMixedRules(self, _):
warning_collector = ninja_wrapper.WarningCollector()
ninja_parser = ninja_wrapper.NinjaBuildOutputStreamingParser(
warning_collector)
for line in _NINJA_STDOUT_MIXED_RULE.splitlines():
ninja_parser.parse(line)
failed_target_list = ninja_parser.failed_target_list
target_dict = ninja_wrapper.get_detailed_info('', '', failed_target_list,
warning_collector)
failure_outputs = ninja_parser.failure_outputs
expected_deps1 = set([])
expected_deps2 = set(['../../base/b.cc', '../../base/b.h', '../../b.cc'])
expected_deps3 = set([])
expected_failure_outputs = (
# Drop last line containing 'ninja: build stopped: ....'
# and concat with newline
'\n'.join(_NINJA_STDOUT_MIXED_RULE.splitlines()[:-1]) + '\n')
self.assertSetEqual(
set(target_dict['failures'][0]['dependencies']), expected_deps1)
self.assertSetEqual(
set(target_dict['failures'][1]['dependencies']), expected_deps2)
self.assertSetEqual(
set(target_dict['failures'][2]['dependencies']), expected_deps3)
self.assertListEqual(warning_collector.get(), [])
self.assertEqual(failure_outputs, expected_failure_outputs)
def testParseArgs(self):
expected_file_name = 'file.json'
expected_ninja_cmd = [
'ninja', '-w', 'dupbuild=err', '-C', 'build/path', 'target1', 'target2',
'-o', 'target3'
]
args = ['-o', expected_file_name]
args.append('--')
args.extend(expected_ninja_cmd)
options = ninja_wrapper.parse_args(args)
self.assertListEqual(expected_ninja_cmd, options.ninja_cmd)
self.assertEqual(expected_file_name, options.ninja_info_output)
def testParseArgsFullName(self):
expected_file_name = 'file.json'
expected_ninja_cmd = [
'ninja', '-w', 'dupbuild=err', '-C', 'build/path', 'target1', 'target2',
'-o', 'target3'
]
args = ['--ninja_info_output', expected_file_name]
args.append('--')
args.extend(expected_ninja_cmd)
options = ninja_wrapper.parse_args(args)
self.assertListEqual(expected_ninja_cmd, options.ninja_cmd)
self.assertEqual(expected_file_name, options.ninja_info_output)
def testParseArgsWithoutFile(self):
expected_ninja_cmd = [
'ninja', '-w', 'dupbuild=err', '-C', 'build/path', 'target1', 'target2'
]
args = []
args.append('--')
args.extend(expected_ninja_cmd)
options = ninja_wrapper.parse_args(args)
self.assertListEqual(expected_ninja_cmd, options.ninja_cmd)
self.assertIsNone(options.ninja_info_output)
if __name__ == '__main__':
unittest.main()