]> cloud.milkyroute.net Git - dolphin.git/commitdiff
redo service menu ruby helpers from ground up more or less
authorHarald Sitter <sitter@kde.org>
Tue, 26 Feb 2019 12:48:08 +0000 (13:48 +0100)
committerHarald Sitter <sitter@kde.org>
Mon, 18 Mar 2019 13:02:16 +0000 (14:02 +0100)
Summary:
- apply ruby community style guidelines
- full rewrite fixing, among other things:
  - inefficient/unreadable String#end_with reimplementation
  - inefficient use of Kernel.system (forked shell to fork a shell)
  - inefficient/unreadable Dir.glob reimplementation
  - inefficient File initialization for single chmod
  - invocation conditions are now actually readable
  - invocation conditions now also force +x on argless scripts, not just
    scripts that need arguments
  - repetitive conditions are now expressed as loops on argument arrays
  - mime detection now uses xdg-mime instead of file (xdg-mime internally
    may fall back to mime but will prefer higher level tools such as
    kmimetypefinder5; giving better results overall)
  - return values of "backtick forks" are now checked and will produce
    suitable errors on stderr
  - fail now takes a log_msg argument which is printed to stderr. this
    is in addition to the error raised as notification for the user, as
    that is unfortunately not so useful for diagnostics
  - overall error handling and logging of problem causes is much improved
- add license headers. the original code was actually fairly exhaustive, so
  this really should have had a header to begin with. the code was
  originally introduced in svn r1045663 on Nov 6 14:56:35 2009 UTC
- add blackbox tests. in the interest of keeping the scripts actually
  simple scripts (as opposed to a bunch of classes used by even simpler
  scirpts) they are now also covered by test rigging which runs them as
  scripts (again, as opposed to individual unit testing of distinct units)
  - the tests optionally can use simplecov to gather coverage metrics
  - also wired up to ctest so it actually gets run

structurally there is actually a fair amount of overlap between the two
scripts, but again, in the interest of keeping things simple I think it's
better to live with that instead of refactoring a shared library out of
it and then use heavy-duty meta-programming

Reviewers: #dolphin, elvisangelaccio

Reviewed By: #dolphin, elvisangelaccio

Subscribers: elvisangelaccio, kfm-devel

Tags: #dolphin

Differential Revision: https://phabricator.kde.org/D19334

src/settings/services/servicemenudeinstallation
src/settings/services/servicemenuinstallation
src/settings/services/test/service_menu_deinstallation_test.rb [new file with mode: 0644]
src/settings/services/test/service_menu_installation_test.rb [new file with mode: 0644]
src/settings/services/test/test_helper.rb [new file with mode: 0644]
src/settings/services/test/test_run.rb [new file with mode: 0755]
src/tests/CMakeLists.txt

index 5e4234262cc2e88346c33e09f77ac030e383a296..9e090e2cda57716bc906c2cef2e5b03e25149c00 100755 (executable)
@@ -1,37 +1,72 @@
 #!/usr/bin/env ruby
+
+# Copyright (C) 2009 Jonathan Schmidt-DominĂ© <devel@the-user.org>
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
 require 'fileutils'
-archive = ARGV[0]
-if archive[(archive.length - 8)..(archive.length)] == ".desktop"
-    FileUtils.rm(`qtpaths --writable-path GenericDataLocation`.strip!  + "/kservices5/ServiceMenus/" + File.basename(archive))
-    exit(0)
+
+ARCHIVE = ARGV[0]
+
+# @param log_msg [String] error that gets logged to CLI
+def fail(log_msg: nil)
+  # FIXME: this is not translated...
+  msg = 'Dolphin service menu installation failed'
+  warn log_msg if log_msg
+  system('kdialog', '--passivepopup', msg, '15')
+  abort
 end
-dir = archive + "-dir"
-# try: deinstall.sh
-# try: deinstall
-# try: installKDE4.sh
-# try: installKDE4
-# try: install.sh
-# try: install
-while true
-    dd = Dir.new(dir)
-    break if dd.count != 3
-    odir = dir
-    for entry in dd
-        dir += "/" + entry if entry != "." && entry != ".."
-    end
-    if !File.directory? dir
-        dir = odir
-        break
-    end
+
+if ARCHIVE.end_with?('.desktop')
+  data_location = `qtpaths --writable-path GenericDataLocation`.strip
+  unless $?.success?
+    fail(log_msg: "Could not get GenericDataLocation #{data_location}")
+  end
+  FileUtils.rm("#{data_location}/kservices5/ServiceMenus/#{File.basename(ARCHIVE)}")
+  exit(0)
 end
