# Copyright 2024, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Integration tests for build_test_suites that require a local build env.""" import os import pathlib import shutil import signal import subprocess import tempfile import time import ci_test_lib class BuildTestSuitesLocalTest(ci_test_lib.TestCase): def setUp(self): self.top_dir = pathlib.Path(os.environ['ANDROID_BUILD_TOP']).resolve() self.executable = self.top_dir.joinpath('build/make/ci/build_test_suites') self.process_session = ci_test_lib.TemporaryProcessSession(self) self.temp_dir = ci_test_lib.TestTemporaryDirectory.create(self) def build_subprocess_args(self, build_args: list[str]): env = os.environ.copy() env['TOP'] = str(self.top_dir) env['OUT_DIR'] = self.temp_dir args = ([self.executable] + build_args,) kwargs = { 'cwd': self.top_dir, 'env': env, 'text': True, } return (args, kwargs) def run_build(self, build_args: list[str]) -> subprocess.CompletedProcess: args, kwargs = self.build_subprocess_args(build_args) return subprocess.run( *args, **kwargs, check=True, capture_output=True, timeout=5 * 60, ) def assert_children_alive(self, children: list[int]): for c in children: self.assertTrue(ci_test_lib.process_alive(c)) def assert_children_dead(self, children: list[int]): for c in children: self.assertFalse(ci_test_lib.process_alive(c)) def test_fails_for_invalid_arg(self): invalid_arg = '--invalid-arg' with self.assertRaises(subprocess.CalledProcessError) as cm: self.run_build([invalid_arg]) self.assertIn(invalid_arg, cm.exception.stderr) def test_builds_successfully(self): self.run_build(['nothing']) def test_can_interrupt_build(self): args, kwargs = self.build_subprocess_args(['general-tests']) p = self.process_session.create(args, kwargs) # TODO(lucafarsi): Replace this (and other instances) with a condition. time.sleep(5) # Wait for the build to get going. self.assertIsNone(p.poll()) # Check that the process is still alive. children = query_child_pids(p.pid) self.assert_children_alive(children) p.send_signal(signal.SIGINT) p.wait() time.sleep(5) # Wait for things to die out. self.assert_children_dead(children) def test_can_kill_build_process_group(self): args, kwargs = self.build_subprocess_args(['general-tests']) p = self.process_session.create(args, kwargs) time.sleep(5) # Wait for the build to get going. self.assertIsNone(p.poll()) # Check that the process is still alive. children = query_child_pids(p.pid) self.assert_children_alive(children) os.killpg(os.getpgid(p.pid), signal.SIGKILL) p.wait() time.sleep(5) # Wait for things to die out. self.assert_children_dead(children) # TODO(hzalek): Replace this with `psutils` once available in the tree. def query_child_pids(parent_pid: int) -> set[int]: p = subprocess.run( ['pgrep', '-P', str(parent_pid)], check=True, capture_output=True, text=True, ) return {int(pid) for pid in p.stdout.splitlines()} if __name__ == '__main__': ci_test_lib.main()