From 7816b66d326dc9df29f2338205a9a0618a32ced3 Mon Sep 17 00:00:00 2001 From: Kasper Markus Date: Thu, 28 Jul 2016 13:13:26 +0200 Subject: [PATCH 1/2] GPII-1885: Implemented isConfigurable and makeConfigurable functions --- documentation/SolutionsRegistryFormat.md | 56 +++++---- .../lifecycleActions/src/LifecycleActions.js | 33 ++++++ .../lifecycleManager/src/LifecycleManager.js | 31 +++++ .../test/js/LifecycleManagerTests.js | 110 ++++++++++++++++++ 4 files changed, 208 insertions(+), 22 deletions(-) diff --git a/documentation/SolutionsRegistryFormat.md b/documentation/SolutionsRegistryFormat.md index c4b2b65c5..a4551af9e 100644 --- a/documentation/SolutionsRegistryFormat.md +++ b/documentation/SolutionsRegistryFormat.md @@ -14,13 +14,13 @@ Each entry in the solution registry should have a unique ID (`Solution.id` in th "start": [ .. ], "stop": [ .. ], "isInstalled": [ .. ], - + "isConfigurable": [ ... ], + "makeConfigurable": [ ... ] + // Not yet supported. Post-Cloud4All features. "install": [ ... ], "uninstall": [ ... ], - "makeConfigurable": [ ... ], "isRunning": [ ... ], - "isConfigurable": [ ... ] } ``` @@ -143,6 +143,37 @@ This directive is used to detect whether a solution is installed. If any of thes ] ``` +####isConfigurable +This is run before configuration to ensure that the application is actually ready to be configured. This is relevant for applications where e.g. a configuration file needs to be present, a wizard needs to be run on the first launch, etc. Each of the blocks will be evaluated, if *any* of them evaluates to true, the solution is considered configurable. The absense of an `isConfigurable` block is also interpreted as the solution being configurable. + +If a solution is not configurable, the `makeConfigurable` block will be executed (see below). + +**Example Entry**: +``` +"isConfigurable": [ + { + "type": "gpii.lifecycleActions.pathExists", + "path": "/tmp/fakemag1.settings.json" + } +] +``` + +####makeConfigurable +Is the actions that need to be taken to make the application configurable (such as running a wizard, creating a default configuration file, adding a new system user, etc). + +**Example Entry**: +``` +"makeConfigurable": [ + { + "type": "gpii.lifecycleActions.createFile", + "options": { + "filename": "/tmp/fakemag1.settings.json", + "content": "{}" + } + } +] +``` + ***** ### UNIMPLEMENTED BLOCKS @@ -161,26 +192,7 @@ To detect whether a solution is running - this is planned to be integrated in th ] ``` -####isConfigurable -This is run before configuration to ensure that the application is actually ready to be configured. This is relevant for applications where e.g. a configuration file needs to be present, a tutorial needs to be run on the first launch, etc. -**Example Entry**: -``` -"isConfigurable": [{ - "type": "gpii.reporter.fileExists", - "path": "${{environment}.XDG_DATA_HOME}/orca/user-settings.conf"" -}] -``` - -####makeConfigurable -Is the actions that need to be taken to make the application configurable (such as running a wizard, creating a default configuration file, adding a new system user, etc). - -**Example Entry**: -``` -"makeConfigurable": [{ - "launch" // A special key meaning "start it, wait until isConfigurable is met, and then stop it" -}] -``` ####install: Used for describing the steps required for installing the application diff --git a/gpii/node_modules/lifecycleActions/src/LifecycleActions.js b/gpii/node_modules/lifecycleActions/src/LifecycleActions.js index 25eac207d..2eeccc04d 100644 --- a/gpii/node_modules/lifecycleActions/src/LifecycleActions.js +++ b/gpii/node_modules/lifecycleActions/src/LifecycleActions.js @@ -18,9 +18,12 @@ var fluid = require("infusion"), child_process = require("child_process"), + fs = require("fs"), gpii = fluid.registerNamespace("gpii"); fluid.registerNamespace("gpii.launch"); +fluid.registerNamespace("gpii.lifecycleActions"); + fluid.defaults("gpii.launch.exec", { gradeNames: "fluid.function", @@ -43,3 +46,33 @@ fluid.defaults("gpii.launch.spawn", { gpii.launch.spawn = child_process.spawn; +fluid.defaults("gpii.lifecycleActions.pathExists", { + gradeNames: "fluid.function", + argumentMap: { + path: 0 + } +}); + +gpii.lifecycleActions.pathExists = function (path) { + if (!path) { + fluid.fail("No path was given as option to gpii.lifecycleActions.pathExists"); + } + return fs.existsSync(path); +}; + +fluid.defaults("gpii.lifecycleActions.createFile", { + gradeNames: "fluid.function", + argumentMap: { + options: 0 + } +}); + +gpii.lifecycleActions.createFile = function (options) { + if (!options.filename) { + fluid.fail("No filename was given as option to gpii.lifecycleActions.createFile"); + } + var content = options.content || ""; + fs.writeFileSync(options.filename, content); +}; + + diff --git a/gpii/node_modules/lifecycleManager/src/LifecycleManager.js b/gpii/node_modules/lifecycleManager/src/LifecycleManager.js index 832fddbf8..685e3a874 100644 --- a/gpii/node_modules/lifecycleManager/src/LifecycleManager.js +++ b/gpii/node_modules/lifecycleManager/src/LifecycleManager.js @@ -82,6 +82,10 @@ var gpii = fluid.registerNamespace("gpii"); funcName: "gpii.lifecycleManager.invokeSettingsHandlers", args: ["{that}", "{arguments}.0", "{arguments}.1"] // solutionId, settingsHandlers + }, + isConfigurable: { + funcName: "gpii.lifecycleManager.isConfigurable", + args: [ "{that}", "{arguments}.0", "{arguments}.1" ] } } }); @@ -315,6 +319,25 @@ var gpii = fluid.registerNamespace("gpii"); return togo; }; + /* returns whether a solution is configurable or not. This is done by going through each of + * the blocks in the isConfigurable part of the solution entry. If *any* of them evaluates to + * true, the solution is considered configurable and true is returned, else false is returned. + * If no 'isConfigured' directive is found in the solutionRecord, true is returned + */ + gpii.lifecycleManager.isConfigurable = function (that, solutionRecord, sessionState) { + if (solutionRecord.isConfigurable !== undefined) { + return fluid.find(solutionRecord.isConfigurable, function (configurableCheck) { + var expanded = sessionState.localResolver(configurableCheck); + var result = gpii.lifecycleManager.invokeAction(expanded, that.nameResolver); + if (result === true) { + return result; + } + }, false); + } else { // if no isConfigurable block is given, we assume it's configurable + return true; + } + }; + /** Invoked on both "start" and "update" phases - in addition to forwarding to gpii.lifecycleManager.executeActions, * it is responsible for saving the settings that are being set (when the fullSnapshot is true) and storing * the list of applied solutions to the sessionState @@ -331,6 +354,14 @@ var gpii = fluid.registerNamespace("gpii"); */ gpii.lifecycleManager.applySolution = function (that, solutionId, solutionRecord, sessionState, lifecycleBlockKeys, fullSnapshot) { var promises = []; + + if (that.isConfigurable(solutionRecord, sessionState) === false) { + if (solutionRecord.makeConfigurable === undefined) { + fluid.fail("Found solution that is not configurable with no makeConfigurable directive"); + } else { + promises.push(that.executeActions(solutionId, solutionRecord, sessionState, "makeConfigurable")); + } + } fluid.each(lifecycleBlockKeys, function (key) { promises.push(that.executeActions(solutionId, solutionRecord, sessionState, key)); }); diff --git a/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js b/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js index 91cbb20f2..7dc034e6a 100644 --- a/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js +++ b/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js @@ -99,6 +99,35 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt ] }; + gpii.tests.lifecycleManager.trueConfigurableDirective = { + "isConfigurable": [ + { + "type": "gpii.tests.lifecycleManager.mockIsConfigurable", + "toReturn": true + } + ] + }; + + gpii.tests.lifecycleManager.falseConfigurableDirective = { + "isConfigurable": [ + { + "type": "gpii.tests.lifecycleManager.mockIsConfigurable", + "toReturn": false + } + ] + }; + + gpii.tests.lifecycleManager.makeConfigurableDirective = { + "makeConfigurable": [ + { + "type": "gpii.tests.lifecycleManager.mockMakeConfigurable", + "options": { + "filename": "${{environment}.JAWS_DIR}configuration.json" + } + } + ] + }; + gpii.tests.lifecycleManager.configurationSpec = gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "red" }), gpii.tests.lifecycleManager.noUpdateLifecycle, @@ -229,6 +258,31 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt } }); + gpii.tests.lifecycleManager.mockIsConfigurable = function (toReturn) { + jqUnit.assertTrue("isConfigurable has been called", true); + return toReturn; + }; + + fluid.defaults("gpii.tests.lifecycleManager.mockIsConfigurable", { + gradeNames: "fluid.function", + argumentMap: { + toReturn: 0 + } + }); + + gpii.tests.lifecycleManager.mockMakeConfigurable = function (options) { + gpii.tests.lifecycleManager.staticRepository.configurableStash = { + options: options + }; + }; + + fluid.defaults("gpii.tests.lifecycleManager.mockMakeConfigurable", { + gradeNames: "fluid.function", + argumentMap: { + options: 0 + } + }); + gpii.tests.lifecycleManager.backingMockSettingsHandler = null; // initialised in every test that requires it gpii.tests.lifecycleManager.initBackingMock = function () { @@ -592,6 +646,60 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt }); }; + gpii.tests.lifecycleManager.configurabilityTestDefs = [{ + name: "configurability test with a configurable solution", + expect: 5, + startPayload: fluid.extend(true, {}, gpii.tests.lifecycleManager.userOptions, + { + lifecycleInstructions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "red" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + gpii.tests.lifecycleManager.trueConfigurableDirective, + gpii.tests.lifecycleManager.makeConfigurableDirective) + }), + stashContent: undefined + }, { + name: "configurability test with a non-configurable solution to be made configurable", + expect: 5, + startPayload: fluid.extend(true, {}, gpii.tests.lifecycleManager.userOptions, + { + lifecycleInstructions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "red" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + gpii.tests.lifecycleManager.falseConfigurableDirective, + gpii.tests.lifecycleManager.makeConfigurableDirective) + }), + stashContent: { + "options": { + "filename": "e:\\Programs and Things\\Jaws\\configuration.json" + } + } + }]; + + // Tests focusing on the isConfigurable and makeConfigurable directives of the solution + gpii.tests.lifecycleManager.buildConfigurabilityTests = function () { + fluid.each(gpii.tests.lifecycleManager.configurabilityTestDefs, function (test) { + jqUnit.asyncTest("gpii.lifecycleManager configurability tests: " + test.name, function () { + gpii.tests.lifecycleManager.setup(); + jqUnit.expect(test.expect); + + var lifecycleManager = gpii.lifecycleManager(gpii.tests.lifecycleManager.testOptions); + gpii.tests.lifecycleManager.initBackingMock(); + + lifecycleManager.start(test.startPayload, function (success) { + jqUnit.assertTrue("gpii.lifecycleManager.start() succeeds", success); + gpii.tests.lifecycleManager.assertExpectedExec(); + var expectedFirstAppliedSettings = (test.expectedFirstAppliedSettings !== undefined) ? test.expectedFirstAppliedSettings : gpii.tests.lifecycleManager.settingsHandlerExpectedInputNewSettings; + gpii.tests.lifecycleManager.assertExpectedSettingsHandler(" - on start", expectedFirstAppliedSettings); + // make configurable shouldn't have been called: + jqUnit.assertDeepEq("checking whether makeConfigurable was run", test.stashContent, gpii.tests.lifecycleManager.staticRepository.configurableStash); + + jqUnit.start(); + }); + }); + }); + }; + gpii.tests.lifecycleManager.runTests = function () { jqUnit.module("Lifecycle Manager", function () { gpii.tests.staticRepository = {}; @@ -683,6 +791,8 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt gpii.tests.lifecycleManager.buildUpdateTests(); + gpii.tests.lifecycleManager.buildConfigurabilityTests(); + jqUnit.asyncTest("gpii.lifecycleManager.update() tests for 'stop' reference", function () { // initial payload: var startPayload = fluid.extend(true, {}, gpii.tests.lifecycleManager.userOptions, { From ca59aff1241a1655d644301214a23d2f6fcccb8c Mon Sep 17 00:00:00 2001 From: Kasper Markus Date: Thu, 15 Sep 2016 14:29:34 +0200 Subject: [PATCH 2/2] GPII-1885: Removed offensive material from LifecycleActions. And fixed up documentation. As discussed with Antranig in the channel today, I will do a separate pull request fixing up the horrible behavior of the settings handlers, where they crash if the settings file doesn't exist --- documentation/SolutionsRegistryFormat.md | 6 ++-- .../lifecycleActions/src/LifecycleActions.js | 33 ------------------- 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/documentation/SolutionsRegistryFormat.md b/documentation/SolutionsRegistryFormat.md index a4551af9e..db163aa21 100644 --- a/documentation/SolutionsRegistryFormat.md +++ b/documentation/SolutionsRegistryFormat.md @@ -144,9 +144,9 @@ This directive is used to detect whether a solution is installed. If any of thes ``` ####isConfigurable -This is run before configuration to ensure that the application is actually ready to be configured. This is relevant for applications where e.g. a configuration file needs to be present, a wizard needs to be run on the first launch, etc. Each of the blocks will be evaluated, if *any* of them evaluates to true, the solution is considered configurable. The absense of an `isConfigurable` block is also interpreted as the solution being configurable. +This is run before configuration to ensure that the application is actually ready to be configured. This is relevant for applications where e.g. a configuration file needs to be present, a wizard needs to be run on the first launch, etc. Each of the blocks will be evaluated, if *any* of them evaluates to true, the solution is considered configurable. The absence of an `isConfigurable` block is also interpreted as the solution being configurable. -If a solution is not configurable, the `makeConfigurable` block will be executed (see below). +If a solution is not configurable, the `makeConfigurable` block will be executed immediately following the `isConfigurable` check (see below). **Example Entry**: ``` @@ -159,7 +159,7 @@ If a solution is not configurable, the `makeConfigurable` block will be executed ``` ####makeConfigurable -Is the actions that need to be taken to make the application configurable (such as running a wizard, creating a default configuration file, adding a new system user, etc). +Is the actions that need to be taken to make the application configurable (such as running a wizard, creating a default configuration file, adding a new system user, etc). This will be triggered by the system if `isConfigurable` has evaluated to false and run immediately following the `isConfigurable` check. **Example Entry**: ``` diff --git a/gpii/node_modules/lifecycleActions/src/LifecycleActions.js b/gpii/node_modules/lifecycleActions/src/LifecycleActions.js index 2eeccc04d..25eac207d 100644 --- a/gpii/node_modules/lifecycleActions/src/LifecycleActions.js +++ b/gpii/node_modules/lifecycleActions/src/LifecycleActions.js @@ -18,12 +18,9 @@ var fluid = require("infusion"), child_process = require("child_process"), - fs = require("fs"), gpii = fluid.registerNamespace("gpii"); fluid.registerNamespace("gpii.launch"); -fluid.registerNamespace("gpii.lifecycleActions"); - fluid.defaults("gpii.launch.exec", { gradeNames: "fluid.function", @@ -46,33 +43,3 @@ fluid.defaults("gpii.launch.spawn", { gpii.launch.spawn = child_process.spawn; -fluid.defaults("gpii.lifecycleActions.pathExists", { - gradeNames: "fluid.function", - argumentMap: { - path: 0 - } -}); - -gpii.lifecycleActions.pathExists = function (path) { - if (!path) { - fluid.fail("No path was given as option to gpii.lifecycleActions.pathExists"); - } - return fs.existsSync(path); -}; - -fluid.defaults("gpii.lifecycleActions.createFile", { - gradeNames: "fluid.function", - argumentMap: { - options: 0 - } -}); - -gpii.lifecycleActions.createFile = function (options) { - if (!options.filename) { - fluid.fail("No filename was given as option to gpii.lifecycleActions.createFile"); - } - var content = options.content || ""; - fs.writeFileSync(options.filename, content); -}; - -