Commit 3d8a965c authored by segfault's avatar segfault
Browse files

Merge branch 'test/16004-adapt-usb-scenarios-to-usb-images+force-all-tests'...

Merge branch 'test/16004-adapt-usb-scenarios-to-usb-images+force-all-tests' into devel (Closes: #16004, #16976)
parents 294674c5 7ea8ce79
......@@ -53,6 +53,10 @@ Package: obfs4proxy
Pin: release o=TorProject,n=obfs4proxy
Pin-Priority: 990
Package: python3-dogtail
Pin: release o=Debian,n=bullseye
Pin-Priority: 999
Package: squashfs-tools
Pin: release o=Debian,n=sid
Pin-Priority: 999
......
......@@ -46,8 +46,7 @@ def get_user_env(user):
# Dogtail does not seem to support the root user interacting with
# other users' applications, and it does not support Python 3 (which
# this script is written in) so let's wrap around an interactive
# other users' applications, so let's wrap around an interactive
# Python shell started as a subprocess.
class PythonSession:
def __init__(self, user = None):
......@@ -63,7 +62,7 @@ class PythonSession:
env = get_user_env(user)
cwd = env['HOME']
self.process = subprocess.Popen(
["python2", "-u", "-c", interactive_shell_code],
["python3", "-u", "-c", interactive_shell_code],
bufsize = 0,
shell=False,
stdin=subprocess.PIPE,
......@@ -74,7 +73,7 @@ class PythonSession:
preexec_fn=mk_switch_user_fn(user)
)
init_code = """
import cStringIO
import io
import json
import sys
orig_stdout = sys.stdout
......@@ -87,8 +86,8 @@ class PythonSession:
# This wrapping ensures that we can run almost any reasonable
# code and capture what it does.
wrapper = """
fake_stdout = cStringIO.StringIO()
fake_stderr = cStringIO.StringIO()
fake_stdout = io.StringIO()
fake_stderr = io.StringIO()
sys.stdout = fake_stdout
sys.stderr = fake_stderr
exc = None
......@@ -110,7 +109,7 @@ class PythonSession:
wrapped_code = wrapper.format(code=indented_code)
self.process.stdin.write(wrapped_code.encode())
self.process.stdin.flush()
return str(self.process.stdout.readline().strip(), 'utf-8')
return self.process.stdout.readline().strip()
def run_cmd_as_user(cmd, user):
......@@ -179,7 +178,7 @@ def main():
print("Error caught while processing line:", file=sys.stderr)
print(" " + line, file=sys.stderr)
print("The error was:", file=sys.stderr)
traceback.print_exc(file=sys.stdout)
traceback.print_exc(file=sys.stderr)
print("-----", file=sys.stderr)
sys.stderr.flush()
exc_str = '{}: {}'.format(type(e).__name__, str(e))
......
......@@ -376,7 +376,7 @@ crda
wireless-regdb
### Automated test suite
python-dogtail
python3-dogtail
python3-serial
python3-systemd
xclip
......
Author: anonym <anonym@riseup.net>
Date: Mon Apr 4 18:04:52 2016 +0200
Add support for only searching among 'showing' nodes.
Here 'showing' refers to pyatspi.STATE_SHOWING, i.e. whether a node is
shown to the end-user or not. Quite often we are only interested in
such nodes, at least when dogtail is used to interact with an
application (e.g. clicking something that isn't there won't
work). Most importantly, this greatly simplifies situations where the
'shown' element we are looking for is hard to exactly pinpoint since
it lacks properties to distinguish it from some not 'shown' elements,
which is quite common when nodes lack names.
Therefore we add a `showingOnly` boolean flag to all Node search
methods. The default will be to not do this, for backwards
compatibility, but the default is configurable via a new
`searchShowingOnly` config option.
--- a/usr/lib/python2.7/dist-packages/dogtail/config.py
+++ b/usr/lib/python2.7/dist-packages/dogtail/config.py
@@ -60,6 +60,9 @@
searchCutoffCount (int):
Number of times to retry when a search fails.
+ searchShowingOnly (boolean):
+ Whether to only search among nodes that are currently being shown.
+
defaultDelay (float):
Default time in seconds to sleep when delaying.
@@ -136,6 +139,7 @@
'searchBackoffDuration': 0.5,
'searchWarningThreshold': 3,
'searchCutoffCount': 20,
+ 'searchShowingOnly': False,
'defaultDelay': 0.5,
'childrenLimit': 100,
--- a/usr/lib/python2.7/dist-packages/dogtail/tree.py
+++ b/usr/lib/python2.7/dist-packages/dogtail/tree.py
@@ -855,12 +855,18 @@
else:
return False
- def _fastFindChild(self, pred, recursive=True):
+ def _fastFindChild(self, pred, recursive=True, showingOnly=None):
"""
Searches for an Accessible using methods from pyatspi.utils
"""
if isinstance(pred, predicate.Predicate):
pred = pred.satisfiedByNode
+ if showingOnly == None:
+ showingOnly = config.searchShowingOnly
+ if showingOnly:
+ orig_pred = pred
+ pred = lambda n: orig_pred(n) and \
+ n.getState().contains(pyatspi.STATE_SHOWING)
if not recursive:
cIter = iter(self)
while True:
@@ -873,7 +879,7 @@
else:
return pyatspi.utils.findDescendant(self, pred)
- def findChild(self, pred, recursive=True, debugName=None, retry=True, requireResult=True):
+ def findChild(self, pred, recursive=True, debugName=None, retry=True, requireResult=True, showingOnly=None):
"""
Search for a node satisyfing the predicate, returning a Node.
@@ -905,7 +911,7 @@
logger.log("searching for %s (attempt %i)" %
(describeSearch(self, pred, recursive, debugName), numAttempts))
- result = self._fastFindChild(pred.satisfiedByNode, recursive)
+ result = self._fastFindChild(pred.satisfiedByNode, recursive, showingOnly=showingOnly)
if result:
assert isinstance(result, Node)
if debugName:
@@ -924,7 +930,7 @@
raise SearchError(describeSearch(self, pred, recursive, debugName))
# The canonical "search for multiple" method:
- def findChildren(self, pred, recursive=True, isLambda=False):
+ def findChildren(self, pred, recursive=True, isLambda=False, showingOnly=None):
"""
Find all children/descendents satisfying the predicate.
You can also use lambdas in place of pred that will enable search also against
@@ -938,6 +944,12 @@
else:
assert isinstance(pred, predicate.Predicate)
compare_func = pred.satisfiedByNode
+ if showingOnly == None:
+ showingOnly = config.searchShowingOnly
+ if showingOnly:
+ orig_compare_func = compare_func
+ compare_func = lambda n: orig_compare_func(n) and \
+ n.getState().contains(pyatspi.STATE_SHOWING)
results = []
numAttempts = 0
@@ -960,7 +972,7 @@
return results
# The canonical "search above this node" method:
- def findAncestor(self, pred):
+ def findAncestor(self, pred, showingOnly=None):
"""
Search up the ancestry of this node, returning the first Node
satisfying the predicate, or None.
@@ -976,7 +988,7 @@
return None
# Various wrapper/helper search methods:
- def child(self, name='', roleName='', description='', label='', recursive=True, retry=True, debugName=None):
+ def child(self, name='', roleName='', description='', label='', recursive=True, retry=True, debugName=None, showingOnly=None):
"""
Finds a child satisying the given criteria.
@@ -985,9 +997,9 @@
also logs the search.
"""
return self.findChild(predicate.GenericPredicate(name=name, roleName=roleName, description=description,
- label=label), recursive=recursive, retry=retry, debugName=debugName)
+ label=label), recursive=recursive, retry=retry, debugName=debugName, showingOnly=showingOnly)
- def isChild(self, name='', roleName='', description='', label='', recursive=True, retry=False, debugName=None):
+ def isChild(self, name='', roleName='', description='', label='', recursive=True, retry=False, debugName=None, showingOnly=None):
"""
Determines whether a child satisying the given criteria exists.
@@ -1002,12 +1014,12 @@
self.findChild(
predicate.GenericPredicate(
name=name, roleName=roleName, description=description, label=label),
- recursive=recursive, retry=retry, debugName=debugName)
+ recursive=recursive, retry=retry, debugName=debugName, showingOnly=showingOnly)
except SearchError:
found = False
return found
- def menu(self, menuName, recursive=True):
+ def menu(self, menuName, recursive=True, showingOnly=None):
"""
Search below this node for a menu with the given name.
@@ -1015,9 +1027,9 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsAMenuNamed(menuName=menuName), recursive)
+ return self.findChild(predicate.IsAMenuNamed(menuName=menuName), recursive, showingOnly=showingOnly)
- def menuItem(self, menuItemName, recursive=True):
+ def menuItem(self, menuItemName, recursive=True, showingOnly=None):
"""
Search below this node for a menu item with the given name.
@@ -1025,9 +1037,9 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsAMenuItemNamed(menuItemName=menuItemName), recursive)
+ return self.findChild(predicate.IsAMenuItemNamed(menuItemName=menuItemName), recursive, showingOnly=showingOnly)
- def textentry(self, textEntryName, recursive=True):
+ def textentry(self, textEntryName, recursive=True, showingOnly=None):
"""
Search below this node for a text entry with the given name.
@@ -1035,9 +1047,9 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsATextEntryNamed(textEntryName=textEntryName), recursive)
+ return self.findChild(predicate.IsATextEntryNamed(textEntryName=textEntryName), recursive, showingOnly=showingOnly)
- def button(self, buttonName, recursive=True):
+ def button(self, buttonName, recursive=True, showingOnly=None):
"""
Search below this node for a button with the given name.
@@ -1045,9 +1057,9 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsAButtonNamed(buttonName=buttonName), recursive)
+ return self.findChild(predicate.IsAButtonNamed(buttonName=buttonName), recursive, showingOnly=showingOnly)
- def childLabelled(self, labelText, recursive=True):
+ def childLabelled(self, labelText, recursive=True, showingOnly=None):
"""
Search below this node for a child labelled with the given text.
@@ -1055,9 +1067,9 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsLabelledAs(labelText), recursive)
+ return self.findChild(predicate.IsLabelledAs(labelText), recursive, showingOnly=showingOnly)
- def childNamed(self, childName, recursive=True):
+ def childNamed(self, childName, recursive=True, showingOnly=None):
"""
Search below this node for a child with the given name.
@@ -1065,9 +1077,9 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsNamed(childName), recursive)
+ return self.findChild(predicate.IsNamed(childName), recursive, showingOnly=showingOnly)
- def tab(self, tabName, recursive=True):
+ def tab(self, tabName, recursive=True, showingOnly=None):
"""
Search below this node for a tab with the given name.
@@ -1075,7 +1087,7 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.IsATabNamed(tabName=tabName), recursive)
+ return self.findChild(predicate.IsATabNamed(tabName=tabName), recursive, showingOnly=showingOnly)
def getUserVisibleStrings(self):
"""
@@ -1138,7 +1150,7 @@
"""
Get all applications.
"""
- return root.findChildren(predicate.GenericPredicate(roleName="application"), recursive=False)
+ return root.findChildren(predicate.GenericPredicate(roleName="application"), recursive=False, showingOnly=False)
def application(self, appName, retry=True):
"""
@@ -1149,11 +1161,11 @@
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return root.findChild(predicate.IsAnApplicationNamed(appName), recursive=False, retry=retry)
+ return root.findChild(predicate.IsAnApplicationNamed(appName), recursive=False, retry=retry, showingOnly=False)
class Application (Node):
- def dialog(self, dialogName, recursive=False):
+ def dialog(self, dialogName, recursive=False, showingOnly=None):
"""
Search below this node for a dialog with the given name,
returning a Window instance.
@@ -1164,9 +1176,9 @@
FIXME: should this method activate the dialog?
"""
- return self.findChild(predicate.IsADialogNamed(dialogName=dialogName), recursive)
+ return self.findChild(predicate.IsADialogNamed(dialogName=dialogName), recursive, showingOnly=showingOnly)
- def window(self, windowName, recursive=False):
+ def window(self, windowName, recursive=False, showingOnly=None):
"""
Search below this node for a window with the given name,
returning a Window instance.
@@ -1179,13 +1191,13 @@
The window will be automatically activated (raised and focused
by the window manager) if wnck bindings are available.
"""
- result = self.findChild(predicate.IsAWindowNamed(windowName=windowName), recursive)
+ result = self.findChild(predicate.IsAWindowNamed(windowName=windowName), recursive, showingOnly=showingOnly)
# FIXME: activate the WnckWindow ?
# if gotWnck:
# result.activate()
return result
- def getWnckApplication(self): # pragma: no cover
+ def getWnckApplication(self, showingOnly=None): # pragma: no cover
"""
Get the wnck.Application instance for this application, or None
@@ -1196,7 +1208,7 @@
FIXME: untested
"""
- window = self.child(roleName='frame')
+ window = self.child(roleName='frame', showingOnly=showingOnly)
if window:
wnckWindow = window.getWnckWindow()
return wnckWindow.get_application()
--
2.10.1
......@@ -937,12 +937,19 @@ Then /^Tails is running version (.+)$/ do |version|
assert_equal(version, v2, "The version doesn't match /etc/os-release")
end
def share_host_files(files)
def size_of_shared_disk_for(files)
files = [files] if files.class == String
assert_equal(Array, files.class)
disk_size = files.map { |f| File.new(f).size } .inject(0, :+)
# Let's add some extra space for filesystem overhead etc.
disk_size += [convert_to_bytes(1, 'MiB'), (disk_size * 0.15).ceil].max
return disk_size
end
def share_host_files(files)
files = [files] if files.class == String
assert_equal(Array, files.class)
disk_size = size_of_shared_disk_for(files)
disk = random_alpha_string(10)
step "I temporarily create an #{disk_size} bytes disk named \"#{disk}\""
step "I create a gpt partition labeled \"#{disk}\" with an ext4 " +
......
# coding: utf-8
# Returns a hash that for each persistence preset the running Tails is aware of,
# for each of the corresponding configuration lines,
# maps the source to the destination.
......@@ -170,22 +171,11 @@ Then /^(no|the "([^"]+)") USB drive is selected$/ do |mode, name|
end
end
When /^I (install|reinstall|upgrade) Tails (?:to|on) USB drive "([^"]+)" (by cloning|from an ISO)$/ do |action, name, source|
When /^I (install|reinstall|upgrade) Tails (?:to|on) USB drive "([^"]+)" by cloning$/ do |action, name|
step "I start Tails Installer"
# If the device was plugged *just* before this step, it might not be
# completely ready (so it's shown) at this stage.
try_for(10) { tails_installer_is_device_selected?(name) }
if source == 'from an ISO'
iso_radio = @installer.child('Use a downloaded Tails ISO image',
roleName: 'radio button')
iso_radio.click
iso_radio.parent.button('(None)').click
file_chooser = @installer.child('Select a File', roleName: 'file chooser')
@screen.type("l", Sikuli::KeyModifier.CTRL)
# The only visible text element will be the path entry
file_chooser.child(roleName: 'text').typeText(@iso_path + '\n')
file_chooser.button('Open').click
end
begin
if action == 'reinstall'
label = 'Reinstall (delete all data)'
......@@ -212,9 +202,9 @@ When /^I (install|reinstall|upgrade) Tails (?:to|on) USB drive "([^"]+)" (by clo
end
end
Given /^I plug and mount a USB drive containing the Tails ISO$/ do
iso_dir = share_host_files(TAILS_ISO)
@iso_path = "#{iso_dir}/#{File.basename(TAILS_ISO)}"
Given(/^I plug and mount a USB drive containing a Tails USB image$/) do
usb_image_dir = share_host_files(TAILS_IMG)
@usb_image_path = "#{usb_image_dir}/#{File.basename(TAILS_IMG)}"
end
Given /^I enable all persistence presets$/ do
......@@ -341,14 +331,6 @@ Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name|
tails_is_installed_helper(target_name, "/lib/live/mount/medium", loader)
end
Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
iso_root = "/mnt/iso"
$vm.execute("mkdir -p #{iso_root}")
$vm.execute("mount -o loop #{@iso_path} #{iso_root}")
tails_is_installed_helper(target_name, iso_root, "isolinux")
$vm.execute("umount #{iso_root}")
end
Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
data_part_dev = $vm.disk_dev(name) + "2"
assert(!$vm.execute("test -b #{data_part_dev}").success?,
......@@ -909,3 +891,44 @@ Then /^the system partition on "([^"]+)" has the expected flags$/ do |name|
assert(flags == expected_flags.to_s,
"Got #{flags} as partition flags on #{part_dev} (for #{name}), instead of the expected #{expected_flags}")
end
Given(/^I install a Tails USB image to the (\d+) MiB disk with GNOME Disks$/) do |size_in_MiB_of_destination_disk|
# GNOME Disks displays devices sizes in GB, with 1 decimal digit precision
size_in_GB_of_destination_disk = convert_from_bytes(
convert_to_bytes(size_in_MiB_of_destination_disk.to_i, 'MiB'),
'GB'
).round(1).to_s
debug_log("Expected size of destination disk: " +
size_in_GB_of_destination_disk)
step 'I start "Disks" via GNOME Activities Overview'
disks = Dogtail::Application.new('gnome-disks')
disks.children(roleName: 'table cell').find { |row|
/^#{size_in_GB_of_destination_disk} GB Drive/.match(row.name)
}.grabFocus
disks.child('Menu', roleName: 'toggle button').click
disks.child('Restore Disk Image…', roleName: 'menu item').click
restore_dialog = disks.child('Restore Disk Image', roleName: 'dialog',
showingOnly: true)
# Open the file chooser
disks.pressKey('Enter')
select_disk_image_dialog = disks.child('Select Disk Image to Restore',
roleName: 'file chooser',
showingOnly: true)
disks.typeText(@usb_image_path)
sleep 2 # avoid ENTER being eaten by the auto-completion system
disks.pressKey('Enter')
try_for(10) do
! select_disk_image_dialog.showing
end
restore_dialog.child('Start Restoring…', roleName: 'push button').click
disks.child('Information', roleName: 'alert', showingOnly: true)
.child('Restore', roleName: 'push button', showingOnly: true)
.click
# Wait until the restoration job is finished
job = disks.child('Job', roleName: 'label', showingOnly: true)
try_for(60) do
! job.showing
end
end
......@@ -176,10 +176,7 @@ When /^I unlock and mount this VeraCrypt (volume|file container) with GNOME Disk
gnome_shell = Dogtail::Application.new('gnome-shell')
menu = gnome_shell.menu('Disks')
menu.click()
@screen.wait_and_click('GnomeDisksAttachDiskImageMenuEntry.png', 10)
# Once we use a more recent Dogtail that can deal with UTF-8 (#12185),
# we can instead do:
# gnome_shell.child('Attach Disk Image…', roleName: 'label').click
gnome_shell.child('Attach Disk Image…', roleName: 'label').click
# Otherwise Disks is sometimes minimized, for some reason I don't understand
sleep 2
attach_dialog = disks.child('Select Disk Image to Attach', roleName: 'file chooser', showingOnly: true)
......
......@@ -189,7 +189,12 @@ module Dogtail
get_field('roleName')
end
# Note: this is a global Dogtail action, which should probably live
def showing
get_field('showing') == 'True'
end
# Note: pressKey and typeText are global Dogtail actions,
# which should probably live
# elsewhere than in our Application class, but currently we lack
# the infrastructure to do that: the Ruby plumbing that generates
# and runs Python code lives in the Application class.
......@@ -199,6 +204,10 @@ module Dogtail
run("dogtail.rawinput.pressKey('#{key}')")
end
def typeText(text)
run("dogtail.rawinput.typeText('#{text}')")
end
TREE_API_APP_SEARCHES.each do |method|
define_method(method) do |*args|
args_str = self.class.args_to_s(args)
......
......@@ -3,9 +3,6 @@ Feature: Installing Tails to a USB drive
As a Tails user
I want to install Tails to a suitable USB drive
# XXX: rename to tails_installer.feature and move things that don't use Tails
# Installer elsewhere?
Scenario: Try installing Tails to a too small USB drive without partition table
Given I have started Tails from DVD without network and logged in
And I temporarily create a 4500 MiB disk named "too-small-device"
......@@ -47,7 +44,7 @@ Feature: Installing Tails to a USB drive
Then the running Tails is installed on USB drive "install"
But there is no persistence partition on USB drive "install"
Scenario: Installing Tails to a pristine USB drive
Scenario: Installing Tails with Tails Installer to a pristine USB drive
Given I have started Tails from DVD without network and logged in
And I temporarily create a 7200 MiB disk named "install"
And I plug USB drive "install"
......@@ -125,10 +122,16 @@ Feature: Installing Tails to a USB drive
And the boot device has safe access rights
And there is no persistence partition on USB drive "isohybrid"
Scenario: Installing Tails with GNOME Disks from a USB image
Given I have started Tails from DVD without network and logged in
And I plug and mount a USB drive containing a Tails USB image
And I create a 7200 MiB disk named "usbimage"
And I plug USB drive "usbimage"
And I install a Tails USB image to the 7200 MiB disk with GNOME Disks
# Depends on scenario: Installing Tails with GNOME Disks from a USB image
Scenario: The system partition is updated when booting from a USB drive where a Tails USB image was copied
Given a computer
And I temporarily create a 7200 MiB disk named "usbimage"
And I write the Tails USB image to disk "usbimage"
And I start Tails from USB drive "usbimage" with network unplugged and I login
Then Tails is running from USB drive "usbimage"
And the label of the system partition on "usbimage" is "Tails"
......
......@@ -21,6 +21,10 @@ Feature: Upgrading an old Tails USB installation
# Installation method inspired by the usb-install-tails-greeter
# checkpoint, variations are using the old Tails USB image and a
# different device name ("old" instead of "__internal")
#
# Boot the system to make sure resizing has happened, and to check
# the system is sane (safe access rights, no persistence, etc.); end
# with unplugging to get both a clean state and a stopped machine.
Scenario: Installing an old version of Tails to a pristine USB drive
Given a computer
And I create a 7200 MiB disk named "old"
......@@ -32,7 +36,7 @@ Feature: Upgrading an old Tails USB installation
And there is no persistence partition on USB drive "old"
And process "udev-watchdog" is running
And udev-watchdog is monitoring the correct device
And I shutdown Tails and wait for the computer to power off
And I unplug USB drive "old"
# Depends on scenario: Installing an old version of Tails to a pristine USB drive
Scenario: Creating a persistent partition with the old Tails USB installation
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment