Luca Farsi | db13644 | 2024-03-26 10:55:21 -0700 | [diff] [blame] | 1 | # Copyright 2024, The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | """Integration tests for build_test_suites that require a local build env.""" |
| 16 | |
| 17 | import os |
| 18 | import pathlib |
| 19 | import shutil |
| 20 | import signal |
| 21 | import subprocess |
| 22 | import tempfile |
| 23 | import time |
| 24 | import ci_test_lib |
| 25 | |
| 26 | |
| 27 | class BuildTestSuitesLocalTest(ci_test_lib.TestCase): |
| 28 | |
| 29 | def setUp(self): |
| 30 | self.top_dir = pathlib.Path(os.environ['ANDROID_BUILD_TOP']).resolve() |
| 31 | self.executable = self.top_dir.joinpath('build/make/ci/build_test_suites') |
| 32 | self.process_session = ci_test_lib.TemporaryProcessSession(self) |
| 33 | self.temp_dir = ci_test_lib.TestTemporaryDirectory.create(self) |
| 34 | |
| 35 | def build_subprocess_args(self, build_args: list[str]): |
| 36 | env = os.environ.copy() |
| 37 | env['TOP'] = str(self.top_dir) |
| 38 | env['OUT_DIR'] = self.temp_dir |
| 39 | |
| 40 | args = ([self.executable] + build_args,) |
| 41 | kwargs = { |
| 42 | 'cwd': self.top_dir, |
| 43 | 'env': env, |
| 44 | 'text': True, |
| 45 | } |
| 46 | |
| 47 | return (args, kwargs) |
| 48 | |
| 49 | def run_build(self, build_args: list[str]) -> subprocess.CompletedProcess: |
| 50 | args, kwargs = self.build_subprocess_args(build_args) |
| 51 | |
| 52 | return subprocess.run( |
| 53 | *args, |
| 54 | **kwargs, |
| 55 | check=True, |
| 56 | capture_output=True, |
| 57 | timeout=5 * 60, |
| 58 | ) |
| 59 | |
| 60 | def assert_children_alive(self, children: list[int]): |
| 61 | for c in children: |
| 62 | self.assertTrue(ci_test_lib.process_alive(c)) |
| 63 | |
| 64 | def assert_children_dead(self, children: list[int]): |
| 65 | for c in children: |
| 66 | self.assertFalse(ci_test_lib.process_alive(c)) |
| 67 | |
| 68 | def test_fails_for_invalid_arg(self): |
| 69 | invalid_arg = '--invalid-arg' |
| 70 | |
| 71 | with self.assertRaises(subprocess.CalledProcessError) as cm: |
| 72 | self.run_build([invalid_arg]) |
| 73 | |
| 74 | self.assertIn(invalid_arg, cm.exception.stderr) |
| 75 | |
| 76 | def test_builds_successfully(self): |
| 77 | self.run_build(['nothing']) |
| 78 | |
| 79 | def test_can_interrupt_build(self): |
| 80 | args, kwargs = self.build_subprocess_args(['general-tests']) |
| 81 | p = self.process_session.create(args, kwargs) |
| 82 | |
| 83 | # TODO(lucafarsi): Replace this (and other instances) with a condition. |
| 84 | time.sleep(5) # Wait for the build to get going. |
| 85 | self.assertIsNone(p.poll()) # Check that the process is still alive. |
| 86 | children = query_child_pids(p.pid) |
| 87 | self.assert_children_alive(children) |
| 88 | |
| 89 | p.send_signal(signal.SIGINT) |
| 90 | p.wait() |
| 91 | |
| 92 | time.sleep(5) # Wait for things to die out. |
| 93 | self.assert_children_dead(children) |
| 94 | |
| 95 | def test_can_kill_build_process_group(self): |
| 96 | args, kwargs = self.build_subprocess_args(['general-tests']) |
| 97 | p = self.process_session.create(args, kwargs) |
| 98 | |
| 99 | time.sleep(5) # Wait for the build to get going. |
| 100 | self.assertIsNone(p.poll()) # Check that the process is still alive. |
| 101 | children = query_child_pids(p.pid) |
| 102 | self.assert_children_alive(children) |
| 103 | |
| 104 | os.killpg(os.getpgid(p.pid), signal.SIGKILL) |
| 105 | p.wait() |
| 106 | |
| 107 | time.sleep(5) # Wait for things to die out. |
| 108 | self.assert_children_dead(children) |
| 109 | |
| 110 | |
| 111 | # TODO(hzalek): Replace this with `psutils` once available in the tree. |
| 112 | def query_child_pids(parent_pid: int) -> set[int]: |
| 113 | p = subprocess.run( |
| 114 | ['pgrep', '-P', str(parent_pid)], |
| 115 | check=True, |
| 116 | capture_output=True, |
| 117 | text=True, |
| 118 | ) |
| 119 | return {int(pid) for pid in p.stdout.splitlines()} |
| 120 | |
| 121 | |
| 122 | if __name__ == '__main__': |
| 123 | ci_test_lib.main() |