2024-12-18 10:05:44 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# Copyright (c) 2024 Stormux
|
|
|
|
# Copyright (c) 2010-2012 The Orca Team
|
|
|
|
# Copyright (c) 2012 Igalia, S.L.
|
|
|
|
# Copyright (c) 2005-2010 Sun Microsystems Inc.
|
|
|
|
#
|
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, write to the
|
|
|
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
|
|
|
# Boston MA 02110-1301 USA.
|
|
|
|
#
|
|
|
|
# Fork of Orca Screen Reader (GNOME)
|
|
|
|
# Original source: https://gitlab.gnome.org/GNOME/orca
|
|
|
|
|
2024-10-17 16:33:13 -04:00
|
|
|
"""Utilities that can be used by tests."""
|
|
|
|
|
|
|
|
import difflib
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gdk", "3.0")
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
|
|
|
|
|
|
from gi.repository import Gio
|
|
|
|
from gi.repository import Gdk
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from macaroon.playback import *
|
|
|
|
|
|
|
|
testLogger = Gio.DBusProxy.new_for_bus_sync(
|
|
|
|
Gio.BusType.SESSION,
|
|
|
|
Gio.DBusProxyFlags.NONE,
|
|
|
|
None,
|
|
|
|
'org.gnome.Cthulhu',
|
|
|
|
'/org/gnome/Cthulhu',
|
|
|
|
'org.gnome.Cthulhu.Logger',
|
|
|
|
None)
|
|
|
|
|
|
|
|
enable_assert = \
|
|
|
|
environ.get('HARNESS_ASSERT', 'yes') in ('yes', 'true', 'y', '1', 1)
|
|
|
|
errFilename = environ.get('HARNESS_ERR', None)
|
|
|
|
outFilename = environ.get('HARNESS_OUT', None)
|
|
|
|
|
|
|
|
if errFilename and len(errFilename):
|
|
|
|
myErr = open(errFilename, 'a', 0)
|
|
|
|
else:
|
|
|
|
myErr = sys.stderr
|
|
|
|
|
|
|
|
if outFilename and len(outFilename):
|
|
|
|
if outFilename == errFilename:
|
|
|
|
myOut = myErr
|
|
|
|
else:
|
|
|
|
myOut = open(outFilename, 'a', 0)
|
|
|
|
else:
|
|
|
|
myOut = sys.stdout
|
|
|
|
|
|
|
|
def getKeyCodeForName(name):
|
|
|
|
keymap = Gdk.Keymap.get_default()
|
|
|
|
success, entries = keymap.get_entries_for_keyval(Gdk.keyval_from_name(name))
|
|
|
|
if success:
|
|
|
|
return entries[-1].keycode
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
def setClipboardText(text):
|
|
|
|
clipboard = Gtk.Clipboard.get(Gdk.Atom.intern("CLIPBOARD", False))
|
|
|
|
clipboard.set_text(text, -1)
|
|
|
|
|
|
|
|
class StartRecordingAction(AtomicAction):
|
|
|
|
'''Tells Cthulhu to log speech and braille output to a string which we
|
|
|
|
can later obtain and use in an assertion (see AssertPresentationAction)'''
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
if enable_assert:
|
|
|
|
AtomicAction.__init__(self, 1000, self._startRecording)
|
|
|
|
else:
|
|
|
|
AtomicAction.__init__(self, 0, lambda: None)
|
|
|
|
|
|
|
|
def _startRecording(self):
|
|
|
|
testLogger.startRecording()
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return 'Start Recording Action'
|
|
|
|
|
|
|
|
def assertListEquality(rawCthulhuResults, expectedList):
|
|
|
|
'''Convert raw speech and braille output obtained from Cthulhu into a
|
|
|
|
list by splitting it at newline boundaries. Compare it to the
|
|
|
|
list passed in and return the actual results if they differ.
|
|
|
|
Otherwise, return None to indicate an equality.'''
|
|
|
|
|
|
|
|
results = rawCthulhuResults.strip().split("\n")
|
|
|
|
|
|
|
|
# Shoot for a string comparison first.
|
|
|
|
#
|
|
|
|
if results == expectedList:
|
|
|
|
return None
|
|
|
|
elif len(results) != len(expectedList):
|
|
|
|
return results
|
|
|
|
|
|
|
|
# If the string comparison failed, do a regex match item by item
|
|
|
|
#
|
|
|
|
for i in range(0, len(expectedList)):
|
|
|
|
if results[i] == expectedList[i]:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
expectedResultRE = re.compile(expectedList[i])
|
|
|
|
if expectedResultRE.match(results[i]):
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
return results
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
class AssertPresentationAction(AtomicAction):
|
|
|
|
'''Ask Cthulhu for the speech and braille logged since the last use
|
|
|
|
of StartRecordingAction and apply an assertion predicate.'''
|
|
|
|
|
|
|
|
totalCount = 0
|
|
|
|
totalSucceed = 0
|
|
|
|
totalFail = 0
|
|
|
|
totalKnownIssues = 0
|
|
|
|
|
|
|
|
def __init__(self, name, expectedResults,
|
|
|
|
assertionPredicate=assertListEquality):
|
|
|
|
'''name: the name of the test
|
|
|
|
expectedResults: the results we want (typically a list of strings
|
|
|
|
that can be treated as regular expressions)
|
|
|
|
assertionPredicate: method to compare actual results to expected
|
|
|
|
results
|
|
|
|
'''
|
|
|
|
# [[[WDW: the pause is to wait for Cthulhu to process an event.
|
|
|
|
# Probably should think of a better way to do this.]]]
|
|
|
|
#
|
|
|
|
if enable_assert:
|
|
|
|
AtomicAction.__init__(self, 1000, self._stopRecording)
|
|
|
|
self._name = sys.argv[0] + ":" + name
|
|
|
|
self._expectedResults = expectedResults
|
|
|
|
self._assertionPredicate = assertionPredicate
|
|
|
|
AssertPresentationAction.totalCount += 1
|
|
|
|
self._num = AssertPresentationAction.totalCount
|
|
|
|
else:
|
|
|
|
AtomicAction.__init__(self, 0, lambda: None)
|
|
|
|
|
|
|
|
def printDiffs(self, results):
|
|
|
|
"""Compare the expected results with the actual results and print
|
|
|
|
out a set of diffs.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
- results: the actual results.
|
|
|
|
|
|
|
|
Returns an indication of whether this test was expected to fail.
|
|
|
|
"""
|
|
|
|
|
|
|
|
knownIssue = False
|
|
|
|
print("DIFFERENCES FOUND:", file=myErr)
|
|
|
|
if isinstance(self._expectedResults, [].__class__):
|
|
|
|
for result in self._expectedResults:
|
|
|
|
if result.startswith("KNOWN ISSUE") \
|
|
|
|
or result.startswith("BUG?"):
|
|
|
|
knownIssue = True
|
|
|
|
else:
|
|
|
|
if self._expectedResults.startswith("KNOWN ISSUE") \
|
|
|
|
or self._expectedResults.startswith("BUG?"):
|
|
|
|
knownIssue = True
|
|
|
|
|
|
|
|
d = difflib.Differ()
|
|
|
|
try:
|
|
|
|
# This can stack trace for some odd reason (UTF-8 characters?),
|
|
|
|
# so we need to capture it. Otherwise, it can hang the tests.
|
|
|
|
#
|
|
|
|
diffs = list(d.compare(self._expectedResults, results))
|
|
|
|
print('\n'.join(list(diffs)), file=myErr)
|
|
|
|
except:
|
|
|
|
print("(ERROR COMPUTING DIFFERENCES!!!)", file=myErr)
|
|
|
|
for i in range(0, max(len(results), len(self._expectedResults))):
|
|
|
|
try:
|
|
|
|
print(" EXPECTED: %s" \
|
|
|
|
% self._expectedResults[i].decode("UTF-8", "replace"), file=myErr)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
print(" ACTUAL: %s" \
|
|
|
|
% results[i].decode("UTF-8", "replace"), file=myErr)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return knownIssue
|
|
|
|
|
|
|
|
def _stopRecording(self):
|
|
|
|
result = testLogger.stopRecording()
|
|
|
|
results = self._assertionPredicate(result, self._expectedResults)
|
|
|
|
if not results:
|
|
|
|
AssertPresentationAction.totalSucceed += 1
|
|
|
|
print("Test %d of %d SUCCEEDED: %s" \
|
|
|
|
% (self._num,
|
|
|
|
AssertPresentationAction.totalCount,
|
|
|
|
self._name), file=myOut)
|
|
|
|
else:
|
|
|
|
AssertPresentationAction.totalFail += 1
|
|
|
|
print("Test %d of %d FAILED: %s" \
|
|
|
|
% (self._num,
|
|
|
|
AssertPresentationAction.totalCount,
|
|
|
|
self._name), file=myErr)
|
|
|
|
|
|
|
|
knownIssue = self.printDiffs(results)
|
|
|
|
if knownIssue:
|
|
|
|
AssertPresentationAction.totalKnownIssues += 1
|
|
|
|
print('[FAILURE WAS EXPECTED - ' \
|
|
|
|
'LOOK FOR KNOWN ISSUE OR BUG? ' \
|
|
|
|
'IN EXPECTED RESULTS]', file=myErr)
|
|
|
|
else:
|
|
|
|
print('[FAILURE WAS UNEXPECTED]', file=myErr)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return 'Assert Presentation Action: %s' % self._name
|
|
|
|
|
|
|
|
class AssertionSummaryAction(AtomicAction):
|
|
|
|
'''Output the summary of successes and failures of
|
|
|
|
AssertPresentationAction assertions.'''
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
AtomicAction.__init__(self, 0, self._printSummary)
|
|
|
|
|
|
|
|
def _printSummary(self):
|
|
|
|
print("SUMMARY: %d SUCCEEDED and %d FAILED (%d UNEXPECTED) of %d for %s"\
|
|
|
|
% (AssertPresentationAction.totalSucceed,
|
|
|
|
AssertPresentationAction.totalFail,
|
|
|
|
(AssertPresentationAction.totalFail \
|
|
|
|
- AssertPresentationAction.totalKnownIssues),
|
|
|
|
AssertPresentationAction.totalCount,
|
|
|
|
sys.argv[0]), file=myOut)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return 'Start Recording Action'
|