// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Red Hat, Inc. // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. const {GIMarshallingTests, Gio, GLib, GObject} = imports.gi; const Foo = GObject.registerClass({ Properties: { 'prop': GObject.ParamSpec.string('prop', '', '', GObject.ParamFlags.READWRITE, ''), }, }, class Foo extends GObject.Object { set prop(v) { throw new Error('set'); } get prop() { throw new Error('get'); } }); const Bar = GObject.registerClass({ Properties: { 'prop': GObject.ParamSpec.string('prop', '', '', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, ''), }, }, class Bar extends GObject.Object {}); describe('Exceptions', function () { it('are thrown from property setter', function () { let foo = new Foo(); expect(() => (foo.prop = 'bar')).toThrowError(/set/); }); it('are thrown from property getter', function () { let foo = new Foo(); expect(() => foo.prop).toThrowError(/get/); }); // FIXME: In the next cases the errors aren't thrown but logged it('are logged from constructor', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); new Foo({prop: 'bar'}); GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterFromConstructor'); }); it('are logged from property setter with binding', function () { let foo = new Foo(); let bar = new Bar(); bar.bind_property('prop', foo, 'prop', GObject.BindingFlags.DEFAULT); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); // wake up the binding so that g_object_set() is called on foo bar.notify('prop'); GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterWithBinding'); }); it('are logged from property getter with binding', function () { let foo = new Foo(); let bar = new Bar(); foo.bind_property('prop', bar, 'prop', GObject.BindingFlags.DEFAULT); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: get*'); // wake up the binding so that g_object_get() is called on foo foo.notify('prop'); GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertyGetterWithBinding'); }); }); describe('logError', function () { afterEach(function () { GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testGErrorMessages'); }); it('logs a warning for a GError', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { logError(e); } }); it('logs a warning with a message if given', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); } catch (e) { logError(e); } }); it('also logs an error for a created GError that is not thrown', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new Gio.IOErrorEnum({message: 'a message', code: 0})); }); it('logs an error created with the GLib.Error constructor', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message')); }); it('logs the quark for a JS-created GError type', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error my-error: a message\nmarker@*'); logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message')); }); it('logs with stack for a GError created from a C struct', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error gi-marshalling-tests-gerror-domain: gi-marshalling-tests-gerror-message\nmarker@*'); logError(GIMarshallingTests.gerror_return()); }); // Now with prefix it('logs an error with a prefix if given', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { logError(e, 'prefix'); } }); it('logs an error with prefix and message', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); } catch (e) { logError(e, 'prefix'); } }); it('logs a SyntaxError', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: SyntaxError:*'); try { Reflect.parse('!@#$%^&'); } catch (e) { logError(e); } }); it('logs an error with cause', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: an error\nmarker@*Caused by: Gio.IOErrorEnum: another error\nmarker2@*'); function marker2() { return new Gio.IOErrorEnum({message: 'another error', code: 0}); } logError(new Error('an error', {cause: marker2()})); }); it('logs a GError with cause', function marker() { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: an error\nmarker@*Caused by: Error: another error\nmarker2@*'); function marker2() { return new Error('another error'); } const e = new Gio.IOErrorEnum({message: 'an error', code: 0}); e.cause = marker2(); logError(e); }); it('logs an error with non-object cause', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: an error\n*Caused by: 3'); logError(new Error('an error', {cause: 3})); }); it('logs an error with a cause tree', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: one\n*Caused by: Error: two\n*Caused by: Error: three\n*'); const three = new Error('three'); const two = new Error('two', {cause: three}); logError(new Error('one', {cause: two})); }); it('logs an error with cyclical causes', function () { // We cannot assert here with GLib.test_expect_message that the * at the // end of the string doesn't match more causes, but at least the idea is // that it shouldn't go into an infinite loop GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: one\n*Caused by: Error: two\n*'); const one = new Error('one'); one.cause = new Error('two', {cause: one}); logError(one); }); }); describe('Exception from function with too few arguments', function () { it('contains the full function name', function () { expect(() => GLib.get_locale_variants()) .toThrowError(/GLib\.get_locale_variants/); }); it('contains the full method name', function () { let file = Gio.File.new_for_path('foo'); expect(() => file.read()).toThrowError(/Gio\.File\.read/); }); }); describe('thrown GError', function () { let err; beforeEach(function () { try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (x) { err = x; } }); it('is an instance of error enum type', function () { expect(err).toEqual(jasmine.any(Gio.IOErrorEnum)); }); it('matches error domain and code', function () { expect(err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND)) .toBeTruthy(); }); it('has properties for domain and code', function () { expect(err.domain).toEqual(Gio.io_error_quark()); expect(err.code).toEqual(Gio.IOErrorEnum.NOT_FOUND); }); }); describe('GError.new_literal', function () { it('constructs a valid GLib.Error', function () { const e = GLib.Error.new_literal( Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'message'); expect(e instanceof GLib.Error).toBeTruthy(); expect(e.code).toEqual(Gio.IOErrorEnum.FAILED); expect(e.message).toEqual('message'); }); it('does not accept invalid domains', function () { expect(() => GLib.Error.new_literal(0, 0, 'message')) .toThrowError(/0 is not a valid domain/); }); });