Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 1 | # Copyright (C) 2016 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 | import adb |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 16 | import argparse |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 17 | import os |
| 18 | import unittest |
| 19 | import fastboot |
| 20 | import subprocess |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 21 | import sys |
| 22 | |
| 23 | # Default values for arguments |
| 24 | device_type = "phone" |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 25 | |
| 26 | class ShellTest(unittest.TestCase): |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 27 | @classmethod |
| 28 | def setUpClass(cls): |
| 29 | cls.fastboot = fastboot.FastbootDevice() |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 30 | |
| 31 | def exists_validvals(self, varname, varlist, validlist): |
| 32 | self.assertIn(varname, varlist) |
| 33 | self.assertIn(varlist[varname], validlist) |
| 34 | return varlist[varname] |
| 35 | |
| 36 | def exists_yes_no(self, varname, varlist): |
| 37 | return self.exists_validvals(varname, varlist, ["yes", "no"]) |
| 38 | |
| 39 | def exists_nonempty(self, varname, varlist): |
| 40 | self.assertIn(varname, varlist) |
| 41 | self.assertGreater(len(varlist[varname]), 0) |
| 42 | return varlist[varname] |
| 43 | |
| 44 | def exists_integer(self, varname, varlist, base=10): |
| 45 | val = 0 |
| 46 | self.assertIn(varname, varlist) |
| 47 | try: |
| 48 | val = int(varlist[varname], base) |
| 49 | except ValueError: |
| 50 | self.fail("%s (%s) is not an integer" % (varname, varlist[varname])) |
| 51 | return val |
| 52 | |
| 53 | def get_exists(self, varname): |
| 54 | val = self.fastboot.getvar(varname) |
| 55 | self.assertIsNotNone(val) |
| 56 | return val |
| 57 | |
| 58 | def get_exists_validvals(self, varname, validlist): |
| 59 | val = self.get_exists(varname) |
| 60 | self.assertIn(val, validlist) |
| 61 | return val |
| 62 | |
| 63 | def get_exists_yes_no(self, varname): |
| 64 | return self.get_exists_validvals(varname, ["yes", "no"]) |
| 65 | |
| 66 | def get_exists_nonempty(self, varname): |
| 67 | val = self.get_exists(varname) |
| 68 | self.assertGreater(len(val), 0) |
| 69 | return val |
| 70 | |
| 71 | def get_exists_integer(self, varname, base=10): |
| 72 | val = self.get_exists(varname) |
| 73 | try: |
| 74 | num = int(val, base) |
| 75 | except ValueError: |
| 76 | self.fail("%s (%s) is not an integer" % (varname, val)) |
| 77 | return num |
| 78 | |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 79 | def get_slotcount(self): |
| 80 | slotcount = 0 |
| 81 | try: |
| 82 | val = self.fastboot.getvar("slot-count") |
| 83 | if val != None: |
| 84 | slotcount = int(val) |
| 85 | except ValueError: |
| 86 | self.fail("slot-count (%s) is not an integer" % val) |
| 87 | except subprocess.CalledProcessError: |
| 88 | print "Does not appear to be an A/B device." |
| 89 | if not slotcount: |
| 90 | print "Does not appear to be an A/B device." |
| 91 | return slotcount |
| 92 | |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 93 | def test_getvarall(self): |
| 94 | """Tests that required variables are reported by getvar all""" |
| 95 | |
| 96 | var_all = self.fastboot.getvar_all() |
| 97 | self.exists_nonempty("version-baseband", var_all) |
| 98 | self.exists_nonempty("version-bootloader", var_all) |
| 99 | self.exists_nonempty("product", var_all) |
| 100 | self.exists_yes_no("secure", var_all) |
| 101 | self.exists_yes_no("unlocked", var_all) |
| 102 | self.exists_validvals("off-mode-charge", var_all, ["0", "1"]) |
| 103 | self.assertIn("variant", var_all) |
| 104 | voltage = self.exists_nonempty("battery-voltage", var_all) |
| 105 | if voltage[-2:].lower() == "mv": |
| 106 | voltage = voltage[:-2] |
| 107 | try: |
| 108 | voltnum = float(voltage) |
| 109 | except ValueError: |
| 110 | self.fail("battery-voltage (%s) is not a number" % (varname, voltage)) |
| 111 | self.exists_yes_no("battery-soc-ok", var_all) |
| 112 | maxdl = self.exists_integer("max-download-size", var_all, 16) |
| 113 | self.assertGreater(maxdl, 0) |
| 114 | |
| 115 | if "slot-count" in var_all: |
| 116 | try: |
| 117 | slotcount = int(var_all["slot-count"]) |
| 118 | except ValueError: |
| 119 | self.fail("slot-count (%s) is not an integer" % var_all["slot-count"]) |
| 120 | if slotcount > 1: |
| 121 | # test for A/B variables |
| 122 | slots = [chr(slotnum+ord('a')) for slotnum in range(slotcount)] |
| 123 | self.exists_validvals("current-slot", var_all, slots) |
| 124 | |
| 125 | # test for slot metadata |
| 126 | for slot in slots: |
| 127 | self.exists_yes_no("slot-unbootable:"+slot, var_all) |
| 128 | self.exists_yes_no("slot-unbootable:"+slot, var_all) |
| 129 | self.exists_integer("slot-retry-count:"+slot, var_all) |
| 130 | else: |
| 131 | print "This does not appear to be an A/B device." |
| 132 | |
| 133 | def test_getvar_nonexistent(self): |
| 134 | """Tests behaviour of nonexistent variables.""" |
| 135 | |
| 136 | self.assertIsNone(self.fastboot.getvar("fhqwhgads")) |
| 137 | |
| 138 | def test_getvar(self): |
| 139 | """Tests all variables separately""" |
| 140 | |
| 141 | self.get_exists_nonempty("version-baseband") |
| 142 | self.get_exists_nonempty("version-bootloader") |
| 143 | self.get_exists_nonempty("product") |
| 144 | self.get_exists_yes_no("secure") |
| 145 | self.get_exists_yes_no("unlocked") |
| 146 | self.get_exists_validvals("off-mode-charge", ["0", "1"]) |
| 147 | self.get_exists("variant") |
| 148 | voltage = self.get_exists_nonempty("battery-voltage") |
| 149 | if voltage[-2:].lower() == "mv": |
| 150 | voltage = voltage[:-2] |
| 151 | try: |
| 152 | voltnum = float(voltage) |
| 153 | except ValueError: |
| 154 | self.fail("battery-voltage (%s) is not a number" % voltage) |
| 155 | self.get_exists_yes_no("battery-soc-ok") |
| 156 | maxdl = self.get_exists_integer("max-download-size", 16) |
| 157 | self.assertGreater(maxdl, 0) |
| 158 | |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 159 | slotcount = self.get_slotcount() |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 160 | if slotcount > 1: |
| 161 | # test for A/B variables |
| 162 | slots = [chr(slotnum+ord('a')) for slotnum in range(slotcount)] |
| 163 | self.get_exists_validvals("current-slot", slots) |
| 164 | |
| 165 | # test for slot metadata |
| 166 | for slot in slots: |
| 167 | self.get_exists_yes_no("slot-unbootable:"+slot) |
| 168 | self.get_exists_yes_no("slot-successful:"+slot) |
| 169 | self.get_exists_integer("slot-retry-count:"+slot) |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 170 | |
| 171 | def test_setactive(self): |
| 172 | """Tests that A/B devices can switch to each slot, and the change persists over a reboot.""" |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 173 | # Test invalid if not an A/B device |
| 174 | slotcount = self.get_slotcount() |
| 175 | if not slotcount: |
| 176 | return |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 177 | |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 178 | maxtries = 0 |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 179 | slots = [chr(slotnum+ord('a')) for slotnum in range(slotcount)] |
| 180 | for slot in slots: |
| 181 | self.fastboot.set_active(slot) |
| 182 | self.assertEqual(slot, self.fastboot.getvar("current-slot")) |
| 183 | self.assertEqual("no", self.fastboot.getvar("slot-unbootable:"+slot)) |
| 184 | self.assertEqual("no", self.fastboot.getvar("slot-successful:"+slot)) |
| 185 | retry = self.get_exists_integer("slot-retry-count:"+slot) |
| 186 | if maxtries == 0: |
| 187 | maxtries = retry |
| 188 | else: |
| 189 | self.assertEqual(maxtries, retry) |
| 190 | self.fastboot.reboot(True) |
| 191 | self.assertEqual(slot, self.fastboot.getvar("current-slot")) |
| 192 | self.assertEqual("no", self.fastboot.getvar("slot-unbootable:"+slot)) |
| 193 | self.assertEqual("no", self.fastboot.getvar("slot-successful:"+slot)) |
| 194 | retry = self.get_exists_integer("slot-retry-count:"+slot) |
| 195 | if maxtries == 0: |
| 196 | maxtries = retry |
| 197 | else: |
| 198 | self.assertEqual(maxtries, retry) |
| 199 | |
| 200 | def test_hasslot(self): |
| 201 | """Tests that A/B devices report partitions that have slots.""" |
| 202 | # Test invalid if not an A/B device |
| 203 | if not self.get_slotcount(): |
| 204 | return |
| 205 | |
| 206 | self.assertEqual("yes", self.fastboot.getvar("has-slot:system")) |
| 207 | self.assertEqual("yes", self.fastboot.getvar("has-slot:boot")) |
| 208 | |
| 209 | # Additional partition on AndroidThings (IoT) devices |
| 210 | if device_type == "iot": |
| 211 | self.assertEqual("yes", self.fastboot.getvar("has-slot:oem")) |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 212 | |
| 213 | if __name__ == '__main__': |
Justin Giorgi | 72de393 | 2017-05-31 15:28:03 -0700 | [diff] [blame] | 214 | parser = argparse.ArgumentParser() |
| 215 | parser.add_argument("--device-type", default="phone", |
| 216 | help="Type of device ('phone' or 'iot').") |
| 217 | parser.add_argument("extra_args", nargs="*") |
| 218 | args = parser.parse_args() |
| 219 | |
| 220 | if args.device_type.lower() not in ("phone", "iot"): |
| 221 | raise ValueError("Unsupported device type '%s'." % args.device_type) |
| 222 | device_type = args.device_type.lower() |
| 223 | |
| 224 | sys.argv[1:] = args.extra_args |
Daniel Rosenberg | e4e3c71 | 2016-08-11 14:47:07 -0700 | [diff] [blame] | 225 | unittest.main(verbosity=3) |