-Dir.chdir(dir)
-def fail()
-    system("kdialog --passivepopup \"Deinstallation failed\" 15")
-    exit(-1)
+dir = "#{ARCHIVE}-dir"
+
+deinstaller = nil
+%w[deinstall.sh deinstall].find do |script|
+  deinstaller = Dir.glob("#{dir}/**/#{script}")[0]
 end
-if !((File.exist?(file = "./deinstall.sh") || File.exist?(file = "./deinstall")) && system(file))
-    fail() if !File.exist?(file = "./installKDE4.sh") && !File.exist?(file = "./installKDE4") && !File.exist?(file = "./install.sh") && !File.exist?(file = "./install")
-File.new(file).chmod(0700)
-    fail() if !system(file + " --remove") && !system(file + " --delete") && !system(file + " --uninstall") && !system(file + " --deinstall")
+
+installer = nil
+%w[install-it.sh install-it installKDE4.sh installKDE4 install.sh install].find do |script|
+  installer = Dir.glob("#{dir}/**/#{script}")[0]
+end
+
+Dir.chdir(dir) do
+  deinstalled = false
+
+  [deinstaller, installer].uniq.compact.each { |f| File.chmod(0o700, f) }
+
+  if deinstaller
+    puts "[servicemenudeinstallation]: Trying to run deinstaller #{deinstaller}"
+    deinstalled = system(deinstaller)
+  elsif installer
+    puts "[servicemenudeinstallation]: Trying to run installer #{installer}"
+    %w[--remove --delete --uninstall --deinstall].any? do |arg|
+      deinstalled = system(installer, arg)
+    end
+  end
+
+  fail unless deinstalled
 end
+
 FileUtils.rm_r(dir)
index 60b699bb6041b3cce0cd4a28f48e6a4c5c188d1d..e2d42bfbf4f0f92f48d3085a58db888df2e7133d 100755 (executable)
 #!/usr/bin/env ruby
-require 'pathname'
+
+# Copyright (C) 2009 Jonathan Schmidt-DominĂ© <devel@the-user.org>
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
 require 'fileutils'
-archive = ARGV[0]
-$servicedir = `qtpaths --writable-path GenericDataLocation`.strip!  + "/kservices5/ServiceMenus/"
-FileUtils.mkdir_p($servicedir) if !File.exist?($servicedir)
-if archive[(archive.length - 8)..(archive.length - 1)] == ".desktop"
-    puts "Single-File Service-Menu"
-    puts archive
-    puts $servicedir
-    FileUtils.cp(archive, $servicedir);
-    exit(0)
+
+ARCHIVE_UNCOMPRESSORS = {
+  'application/x-tar' => :"tar -xf %s -C %s",
+  'application/tar' => :"tar -xf %s -C %s",
+  'application/x-gzip' => :"tar -zxf %s -C %s",
+  'application/gzip' => :"tar -zxf %s -C %s",
+  'application/x-gzip-compressed-tar' => :"tar -zxf %s -C %s",
+  'application/gzip-compressed-tar' => :"tar -zxf %s -C %s",
+  'application/x-gzip-compressed' => :"tar -zxf %s -C %s",
+  'application/gzip-compressed' => :"tar -zxf %s -C %s",
+  'application/bzip' => :"tar -jxf %s -C %s",
+  'application/bzip2' => :"tar -jxf %s -C %s",
+  'application/x-bzip' => :"tar -jxf %s -C %s",
+  'application/x-bzip2' => :"tar -jxf %s -C %s",
+  'application/bzip-compressed' => :"tar -jxf %s -C %s",
+  'application/bzip2-compressed' => :"tar -jxf %s -C %s",
+  'application/x-bzip-compressed' => :"tar -jxf %s -C %s",
+  'application/x-bzip2-compressed' => :"tar -jxf %s -C %s",
+  'application/bzip-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/bzip2-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/x-bzip-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/x-bzip2-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/zip' => :"unzip %s -d %s",
+  'application/x-zip' => :"unzip %s -d %s",
+  'application/x-zip-compressed' => :"unzip %s -d %s",
+  'multipart/x-zip' => :"unzip %s -d %s",
+  'application/tgz' => :"tar -zxf %s -C %s",
+  'application/x-compressed-gtar' => :"tar -zxf %s -C %s",
+  'file/tgz' => :"tar -zxf %s -C %s",
+  'multipart/x-tar-gz' => :"tar -zxf %s -C %s",
+  'application/x-gunzip' => :"tar -zxf %s -C %s",
+  'application/gzipped' => :"tar -zxf %s -C %s",
+  'gzip/document' => :"tar -zxf %s -C %s",
+  'application/x-bz2 ' => :"tar -jxf %s -C %s",
+  'application/x-gtar' => :"tar -xf %s -C %s",
+  'multipart/x-tar' => :"tar -xf %s -C %s"
+}
+
+ARCHIVE = ARGV[0]
+
+# @param log_msg [String] error that gets logged to CLI
+def fail(log_msg: nil)
+  # FIXME: this is not translated...
+  msg = 'Dolphin service menu installation failed'
+  warn log_msg if log_msg
+  system('kdialog', '--passivepopup', msg, '15')
+  abort
 end
