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 17import os 18import pathlib 19import shutil 20import signal 21import subprocess 22import tempfile 23import time 24import ci_test_lib 25 26 27class 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. 112def 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 122if __name__ == '__main__': 123 ci_test_lib.main() 124