diff options
Diffstat (limited to 'chrome/test/functional/autotour.py')
-rw-r--r-- | chrome/test/functional/autotour.py | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/chrome/test/functional/autotour.py b/chrome/test/functional/autotour.py new file mode 100644 index 0000000..7947550 --- /dev/null +++ b/chrome/test/functional/autotour.py @@ -0,0 +1,212 @@ +#!/usr/bin/python +# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import random +import time + + +"""Autotour is a semi automatic exploratory framework for exploring actions +defined on an object. It uses decorators to mark methods as actions and an +explorer object to explore. +""" + +def GodelAction(weight=1, requires=''): + """Action Decorator + + This function is the key to exploration. In effect, it annotates the wrapping + function with attributes such as is_action = True and sets some weights and + the requires condition. The explorer can invoke functions based on these + attributes. + + Args: + weight: Weight for the action, default is set to 1. Can be any number >= 0 + requires: Precondition for an action to be executed. This usually points + to a function which returns a boolean result. + """ + def custom(target): + """This is the decorator which sets the attributes for + is_action, weight and requires. + + Args: + target: Function to be decorated. + + Returns: + The wrapped function with correct attributes set. + """ + def wrapper(self, *args, **kwargs): + target(self, *args, **kwargs) + wrapper.is_action = True + wrapper.weight = weight + wrapper.requires = requires + return wrapper + return custom + + +class Godel(object): + """Base class for all exploratory objects. + + All objects that wish to be explored must inherit this class. + It provides an important method GetActions, which looks at all the functions + and returns only those that have the is_action attribute set. + """ + + def Initialize(self, uniqueid): + self._uniqueid = uniqueid + + def GetName(self): + return type(self).__name__ + str(self._uniqueid) + + def GetActions(self): + """Gets all the actions for this class.""" + return [method for method in dir(self) + if hasattr(getattr(self, method), 'is_action')] + + def GetWeight(self, method): + """Returns the weight of a given method. + + Args: + method: Name of the Method whose Weight is queried + """ + method_obj = getattr(self, method) + return getattr(method_obj, 'weight', 1) + + def SetWeight(self, method, weight): + """Sets the weight for a given method.""" + method_obj = getattr(self, method) + method_obj.im_func.weight = weight + + +class Explorer(object): + """Explorer class that controls the exploration of the object. + + This class has methods to add the exploration object and + initiate exploration on them. + """ + + def __init__(self): + self._seed = time.time() + logging.info('#Seeded with %s' % self._seed) + random.seed(self._seed) + self._actionlimit = -1 + self._godels = [] + self._fh = logging.FileHandler(str(self._seed)) + self._log = logging.getLogger() + self._log.addHandler(self._fh) + self._log.setLevel(logging.DEBUG) + self._uniqueid = 0 + + def NextId(self): + """Gets the NextId by incrementing a counter.""" + self._uniqueid = self._uniqueid + 1 + return self._uniqueid + + def Add(self, obj): + """Adds an object which inherits from Godel to be explored. + + Args: + obj: Object to be explored which usually inherits from the Godel class. + """ + uniqueid = self.NextId() + obj.Initialize(uniqueid) + name = type(obj).__name__ + self._log.info('%s = %s()' % (name + str(uniqueid), name)) + self._godels.append(obj) + + def MeetsRequirement(self, godel, methodname): + """Method that returns true if the method's precondition is satisfied. + It does so by using the attribute "Requires" which is set by the decorator + and invokes it which must return a boolean value + Args: + godel: Godel object on which the requirement needs to be tested. + methodname: Method name which needs to be called to test. + Returns: + True if the methodname invoked returned True or methodname was empty, + False otherwise + """ + method = getattr(godel, methodname) + requires = method.im_func.requires + if callable(requires): + return requires(godel) + else: + if len(requires) > 0: + precondition = getattr(godel, requires) + return precondition() + else: + return True + + def GetAvailableActions(self): + """Returns a list of only those actions that satisfy their preconditions""" + action_list = [] + for godel in self._godels: + for action in godel.GetActions(): + if self.MeetsRequirement(godel, action): + action_list.append([godel, action, godel.GetWeight(action)]) + + return action_list + + def Choose(self, action_list): + """Choosing function which allows to choose a method based on random + but weighted scale. So if one method has twice the weight, it is twice as + likely to be choosen than the other. + + Args: + action_list: A list of Actions from which to choose. + + Returns: + Chosen Action or None. + """ + total = sum([action_info[2] for action_info in action_list]) + # Find a pivot value randomly from he total weight. + index = random.randint(0, total) + for action_info in action_list: + # Decrease the total weight by the current action weight. + total = total - action_info[2] + # If total has fallen below the pivot, then we select the current action + if total <= index: + return action_info; + return None + + def Execute(self, action_info): + """Executes the action and logs to console the action taken. + + Args: + action_info: Action Info for the action to execute. + action_info[0] is the object on which the action is to be invoked. + action_info[1] is the name of the method which is to be invoked. + action_info[2] is the weight of the method. + + """ + action = getattr(action_info[0], action_info[1]) + self._log.info('%s.%s()' % (action_info[0].GetName(), action_info[1])) + action() + + def Explore(self, function=None): + """Sets the exploration in progress by repeatedly seeing if + any actions are available and if so continues to call them. It times out + after specified action limit. + + Args: + function: A function which can be called to determine if the execution + should continue. This function is invoked after each step and + if it returns True, execution stops. This is useful in writing + tests which explore until a particular condition is met. + + Returns: + True, if given |function| returns True, OR if no more action could be + chosen. False, otherwise. + """ + count = 0 + while(True): + if self._actionlimit > 0 and count > self._actionlimit: + return False + action_list = self.GetAvailableActions() + action_info = self.Choose(action_list) + if action_info is None: + return function is None + self.Execute(action_info) + count = count + 1 + if function is not None and function(): + return True
\ No newline at end of file |