Commit a1e76036 authored by anonym's avatar anonym
Browse files

Merge remote-tracking branch 'origin/devel' into test/9521-chutney

parents 77d16ece a6aa7504
......@@ -18,14 +18,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 'open3'
require 'rbconfig'
require 'rubygems'
require 'vagrant'
require 'uri'
$:.unshift File.expand_path('../vagrant/lib', __FILE__)
require 'tails_build_settings'
require 'vagrant_version'
require_relative 'vagrant/lib/tails_build_settings'
# Path to the directory which holds our Vagrantfile
VAGRANT_PATH = File.expand_path('../vagrant', __FILE__)
......@@ -40,58 +37,61 @@ EXPORTED_VARIABLES = ['http_proxy', 'MKSQUASHFS_OPTIONS', 'TAILS_RAM_BUILD', 'TA
EXTERNAL_HTTP_PROXY = ENV['http_proxy']
# In-VM proxy URL
INTERNEL_HTTP_PROXY = "http://#{VIRTUAL_MACHINE_HOSTNAME}:3142"
INTERNAL_HTTP_PROXY = "http://#{VIRTUAL_MACHINE_HOSTNAME}:3142"
def primary_vm
env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
if vagrant_old
return env.primary_vm
else
name = env.primary_machine_name
return env.machine(name, env.default_provider)
end
end
def primary_vm_state
if vagrant_old
return primary_vm.state
else
return primary_vm.state.id
end
class VagrantCommandError < StandardError
end
def primary_vm_chan
if vagrant_old
return primary_vm.channel
else
return primary_vm.communicate
# Runs the vagrant command, letting stdout/stderr through. Throws an
# exception unless the vagrant command succeeds.
def run_vagrant(*args)
Process.wait Kernel.spawn('vagrant', *args, :chdir => './vagrant')
if $?.exitstatus != 0
raise(VagrantCommandError, "'vagrant #{args}' command failed: " +
"#{$?.exitstatus}")
end
end
def vm_id
if vagrant_old
primary_vm.uuid
else
primary_vm.id
# Runs the vagrant command, not letting stdout/stderr through, and
# returns [stdout, stderr, Preocess:Status].
def capture_vagrant(*args)
stdout, stderr, proc_status =
Open3.capture3('vagrant', *args, :chdir => './vagrant')
if proc_status.exitstatus != 0
raise(VagrantCommandError, "'vagrant #{args}' command failed: " +
"#{proc_status.exitstatus}")
end
return stdout, stderr
end
def vm_driver
if vagrant_old
primary_vm.driver
else
primary_vm.provider.driver
def vagrant_ssh_config(key)
# Cache results
if $vagrant_ssh_config.nil?
$vagrant_ssh_config = capture_vagrant('ssh-config').first.split("\n") \
.map { |line| line.strip.split(/\s+/, 2) } .to_h
# The path in the ssh-config output is quoted, which is not what
# is expected outside of a shell, so let's get rid of the quotes.
$vagrant_ssh_config['IdentityFile'].gsub!(/^"|"$/, '')
end
$vagrant_ssh_config[key]
end
def current_vm_cpus
info = vm_driver.execute 'showvminfo', vm_id, '--machinereadable'
$1.to_i if info =~ /^cpus=(\d+)/
capture_vagrant('ssh', '-c', 'grep -c "^processor\s*:" /proc/cpuinfo').first.chomp.to_i
end
def vm_running?
primary_vm_state == :running
def vm_state
out, _ = capture_vagrant('status')
status_line = out.split("\n")[2]
if status_line['not created']
return :not_created
elsif status_line['shutoff']
return :poweroff
elsif status_line['running']
return :running
else
raise "could not determine VM state"
end
end
def enough_free_host_memory_for_ram_build?
......@@ -106,9 +106,7 @@ def enough_free_host_memory_for_ram_build?
end
def free_vm_memory
primary_vm_chan.execute("free", :error_check => false) do |fd, data|
return data.split[16].to_i
end
capture_vagrant('ssh', '-c', 'free').first.chomp.split[16].to_i
end
def enough_free_vm_memory_for_ram_build?
......@@ -116,7 +114,7 @@ def enough_free_vm_memory_for_ram_build?
end
def enough_free_memory_for_ram_build?
if vm_running?
if vm_state == :running
enough_free_vm_memory_for_ram_build?
else
enough_free_host_memory_for_ram_build?
......@@ -172,7 +170,7 @@ task :parse_build_options do
abort "No HTTP proxy set, but one is required by TAILS_BUILD_OPTIONS. Aborting." unless EXTERNAL_HTTP_PROXY
ENV['http_proxy'] = EXTERNAL_HTTP_PROXY
when 'vmproxy'
ENV['http_proxy'] = INTERNEL_HTTP_PROXY
ENV['http_proxy'] = INTERNAL_HTTP_PROXY
when 'noproxy'
ENV['http_proxy'] = nil
# SquashFS compression settings
......@@ -221,6 +219,26 @@ task :ensure_clean_repository do
end
end
def list_artifacts
user = vagrant_ssh_config('User')
stdout = capture_vagrant('ssh', '-c', "find '/home/#{user}/' -maxdepth 1 " +
"-name 'tails-*.iso*'").first
stdout.split("\n")
rescue VagrantCommandError
return Array.new
end
def remove_artifacts
list_artifacts.each do |artifact|
run_vagrant('ssh', '-c', "sudo rm -f '#{artifact}'")
end
end
desc "Make sure the vagrant user's home directory has no undesired artifacts"
task :ensure_clean_home_directory => ['vm:up'] do
remove_artifacts
end
task :validate_http_proxy do
if ENV['http_proxy']
proxy_host = URI.parse(ENV['http_proxy']).host
......@@ -242,7 +260,7 @@ task :validate_http_proxy do
end
desc 'Build Tails'
task :build => ['parse_build_options', 'ensure_clean_repository', 'validate_http_proxy', 'vm:up'] do
task :build => ['parse_build_options', 'ensure_clean_repository', 'ensure_clean_home_directory', 'validate_http_proxy', 'vm:up'] do
if ENV['TAILS_RAM_BUILD'] && not(enough_free_memory_for_ram_build?)
$stderr.puts <<-END_OF_MESSAGE.gsub(/^ /, '')
......@@ -269,26 +287,41 @@ task :build => ['parse_build_options', 'ensure_clean_repository', 'validate_http
end
exported_env = EXPORTED_VARIABLES.select { |k| ENV[k] }.
collect { |k| "#{k}='#{ENV[k]}'" }.join(' ')
status = primary_vm_chan.execute("#{exported_env} build-tails",
:error_check => false) do |fd, data|
(fd == :stdout ? $stdout : $stderr).write data
collect { |k| "#{k}='#{ENV[k]}'" }.join(' ')
run_vagrant('ssh', '-c', "#{exported_env} build-tails")
artifacts = list_artifacts
raise 'No build artifacts was found!' if artifacts.empty?
user = vagrant_ssh_config('User')
hostname = vagrant_ssh_config('HostName')
key_file = vagrant_ssh_config('IdentityFile')
$stderr.puts "Retrieving artifacts from Vagrant build box."
artifacts.each do |artifact|
run_vagrant('ssh', '-c', "sudo chown #{user} '#{artifact}'")
Process.wait(
Kernel.spawn(
'scp',
'-i', key_file,
# We need this since the user will not necessarily have a
# known_hosts entry. It is safe since an attacker must
# compromise libvirt's network config or the user running the
# command to modify the #{hostname} below.
'-o', 'StrictHostKeyChecking=no',
"#{user}@#{hostname}:#{artifact}", '.'
)
)
raise "Failed to fetch artifact '#{artifact}'" unless $?.success?
end
# Move build products to the current directory
FileUtils.mv Dir.glob("#{VAGRANT_PATH}/tails-*"),
File.expand_path('..', __FILE__), :force => true
exit status
remove_artifacts
end
namespace :vm do
desc 'Start the build virtual machine'
task :up => ['parse_build_options', 'validate_http_proxy'] do
case primary_vm_state
case vm_state
when :not_created
# Do not use non-existant in-VM proxy to download the basebox
if ENV['http_proxy'] == INTERNEL_HTTP_PROXY
if ENV['http_proxy'] == INTERNAL_HTTP_PROXY
ENV['http_proxy'] = nil
restore_internal_proxy = true
end
......@@ -315,31 +348,56 @@ namespace :vm do
END_OF_MESSAGE
end
env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
result = env.cli('up')
abort "'vagrant up' failed" unless result
run_vagrant('up')
ENV['http_proxy'] = INTERNAL_HTTP_PROXY if restore_internal_proxy
end
ENV['http_proxy'] = INTERNEL_HTTP_PROXY if restore_internal_proxy
desc 'SSH into the builder VM'
task :ssh do
run_vagrant('ssh')
end
desc 'Stop the build virtual machine'
task :halt do
env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
result = env.cli('halt')
abort "'vagrant halt' failed" unless result
run_vagrant('halt')
end
desc 'Re-run virtual machine setup'
task :provision => ['parse_build_options', 'validate_http_proxy'] do
env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
result = env.cli('provision')
abort "'vagrant provision' failed" unless result
run_vagrant('provision')
end
desc 'Destroy build virtual machine (clean up all files)'
task :destroy do
env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
result = env.cli('destroy', '--force')
abort "'vagrant destroy' failed" unless result
run_vagrant('destroy', '--force')
end
end
namespace :basebox do
desc 'Generate a new base box'
task :create do
box_dir = VAGRANT_PATH + '/definitions/tails-builder'
Dir.chdir(box_dir) do
`./generate-tails-builder-box.sh`
raise 'Base box generation failed!' unless $?.success?
end
box = Dir.glob("#{box_dir}/*.box").sort_by {|f| File.mtime(f) } .last
$stderr.puts <<-END_OF_MESSAGE.gsub(/^ /, '')
You have successfully generated a new Vagrant base box:
#{box}
To install the new base box, please run:
$ vagrant box add #{box}
To actually make Tails build using this base box, the `config.vm.box` key
in `vagrant/Vagrantfile` has to be updated. Please check the documentation
for details.
END_OF_MESSAGE
end
end
......@@ -180,20 +180,18 @@ set -o pipefail
time eatmydata lb build noauto ${@}
RET=$?
if [ -e "${BUILD_FILENAME}.${BUILD_FILENAME_EXT}" ]; then
if [ "$RET" -eq 0 ]; then
[ -z "$JENKINS_URL" ] || date --utc '+%s' > "$BUILD_END_FILENAME"
echo "Image was successfully created"
if [ "$LB_BINARY_IMAGES" = iso ]; then
ISO_FILE="${BUILD_FILENAME}.${BUILD_FILENAME_EXT}"
print_iso_size "$ISO_FILE"
echo "Hybriding it..."
isohybrid $AMNESIA_ISOHYBRID_OPTS "$ISO_FILE"
print_iso_size "$ISO_FILE"
truncate -s %2048 "$ISO_FILE"
print_iso_size "$ISO_FILE"
fi
else
echo "Warning: image created, but lb build exited with code $RET"
echo "Image was successfully created"
[ "$RET" -eq 0 ] || \
echo "Warning: lb build exited with code $RET"
[ -z "$JENKINS_URL" ] || date --utc '+%s' > "$BUILD_END_FILENAME"
if [ "$LB_BINARY_IMAGES" = iso ]; then
ISO_FILE="${BUILD_FILENAME}.${BUILD_FILENAME_EXT}"
print_iso_size "$ISO_FILE"
echo "Hybriding it..."
isohybrid $AMNESIA_ISOHYBRID_OPTS "$ISO_FILE"
print_iso_size "$ISO_FILE"
truncate -s %2048 "$ISO_FILE"
print_iso_size "$ISO_FILE"
fi
echo "Renaming generated files..."
mv -i "${BUILD_FILENAME}.${BUILD_FILENAME_EXT}" "${BUILD_DEST_FILENAME}"
......
......@@ -29,7 +29,7 @@ apt-get --yes purge \
### since they have Priority: standard.
apt-get --yes purge \
apt-listchanges at bsd-mailx dc debian-faq doc-debian dselect \
'^exim4*' ftp m4 mlocate mutt ncurses-term nfs-common portmap procmail python-apt \
'^exim4*' ftp m4 mlocate mutt ncurses-term nfs-common portmap procmail \
python-reportbug reportbug telnet texinfo time w3m wamerican
### Deinstall some other unwanted packages.
......
......@@ -388,6 +388,7 @@ crda
wireless-regdb
### Automated test suite
python-dogtail
python3-serial
python3-systemd
xdotool
......
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' element.
Therefore we add a `showingOnly` boolean flag to all search methods
where it makes sense (e.g. it doesn't make sense for Application:s
since they seem to always be considered not 'showing'). The default
will be to not do this, for backwards-compatibility, but the default
is configurable via a new `searchShowingOnly` config option.
--- a/usr/share/pyshared/dogtail/config.py
+++ b/usr/share/pyshared/dogtail/config.py
@@ -58,6 +58,9 @@ class _Config(object):
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.
@@ -134,6 +137,7 @@ class _Config(object):
'searchBackoffDuration': 0.5,
'searchWarningThreshold': 3,
'searchCutoffCount': 20,
+ 'searchShowingOnly': False,
'defaultDelay': 0.5,
'childrenLimit': 100,
--- a/usr/share/pyshared/dogtail/tree.py
+++ b/usr/share/pyshared/dogtail/tree.py
@@ -819,12 +819,18 @@ class Node(object):
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:
@@ -839,7 +845,7 @@ class Node(object):
return pyatspi.utils.findDescendant(self, pred)
def findChild(self, pred, recursive=True, debugName=None,
- retry=True, requireResult=True):
+ retry=True, requireResult=True, showingOnly=None):
"""
Search for a node satisyfing the predicate, returning a Node.
@@ -871,7 +877,7 @@ class Node(object):
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:
@@ -891,12 +897,12 @@ class Node(object):
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.
"""
if isLambda is True:
- nodes = self.findChildren(predicate.GenericPredicate(), recursive=recursive)
+ nodes = self.findChildren(predicate.GenericPredicate(), recursive=recursive, showingOnly=showingOnly)
result = []
for node in nodes:
try:
@@ -907,6 +913,12 @@ class Node(object):
return result
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)
result = []
@@ -929,7 +941,7 @@ class Node(object):
return descendants
# 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.
@@ -945,7 +957,7 @@ class Node(object):
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.
@@ -953,9 +965,9 @@ class Node(object):
if no such child is found, and will eventually raise an exception. It
also logs the search.
"""
- return self.findChild(predicate.GenericPredicate(name=name, roleName=roleName, description=description, label=label), recursive=recursive, retry=retry, debugName=debugName)
+ return self.findChild(predicate.GenericPredicate(name=name, roleName=roleName, description=description, 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.
@@ -970,12 +982,12 @@ class Node(object):
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.
@@ -983,9 +995,9 @@ class Node(object):
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.
@@ -993,9 +1005,9 @@ class Node(object):
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.
@@ -1003,9 +1015,9 @@ class Node(object):
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.
@@ -1013,9 +1025,9 @@ class Node(object):
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.
@@ -1023,9 +1035,9 @@ class Node(object):
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.
@@ -1033,9 +1045,9 @@ class Node(object):
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.
@@ -1043,7 +1055,7 @@ class Node(object):
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):
"""
@@ -1109,7 +1121,7 @@ class Root (Node):
Get all applications.
"""
return root.findChildren(predicate.GenericPredicate(
- roleName="application"), recursive=False)
+ roleName="application"), recursive=False, showingOnly=False)
def application(self, appName, retry=True):
"""
@@ -1120,12 +1132,12 @@ class Root (Node):
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.
@@ -1136,9 +1148,9 @@ class Application (Node):
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):
"""
</