-def mimeType(filename)
-    IO.popen("file --mime-type -b " + filename).gets().strip!()
+
+def mime_type(filename)
+  ret = `xdg-mime query filetype #{filename}`.strip
+  return ret if $?.success?
+
+  warn 'Failed to xdg-mime'
+  fail(log_msg: "Failed to xdg-mime #{filename}: #{ret}")
 end
-$archivetypes = { "application/x-tar" => :"tar -xf %s -C %s",
-        "application/tar" => :"tar -xf %s -C %s",
-        "application/x-gzip" => :"tar -zxf %s -C %s",
-        "application/gzip" => :"tar -zxf %s -C %s",
-        "application/x-gzip-compressed-tar" => :"tar -zxf %s -C %s",
-        "application/gzip-compressed-tar" => :"tar -zxf %s -C %s",
-        "application/x-gzip-compressed" => :"tar -zxf %s -C %s",
-        "application/gzip-compressed" => :"tar -zxf %s -C %s",
-        "application/bzip" => :"tar -jxf %s -C %s",
-        "application/bzip2" => :"tar -jxf %s -C %s",
-        "application/x-bzip" => :"tar -jxf %s -C %s",
-        "application/x-bzip2" => :"tar -jxf %s -C %s",
-        "application/bzip-compressed" => :"tar -jxf %s -C %s",
-        "application/bzip2-compressed" => :"tar -jxf %s -C %s",
-        "application/x-bzip-compressed" => :"tar -jxf %s -C %s",
-        "application/x-bzip2-compressed" => :"tar -jxf %s -C %s",
-        "application/bzip-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/bzip2-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/x-bzip-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/x-bzip2-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/zip" => :"unzip %s -d %s",
-        "application/x-zip" => :"unzip %s -d %s",
-        "application/x-zip-compressed" => :"unzip %s -d %s",
-        "multipart/x-zip" => :"unzip %s -d %s",
-        "application/tgz" => :"tar -zxf %s -C %s",
-        "application/x-compressed-gtar" => :"tar -zxf %s -C %s",
-        "file/tgz" => :"tar -zxf %s -C %s",
-        "multipart/x-tar-gz" => :"tar -zxf %s -C %s",
-        "application/x-gunzip" => :"tar -zxf %s -C %s",
-        "application/gzipped" => :"tar -zxf %s -C %s",
-        "gzip/document" => :"tar -zxf %s -C %s",
-        "application/x-bz2 " => :"tar -jxf %s -C %s",
-        "application/x-gtar" => :"tar -xf %s -C %s",
-        "multipart/x-tar" => :"tar -xf %s -C %s"
-}
+
 def uncompress(filename, output)
-    system(sprintf($archivetypes[mimeType(filename)].to_s, filename, output))
+  uncompressor = ARCHIVE_UNCOMPRESSORS.fetch(mime_type(filename)).to_s
+  system(format(uncompressor, filename, output))
+rescue KeyError => e
+  # If a mimetype doesn't have an uncompressor mapped we'll get a keyerror.
+  # we'll log the error but visually report the failure.
+  fail(log_msg: "Unmapped compression format #{filename}; #{e.message}")
 end
