cthulhu/test/harness/utils.py
2024-12-18 10:05:44 -05:00

246 lines
8.5 KiB
Python

#!/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
"""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'