1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import itertools 18 19from vts.runners.host import const 20 21 22class CheckSetupCleanup(object): 23 """An abstract class for defining environment setup job. 24 25 Usually such a job contains check -> setup -> cleanup workflow 26 27 Attributes: 28 to_check: bool, whether or not to check the defined environment 29 requirement. Default: True 30 to_setup: bool, whether or not to setup the defined environment 31 requirement. Default: False 32 to_cleanup: bool, whether or not to cleanup the defined environment 33 requirement if it is set up by this class. Default: False 34 context: ShellEnvironment object that provides AddCleanupJobs and 35 ExecuteShellCommand method 36 note: string, used by GetNote to generate error message if fail. 37 """ 38 39 def __init__(self): 40 self.to_check = True 41 self.to_setup = False 42 self.to_cleanup = False 43 self.context = None 44 self.note = None 45 46 @property 47 def note(self): 48 return self._note 49 50 @note.setter 51 def note(self, note): 52 self._note = note 53 54 @property 55 def context(self): 56 return self._context 57 58 @context.setter 59 def context(self, context): 60 self._context = context 61 62 def Execute(self): 63 """Execute the check, setup, and cleanup. 64 65 Will execute setup and cleanup only if the boolean switches for them 66 are True. It will NOT execute cleanup if check function passes. 67 68 Return: 69 tuple(bool, string), a tuple of True and empty string if success, 70 a tuple of False and error message if fail. 71 """ 72 73 if self.context is None: 74 self.note = "Error: Environment definition context not set" 75 return False 76 77 if not self.InternalCall(self.ValidateInputs): 78 return False 79 80 check_result = False 81 if self.to_check: 82 check_result = self.InternalCall(self.Check) 83 84 if check_result or not self.to_setup: 85 return check_result 86 87 if self.to_cleanup: 88 self.context.AddCleanupJob(self.InternalCall, self.Cleanup) 89 90 return self.InternalCall(self.Setup) 91 92 def __str__(self): 93 return ("Shell Environment Check Definition Class:{cls} " 94 "Variables: {props}").format( 95 cls=self.__class__.__name__, props=vars(self)) 96 97 def GetNote(self): 98 """Get a string note as error message. Can be override by sub-class""" 99 if not self.note: 100 self.note = "Shell environment definition unsatisfied" 101 return "{}\nat: {}".format(self.note, self) 102 103 def InternalCall(self, method): 104 """Internal method to call sub class inherited methods. 105 106 It call the function, check results, and put failure note if not set 107 """ 108 self.note = None 109 success = method() 110 if not success and not self.note: 111 self.note = ("Shell environment definition unsatisfied: step [%s] " 112 "failed.") % method.__name__ 113 return success 114 115 def ValidateInputs(self): 116 """Validate input parameters. Can be override by sub-class 117 118 Return: 119 tuple(bool, string), a tuple of True and empty string if pass, 120 a tuple of False and error message if fail. 121 """ 122 return True 123 124 def ToListLike(self, obj): 125 """Convert single item to list of single item. 126 127 If input is already a list like object, the same object will 128 be returned. 129 This method is for the convenience of writing child class. 130 131 Arguments: 132 obj: any object 133 """ 134 if not self.IsListLike(obj): 135 return [obj] 136 else: 137 return obj 138 139 def IsListLike(self, obj): 140 """Checks whether a object is list-like. 141 142 This method is for the convenience of writing child class. 143 It will check for existence of __iter__ and __getitem__ 144 in attributes of the object. 145 String is not considered list-like, tuple is. 146 147 Arguments: 148 obj: any object 149 """ 150 return hasattr(obj, '__iter__') and hasattr(obj, '__getitem__') 151 152 def NormalizeInputLists(self, *inputs): 153 """Normalize inputs to lists of same length. 154 155 This method is for the convenience of writing child class. 156 If there are lists in inputs, they should all be of same length; 157 otherwise, None is returned. 158 If there are lists and single items in inputs, single items are 159 duplicated to a list of other list's length. 160 If there are only single items in inputs, they all get converted to 161 single item lists. 162 163 Arguments: 164 inputs: any inputs 165 166 Return: 167 a generator of normalized inputs 168 """ 169 # If any of inputs is None or empty, inputs are considered not valid 170 # Definition classes wish to take None input should not use this method 171 if not all(inputs): 172 return None 173 174 lists = filter(self.IsListLike, inputs) 175 if not lists: 176 # All inputs are single items. Convert them to lists 177 return ([i] for i in inputs) 178 179 lengths = set(map(len, lists)) 180 if len(lengths) != 1: 181 # lists in inputs have different lengths, cannot normalize 182 return None 183 length = lengths.pop() 184 185 return (i if self.IsListLike(i) else list(itertools.repeat(i, length)) 186 for i in inputs) 187 188 def Check(self): 189 """Check function for the class. 190 191 Used to check environment. Can be override by sub-class 192 """ 193 self.note = "Check step undefined." 194 return False 195 196 def Setup(self): 197 """Check function for the class. 198 199 Used to setup environment if check fail. Can be override by sub-class 200 """ 201 self.note = "Setup step undefined." 202 return False 203 204 def Cleanup(self): 205 """Check function for the class. 206 207 Used to cleanup setup if check fail. Can be override by sub-class 208 """ 209 self.note = "Cleanup step undefined." 210 return False 211 212 @property 213 def shell(self): 214 """returns an object that can execute a shell command""" 215 return self.context.shell 216 217 def ExecuteShellCommand(self, cmd): 218 """Execute a shell command or a list of shell commands. 219 220 Args: 221 command: str, the command to execute; Or 222 list of str, a list of commands to execute 223 224 return: 225 A dictionary containing results in format: 226 {const.STDOUT: [stdouts], 227 const.STDERR: [stderrs], 228 const.EXIT_CODE: [exit_codes]}. 229 """ 230 return self.shell.Execute(cmd) 231