| #!/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() |