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
15import adb
16import argparse
17import os
18import unittest
19import fastboot
20import subprocess
21import sys
22
23# Default values for arguments
24device_type = "phone"
25
26class ShellTest(unittest.TestCase):
27    @classmethod
28    def setUpClass(cls):
29        cls.fastboot = fastboot.FastbootDevice()
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
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
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
159        slotcount = self.get_slotcount()
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)
170
171    def test_setactive(self):
172        """Tests that A/B devices can switch to each slot, and the change persists over a reboot."""
173        # Test invalid if not an A/B device
174        slotcount = self.get_slotcount()
175        if not slotcount:
176            return
177
178        maxtries = 0
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"))
212
213if __name__ == '__main__':
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
225    unittest.main(verbosity=3)
226