summaryrefslogtreecommitdiffstats
path: root/chrome/test/functional/autotour.py
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/test/functional/autotour.py')
-rw-r--r--chrome/test/functional/autotour.py212
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