diff options
author | nirnimesh@chromium.org <nirnimesh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-09 05:55:53 +0000 |
---|---|---|
committer | nirnimesh@chromium.org <nirnimesh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-09 05:55:53 +0000 |
commit | 40aa07af9babb4073a6e59c05d40b00561ccb091 (patch) | |
tree | 1b68d25d493cc2864266930a8b4b1e4de3eda262 /chrome/test | |
parent | bfdf36ec3bd8f644b70e9e60c597b2bf087e5970 (diff) | |
download | chromium_src-40aa07af9babb4073a6e59c05d40b00561ccb091.zip chromium_src-40aa07af9babb4073a6e59c05d40b00561ccb091.tar.gz chromium_src-40aa07af9babb4073a6e59c05d40b00561ccb091.tar.bz2 |
Exploratory Tool for long running and adhoc testing.
Sample Test Model
Omnibox Test Model
BUG=None
TEST=None
Review URL: http://codereview.chromium.org/4089005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@65503 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test')
-rw-r--r-- | chrome/test/functional/autotour.py | 212 | ||||
-rw-r--r-- | chrome/test/functional/exploratory.py | 66 | ||||
-rw-r--r-- | chrome/test/functional/omniboxmodel.py | 135 |
3 files changed, 413 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 diff --git a/chrome/test/functional/exploratory.py b/chrome/test/functional/exploratory.py new file mode 100644 index 0000000..2228166 --- /dev/null +++ b/chrome/test/functional/exploratory.py @@ -0,0 +1,66 @@ +#!/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 pyauto_functional +from pyauto import PyUITest +from pyauto import GURL + +import autotour + + +class ChromeTest(autotour.Godel, PyUITest): + """Test Example class which explores OpenTab, CloseTab and Navigate + actions and calls them randomly using the Exploratory framework. + Each method defines a weight and a precondition to test + using the GodelAction decorator before it can be called. The exploratory + framework uses these annotation and automatically calls different actions + and drives browser testing. + """ + def __init__(self, methodName='runTest', **kwargs): + PyUITest.__init__(self, methodName=methodName, **kwargs) + self._tab_count = 0 + + def CanOpenTab(self): + """Pre condition for opening a tab.""" + return self._tab_count < 5 + + def CanCloseTab(self): + """Pre condition for closing a tab.""" + return self._tab_count > 1 + + @autotour.GodelAction(10, CanOpenTab) + def OpenTab(self): + """Opens a new tab and goes to Google.com in the first window.""" + logging.info('In Open Tab') + self._tab_count = self._tab_count + 1 + self.AppendTab(GURL('http://www.google.com')) + + @autotour.GodelAction(10, CanCloseTab) + def CloseTab(self): + """Closes the first tab in the first window.""" + logging.info('In Close Tab') + self._tab_count = self._tab_count - 1 + self.GetBrowserWindow(0).GetTab(0).Close(True) + + @autotour.GodelAction(10, CanCloseTab) + def Navigate(self): + """Navigates to yahoo.com in the current window.""" + logging.info("In Navigate") + self.NavigateToURL('http://www.yahoo.com') + + def testExplore(self): + e = autotour.Explorer() + logging.info('Explorer created') + e.Add(self) + logging.info('Object added') + e.Explore() + logging.info('Done') + + +if __name__ == '__main__': + pyauto_functional.Main() + diff --git a/chrome/test/functional/omniboxmodel.py b/chrome/test/functional/omniboxmodel.py new file mode 100644 index 0000000..91326b0 --- /dev/null +++ b/chrome/test/functional/omniboxmodel.py @@ -0,0 +1,135 @@ +#!/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 pyauto_functional +from pyauto import PyUITest +from pyauto import GURL + +import autotour + + +class OmniboxModelTest(autotour.Godel, PyUITest): + """Omnibox Model which Opens tabs, navigates to specific URL's and keeps track + of URL's visited which are then verified agains the Omnibox's info. + """ + def __init__(self, methodName='runTest', **kwargs): + PyUITest.__init__(self, methodName=methodName, **kwargs) + self._tab_count = 1 + self._url_map = {} + self.InitUrls() + + def InitUrls(self): + """Setup Url Map which stores the name of the url and a list of url and + visited count. + """ + self._url_map = { + 'google': ('http://www.google.com', 0), + 'yahoo': ('http://www.yahoo.com', 0), + 'msn': ('http://www.msn.com', 0), + 'facebook': ('http://www.facebook.com', 0), + 'twitter': ('http://www.twitter.com', 0), + } + + def CanOpenTab(self): + return self._tab_count < 5 + + def CanCloseTab(self): + return self._tab_count > 1 + + def CanNavigate(self): + # FIX: CanNavigate can be called if there is atleast one tab to be closed. + # Currently this condition is incorrect because CanCloseTab leaves atleast + # one tab because without that, there is some crash which is under + # investigation. + if self.CanCloseTab(): + return False + for key in self._url_map: + if self._url_map[key][1] == 0: + return True + return False + + def _GetOmniboxMatchesFor(self, text, windex=0, attr_dict=None): + """Fetch omnibox matches with the given attributes for the given query. + + Args: + text: the query text to use + windex: the window index to work on. Defaults to 0 (first window) + attr_dict: the dictionary of properties to be satisfied + + Returns: + a list of match items + """ + self.SetOmniboxText(text, windex=windex) + self.WaitUntilOmniboxQueryDone(windex=windex) + if not attr_dict: + matches = self.GetOmniboxInfo(windex=windex).Matches() + else: + matches = self.GetOmniboxInfo(windex=windex).MatchesWithAttributes( + attr_dict=attr_dict) + return matches + + @autotour.GodelAction(1, CanOpenTab) + def OpenTab(self): + """Opens a tab in the first window and navigates to a random site from + url map. + """ + logging.info('#In Open Tab') + self._tab_count = self._tab_count + 1 + key = random.choice(self._url_map.keys()) + logging.info('#Navigating to ' + self._url_map[key][0]) + self.AppendTab(GURL(self._url_map[key][0])) + self._url_map[key][1] = self._url_map[key][1] + 1 + self.VerifyOmniboxInfo() + + @autotour.GodelAction(10, CanCloseTab) + def CloseTab(self): + """Closes the first tab from the first window""" + self._tab_count = self._tab_count - 1 + self.GetBrowserWindow(0).GetTab(0).Close(True) + + def VerifyOmniboxInfo(self): + for key in self._url_map.keys(): + """Verify inline autocomplete for a pre-visited url.""" + search_for = key[:3] + matches = self._GetOmniboxMatchesFor(search_for, windex=0) + self.assertTrue(matches) + # Omnibox should suggest auto completed url as the first item + matches_description = matches[0] + term_to_find = search_for + if self._url_map[key][1] > 0: + logging.info('#verifying : ' + key) + logging.info('#verifying ' + key + ' text ' + search_for) + term_to_find = self._url_map[key][0][7:] + self.assertEqual('history-url', matches_description['type']) + self.assertTrue(self._url_map[key][0][11:] in + self.GetOmniboxInfo().Text()) + self.assertTrue(term_to_find in matches_description['contents']) + + @autotour.GodelAction(10, CanNavigate) + def Navigate(self): + """Navigates to a URL by picking a random url from list""" + logging.info('#In Navigate') + index = random.randint(0, len(self._url_map.keys()) - 1) + key = self._url_map.keys()[index] + logging.info('#navigating to ' + self._url_map[key][0]) + self.NavigateToURL(self._url_map[key][0]) + self._url_map[key][1] = self._url_map[key][1] + 1 + self.VerifyOmniboxInfo() + + def testExplore(self): + e = autotour.Explorer() + logging.info('#Explorer created') + e.Add(self) + logging.info('#Object added') + e.Explore(self.CanNavigate) + logging.info('#Done') + + +if __name__ == '__main__': + pyauto_functional.Main() + |