[gjs] package: Add checkSymbol() to check for symbol availability



commit 588f4003bc0f54be2d9f26acbadfff3597d9312d
Author: Florian Müllner <fmuellner gnome org>
Date:   Fri Mar 3 21:01:21 2017 +0100

    package: Add checkSymbol() to check for symbol availability
    
    GI allows to specify the API version to import, but not a minimum
    version of said API. This can lead to hard to track down bugs at
    runtime, in particular when the failure doesn't trigger a warning
    or exception (for example when setting a non-existent GObject
    property).
    To address this, add a checkSymbols() method that test whether a
    library can be imported and contains a particular symbol.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=779593

 Makefile-test.am                  |    1 +
 installed-tests/js/testPackage.js |   83 +++++++++++++++++++++++++++++++++++++
 modules/package.js                |   57 +++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 0 deletions(-)
---
diff --git a/Makefile-test.am b/Makefile-test.am
index ff3ff33..af2fd8f 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -237,6 +237,7 @@ common_jstests_files =                                              \
        installed-tests/js/testMainloop.js                      \
        installed-tests/js/testMetaClass.js                     \
        installed-tests/js/testNamespace.js                     \
+       installed-tests/js/testPackage.js                       \
        installed-tests/js/testParamSpec.js                     \
        installed-tests/js/testSignals.js                       \
        installed-tests/js/testSystem.js                        \
diff --git a/installed-tests/js/testPackage.js b/installed-tests/js/testPackage.js
new file mode 100644
index 0000000..bcaf3db
--- /dev/null
+++ b/installed-tests/js/testPackage.js
@@ -0,0 +1,83 @@
+const Pkg = imports.package;
+
+describe('Package module', function () {
+    it('finds an existing library', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent library', function () {
+        expect(Pkg.checkSymbol('Rägräss', '1.0')).toEqual(false);
+    });
+
+    it('finds a function', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'get_variant')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent function', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'get_väriänt')).toEqual(false);
+    });
+
+    it('finds a class', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent class', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestNoObj')).toEqual(false);
+    });
+
+    it('finds a property', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.bare')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent property', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.bäre')).toEqual(false);
+    });
+
+    it('finds a static function', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.static_method')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent static function', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.stätic_methöd')).toEqual(false);
+    });
+
+    it('finds a method', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.null_out')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent method', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.nüll_out')).toEqual(false);
+    });
+
+    it('finds an interface', function () {
+        expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent interface', function () {
+        expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interfäce')).toEqual(false);
+    });
+
+    it('finds an interface method', function () {
+        expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface.test_int8_in')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent interface method', function () {
+        expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface.test_int42_in')).toEqual(false);
+    });
+
+    it('finds an enum value', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestEnum.VALUE1')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent enum value', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'TestEnum.value1')).toEqual(false);
+    });
+
+    it('finds a constant', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'BOOL_CONSTANT')).toEqual(true);
+    });
+
+    it('doesn\'t find a non-existent constant', function () {
+        expect(Pkg.checkSymbol('Regress', '1.0', 'BööL_CONSTANT')).toEqual(false);
+    });
+});
diff --git a/modules/package.js b/modules/package.js
index 93adce4..03fceaa 100644
--- a/modules/package.js
+++ b/modules/package.js
@@ -26,6 +26,7 @@
 const GLib = imports.gi.GLib;
 const GIRepository = imports.gi.GIRepository;
 const Gio = imports.gi.Gio;
+const GObject = imports.gi.GObject;
 const System = imports.system;
 
 const Gettext = imports.gettext;
@@ -246,6 +247,62 @@ function require(libs) {
     }
 }
 
+/**
+ * checkSymbol:
+ * @lib: an external dependency to import
+ * @version: optional version of the dependency
+ * @symbol: optional symbol to check for
+ *
+ * Check whether an external GI typelib can be imported
+ * and provides @symbol.
+ *
+ * Symbols may refer to
+ *  - global functions         ('main_quit')
+ *  - classes                  ('Window')
+ *  - class / instance methods ('IconTheme.get_default' / 'IconTheme.has_icon')
+ *  - GObject properties       ('Window.default_height')
+ *
+ * Returns: %true if @lib can be imported and provides @symbol, %false otherwise
+ */
+function checkSymbol(lib, version, symbol) {
+    let Lib = null;
+
+    if (version)
+        imports.gi.versions[lib] = version;
+
+    try {
+        Lib = imports.gi[lib];
+    } catch(e) {
+        return false;
+    }
+
+    if (!symbol)
+        return true; // Done
+
+    let [klass, sym] = symbol.split('.');
+    if (klass === symbol) // global symbol
+        return (typeof Lib[symbol] !== 'undefined');
+
+    let obj = Lib[klass];
+    if (typeof obj === 'undefined')
+        return false;
+
+    if (typeof obj[sym] !== 'undefined' ||
+        (obj.prototype && typeof obj.prototype[sym] !== 'undefined'))
+        return true; // class- or object method
+
+    // GObject property
+    let pspec = null;
+    if (GObject.type_is_a(obj.$gtype, GObject.TYPE_INTERFACE)) {
+        let iface = GObject.type_default_interface_ref(obj.$gtype);
+        pspec = GObject.Object.interface_find_property(iface, sym);
+    } else if (GObject.type_is_a(obj.$gtype, GObject.TYPE_OBJECT)) {
+        pspec = GObject.Object.find_property.call(obj.$gtype, sym);
+    }
+
+    return (pspec !== null);
+}
+
 function initGettext() {
     Gettext.bindtextdomain(_pkgname, localedir);
     Gettext.textdomain(_pkgname);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]