xref: /aosp_15_r20/external/pigweed/pw_presubmit/py/ninja_parser_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1#!/usr/bin/env python3
2# Copyright 2022 The Pigweed Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5# use this file except in compliance with the License. You may obtain a copy of
6# the License at
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations under
14# the License.
15"""Tests for ninja_parser."""
16
17import difflib
18from pathlib import Path
19import unittest
20from unittest.mock import MagicMock, mock_open, patch
21
22from pw_presubmit import ninja_parser
23
24_STOP = 'ninja: build stopped:\n'
25
26_REAL_BUILD_INPUT = """
27[1168/1797] cp ../../pw_software_update/py/dev_sign_test.py python/gen/pw_software_update/py/py.generated_python_package/dev_sign_test.py
28[1169/1797] ACTION //pw_presubmit/py:py.lint.mypy(//pw_build/python_toolchain:python)
29\x1b[31mFAILED: python/gen/pw_presubmit/py/py.lint.mypy.pw_pystamp
30python [...]/py/pw_build/python_runner.py --gn-root ../../ --current-path ../../pw_presubmit/py --default-toolchain=//pw_toolchain/default:default --current-toolchain=//pw_build/python_toolchain:python --env=MYPY_FORCE_COLOR=1 --touch python/gen/pw_presubmit/py/py.lint.mypy.pw_pystamp --capture-output --module mypy --python-virtualenv-config python/gen/pw_env_setup/pigweed_build_venv/venv_metadata.json --python-dep-list-files python/gen/pw_presubmit/py/py.lint.mypy_metadata_path_list.txt -- --pretty --show-error-codes ../../pw_presubmit/py/pw_presubmit/__init__.py ../../pw_presubmit/py/pw_presubmit/build.py ../../pw_presubmit/py/pw_presubmit/cli.py ../../pw_presubmit/py/pw_presubmit/cpp_checks.py ../../pw_presubmit/py/pw_presubmit/format_code.py ../../pw_presubmit/py/pw_presubmit/git_repo.py ../../pw_presubmit/py/pw_presubmit/inclusive_language.py ../../pw_presubmit/py/pw_presubmit/install_hook.py ../../pw_presubmit/py/pw_presubmit/keep_sorted.py ../../pw_presubmit/py/pw_presubmit/ninja_parser.py ../../pw_presubmit/py/pw_presubmit/npm_presubmit.py ../../pw_presubmit/py/pw_presubmit/pigweed_presubmit.py ../../pw_presubmit/py/pw_presubmit/presubmit.py ../../pw_presubmit/py/pw_presubmit/python_checks.py ../../pw_presubmit/py/pw_presubmit/shell_checks.py ../../pw_presubmit/py/pw_presubmit/todo_check.py ../../pw_presubmit/py/pw_presubmit/tools.py ../../pw_presubmit/py/git_repo_test.py ../../pw_presubmit/py/keep_sorted_test.py ../../pw_presubmit/py/ninja_parser_test.py ../../pw_presubmit/py/presubmit_test.py ../../pw_presubmit/py/tools_test.py ../../pw_presubmit/py/setup.py
31  Requirement already satisfied: pyserial in c:\\b\\s\\w\\ir\\x\\w\\co\\environment\\pigweed-venv\\lib\\site-packages (from pigweed==0.0.13+20230126212203) (3.5)
32../../pw_presubmit/py/presubmit_test.py:63: error: Module has no attribute
33"Filter"  [attr-defined]
34            TestData(presubmit.Filter(suffix=('.a', '.b')), 'foo.c', False...
35                     ^
36Found 1 error in 1 file (checked 23 source files)
37[1170/1797] stamp python/obj/pw_snapshot/metadata_proto.python._mirror_sources_to_out_dir.stamp
38[1171/1797] stamp python/obj/pw_software_update/py/py._mirror_sources_to_out_dir_dev_sign_test.py.stamp
39[1172/1797] ACTION //pw_log:protos.python(//pw_build/python_toolchain:python)
40[1173/1797] ACTION //pw_thread_freertos/py:py.lint.pylint(//pw_build/python_toolchain:python)
41[1174/1797] ACTION //pw_symbolizer/py:py.lint.pylint(//pw_build/python_toolchain:python)
42[1175/1797] ACTION //pw_symbolizer/py:py.lint.pylint(//pw_build/python_toolchain:python)
43[1176/1797] ACTION //pw_thread_freertos/py:py.lint.pylint(//pw_build/python_toolchain:python)
44[1177/1797] ACTION //pw_tls_client/py:py.lint.pylint(//pw_build/python_toolchain:python)
45[1178/1797] ACTION //pw_symbolizer/py:py.lint.pylint(//pw_build/python_toolchain:python)
46[1179/1797] ACTION //pw_console/py:py.lint.pylint(//pw_build/python_toolchain:python)
47[1180/1797] ACTION //pw_tls_client/py:py.lint.pylint(//pw_build/python_toolchain:python)
48[1181/1797] ACTION //pw_console/py:py.lint.pylint(//pw_build/python_toolchain:python)
49[1182/1797] ACTION //pw_console/py:py.lint.pylint(//pw_build/python_toolchain:python)
50[1183/1797] ACTION //pw_tls_client/py:py.lint.mypy(//pw_build/python_toolchain:python)
51[1184/1797] ACTION //pw_symbolizer/py:py.lint.pylint(//pw_build/python_toolchain:python)
52[1185/1797] ACTION //pw_thread_freertos/py:py.lint.pylint(//pw_build/python_toolchain:python)
53[1186/1797] ACTION //pw_tls_client/py:py.lint.pylint(//pw_build/python_toolchain:python)
54ninja: build stopped: subcommand failed.
55[FINISHED]
56"""
57
58_REAL_BUILD_SUMMARY = """
59[1169/1797] ACTION //pw_presubmit/py:py.lint.mypy(//pw_build/python_toolchain:python)
60\x1b[31mFAILED: python/gen/pw_presubmit/py/py.lint.mypy.pw_pystamp
61python [...]/py/pw_build/python_runner.py --gn-root ../../ --current-path ../../pw_presubmit/py --default-toolchain=//pw_toolchain/default:default --current-toolchain=//pw_build/python_toolchain:python --env=MYPY_FORCE_COLOR=1 --touch python/gen/pw_presubmit/py/py.lint.mypy.pw_pystamp --capture-output --module mypy --python-virtualenv-config python/gen/pw_env_setup/pigweed_build_venv/venv_metadata.json --python-dep-list-files python/gen/pw_presubmit/py/py.lint.mypy_metadata_path_list.txt -- --pretty --show-error-codes [...]/py/pw_presubmit/__init__.py [...]/py/pw_presubmit/build.py [...]/py/pw_presubmit/cli.py [...]/py/pw_presubmit/cpp_checks.py [...]/py/pw_presubmit/format_code.py [...]/py/pw_presubmit/git_repo.py [...]/py/pw_presubmit/inclusive_language.py [...]/py/pw_presubmit/install_hook.py [...]/py/pw_presubmit/keep_sorted.py [...]/py/pw_presubmit/ninja_parser.py [...]/py/pw_presubmit/npm_presubmit.py [...]/py/pw_presubmit/pigweed_presubmit.py [...]/py/pw_presubmit/presubmit.py [...]/py/pw_presubmit/python_checks.py [...]/py/pw_presubmit/shell_checks.py [...]/py/pw_presubmit/todo_check.py [...]/py/pw_presubmit/tools.py ../../pw_presubmit/py/git_repo_test.py ../../pw_presubmit/py/keep_sorted_test.py ../../pw_presubmit/py/ninja_parser_test.py ../../pw_presubmit/py/presubmit_test.py ../../pw_presubmit/py/tools_test.py ../../pw_presubmit/py/setup.py
62../../pw_presubmit/py/presubmit_test.py:63: error: Module has no attribute
63"Filter"  [attr-defined]
64            TestData(presubmit.Filter(suffix=('.a', '.b')), 'foo.c', False...
65                     ^
66Found 1 error in 1 file (checked 23 source files)
67"""
68
69_REAL_TEST_INPUT = r"""
70[20278/31188] Finished [ld pw_strict_host_clang_size_optimized/obj/pw_rpc/bin/test_rpc_server] (0.0s)
71[20278/31188] Started  [c++ pw_strict_host_clang_size_optimized/obj/pw_rpc/pwpb/fake_channel_output_test.lib.fake_channel_output_test.cc.o]
72[20279/31188] Finished [c++ pw_strict_host_clang_size_optimized/obj/pw_log_rpc/log_service.log_service.cc.o] (4.9s)
73[20279/31188] Started  [c++ pw_strict_host_clang_size_optimized/obj/pw_rpc/pwpb/method_info_test.lib.method_info_test.cc.o]
74[20280/31188] Finished [ACTION //pw_rpc:cpp_client_server_integration_test(//targets/host/pigweed_internal:pw_strict_host_clang_debug)] (10.2s)
75[ACTION //pw_rpc:cpp_client_server_integration_test(//targets/host/pigweed_internal:pw_strict_host_clang_debug)]
76[31mFAILED: [0mpw_strict_host_clang_debug/gen/pw_rpc/cpp_client_server_integration_test.pw_pystamp
77python [...]/py/pw_build/python_runner.py --gn-root ../../ --current-path ../../pw_rpc --default-toolchain=//pw_toolchain/default:default --current-toolchain=//targets/host/pigweed_internal:pw_strict_host_clang_debug --touch pw_strict_host_clang_debug/gen/pw_rpc/cpp_client_server_integration_test.pw_pystamp --capture-output --python-virtualenv-config python/gen/pw_env_setup/pigweed_build_venv/venv_metadata.json --python-dep-list-files pw_strict_host_clang_debug/gen/pw_rpc/cpp_client_server_integration_test_metadata_path_list.txt -- [...]/py/pw_rpc/testing.py --server \<TARGET_FILE\(:test_rpc_server\)\> --client \<TARGET_FILE\(:client_integration_test\)\> -- 30577
78[35m[1mINF[0m  Starting pw_rpc server on port 30577
79[35m[1mINF[0m  Connecting to pw_rpc client at localhost:30577
80[35m[1mINF[0m  [==========] Running all tests.
81[35m[1mINF[0m  [ RUN      ] RawRpcIntegrationTest.Unary
82[35m[1mINF[0m  Test output line
83[35m[1mINF[0m  [       OK ] RawRpcIntegrationTest.Unary
84[35m[1mINF[0m  [ RUN      ] RawRpcIntegrationTest.BidirectionalStreaming
85[35m[1mINF[0m  [       OK ] RawRpcIntegrationTest.BidirectionalStreaming
86[35m[1mINF[0m  [ RUN      ] RawRpcIntegrationTest.OnNextOverwritesItsOwnCall
87[33m[1mWRN[0m  RPC client received stream message for an unknown call
88[31m
89
90   ▄████▄      ██▀███      ▄▄▄           ██████     ██░ ██
91  ▒██▀ ▀█     ▓██ ▒ ██▒   ▒████▄       ▒██    ▒    ▓██░ ██▒
92  ▒▓█ �� ▄    ▓██ ░▄█ ▒   ▒██  ▀█▄     ░ ▓██▄      ▒██▀▀██░
93  ▒▓▓▄ ▄██▒   ▒██▀▀█▄     ░██▄▄▄▄██      ▒   ██▒   ░▓█ ░██
94  ▒ ▓███▀ ░   ░██▓ ▒██▒    ▓█   ▓██▒   ▒██████▒▒   ░▓█▒░██▓
95  ░ ░▒ ▒  ░   ░ ▒▓ ░▒▓░    ▒▒   ▓▒█░   ▒ ▒▓▒ ▒ ░    ▒ ░░▒░▒
96    ░  ▒        ░▒ ░ ▒░     ▒   ▒▒ ░   ░ ░▒  ░ ░    ▒ ░▒░ ░
97  ░             ░░   ░      ░   ▒      ░  ░  ░      ░  ░░ ░
98  ░ ░            ░              ░  ░         ░      ░  ░  ░
99100
101[0m
102  Welp, that didn't go as planned. It seems we crashed. Terribly sorry!
103
104[33m  CRASH MESSAGE[0m
105
106     Check failed: sem_.try_acquire_for(10s).
107
108[33m  CRASH FILE & LINE[0m
109
110     pw_rpc/client_integration_test.cc:52
111
112[33m  CRASH FUNCTION[0m
113
114     const char *rpc_test::(anonymous namespace)::StringReceiver::Wait()
115
116[20281/31188] Finished [c++ pw_strict_host_clang_size_optimized/obj/pw_log_rpc/rpc_log_drain.rpc_log_drain.cc.o] (5.1s)
117[20282/31188] Finished [c++ pw_strict_host_clang_size_optimized/obj/pw_log_rpc/log_filter_service_test.lib.log_filter_service_test.cc.o] (5.9s)
118[20283/31188] Finished [c++ pw_strict_host_clang_size_optimized/obj/pw_rpc/fuzz/engine_test.lib.engine_test.cc.o] (2.4s)
119"""
120
121_REAL_TEST_SUMMARY = r"""
122[ACTION //pw_rpc:cpp_client_server_integration_test(//targets/host/pigweed_internal:pw_strict_host_clang_debug)]
123[31mFAILED: [0mpw_strict_host_clang_debug/gen/pw_rpc/cpp_client_server_integration_test.pw_pystamp
124python [...]/py/pw_build/python_runner.py --gn-root ../../ --current-path ../../pw_rpc --default-toolchain=//pw_toolchain/default:default --current-toolchain=//targets/host/pigweed_internal:pw_strict_host_clang_debug --touch pw_strict_host_clang_debug/gen/pw_rpc/cpp_client_server_integration_test.pw_pystamp --capture-output --python-virtualenv-config python/gen/pw_env_setup/pigweed_build_venv/venv_metadata.json --python-dep-list-files pw_strict_host_clang_debug/gen/pw_rpc/cpp_client_server_integration_test_metadata_path_list.txt -- [...]/py/pw_rpc/testing.py --server \<TARGET_FILE\(:test_rpc_server\)\> --client \<TARGET_FILE\(:client_integration_test\)\> -- 30577
125[35m[1mINF[0m  Starting pw_rpc server on port 30577
126[35m[1mINF[0m  Connecting to pw_rpc client at localhost:30577
127[35m[1mINF[0m  [==========] Running all tests.
128[35m[1mINF[0m  [ RUN      ] RawRpcIntegrationTest.OnNextOverwritesItsOwnCall
129[33m[1mWRN[0m  RPC client received stream message for an unknown call
130[31m
131   ▄████▄      ██▀███      ▄▄▄           ██████     ██░ ██
132  ▒██▀ ▀█     ▓██ ▒ ██▒   ▒████▄       ▒██    ▒    ▓██░ ██▒
133  ▒▓█ �� ▄    ▓██ ░▄█ ▒   ▒██  ▀█▄     ░ ▓██▄      ▒██▀▀██░
134  ▒▓▓▄ ▄██▒   ▒██▀▀█▄     ░██▄▄▄▄██      ▒   ██▒   ░▓█ ░██
135  ▒ ▓███▀ ░   ░██▓ ▒██▒    ▓█   ▓██▒   ▒██████▒▒   ░▓█▒░██▓
136  ░ ░▒ ▒  ░   ░ ▒▓ ░▒▓░    ▒▒   ▓▒█░   ▒ ▒▓▒ ▒ ░    ▒ ░░▒░▒
137    ░  ▒        ░▒ ░ ▒░     ▒   ▒▒ ░   ░ ░▒  ░ ░    ▒ ░▒░ ░
138  ░             ░░   ░      ░   ▒      ░  ░  ░      ░  ░░ ░
139  ░ ░            ░              ░  ░         ░      ░  ░  ░
140141[0m
142  Welp, that didn't go as planned. It seems we crashed. Terribly sorry!
143[33m  CRASH MESSAGE[0m
144     Check failed: sem_.try_acquire_for(10s).
145[33m  CRASH FILE & LINE[0m
146     pw_rpc/client_integration_test.cc:52
147[33m  CRASH FUNCTION[0m
148     const char *rpc_test::(anonymous namespace)::StringReceiver::Wait()
149"""
150
151
152class TestNinjaParser(unittest.TestCase):
153    """Test ninja_parser."""
154
155    def _run(self, contents: str) -> str:  # pylint: disable=no-self-use
156        path = MagicMock(spec=Path('foo/bar'))
157
158        def mocked_open_read(*args, **kwargs):
159            return mock_open(read_data=contents)(*args, **kwargs)
160
161        with patch.object(path, 'open', mocked_open_read):
162            return ninja_parser.parse_ninja_stdout(path)
163
164    def _assert_equal(self, left, right):
165        if (
166            not isinstance(left, str)
167            or not isinstance(right, str)
168            or '\n' not in left
169            or '\n' not in right
170        ):
171            return self.assertEqual(left, right)
172
173        diff = ''.join(
174            difflib.unified_diff(left.splitlines(True), right.splitlines(True))
175        )
176        return self.assertSequenceEqual(left, right, f'\n{diff}\n')
177
178    def test_simple(self) -> None:
179        error = '[2/10] baz\nFAILED: something\nerror 1\nerror 2\n'
180        result = self._run('[0/10] foo\n[1/10] bar\n' + error + _STOP)
181        self._assert_equal(error.strip(), result.strip())
182
183    def test_short(self) -> None:
184        error = '[2/10] baz\nFAILED: something\n'
185        result = self._run('[0/10] foo\n[1/10] bar\n' + error + _STOP)
186        self._assert_equal(error.strip(), result.strip())
187
188    def test_unexpected(self) -> None:
189        error = '[2/10] baz\nERROR: something\nerror 1\n'
190        result = self._run('[0/10] foo\n[1/10] bar\n' + error)
191        self._assert_equal('', result.strip())
192
193    def test_real_build(self) -> None:
194        result = self._run(_REAL_BUILD_INPUT)
195        self._assert_equal(_REAL_BUILD_SUMMARY.strip(), result.strip())
196
197    def test_real_test(self) -> None:
198        result = self._run(_REAL_TEST_INPUT)
199        self._assert_equal(_REAL_TEST_SUMMARY.strip(), result.strip())
200
201
202if __name__ == '__main__':
203    unittest.main()
204