You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
123 lines
4.0 KiB
123 lines
4.0 KiB
#!/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()
|