#!/usr/bin/env python # -*- coding: utf-8 -*- """ Unit tests for the dogtail.Node class Notes on pyunit (the "unittest" module): Test classes are written as subclass of unittest.TestCase. A test is a method of such a class, beginning with the string "test" unittest.main() will run all such methods. Use "-v" to get feedback on which tests are being run. Tests are run in alphabetical order; all failure reports are gathered at the end. setUp and tearDown are "magic" methods, called before and after each such test method is run. """ __author__="Dave Malcolm " import unittest import time import dogtail.tree import dogtail.predicate import dogtail.config dogtail.config.config.logDebugToFile = False import pyatspi class GtkDemoTest(unittest.TestCase): """ TestCase subclass which handles bringing up and shutting down gtk-demo as a fixture. Used for writing other test cases. """ def setUp(self): import dogtail.utils self.pid = dogtail.utils.run('gtk-demo') self.app = dogtail.tree.root.application('gtk-demo') def tearDown(self): import os, signal os.kill(self.pid, signal.SIGKILL) # Sleep just enough to let the app actually die. # AT-SPI doesn't like being hammered too fast. time.sleep(0.5) def runDemo(self, demoName): """ Click on the named demo within the gtk-demo app. """ tree = self.app.child(roleName="tree table") tree.child(demoName).doubleClick() class TestNodeAttributes(GtkDemoTest): """ Unit tests for the the various synthesized attributes of a Node """ def testGetBogus(self): "Getting a non-existant attribute should raise an attribute error" self.assertRaises(AttributeError, getattr, self.app, "thisIsNotAnAttribute") #FIXME: should setattr for a non-existant attr be allowed? # 'name' (read-only string): def testGetName(self): """ Node.name of the gtk-demo app should be "gtk-demo" """ self.assertEquals(self.app.name, 'gtk-demo') self.assertEquals(dogtail.tree.root.name, 'main') def testSetName(self): "Node.name should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "name", "hello world") # 'roleName' (read-only string): def testGetRoleName(self): """ roleName of the gtk-demo app should be "application" """ self.assertEquals(self.app.roleName, 'application') def testSetRoleName(self): "Node.roleName should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "roleName", "hello world") # 'role' (read-only atspi role enum): def testGetRole(self): "Node.role for a gtk-demo app should be SPI_ROLE_APPLICATION" self.assertEquals(self.app.role, dogtail.tree.pyatspi.ROLE_APPLICATION) def testSetRole(self): "Node.role should be read-only" # FIXME should be AttributeError? self.assertRaises(RuntimeError, self.app.__setattr__, "role", pyatspi.Atspi.Role(1)) # 'description' (read-only string): def testGetDescription(self): # FIXME: can we get a more interesting test case here? self.assertEquals(self.app.description, "") def testSetDescription(self): "Node.description should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "description", "hello world") # 'parent' (read-only Node instance): def testGetParent(self): # the app has a parent if gnome-shell is used, so parent.parent is a safe choice if filter(lambda x: x.name == 'gnome-shell', self.app.applications()): self.assertEquals(self.app.parent.parent, None) self.assertEquals(self.app.children[0].parent, self.app) def testSetParent(self): "Node.parent should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "parent", None) # 'children' (read-only list of Node instances): def testGetChildren(self): "A fresh gtk-demo app should have a single child: the window." kids = self.app.children self.assertEquals(len(kids), 1) self.assertEquals(kids[0].name, "GTK+ Code Demos") self.assertEquals(kids[0].roleName, "frame") def testSetChildren(self): "Node.children should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "children", []) # 'text' (string): def testSimpleTextEntry(self): """ Use gtk-demo's text entry example to check that reading and writing Node.text works as expected """ self.runDemo('Dialog and Message Boxes') wnd = self.app.window('Dialogs') wnd.button('Interactive Dialog').click() dlg = self.app.dialog('Interactive Dialog') entry1 = dlg.child(label='Entry 1') entry2 = dlg.child(label='Entry 2') # Try reading the entries: self.assertEquals(entry1.text, "") self.assertEquals(entry2.text, "") # Set them... entry1.text = "hello" entry2.text = "world" # Ensure that they got set: self.assertEquals(entry1.text, "hello") self.assertEquals(entry2.text, "world") # and try again, searching for them again, to ensure it actually affected the UI: self.assertEquals(dlg.child(label='Entry 1').text, "hello") self.assertEquals(dlg.child(label='Entry 2').text, "world") # Ensure app.text is None self.assertEquals(self.app.text, None) # Ensure a label's text is read-only as expected: # FIXME: this doesn't work; the label has no 'text'; it has a name. we wan't a readonly text entry # label = dlg.child('Entry 1') # self.assertRaises(dogtail.tree.ReadOnlyError, label.text.__setattr__, "text", "hello world") # FIXME: should we assert that things are logged and delays are added? # FIXME: should have a test case involving the complex GtkTextView widget def testCaretOffset(self): "Make sure the caret offset works as expected" self.runDemo('Dialog and Message Boxes') wnd = self.app.window('Dialogs') entry1 = wnd.child(label = 'Entry 1') entry2 = wnd.child(label = 'Entry 2') # Try reading the entries: self.assertEquals(entry1.text,'') self.assertEquals(entry2.text,'') # Set them... s1 = "I just need a sentence" s2 = "And maybe a second one to be sure" entry1.text = s1 entry2.text = s2 # Make sure the caret offset is zero self.assertEquals(entry1.caretOffset, 0) self.assertEquals(entry2.caretOffset, 0) # Set the caret offset to something ridiculous entry1.caretOffset = len(s1 * 3) entry2.caretOffset = len(s2 * 3) # Make sure the caret offset only goes as far as the end of the string self.assertEquals(entry1.caretOffset, len(s1)) self.assertEquals(entry2.caretOffset, len(s2)) def splitByOffsets(node, string): # Verify the equality of node.text and string, word by word. # I realize this doesn't really test dogtail itself, but that could # change in the future and I don't want to throw the code away. textIface = node.queryText() endOffset = -1 # We only set this now so the loop looks nicer startOffset = 0 while startOffset != len(string): (text, startOffset, endOffset) = textIface.getTextAtOffset( startOffset, pyatspi.TEXT_BOUNDARY_WORD_START) self.assertEquals(startOffset, string.find(text, startOffset, endOffset)) startOffset = endOffset splitByOffsets(entry1, s1) splitByOffsets(entry2, s2) # 'combovalue' (read/write string): def testSetComboValue(self): self.runDemo('Combo boxes') wnd = self.app.window('Combo boxes') combo1 = wnd.child('Some stock icons').child(roleName = 'combo box') combo1.combovalue = 'Clear' self.assertEquals(combo1.combovalue, 'Clear') # 'stateSet' (read-only StateSet instance): def testGetStateSet(self): "Node.sensitive should be False for the gtk-demo app node" self.assert_(not self.app.sensitive) def testSetStateSet(self): "Node.stateSet should be read-only" # FIXME should be AttributeError? self.assertRaises(RuntimeError, self.app.__setattr__, "states", pyatspi.StateSet()) # 'relations' (read-only list of atspi.Relation instances): def testGetRelations(self): # FIXME once relations are used for something other than labels pass # 'labelee' (read-only list of Node instances): def testGetLabelee(self): "Entry1/2's labelee should be a text widget" self.runDemo('Dialog and Message Boxes') wnd = self.app.window('Dialogs') label = wnd.child(roleName = 'label') self.assertEquals(label.labelee.roleName, 'text') def testSetLabelee(self): "Node.labelee should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "labellee", None) # 'labeler' (read-only list of Node instances): def testGetLabeler(self): "The text areas in the 'Dialogs' window should have labelers." self.runDemo('Dialog and Message Boxes') wnd = self.app.window('Dialogs') text = wnd.child(roleName = 'text') self.assertEquals(text.labeler.name, 'Entry 2') def testSetLabeller(self): "Node.labeller should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "labeller", None) # 'sensitive' (read-only boolean): def testGetSensitive(self): """ Node.sensitive should not be set for the gtk-demo app. It should be set for the window within the app. """ self.assert_(not self.app.sensitive) self.assert_(self.app.children[0].sensitive) def testSetSensitive(self): "Node.sensitive should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "sensitive", True) # 'showing' (read-only boolean): def testGetShowing(self): "Node.showing should not be set for the gtk-demo. It should be set for the window within the app" self.assert_(not self.app.showing) self.assert_(self.app.children[0].showing) def testSetShowing(self): "Node.showing should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "showing", True) # 'actions' (read-only list of Action instances): def testGetActions(self): "Node.actions should be an empty list for the app node" self.assertEquals(len(self.app.actions), 0) def testSetActions(self): "Node.actions should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "actions", {}) # 'extents' (readonly tuple): def testGetExtents(self): "Node.extents should be a 4-tuple for a window, with non-zero size" (x,y,w,h) = self.app.children[0].extents self.assert_(w>0) self.assert_(h>0) def testSetExtents(self): "Node.extents should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "extents", (0,0,640,480)) # 'position' (readonly tuple): def testGetPosition(self): "Node.position should be a 2-tuple for a window" (x,y) = self.app.children[0].position def testSetPosition(self): "Node.position should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "position", (0,0)) # 'size' (readonly tuple): def testGetSize(self): "Node.size should be a 2-tuple for a window, with non-zero values" (w,h) = self.app.children[0].size self.assert_(w>0) self.assert_(h>0) def testSetSize(self): "Node.size should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "size", (640,480)) # 'toolkitName' (readonly string): def testGetToolkit(self): self.assertEquals(self.app.toolkitName, "GAIL") def testSetToolkit(self): "Node.toolkit should be read-only" self.assertRaises(AttributeError, self.app.__setattr__, "toolkitName", "GAIL") # 'ID' def testGetID(self): "Node.id should be numeric" self.assertEquals(type(self.app.id), type(42)) def testSetID(self): "Node.id should be read-only" self.assertRaises(AttributeError, setattr, self.app, "id", 42) class TestSelection(GtkDemoTest): def testTabs(self): """ Tabs in the gtk-demo should be selectable, and be queryable for "isSelected", and the results should change as they are selected. """ # Use the Info/Source tabs of gtk-demo: info = self.app.child('Info') source = self.app.child('Source') # Check initial state: self.assert_(info.isSelected) self.assert_(not source.isSelected) # Select other tab: source.select() # Check new state: self.assert_(not info.isSelected, False) self.assert_(source.isSelected) # Deselect tab: #source.deselect() # Check state: #self.assert_(info.isSelected) #self.assert_(not source.isSelected) class TestValue(GtkDemoTest): def testGetValue(self): "The scrollbar starts out at position zero." sb = self.app.child(roleName = 'scroll bar') self.assertEquals(sb.value, 0) def testSetValue(self): "Ensure that we can set the value of the scrollbar." sb = self.app.child(roleName = 'scroll bar') sb.value = 100 self.assertEquals(sb.value, 100) def testMinValue(self): "Ensure that the minimum value for the scrollbar is correct." sb = self.app.child(roleName = 'scroll bar') self.assertEquals(sb.minValue, 0) def testMaxValue(self): "Ensure that the maximum value for the scrollbar is plausible." sb = self.app.child(roleName = 'scroll bar') self.assert_(sb.maxValue > 250) def testMinValueIncrement(self): "Ensure that the minimum value increment of the scrollbar is an int." sb = self.app.child(roleName = 'scroll bar') self.assertEquals(sb.minValueIncrement, sb.minValueIncrement) class TestSearching(GtkDemoTest): # FIXME: should test the various predicates and the search methods of Node def testFindChildren(self): """ Ensure that there are the correct number of table cells in the list of demos. """ pred = dogtail.predicate.GenericPredicate(roleName = 'table cell') tableCells = self.app.findChildren(pred) def get_table_cells_recursively(node): counter = 0 for child in node.children: if child.roleName == 'table cell': counter += 1 counter += get_table_cells_recursively(child) return counter counter = get_table_cells_recursively(self.app) self.assertEquals(len(tableCells), counter) def testFindChildren2(self): "Ensure that there are two tabs in the second page tab list." pred = dogtail.predicate.GenericPredicate(roleName = 'page tab list') pageTabLists = self.app.findChildren(pred) pred = dogtail.predicate.GenericPredicate(roleName = 'page tab') # The second page tab list is the one with the 'Info' and 'Source' tabs pageTabs = pageTabLists[1].findChildren(pred) self.assertEquals(len(pageTabs), 2) def testFindChildrenNonRecursive(self): """ Ensure that there are the correct number of table cells in the Tree Store demo. """ # The next several lines exist to expand the 'Tree View' item and # scroll down, so that runDemo() will work. # FIXME: make runDemo() handle this for us. treeViewCell = self.app.child('Tree View', roleName = 'table cell') treeViewCell.typeText('+') dogtail.tree.doDelay() sb = self.app.child(roleName = 'scroll bar') sb.value = sb.maxValue self.runDemo('Tree Store') wnd = self.app.window('Card planning sheet') table = wnd.child(roleName = 'tree table') pred = dogtail.predicate.GenericPredicate(roleName = 'table cell') dogtail.config.config.childrenLimit = 10000 cells = table.findChildren(pred, recursive = False) direct_cells = filter(lambda cell: cell.roleName=='table cell', table.children) self.assertEquals(len(cells), len(direct_cells)) class TestActions(GtkDemoTest): # FIXME: should test the various actions pass class TestProcedural(GtkDemoTest): # FIXME: should test the procedural API pass class TestExceptions(GtkDemoTest): def test_exception(self): # Kill the gtk-demo prematurely: import os, signal os.kill(self.pid, signal.SIGKILL) import gi._glib # Ensure that we get an exception when we try to work further with it: self.assertRaises(gi._glib.GError, self.app.dump) class TestConfiguration(unittest.TestCase): def test_get_set_all_properties(self): for option in dogtail.config.config.defaults.keys(): print("Setting config.%s property" % option) value = '' if 'Dir' in option: value = '/tmp/dogtail/' # Special value for dir-related properties dogtail.config.config.__setattr__(option, value) self.assertEquals(dogtail.config.config.__getattr__(option), value) def test_default_directories_created(self): import os.path self.assertEquals(os.path.isdir(dogtail.config.config.scratchDir), True) self.assertEquals(os.path.isdir(dogtail.config.config.logDir), True) self.assertEquals(os.path.isdir(dogtail.config.config.dataDir), True) def test_set(self): self.assertRaises(AttributeError, setattr, dogtail.config.config, 'nosuchoption', 42) def test_get(self): self.assertRaises(AttributeError, getattr, dogtail.config.config, 'nosuchoption') def helper_create_directory_and_set_option(self, path, property_name): import os.path if os.path.isdir(path): import shutil shutil.rmtree(path) dogtail.config.config.__setattr__(property_name, path) self.assertEquals(os.path.isdir(path), True) def test_create_scratch_directory(self): new_folder = "/tmp/dt" self.helper_create_directory_and_set_option(new_folder, 'scratchDir') def test_create_data_directory(self): new_folder = "/tmp/dt_data" self.helper_create_directory_and_set_option(new_folder, 'dataDir') def test_create_log_directory(self): new_folder = "/tmp/dt_log" self.helper_create_directory_and_set_option(new_folder, 'logDir') def test_load(self): dogtail.config.config.load({'actionDelay': 2.0}) self.assertEquals(dogtail.config.config.actionDelay, 2.0) def test_reset(self): default_actionDelay = dogtail.config.config.defaults['actionDelay'] dogtail.config.config.actionDelay = 2.0 dogtail.config.config.reset() self.assertEquals(dogtail.config.config.actionDelay, default_actionDelay) def trap_stdout(function, args=None): import sys from StringIO import StringIO saved_stdout = sys.stdout try: out = StringIO() sys.stdout = out import types if type(args) is dict: function(**args) elif args: function(args) else: function() output = out.getvalue().strip() finally: sys.stdout = saved_stdout return output class TestDump(GtkDemoTest): def test_dump_to_stdout(self): child = self.app.child('Source') output = trap_stdout(child.dump) self.assertEquals(output, """[page tab | Source] [scroll pane | ] [text | ] [scroll bar | ] [action | activate | ] [scroll bar | ] [action | activate | ]""") if __name__ == '__main__': unittest.main()