diff --git a/gio/gio-tool-info.c b/gio/gio-tool-info.c index dbdc6b69e..a773d8d6c 100644 --- a/gio/gio-tool-info.c +++ b/gio/gio-tool-info.c @@ -177,7 +177,8 @@ show_info (GFile *file, GFileInfo *info) g_free (flatten); } - name = g_file_info_get_name (info); + name = g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_NAME) ? + g_file_info_get_name (info) : NULL; if (name) { escaped = escape_string (name); diff --git a/gio/gio-tool.c b/gio/gio-tool.c index a9701090d..6315b48bf 100644 --- a/gio/gio-tool.c +++ b/gio/gio-tool.c @@ -219,35 +219,67 @@ handle_version (int argc, char *argv[], gboolean do_help) return 0; } -static void -usage (void) +typedef int (*HandleSubcommand) (int argc, char *argv[], gboolean do_help); + +static const struct { - g_printerr ("%s\n", _("Usage:")); - g_printerr (" gio %s %s\n", _("COMMAND"), _("[ARGS…]")); - g_printerr ("\n"); - g_printerr ("%s\n", _("Commands:")); - g_printerr (" help %s\n", _("Print help")); - g_printerr (" version %s\n", _("Print version")); - g_printerr (" cat %s\n", _("Concatenate files to standard output")); - g_printerr (" copy %s\n", _("Copy one or more files")); - g_printerr (" info %s\n", _("Show information about locations")); - g_printerr (" launch %s\n", _("Launch an application from a desktop file")); - g_printerr (" list %s\n", _("List the contents of locations")); - g_printerr (" mime %s\n", _("Get or set the handler for a mimetype")); - g_printerr (" mkdir %s\n", _("Create directories")); - g_printerr (" monitor %s\n", _("Monitor files and directories for changes")); - g_printerr (" mount %s\n", _("Mount or unmount the locations")); - g_printerr (" move %s\n", _("Move one or more files")); - g_printerr (" open %s\n", _("Open files with the default application")); - g_printerr (" rename %s\n", _("Rename a file")); - g_printerr (" remove %s\n", _("Delete one or more files")); - g_printerr (" save %s\n", _("Read from standard input and save")); - g_printerr (" set %s\n", _("Set a file attribute")); - g_printerr (" trash %s\n", _("Move files or directories to the trash")); - g_printerr (" tree %s\n", _("Lists the contents of locations in a tree")); - g_printerr ("\n"); - g_printerr (_("Use %s to get detailed help.\n"), "“gio help COMMAND”"); - exit (1); + const char *name; /* as used on the command line */ + HandleSubcommand handle_func; /* (nullable) for "help" only */ + const char *description; /* translatable */ +} gio_subcommands[] = { + { "help", NULL, N_("Print help") }, + { "version", handle_version, N_("Print version") }, + { "cat", handle_cat, N_("Concatenate files to standard output") }, + { "copy", handle_copy, N_("Copy one or more files") }, + { "info", handle_info, N_("Show information about locations") }, + { "launch", handle_launch, N_("Launch an application from a desktop file") }, + { "list", handle_list, N_("List the contents of locations") }, + { "mime", handle_mime, N_("Get or set the handler for a mimetype") }, + { "mkdir", handle_mkdir, N_("Create directories") }, + { "monitor", handle_monitor, N_("Monitor files and directories for changes") }, + { "mount", handle_mount, N_("Mount or unmount the locations") }, + { "move", handle_move, N_("Move one or more files") }, + { "open", handle_open, N_("Open files with the default application") }, + { "rename", handle_rename, N_("Rename a file") }, + { "remove", handle_remove, N_("Delete one or more files") }, + { "save", handle_save, N_("Read from standard input and save") }, + { "set", handle_set, N_("Set a file attribute") }, + { "trash", handle_trash, N_("Move files or directories to the trash") }, + { "tree", handle_tree, N_("Lists the contents of locations in a tree") }, +}; + +static void +usage (gboolean is_error) +{ + GString *out = NULL; + size_t name_width = 0; + + out = g_string_new (""); + g_string_append_printf (out, "%s\n", _("Usage:")); + g_string_append_printf (out, " gio %s %s\n", _("COMMAND"), _("[ARGS…]")); + g_string_append_c (out, '\n'); + g_string_append_printf (out, "%s\n", _("Commands:")); + + /* Work out the maximum name length for column alignment. */ + for (size_t i = 0; i < G_N_ELEMENTS (gio_subcommands); i++) + name_width = MAX (name_width, strlen (gio_subcommands[i].name)); + + for (size_t i = 0; i < G_N_ELEMENTS (gio_subcommands); i++) + { + g_string_append_printf (out, " %-*s %s\n", + (int) name_width, gio_subcommands[i].name, + _(gio_subcommands[i].description)); + } + + g_string_append_c (out, '\n'); + g_string_append_printf (out, _("Use %s to get detailed help.\n"), "“gio help COMMAND”"); + + if (is_error) + g_printerr ("%s", out->str); + else + g_print ("%s", out->str); + + g_string_free (out, TRUE); } int @@ -277,7 +309,7 @@ main (int argc, char **argv) if (argc < 2) { - usage (); + usage (TRUE); return 1; } @@ -290,7 +322,7 @@ main (int argc, char **argv) { if (argc == 1) { - usage (); + usage (FALSE); return 0; } else @@ -301,50 +333,24 @@ main (int argc, char **argv) } else if (g_str_equal (command, "--help")) { - usage (); + usage (FALSE); return 0; } else if (g_str_equal (command, "--version")) command = "version"; - if (g_str_equal (command, "version")) - return handle_version (argc, argv, do_help); - else if (g_str_equal (command, "cat")) - return handle_cat (argc, argv, do_help); - else if (g_str_equal (command, "copy")) - return handle_copy (argc, argv, do_help); - else if (g_str_equal (command, "info")) - return handle_info (argc, argv, do_help); - else if (g_str_equal (command, "launch")) - return handle_launch (argc, argv, do_help); - else if (g_str_equal (command, "list")) - return handle_list (argc, argv, do_help); - else if (g_str_equal (command, "mime")) - return handle_mime (argc, argv, do_help); - else if (g_str_equal (command, "mkdir")) - return handle_mkdir (argc, argv, do_help); - else if (g_str_equal (command, "monitor")) - return handle_monitor (argc, argv, do_help); - else if (g_str_equal (command, "mount")) - return handle_mount (argc, argv, do_help); - else if (g_str_equal (command, "move")) - return handle_move (argc, argv, do_help); - else if (g_str_equal (command, "open")) - return handle_open (argc, argv, do_help); - else if (g_str_equal (command, "rename")) - return handle_rename (argc, argv, do_help); - else if (g_str_equal (command, "remove")) - return handle_remove (argc, argv, do_help); - else if (g_str_equal (command, "save")) - return handle_save (argc, argv, do_help); - else if (g_str_equal (command, "set")) - return handle_set (argc, argv, do_help); - else if (g_str_equal (command, "trash")) - return handle_trash (argc, argv, do_help); - else if (g_str_equal (command, "tree")) - return handle_tree (argc, argv, do_help); - else - usage (); + /* Work out which subcommand it is. */ + for (size_t i = 0; i < G_N_ELEMENTS (gio_subcommands); i++) + { + if (g_str_equal (command, gio_subcommands[i].name)) + { + g_assert (gio_subcommands[i].handle_func != NULL); + return gio_subcommands[i].handle_func (argc, argv, do_help); + } + } + + /* Unknown subcommand. */ + usage (TRUE); return 1; } diff --git a/gio/meson.build b/gio/meson.build index 9f7c220f5..0eb4ca6a4 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -985,7 +985,7 @@ gio_tool_sources = [ 'gio-tool-tree.c', ] -executable('gio', gio_tool_sources, +gio_tool = executable('gio', gio_tool_sources, install : true, install_tag : 'bin', c_args : gio_c_args, diff --git a/gio/tests/codegen.py b/gio/tests/codegen.py index 9fd4321f3..e27a412ba 100644 --- a/gio/tests/codegen.py +++ b/gio/tests/codegen.py @@ -116,6 +116,7 @@ class TestCodegen(unittest.TestCase): env = os.environ.copy() env["LC_ALL"] = "C.UTF-8" + env["G_DEBUG"] = "fatal-warnings" print("Environment:", env) # We want to ensure consistent line endings... diff --git a/gio/tests/gio-tool.py b/gio/tests/gio-tool.py new file mode 100644 index 000000000..593ac9767 --- /dev/null +++ b/gio/tests/gio-tool.py @@ -0,0 +1,132 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# +# Copyright © 2018, 2019 Endless Mobile, Inc. +# Copyright © 2023 Philip Withnall +# +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# 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., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +"""Integration tests for the gio utility.""" + +import collections +import os +import shutil +import subprocess +import tempfile +import unittest + +import taptestrunner + + +Result = collections.namedtuple("Result", ("info", "out", "err")) + + +class TestGioTool(unittest.TestCase): + """Integration test for running the gio tool. + + This can be run when installed or uninstalled. When uninstalled, it + requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set. + + The idea with this test harness is to test the gio utility, its + handling of command line arguments, its exit statuses, and its actual + effects on the file system. + """ + + # Track the cwd, we want to back out to that to clean up our tempdir + cwd = "" + + def setUp(self): + self.timeout_seconds = 6 # seconds per test + self.tmpdir = tempfile.TemporaryDirectory() + self.cwd = os.getcwd() + os.chdir(self.tmpdir.name) + print("tmpdir:", self.tmpdir.name) + + ext = "" + if os.name == "nt": + ext = ".exe" + + if "G_TEST_BUILDDIR" in os.environ: + self.__gio = os.path.join( + os.environ["G_TEST_BUILDDIR"], + "..", + "gio" + ext, + ) + else: + self.__gio = shutil.which("gio" + ext) + print("gio:", self.__gio) + + def tearDown(self): + os.chdir(self.cwd) + self.tmpdir.cleanup() + + def runGio(self, *args): + argv = [self.__gio] + argv.extend(args) + print("Running:", argv) + + env = os.environ.copy() + env["LC_ALL"] = "C.UTF-8" + env["G_DEBUG"] = "fatal-warnings" + print("Environment:", env) + + # We want to ensure consistent line endings... + info = subprocess.run( + argv, + timeout=self.timeout_seconds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + universal_newlines=True, + ) + info.check_returncode() + out = info.stdout.strip() + err = info.stderr.strip() + + result = Result(info, out, err) + + print("Output:", result.out) + return result + + def test_help(self): + """Test the --help argument and help subcommand.""" + result = self.runGio("--help") + result2 = self.runGio("help") + + self.assertEqual(result.out, result2.out) + self.assertEqual(result.err, result2.err) + + self.assertIn("Usage:\n gio COMMAND", result.out) + self.assertIn("List the contents of locations", result.out) + + def test_no_args(self): + """Test running with no arguments at all.""" + with self.assertRaises(subprocess.CalledProcessError): + self.runGio() + + def test_info_non_default_attributes(self): + """Test running `gio info --attributes` with a non-default list.""" + with tempfile.NamedTemporaryFile(dir=self.tmpdir.name) as tmpfile: + result = self.runGio( + "info", "--attributes=standard::content-type", tmpfile.name + ) + self.assertIn("standard::content-type: application/x-zerosize", result.out) + + +if __name__ == "__main__": + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gio/tests/meson.build b/gio/tests/meson.build index 0146a66a7..75fd9b5a8 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -181,6 +181,10 @@ python_tests = { 'suite': ['gdbus-codegen', 'slow'], 'timeout': 90, }, + 'gio-tool.py' : { + 'depends' : gio_tool, + 'can_fail' : host_system == 'windows', + }, } test_env = environment() @@ -1074,6 +1078,7 @@ endforeach foreach test_name, extra_args : python_tests depends = [extra_args.get('depends', [])] suite = ['gio', 'no-valgrind'] + extra_args.get('suite', []) + timeout = extra_args.get('timeout', test_timeout) if extra_args.get('can_fail', false) suite += 'failing' @@ -1090,7 +1095,7 @@ foreach test_name, extra_args : python_tests depends: depends, args: ['-B', files(test_name)], env: test_env, - timeout: extra_args.get('timeout'), + timeout: timeout, suite: suite, ) diff --git a/glib/tests/messages-low-memory.py b/glib/tests/messages-low-memory.py index 8e071b164..eb8036958 100644 --- a/glib/tests/messages-low-memory.py +++ b/glib/tests/messages-low-memory.py @@ -63,6 +63,7 @@ class TestMessagesLowMemory(unittest.TestCase): env = os.environ.copy() env["LC_ALL"] = "C.UTF-8" + env["G_DEBUG"] = "fatal-warnings" print("Environment:", env) # We want to ensure consistent line endings... diff --git a/gobject/tests/genmarshal.py b/gobject/tests/genmarshal.py index 1062dfe62..e2ecf7424 100644 --- a/gobject/tests/genmarshal.py +++ b/gobject/tests/genmarshal.py @@ -88,6 +88,7 @@ class TestGenmarshal(unittest.TestCase): env = os.environ.copy() env["LC_ALL"] = "C.UTF-8" + env["G_DEBUG"] = "fatal-warnings" print("Environment:", env) # We want to ensure consistent line endings... diff --git a/gobject/tests/gobject-query.py b/gobject/tests/gobject-query.py index 618b6bae9..aab03be77 100644 --- a/gobject/tests/gobject-query.py +++ b/gobject/tests/gobject-query.py @@ -59,6 +59,7 @@ class TestGobjectQuery(unittest.TestCase): env = os.environ.copy() env["LC_ALL"] = "C.UTF-8" + env["G_DEBUG"] = "fatal-warnings" print("Environment:", env) # We want to ensure consistent line endings... diff --git a/gobject/tests/mkenums.py b/gobject/tests/mkenums.py index 0b40daeaf..c23a80178 100644 --- a/gobject/tests/mkenums.py +++ b/gobject/tests/mkenums.py @@ -100,6 +100,7 @@ class TestMkenums(unittest.TestCase): env = os.environ.copy() env["LC_ALL"] = "C.UTF-8" + env["G_DEBUG"] = "fatal-warnings" print("Environment:", env) # We want to ensure consistent line endings...