-dir = archive + "-dir"
-if File.exist?(dir)
-    FileUtils.rm_r(dir)
+
+data_location = `qtpaths --writable-path GenericDataLocation`.strip
+unless $?.success?
+  fail(log_msg: "Could not get GenericDataLocation #{data_location}")
 end
+servicedir = "#{data_location}/kservices5/ServiceMenus/"
+
+FileUtils.mkdir_p(servicedir) unless File.exist?(servicedir)
+if ARCHIVE.end_with?('.desktop')
+  puts 'Single-File Service-Menu'
+  puts ARCHIVE
+  puts servicedir
+  FileUtils.cp(ARCHIVE, servicedir)
+  exit
+end
+
+dir = "#{ARCHIVE}-dir"
+
+FileUtils.rm_r(dir) if File.exist?(dir)
 FileUtils.mkdir(dir)
-exit(-1) if !uncompress(archive, dir)
-# try: install-it.sh
-# try: install-it
-# try: installKDE4.sh
-# try: installKDE4
-# try: install.sh
-# try: install
-while true
-    dd = Dir.new(dir)
-    break if dd.count != 3
-    odir = dir
-    for entry in dd
-        dir += "/" + entry if entry != "." && entry != ".."
-    end
-    if !File.directory? dir
-        dir = odir
-        break
-    end
+
+fail(log_msg: 'uncompress failed') unless uncompress(ARCHIVE, dir)
+
+install_it = nil
+%w[install-it.sh install-it].find do |script|
+  install_it = Dir.glob("#{dir}/**/#{script}")[0]
+end
+
+installer = nil
+%w[installKDE4.sh installKDE4 install.sh install].find do |script|
+  installer = Dir.glob("#{dir}/**/#{script}")[0]
 end
-Dir.chdir(dir)
-def fail()
-    system("kdialog --passivepopup \"Installation failed\" 15")
-    exit(-1)
+
+Dir.chdir(dir) do
+  installed = false
+
+  [install_it, installer].uniq.compact.each { |f| File.chmod(0o700, f) }
+
+  if install_it
+    puts "[servicemenuinstallation]: Trying to run install_it #{install_it}"
+    installed = system(install_it)
+  elsif installer
+    puts "[servicemenuinstallation]: Trying to run installer #{installer}"
+    %w[--local --local-install --install].any? do |arg|
+      installed = system(installer, arg)
+    end
+  end
+
+  fail unless installed
 end
