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 ░ ░ ░ ░ ░ ░ ░ ░ ░ 99 ░ 100 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 ░ ░ ░ ░ ░ ░ ░ ░ ░ 140 ░ 141[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