1# -*- Mode: Python; py-indent-offset: 4 -*-
2# vim: tabstop=4 shiftwidth=4 expandtab
3
4import os
5import unittest
6import warnings
7
8import pytest
9
10import gi.overrides
11from gi import PyGIWarning
12from gi.repository import GLib, Gio
13
14from .helper import ignore_gi_deprecation_warnings
15
16
17class TestGio(unittest.TestCase):
18    def test_file_enumerator(self):
19        self.assertEqual(Gio.FileEnumerator, gi.overrides.Gio.FileEnumerator)
20        f = Gio.file_new_for_path("./")
21
22        iter_info = []
23        for info in f.enumerate_children("standard::*", 0, None):
24            iter_info.append(info.get_name())
25
26        next_info = []
27        enumerator = f.enumerate_children("standard::*", 0, None)
28        while True:
29            info = enumerator.next_file(None)
30            if info is None:
31                break
32            next_info.append(info.get_name())
33
34        self.assertEqual(iter_info, next_info)
35
36    def test_menu_item(self):
37        menu = Gio.Menu()
38        item = Gio.MenuItem()
39        item.set_attribute([("label", "s", "Test"),
40                            ("action", "s", "app.test")])
41        menu.append_item(item)
42        value = menu.get_item_attribute_value(0, "label", GLib.VariantType.new("s"))
43        self.assertEqual("Test", value.unpack())
44        value = menu.get_item_attribute_value(0, "action", GLib.VariantType.new("s"))
45        self.assertEqual("app.test", value.unpack())
46
47    def test_volume_monitor_warning(self):
48        with warnings.catch_warnings(record=True) as warn:
49            warnings.simplefilter('always')
50            Gio.VolumeMonitor()
51            self.assertEqual(len(warn), 1)
52            self.assertTrue(issubclass(warn[0].category, PyGIWarning))
53            self.assertRegexpMatches(str(warn[0].message),
54                                     '.*Gio\\.VolumeMonitor\\.get\\(\\).*')
55
56
57class TestGSettings(unittest.TestCase):
58    def setUp(self):
59        self.settings = Gio.Settings.new('org.gnome.test')
60        # we change the values in the tests, so set them to predictable start
61        # value
62        self.settings.reset('test-string')
63        self.settings.reset('test-array')
64        self.settings.reset('test-boolean')
65        self.settings.reset('test-enum')
66
67    def test_iter(self):
68        assert set(list(self.settings)) == set([
69            'test-tuple', 'test-array', 'test-boolean', 'test-string',
70            'test-enum', 'test-range'])
71
72    def test_get_set(self):
73        for key in self.settings:
74            old_value = self.settings[key]
75            self.settings[key] = old_value
76            assert self.settings[key] == old_value
77
78    def test_native(self):
79        self.assertTrue('test-array' in self.settings.list_keys())
80
81        # get various types
82        v = self.settings.get_value('test-boolean')
83        self.assertEqual(v.get_boolean(), True)
84        self.assertEqual(self.settings.get_boolean('test-boolean'), True)
85
86        v = self.settings.get_value('test-string')
87        self.assertEqual(v.get_string(), 'Hello')
88        self.assertEqual(self.settings.get_string('test-string'), 'Hello')
89
90        v = self.settings.get_value('test-array')
91        self.assertEqual(v.unpack(), [1, 2])
92
93        v = self.settings.get_value('test-tuple')
94        self.assertEqual(v.unpack(), (1, 2))
95
96        v = self.settings.get_value('test-range')
97        assert v.unpack() == 123
98
99        # set a value
100        self.settings.set_string('test-string', 'World')
101        self.assertEqual(self.settings.get_string('test-string'), 'World')
102
103        self.settings.set_value('test-string', GLib.Variant('s', 'Goodbye'))
104        self.assertEqual(self.settings.get_string('test-string'), 'Goodbye')
105
106    def test_constructor(self):
107        # default constructor uses path from schema
108        self.assertEqual(self.settings.get_property('path'), '/tests/')
109
110        # optional constructor arguments
111        with_path = Gio.Settings.new_with_path('org.gnome.nopathtest', '/mypath/')
112        self.assertEqual(with_path.get_property('path'), '/mypath/')
113        self.assertEqual(with_path['np-int'], 42)
114
115    def test_dictionary_api(self):
116        self.assertEqual(len(self.settings), 6)
117        self.assertTrue('test-array' in self.settings)
118        self.assertTrue('test-array' in self.settings.keys())
119        self.assertFalse('nonexisting' in self.settings)
120        self.assertFalse(4 in self.settings)
121        self.assertEqual(bool(self.settings), True)
122
123    def test_get(self):
124        self.assertEqual(self.settings['test-boolean'], True)
125        self.assertEqual(self.settings['test-string'], 'Hello')
126        self.assertEqual(self.settings['test-enum'], 'banana')
127        self.assertEqual(self.settings['test-array'], [1, 2])
128        self.assertEqual(self.settings['test-tuple'], (1, 2))
129
130        self.assertRaises(KeyError, self.settings.__getitem__, 'unknown')
131        self.assertRaises(KeyError, self.settings.__getitem__, 2)
132
133    def test_set(self):
134        self.settings['test-boolean'] = False
135        self.assertEqual(self.settings['test-boolean'], False)
136        self.settings['test-string'] = 'Goodbye'
137        self.assertEqual(self.settings['test-string'], 'Goodbye')
138        self.settings['test-array'] = [3, 4, 5]
139        self.assertEqual(self.settings['test-array'], [3, 4, 5])
140        self.settings['test-enum'] = 'pear'
141        self.assertEqual(self.settings['test-enum'], 'pear')
142
143        self.assertRaises(TypeError, self.settings.__setitem__, 'test-string', 1)
144        self.assertRaises(ValueError, self.settings.__setitem__, 'test-enum', 'plum')
145        self.assertRaises(KeyError, self.settings.__setitem__, 'unknown', 'moo')
146
147    def test_set_range(self):
148        self.settings['test-range'] = 7
149        assert self.settings['test-range'] == 7
150        self.settings['test-range'] = 65535
151        assert self.settings['test-range'] == 65535
152
153        with pytest.raises(ValueError, match=".*7 - 65535.*"):
154            self.settings['test-range'] = 7 - 1
155
156        with pytest.raises(ValueError, match=".*7 - 65535.*"):
157            self.settings['test-range'] = 65535 + 1
158
159    def test_empty(self):
160        empty = Gio.Settings.new_with_path('org.gnome.empty', '/tests/')
161        self.assertEqual(len(empty), 0)
162        self.assertEqual(bool(empty), True)
163        self.assertEqual(empty.keys(), [])
164
165    @ignore_gi_deprecation_warnings
166    def test_change_event(self):
167        changed_log = []
168        change_event_log = []
169
170        def on_changed(settings, key):
171            changed_log.append((settings, key))
172
173        def on_change_event(settings, keys, n_keys):
174            change_event_log.append((settings, keys, n_keys))
175
176        self.settings.connect('changed', on_changed)
177        self.settings.connect('change-event', on_change_event)
178        self.settings['test-string'] = 'Moo'
179        self.assertEqual(changed_log, [(self.settings, 'test-string')])
180        self.assertEqual(change_event_log, [(self.settings,
181                                             [GLib.quark_from_static_string('test-string')],
182                                             1)])
183
184
185@unittest.skipIf(os.name == "nt", "FIXME")
186class TestGFile(unittest.TestCase):
187    def setUp(self):
188        self.file, self.io_stream = Gio.File.new_tmp('TestGFile.XXXXXX')
189
190    def tearDown(self):
191        try:
192            self.file.delete(None)
193            # test_delete and test_delete_async already remove it
194        except GLib.GError:
195            pass
196
197    def test_replace_contents(self):
198        content = b'hello\0world\x7F!'
199        succ, etag = self.file.replace_contents(content, None, False,
200                                                Gio.FileCreateFlags.NONE, None)
201        new_succ, new_content, new_etag = self.file.load_contents(None)
202
203        self.assertTrue(succ)
204        self.assertTrue(new_succ)
205        self.assertEqual(etag, new_etag)
206        self.assertEqual(content, new_content)
207
208    # https://bugzilla.gnome.org/show_bug.cgi?id=690525
209    def disabled_test_replace_contents_async(self):
210        content = b''.join(bytes(chr(i), 'utf-8') for i in range(128))
211
212        def callback(f, result, d):
213            # Quit so in case of failed assertations loop doesn't keep running.
214            main_loop.quit()
215            succ, etag = self.file.replace_contents_finish(result)
216            new_succ, new_content, new_etag = self.file.load_contents(None)
217            d['succ'], d['etag'] = self.file.replace_contents_finish(result)
218            load = self.file.load_contents(None)
219            d['new_succ'], d['new_content'], d['new_etag'] = load
220
221        data = {}
222        self.file.replace_contents_async(content, None, False,
223                                         Gio.FileCreateFlags.NONE, None,
224                                         callback, data)
225        main_loop = GLib.MainLoop()
226        main_loop.run()
227        self.assertTrue(data['succ'])
228        self.assertTrue(data['new_succ'])
229        self.assertEqual(data['etag'], data['new_etag'])
230        self.assertEqual(content, data['new_content'])
231
232    def test_tmp_exists(self):
233        # A simple test to check if Gio.File.new_tmp is working correctly.
234        self.assertTrue(self.file.query_exists(None))
235
236    def test_delete(self):
237        self.file.delete(None)
238        self.assertFalse(self.file.query_exists(None))
239
240    def test_delete_async(self):
241        def callback(f, result, data):
242            main_loop.quit()
243
244        self.file.delete_async(0, None, callback, None)
245        main_loop = GLib.MainLoop()
246        main_loop.run()
247        self.assertFalse(self.file.query_exists(None))
248
249
250@unittest.skipIf(os.name == "nt", "crashes on Windows")
251class TestGApplication(unittest.TestCase):
252    def test_command_line(self):
253        class App(Gio.Application):
254            args = None
255
256            def __init__(self):
257                super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
258
259            def do_command_line(self, cmdline):
260                self.args = cmdline.get_arguments()
261                return 42
262
263        app = App()
264        res = app.run(['spam', 'eggs'])
265
266        self.assertEqual(res, 42)
267        self.assertSequenceEqual(app.args, ['spam', 'eggs'])
268
269    def test_local_command_line(self):
270        class App(Gio.Application):
271            local_args = None
272
273            def __init__(self):
274                super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
275
276            def do_local_command_line(self, args):
277                self.local_args = args[:]  # copy
278                args.remove('eggs')
279
280                # True skips do_command_line being called.
281                return True, args, 42
282
283        app = App()
284        res = app.run(['spam', 'eggs'])
285
286        self.assertEqual(res, 42)
287        self.assertSequenceEqual(app.local_args, ['spam', 'eggs'])
288
289    def test_local_and_remote_command_line(self):
290        class App(Gio.Application):
291            args = None
292            local_args = None
293
294            def __init__(self):
295                super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
296
297            def do_command_line(self, cmdline):
298                self.args = cmdline.get_arguments()
299                return 42
300
301            def do_local_command_line(self, args):
302                self.local_args = args[:]  # copy
303                args.remove('eggs')
304
305                # False causes do_command_line to be called with args.
306                return False, args, 0
307
308        app = App()
309        res = app.run(['spam', 'eggs'])
310
311        self.assertEqual(res, 42)
312        self.assertSequenceEqual(app.args, ['spam'])
313        self.assertSequenceEqual(app.local_args, ['spam', 'eggs'])
314
315    @unittest.skipUnless(hasattr(Gio.Application, 'add_main_option'),
316                         'Requires newer version of GLib')
317    def test_add_main_option(self):
318        stored_options = []
319
320        def on_handle_local_options(app, options):
321            stored_options.append(options)
322            return 0  # Return 0 if options have been handled
323
324        def on_activate(app):
325            pass
326
327        app = Gio.Application()
328        app.add_main_option(long_name='string',
329                            short_name=b's',
330                            flags=0,
331                            arg=GLib.OptionArg.STRING,
332                            description='some string')
333
334        app.connect('activate', on_activate)
335        app.connect('handle-local-options', on_handle_local_options)
336        app.run(['app', '-s', 'test string'])
337
338        self.assertEqual(len(stored_options), 1)
339        options = stored_options[0]
340        self.assertTrue(options.contains('string'))
341        self.assertEqual(options.lookup_value('string').unpack(),
342                         'test string')
343