-if !((File.exist?(file = "./install-it.sh") || File.exist?(file = "./install-it")) && system(file))
-    fail() if !File.exist?(file = "./installKDE4.sh") && !File.exist?(file = "./installKDE4") && !File.exist?(file = "./install.sh") && !File.exist?(file = "./install")
-    File.new(file).chmod(0700)
-    fail() if !system(file + " --local") && !system(file + "--local-install") && !system(file + " --install")
-end    
diff --git a/src/settings/services/test/service_menu_deinstallation_test.rb b/src/settings/services/test/service_menu_deinstallation_test.rb
new file mode 100644 (file)
index 0000000..2b3514a
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+require_relative 'test_helper'
+
+require 'tmpdir'
+
+class ServiceMenuDeinstallationTest < Test::Unit::TestCase
+  def setup
+    @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}")
+    @pwdir = Dir.pwd
+    Dir.chdir(@tmpdir)
+
+    ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data')
+  end
+
+  def teardown
+    Dir.chdir(@pwdir)
+    FileUtils.rm_rf(@tmpdir)
+
+    ENV.delete('XDG_DATA_HOME')
+  end
+
+  def test_run_deinstall
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    archive_base = "#{service_dir}/foo.zip"
+    archive_dir = "#{archive_base}-dir/foo-1.1/"
+    FileUtils.mkpath(archive_dir)
+    File.write("#{archive_dir}/deinstall.sh", <<-DEINSTALL_SH)
+#!/bin/sh
+touch #{@tmpdir}/deinstall.sh-run
+    DEINSTALL_SH
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+touch #{@tmpdir}/install.sh-run
+    INSTALL_SH
+
+    assert(covered_system('servicemenudeinstallation', archive_base))
+
+    # deinstaller should be run
+    # installer should not be run
+    # archive_dir should have been correctly removed
+
+    assert_path_exist('deinstall.sh-run')
+    assert_path_not_exist('install.sh-run')
+    assert_path_not_exist(archive_dir)
+  end
+
+  def test_run_install_with_arg
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    archive_base = "#{service_dir}/foo.zip"
+    archive_dir = "#{archive_base}-dir/foo-1.1/"
+    FileUtils.mkpath(archive_dir)
+
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+if [ "$@" = "--uninstall" ]; then
+  touch #{@tmpdir}/install.sh-run
+  exit 0
+fi
+exit 1
+    INSTALL_SH
+
+    assert(covered_system('servicemenudeinstallation', archive_base))
+
+    assert_path_not_exist('deinstall.sh-run')
+    assert_path_exist('install.sh-run')
+    assert_path_not_exist(archive_dir)
+  end
+
+  # no scripts in sight
+  def test_run_fail
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    archive_base = "#{service_dir}/foo.zip"
+    archive_dir = "#{archive_base}-dir/foo-1.1/"
+    FileUtils.mkpath(archive_dir)
+
+    refute(covered_system('servicemenudeinstallation', archive_base))
+
+    # I am unsure if deinstallation really should keep the files around. But
+    # that's how it behaved originally so it's supposedly intentional
+    #   - sitter, 2019
+    assert_path_exist(archive_dir)
+  end
+
+  # For desktop files things are a bit special. There is one in .local/share/servicemenu-download
+  # and another in the actual ServiceMenus dir. The latter gets removed by the
+  # script, the former by KNS.
+  def test_run_desktop
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    downloaded_file = "#{service_dir}/foo.desktop"
+    FileUtils.mkpath(service_dir)
+    FileUtils.touch(downloaded_file)
+
+    menu_dir = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/"
+    installed_file = "#{menu_dir}/foo.desktop"
+    FileUtils.mkpath(menu_dir)
+    FileUtils.touch(installed_file)
+
+    assert(covered_system('servicemenudeinstallation', downloaded_file))
+
+    assert_path_exist(downloaded_file)
+    assert_path_not_exist(installed_file)
+  end
+end
diff --git a/src/settings/services/test/service_menu_installation_test.rb b/src/settings/services/test/service_menu_installation_test.rb
new file mode 100644 (file)
index 0000000..3c9fc90
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+require_relative 'test_helper'
+
+require 'tmpdir'
+
+class ServiceMenuInstallationTest < Test::Unit::TestCase
+  def setup
+    @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}")
+    @pwdir = Dir.pwd
+    Dir.chdir(@tmpdir)
+
+    ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data')
+  end
+
+  def teardown
+    Dir.chdir(@pwdir)
+    FileUtils.rm_rf(@tmpdir)
+
+    ENV.delete('XDG_DATA_HOME')
+  end
+
+  def test_run_install
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    FileUtils.mkpath(service_dir)
+    archive = "#{service_dir}/foo.tar"
+
+    archive_dir = 'foo' # relative so tar cf is relative without fuzz
+    FileUtils.mkpath(archive_dir)
+    File.write("#{archive_dir}/install-it.sh", <<-INSTALL_IT_SH)
+#!/bin/sh
+touch #{@tmpdir}/install-it.sh-run
+INSTALL_IT_SH
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+touch #{@tmpdir}/install.sh-run
+    INSTALL_SH
+    assert(system('tar', '-cf', archive, archive_dir))
+
+    assert(covered_system('servicemenuinstallation', archive))
+
+    tar_dir = "#{service_dir}/foo.tar-dir"
+    tar_extract_dir = "#{service_dir}/foo.tar-dir/foo"
+    assert_path_exist(tar_dir)
+    assert_path_exist(tar_extract_dir)
+    assert_path_exist("#{tar_extract_dir}/install-it.sh")
+    assert_path_exist("#{tar_extract_dir}/install.sh")
+  end
+
+  def test_run_install_with_arg
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    FileUtils.mkpath(service_dir)
+    archive = "#{service_dir}/foo.tar"
+
+    archive_dir = 'foo' # relative so tar cf is relative without fuzz
+    FileUtils.mkpath(archive_dir)
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+if [ "$@" = "--install" ]; then
+  touch #{@tmpdir}/install.sh-run
+  exit 0
+fi
+exit 1
+    INSTALL_SH
+    assert(system('tar', '-cf', archive, archive_dir))
+
+    assert(covered_system('servicemenuinstallation', archive))
+
+    tar_dir = "#{service_dir}/foo.tar-dir"
+    tar_extract_dir = "#{service_dir}/foo.tar-dir/foo"
+    assert_path_exist(tar_dir)
+    assert_path_exist(tar_extract_dir)
+    assert_path_not_exist("#{tar_extract_dir}/install-it.sh")
+    assert_path_exist("#{tar_extract_dir}/install.sh")
+  end
+
+  def test_run_fail
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    FileUtils.mkpath(service_dir)
+    archive = "#{service_dir}/foo.tar"
+
+    archive_dir = 'foo' # relative so tar cf is relative without fuzz
+    FileUtils.mkpath(archive_dir)
+    assert(system('tar', '-cf', archive, archive_dir))
+
+    refute(covered_system('servicemenuinstallation', archive))
+  end
+
+  def test_run_desktop
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    downloaded_file = "#{service_dir}/foo.desktop"
+    FileUtils.mkpath(service_dir)
+    FileUtils.touch(downloaded_file)
+
+    menu_dir = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/"
+    installed_file = "#{menu_dir}/foo.desktop"
+    FileUtils.mkpath(menu_dir)
+    FileUtils.touch(installed_file)
+
+    assert(covered_system('servicemenuinstallation', downloaded_file))
+
+    assert_path_exist(downloaded_file)
+    assert_path_exist(installed_file)
+  end
+end
diff --git a/src/settings/services/test/test_helper.rb b/src/settings/services/test/test_helper.rb
new file mode 100644 (file)
index 0000000..98c29c1
--- /dev/null
@@ -0,0 +1,78 @@
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+begin
+  require 'simplecov'
+  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
+    [
+      SimpleCov::Formatter::HTMLFormatter
+    ]
+  )
+  SimpleCov.start
+rescue LoadError
+  warn 'SimpleCov not loaded'
+end
+
+# FIXME: add coverage report for jenkins?
+
+$LOAD_PATH.unshift(File.absolute_path('../', __dir__)) # ../
+
+def __test_method_name__
+  return @method_name if defined?(:@method_name)
+  index = 0
+  caller = ''
+  until caller.start_with?('test_')
+    caller = caller_locations(index, 1)[0].label
+    index += 1
+  end
+  caller
+end
+
+# system() variant which sets up merge-coverage. simplecov supports merging
+# of multiple coverage sets. we use this to get coverage metrics on the
+# binaries without having to refactor the script into runnable classes.
+def covered_system(cmd, *argv)
+  pid = fork do
+    Kernel.module_exec do
+      alias_method(:real_system, :system)
+      define_method(:system) do |*args|
+        return true if args.include?('kdialog') # disable kdialog call
+        real_system(*args)
+      end
+    end
+
+    begin
+      require 'simplecov'
+      SimpleCov.start do
+        command_name "#{cmd}_#{__test_method_name__}"
+        merge_timeout 16
+      end
+    rescue LoadError
+      warn 'SimpleCov not loaded'
+    end
+
+    ARGV.replace(argv)
+    load "#{__dir__}/../#{cmd}"
+    puts 'all good, fork ending!'
+    exit 0
+  end
+  waitedpid, status = Process.waitpid2(pid)
+  assert_equal(pid, waitedpid)
+  status.success? # behave like system and return the success only
+end
+
+require 'test/unit'
diff --git a/src/settings/services/test/test_run.rb b/src/settings/services/test/test_run.rb
new file mode 100755 (executable)
index 0000000..b2149bb
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# This is a fancy wrapper around test_helper to prevent the collector from
+# loading the helper twice as it would occur if we ran the helper directly.
+
+require_relative 'test_helper'
+
+Test::Unit::AutoRunner.run(true, File.absolute_path(__dir__))
index 07e4257a0becf6f9fb2a4b51619fe56dbebd1e6f..d788479bd5170cd143018576c181f2e28177ee5a 100644 (file)
@@ -69,3 +69,5 @@ ecm_add_test(placesitemmodeltest.cpp
 TEST_NAME placesitemmodeltest
 LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
 
+add_test(NAME servicemenutest
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../settings/services/test/test_run.rb)