#!/usr/bin/env python3 # # Copyright (C) 2021 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. # """Repeatedly install an A/B update to an Android device over adb.""" import argparse import sys from pathlib import Path import subprocess import signal def CleanupLoopDevices(): # b/184716804 clean up unused loop devices subprocess.check_call(["adb", "shell", "su", "0", "losetup", '-D']) def CancelOTA(): subprocess.call(["adb", "shell", "su", "0", "update_engine_client", "--cancel"]) def PerformOTAThenPause(otafile: Path, update_device_script: Path): python = sys.executable ota_cmd = [python, str(update_device_script), str(otafile), "--no-postinstall", "--no-slot-switch"] p = subprocess.Popen(ota_cmd) pid = p.pid try: ret = p.wait(10) if ret is not None and ret != 0: raise RuntimeError("OTA failed to apply") if ret == 0: print("OTA finished early? Surprise.") return except subprocess.TimeoutExpired: pass print(f"Killing {pid}") subprocess.check_call(["pkill", "-INT", "-P", str(pid)]) p.send_signal(signal.SIGINT) p.wait() def PerformTest(otafile: Path, resumes: int, timeout: int): """Install an OTA to device, raising exceptions on failure Args: otafile: Path to the ota.zip to install Return: None if no error, if there's an error exception will be thrown """ assert otafile.exists() print("Applying", otafile) script_dir = Path(__file__).parent.absolute() update_device_script = script_dir / "update_device.py" assert update_device_script.exists() print(update_device_script) python = sys.executable for i in range(resumes): print("Pause/Resume for the", i+1, "th time") PerformOTAThenPause(otafile, update_device_script) CancelOTA() CleanupLoopDevices() ota_cmd = [python, str(update_device_script), str(otafile), "--no-postinstall"] print("Finishing OTA Update", ota_cmd) output = subprocess.check_output( ota_cmd, stderr=subprocess.STDOUT, timeout=timeout).decode() print(output) if "onPayloadApplicationComplete(ErrorCode::kSuccess" not in output: raise RuntimeError("Failed to finish OTA") subprocess.call( ["adb", "shell", "su", "0", "update_engine_client", "--cancel"]) subprocess.check_call( ["adb", "shell", "su", "0", "update_engine_client", "--reset_status"]) CleanupLoopDevices() def main(): parser = argparse.ArgumentParser( description='Android A/B OTA stress test helper.') parser.add_argument('otafile', metavar='PAYLOAD', type=Path, help='the OTA package file (a .zip file) or raw payload \ if device uses Omaha.') parser.add_argument('-n', "--iterations", type=int, default=10, metavar='ITERATIONS', help='The number of iterations to run the stress test, or\ -1 to keep running until CTRL+C') parser.add_argument('-r', "--resumes", type=int, default=5, metavar='RESUMES', help='The number of iterations to pause the update when \ installing') parser.add_argument('-t', "--timeout", type=int, default=60*60, metavar='TIMEOUTS', help='Timeout, in seconds, when waiting for OTA to \ finish') args = parser.parse_args() print(args) n = args.iterations while n != 0: PerformTest(args.otafile, args.resumes, args.timeout) n -= 1 if __name__ == "__main__": main()