From 24721afec7ff9a95fb99b45bf786ff97fb57c629 Mon Sep 17 00:00:00 2001 From: abose Date: Fri, 8 Dec 2023 13:26:01 +0530 Subject: [PATCH] feat: phNode, node inspector, phoenix node fs integration --- .eslintrc.js | 8 +- package-lock.json | 18 +-- package.json | 2 +- src-node/index.js | 142 +++++++++++++++++- src-node/package-lock.json | 8 +- src-node/package.json | 2 +- src/document/DocumentCommandHandlers.js | 2 + src/extensions/default/DebugCommands/main.js | 13 ++ .../impls/appshell/AppshellFileSystem.js | 2 +- src/index.html | 1 + src/nls/root/strings.js | 1 + src/node-loader.js | 108 +++++++++++++ src/phoenix/init_vfs.js | 2 +- src/worker/IndexingWorker.js | 5 + src/worker/file-Indexing-Worker-thread.js | 6 + test/spec/LowLevelFileIO-test.js | 4 +- test/spec/SpecRunnerUtils.js | 3 + 17 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 src/node-loader.js diff --git a/.eslintrc.js b/.eslintrc.js index c104254a0f..e013ff6d4b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -100,5 +100,11 @@ module.exports = { "blockBindings": true, "classes": true } - } + }, + "overrides": [{ + "files": ["src-node/**/*.js", "src-node/*.js"], + "env": { + "node": true + } + }] }; diff --git a/package-lock.json b/package-lock.json index 69ce34d2dc..b536d6d5bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "phoenix", - "version": "3.2.14-0", + "version": "3.2.16-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "3.2.14-0", + "version": "3.2.16-0", "dependencies": { "@bugsnag/js": "^7.18.0", "@floating-ui/dom": "^0.5.4", "@fortawesome/fontawesome-free": "^6.1.2", "@highlightjs/cdn-assets": "^11.5.1", - "@phcode/fs": "^2.0.5", + "@phcode/fs": "^2.0.6", "@pixelbrackets/gfm-stylesheet": "^1.1.0", "@prettier/plugin-php": "0.18.9", "bootstrap": "^5.1.3", @@ -1441,9 +1441,9 @@ } }, "node_modules/@phcode/fs": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-2.0.5.tgz", - "integrity": "sha512-M033f5MOtl391FfxRAfGI1DiNj0jUirUn46S6bMhZZRafFE+CYfUv6doPRPBoA/kL1ly28Lxh6bBekeJCJHKGg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-2.0.6.tgz", + "integrity": "sha512-cNC8lQ+2S6wRy9lUBO42/x16t1VTvAirgkufq5VSQ1jGUcCJ5D+URKYksR251itFHvuy6pAWLErrDlPjzONLEg==", "dependencies": { "chokidar": "^3.5.3", "ignore": "^5.2.4", @@ -19289,9 +19289,9 @@ } }, "@phcode/fs": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-2.0.5.tgz", - "integrity": "sha512-M033f5MOtl391FfxRAfGI1DiNj0jUirUn46S6bMhZZRafFE+CYfUv6doPRPBoA/kL1ly28Lxh6bBekeJCJHKGg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-2.0.6.tgz", + "integrity": "sha512-cNC8lQ+2S6wRy9lUBO42/x16t1VTvAirgkufq5VSQ1jGUcCJ5D+URKYksR251itFHvuy6pAWLErrDlPjzONLEg==", "requires": { "chokidar": "^3.5.3", "ignore": "^5.2.4", diff --git a/package.json b/package.json index 39dc31afe0..02704fc8eb 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@floating-ui/dom": "^0.5.4", "@fortawesome/fontawesome-free": "^6.1.2", "@highlightjs/cdn-assets": "^11.5.1", - "@phcode/fs": "^2.0.5", + "@phcode/fs": "^2.0.6", "@pixelbrackets/gfm-stylesheet": "^1.1.0", "@prettier/plugin-php": "0.18.9", "bootstrap": "^5.1.3", diff --git a/src-node/index.js b/src-node/index.js index 6b2b3db0f6..a1b8cf2c78 100644 --- a/src-node/index.js +++ b/src-node/index.js @@ -1 +1,141 @@ -console.log("hello world"); +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * Original work Copyright (c) 2014 - 2021 Adobe Systems Incorporated. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +const readline = require('readline'); +const http = require('http'); +const net = require('net'); +const PhoenixFS = require('@phcode/fs/dist/phoenix-fs'); + +function randomNonce(byteLength) { + const randomBuffer = new Uint8Array(byteLength); + crypto.getRandomValues(randomBuffer); + + // Define the character set for the random string + const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + // Convert the ArrayBuffer to a case-sensitive random string with numbers + let randomId = ''; + Array.from(randomBuffer).forEach(byte => { + randomId += charset[byte % charset.length]; + }); + + return randomId; +} + +const COMMAND_RESPONSE_PREFIX = 'phnodeResp:'; +// Generate a random 64-bit url. This should take 100 million+ of years to crack with current http connection speed. +const PHOENIX_FS_URL = `/PhoenixFS${randomNonce(8)}`; +const PHOENIX_NODE_URL = `/PhoenixNode${randomNonce(8)}`; + +const savedConsoleLog = console.log; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +let serverPortResolve; +const serverPortPromise = new Promise((resolve) => { serverPortResolve = resolve; }); + +let orphanExitTimer = setTimeout(()=>{ + process.exit(1); +}, 60000); + +function _sendResponse(responseMessage, commandID) { + savedConsoleLog(COMMAND_RESPONSE_PREFIX + JSON.stringify({ + message: responseMessage, + commandID + }) + "\n"); +} + +function processCommand(line) { + try{ + let jsonCmd = JSON.parse(line); + switch (jsonCmd.commandCode) { + case "terminate": process.exit(0); return; + case "heartBeat": + clearTimeout(orphanExitTimer); + orphanExitTimer = setTimeout(()=>{ + process.exit(1); + }, 60000); + return; + case "ping": _sendResponse("pong", jsonCmd.commandID); return; + case "setDebugMode": + if(jsonCmd.commandData) { + console.log = savedConsoleLog; + } else { + console.log = function () {}; // swallow logs + } + _sendResponse("done", jsonCmd.commandID); return; + case "setPhoenixFSDebugMode": + PhoenixFS.setDebugMode(jsonCmd.commandData); + _sendResponse("done", jsonCmd.commandID); return; + case "getEndpoints": + serverPortPromise.then(port =>{ + _sendResponse({ + port, + phoenixFSURL: `ws://localhost:${port}${PHOENIX_FS_URL}`, + phoenixNodeURL: `ws://localhost:${port}${PHOENIX_NODE_URL}` + }, jsonCmd.commandID); + }); + return; + default: console.error("unknown command: "+ line); + } + } catch (e) { + console.error(e); + } +} + +rl.on('line', (line) => { + processCommand(line); +}); + +function getFreePort() { + return new Promise((resolve)=>{ + const server = net.createServer(); + + server.listen(0, () => { + const port = server.address().port; + server.close(() => { + resolve(port); + }); + }); + }); +} + +// Create an HTTP server +const server = http.createServer((req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('WebSocket server running'); +}); + +getFreePort().then((port) => { + console.log('Server Opened on port: ', port); + + PhoenixFS.CreatePhoenixFsServer(server, PHOENIX_FS_URL); + // Start the HTTP server on port 3000 + server.listen(port, () => { + serverPortResolve(port); + console.log(`Server running on http://localhost:${port}`); + console.log(`Phoenix node tauri FS url is ws://localhost:${port}${PHOENIX_FS_URL}`); + }); + +}); diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 3911f8874f..b6912d4100 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -9,7 +9,7 @@ "version": "3.2.9-0", "license": "GNU-AGPL3.0", "dependencies": { - "@phcode/fs": "^2.0.5", + "@phcode/fs": "^2.0.6", "npm": "10.1.0" }, "engines": { @@ -17,9 +17,9 @@ } }, "node_modules/@phcode/fs": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-2.0.5.tgz", - "integrity": "sha512-M033f5MOtl391FfxRAfGI1DiNj0jUirUn46S6bMhZZRafFE+CYfUv6doPRPBoA/kL1ly28Lxh6bBekeJCJHKGg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-2.0.6.tgz", + "integrity": "sha512-cNC8lQ+2S6wRy9lUBO42/x16t1VTvAirgkufq5VSQ1jGUcCJ5D+URKYksR251itFHvuy6pAWLErrDlPjzONLEg==", "dependencies": { "chokidar": "^3.5.3", "ignore": "^5.2.4", diff --git a/src-node/package.json b/src-node/package.json index 76accd742d..fb253d3237 100644 --- a/src-node/package.json +++ b/src-node/package.json @@ -19,7 +19,7 @@ }, "IMPORTANT!!": "Adding things here will bloat up the package size", "dependencies": { - "@phcode/fs": "^2.0.5", + "@phcode/fs": "^2.0.6", "npm": "10.1.0" } } \ No newline at end of file diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 9663f2f1e7..54286e71e2 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -1790,6 +1790,7 @@ define(function (require, exports, module) { // Defer for a more successful reload - issue #11539 window.setTimeout(function () { + window.PhNodeEngine && window.PhNodeEngine.terminateNode(); window.location.href = href; }, 1000); }).fail(function () { @@ -1900,6 +1901,7 @@ define(function (require, exports, module) { event.preventDefault(); _handleWindowGoingAway(null, closeSuccess=>{ console.log('close success: ', closeSuccess); + window.PhNodeEngine.terminateNode(); Phoenix.app.closeWindow(); }, closeFail=>{ console.log('close fail: ', closeFail); diff --git a/src/extensions/default/DebugCommands/main.js b/src/extensions/default/DebugCommands/main.js index 1e1ff1e7b5..9eda8bae46 100644 --- a/src/extensions/default/DebugCommands/main.js +++ b/src/extensions/default/DebugCommands/main.js @@ -71,6 +71,7 @@ define(function (require, exports, module) { DEBUG_RELOAD_WITHOUT_USER_EXTS = "debug.reloadWithoutUserExts", DEBUG_SWITCH_LANGUAGE = "debug.switchLanguage", DEBUG_ENABLE_LOGGING = "debug.enableLogging", + DEBUG_ENABLE_PHNODE_INSPECTOR = "debug.enablePhNodeInspector", DEBUG_LIVE_PREVIEW_LOGGING = "debug.livePreviewLogging", DEBUG_OPEN_VFS = "debug.openVFS", DEBUG_OPEN_EXTENSION_FOLDER = "debug.openExtensionFolders", @@ -688,6 +689,7 @@ define(function (require, exports, module) { CommandManager.get(DEBUG_LIVE_PREVIEW_LOGGING).setEnabled(isLogging); logger.loggingOptions.logLivePreview = window.isLoggingEnabled(LOG_LIVE_PREVIEW_KEY); CommandManager.get(DEBUG_LIVE_PREVIEW_LOGGING).setChecked(logger.loggingOptions.logLivePreview); + CommandManager.get(DEBUG_ENABLE_PHNODE_INSPECTOR).setChecked(window.PhNodeEngine && window.PhNodeEngine.isInspectEnabled()); } function _handleLogging() { @@ -695,6 +697,11 @@ define(function (require, exports, module) { _updateLogToConsoleMenuItemChecked(); } + function _handlePhNodeInspectEnable() { + window.PhNodeEngine.setInspectEnabled(!window.PhNodeEngine.isInspectEnabled()); + _updateLogToConsoleMenuItemChecked(); + } + function _handleLivePreviewLogging() { window.toggleLoggingKey(LOG_LIVE_PREVIEW_KEY); _updateLogToConsoleMenuItemChecked(); @@ -749,6 +756,7 @@ define(function (require, exports, module) { CommandManager.register(switchLanguageStr, DEBUG_SWITCH_LANGUAGE, handleSwitchLanguage); CommandManager.register(Strings.CMD_ENABLE_LOGGING, DEBUG_ENABLE_LOGGING, _handleLogging); + CommandManager.register(Strings.CMD_ENABLE_PHNODE_INSPECTOR, DEBUG_ENABLE_PHNODE_INSPECTOR, _handlePhNodeInspectEnable); CommandManager.register(Strings.CMD_ENABLE_LIVE_PREVIEW_LOGS, DEBUG_LIVE_PREVIEW_LOGGING, _handleLivePreviewLogging); CommandManager.register(Strings.CMD_OPEN_VFS, DEBUG_OPEN_VFS, _openVFS); CommandManager.register(Strings.CMD_OPEN_EXTENSIONS_FOLDER, DEBUG_OPEN_EXTENSION_FOLDER, _openExtensionsFolder); @@ -771,6 +779,9 @@ define(function (require, exports, module) { debugMenu.addMenuItem(DEBUG_SHOW_PERF_DATA); debugMenu.addMenuDivider(); debugMenu.addMenuItem(DEBUG_ENABLE_LOGGING); + debugMenu.addMenuItem(DEBUG_ENABLE_PHNODE_INSPECTOR, undefined, undefined, undefined, { + hideWhenCommandDisabled: true + }); debugMenu.addMenuItem(DEBUG_LIVE_PREVIEW_LOGGING); debugMenu.addMenuDivider(); debugMenu.addMenuItem(DEBUG_OPEN_VFS); @@ -785,6 +796,8 @@ define(function (require, exports, module) { .setEnabled(extensionDevelopment.isProjectLoadedAsExtension()); CommandManager.get(DEBUG_OPEN_EXTENSION_FOLDER) .setEnabled(Phoenix.browser.isTauri); // only show in tauri + CommandManager.get(DEBUG_ENABLE_PHNODE_INSPECTOR) + .setEnabled(Phoenix.browser.isTauri); // only show in tauri CommandManager.get(DEBUG_OPEN_VIRTUAL_SERVER) .setEnabled(!Phoenix.browser.isTauri); // don't show in tauri as there is no virtual server in tauri diff --git a/src/filesystem/impls/appshell/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js index 63de872efe..055f0c6cc0 100644 --- a/src/filesystem/impls/appshell/AppshellFileSystem.js +++ b/src/filesystem/impls/appshell/AppshellFileSystem.js @@ -469,7 +469,7 @@ define(function (require, exports, module) { path = _normalise_path(path); if (typeof mode === "function") { callback = mode; - mode = parseInt("0755", 8); + mode = 0o755; } appshell.fs.mkdirs(path, mode, true, function (err) { if (err) { diff --git a/src/index.html b/src/index.html index 79159cd3b3..6fd4e24373 100644 --- a/src/index.html +++ b/src/index.html @@ -459,6 +459,7 @@ + diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index b1afad9d71..381749b477 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -696,6 +696,7 @@ define({ "CMD_RUN_UNIT_TESTS": "Run Tests", "CMD_SHOW_PERF_DATA": "Show Performance Data", "CMD_ENABLE_LOGGING": "Enable Detailed Logs", + "CMD_ENABLE_PHNODE_INSPECTOR": "Enable PhNode Inspector", "CMD_ENABLE_LIVE_PREVIEW_LOGS": "Live Preview Logs", "CMD_OPEN_BRACKETS_SOURCE": "Open {APP_NAME} Source", diff --git a/src/node-loader.js b/src/node-loader.js new file mode 100644 index 0000000000..ae91d3bab8 --- /dev/null +++ b/src/node-loader.js @@ -0,0 +1,108 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global Phoenix, fs*/ + +if(Phoenix.browser.isTauri) { + window.nodeSetupDonePromise = new Promise((resolve, reject) =>{ + const NODE_COMMANDS = { + TERMINATE: "terminate", + PING: "ping", + SET_DEBUG_MODE: "setDebugMode", + SET_PHOENIX_FS_DEBUG_MODE: "setPhoenixFSDebugMode", + HEART_BEAT: "heartBeat", + GET_ENDPOINTS: "getEndpoints" + }; + const COMMAND_RESPONSE_PREFIX = 'phnodeResp:'; + let command, child; + let resolved = false; + let commandID = 0, pendingCommands = {}; + const PHNODE_PREFERENCES_KEY = "PhNode.Prefs"; + function setInspectEnabled(enabled) { + const prefs = JSON.parse(localStorage.getItem(PHNODE_PREFERENCES_KEY) || "{}"); + prefs.inspectEnabled = enabled; + localStorage.setItem(PHNODE_PREFERENCES_KEY, JSON.stringify(prefs)); + } + function isInspectEnabled() { + const prefs = JSON.parse(localStorage.getItem(PHNODE_PREFERENCES_KEY) || "{}"); + return !!prefs.inspectEnabled; + } + + window.__TAURI__.path.resolveResource("src-node/index.js") + .then(async nodeSrcPath=>{ + const argsArray = isInspectEnabled() ? ['--inspect', nodeSrcPath] : [nodeSrcPath, '']; + command = window.__TAURI__.shell.Command.sidecar('phnode', argsArray); + command.on('close', data => { + window.isNodeTerminated = true; + console.log(`PhNode: command finished with code ${data.code} and signal ${data.signal}`); + if(!resolved) { + reject(); + } + }); + command.on('error', error => console.error(`PhNode: command error: "${error}"`)); + command.stdout.on('data', line => { + if(line){ + if(line.startsWith(COMMAND_RESPONSE_PREFIX)){ + // its a js response object + line = line.replace(COMMAND_RESPONSE_PREFIX, ""); + const jsonMsg = JSON.parse(line); + pendingCommands[jsonMsg.commandID].resolve(jsonMsg.message); + delete pendingCommands[jsonMsg.commandID]; + } else { + console.log(`PhNode: ${line}`); + } + } + }); + command.stderr.on('data', line => console.error(`PhNode: ${line}`)); + child = await command.spawn(); + + const execNode = function (commandCode, commandData) { + const newCommandID = commandID ++; + child.write(JSON.stringify({ + commandCode: commandCode, commandID: newCommandID, commandData + }) + "\n"); + let resolveP, rejectP; + const promise = new Promise((resolve, reject) => { resolveP = resolve; rejectP=reject; }); + pendingCommands[newCommandID]= {resolve: resolveP, reject: rejectP}; + return promise; + }; + + window.PhNodeEngine = { + setInspectEnabled, + isInspectEnabled, + terminateNode: function () { + execNode(NODE_COMMANDS.TERMINATE); + } + }; + + execNode(NODE_COMMANDS.GET_ENDPOINTS) + .then(message=>{ + fs.setNodeWSEndpoint(message.phoenixFSURL); + fs.forceUseNodeWSEndpoint(true); + resolve(message); + }); + setInterval(()=>{ + if(!window.isNodeTerminated) { + execNode(NODE_COMMANDS.HEART_BEAT); + } + }, 10000); + }); + }); +} diff --git a/src/phoenix/init_vfs.js b/src/phoenix/init_vfs.js index d079020127..ab3ac62e94 100644 --- a/src/phoenix/init_vfs.js +++ b/src/phoenix/init_vfs.js @@ -82,7 +82,7 @@ function _setupVFS(fsLib, pathLib){ cb(); return; } - Phoenix.fs.mkdirs(path, 777, true, function(err) { + Phoenix.fs.mkdirs(path, 0o755, true, function(err) { if (err && err.code !== 'EEXIST') { cb(err); } diff --git a/src/worker/IndexingWorker.js b/src/worker/IndexingWorker.js index 612cb7848a..73951a5738 100644 --- a/src/worker/IndexingWorker.js +++ b/src/worker/IndexingWorker.js @@ -77,6 +77,11 @@ define(function (require, exports, module) { } EventDispatcher.makeEventDispatcher(exports); WorkerComm.createWorkerComm(_FileIndexingWorker, exports); + if(window.nodeSetupDonePromise){ + window.nodeSetupDonePromise.then(nodeConfig =>{ + exports.execPeer("setTauriFSWS", nodeConfig.phoenixFSURL); + }); + } /** * To communicate between the IndexingWorker and Phoenix, the following methods are available: * `loadScriptInWorker`, `execPeer`, `setExecHandler`, `triggerPeer` and other APIs described diff --git a/src/worker/file-Indexing-Worker-thread.js b/src/worker/file-Indexing-Worker-thread.js index 7172fd20fe..dd93a6d466 100644 --- a/src/worker/file-Indexing-Worker-thread.js +++ b/src/worker/file-Indexing-Worker-thread.js @@ -269,6 +269,12 @@ function documentChanged(updateObject) { projectCache[updateObject.filePath] = updateObject.docContents; } +function setTauriWsFS(nodeWSURL) { + fs.setNodeWSEndpoint(nodeWSURL); + fs.forceUseNodeWSEndpoint(true); +} + +WorkerComm.setExecHandler("setTauriFSWS", setTauriWsFS); WorkerComm.setExecHandler("initCache", initCache); WorkerComm.setExecHandler("filesChanged", addFilesToCache); WorkerComm.setExecHandler("documentChanged", documentChanged); diff --git a/test/spec/LowLevelFileIO-test.js b/test/spec/LowLevelFileIO-test.js index 02068ce509..f504fe79df 100644 --- a/test/spec/LowLevelFileIO-test.js +++ b/test/spec/LowLevelFileIO-test.js @@ -427,7 +427,7 @@ define(function (require, exports, module) { statCB = statSpy(), unlinkCB = errSpy(); - brackets.fs.mkdir(delDirName, parseInt("777", 0), cb); + brackets.fs.mkdir(delDirName, 0o777, cb); await awaitsFor(function () { return cb.wasCalled; }, "makeDir to finish"); @@ -466,7 +466,7 @@ define(function (require, exports, module) { statCB = statSpy(), trashCB = errSpy(); - brackets.fs.mkdir(newDirName, parseInt("777", 0), cb); + brackets.fs.mkdir(newDirName, 0o777, cb); await awaitsFor(function () { return cb.wasCalled; }, "makedir to finish"); diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index d0f5ebb373..6daaaa8327 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -536,6 +536,9 @@ define(function (require, exports, module) { _testWindow.console[method] = function () { var log = ["[testWindow] "].concat(Array.prototype.slice.call(arguments, 0)); console[method].apply(console, log); + if(!_testWindow){ + return; + } originalMethod.apply(_testWindow.console, arguments); }; });