From 6d7e7cdcaf4d8e262865697f605e520f21596c6d Mon Sep 17 00:00:00 2001 From: apple <245524539+apples-kksk@users.noreply.github.com> Date: Thu, 14 May 2026 16:30:11 +0800 Subject: [PATCH 1/2] Add nw.App.restart API --- docs/References/App.md | 6 + src/api/app/app.js | 4 + src/api/nw_app.idl | 1 + src/api/nw_app_api.cc | 144 +++++++++++++++++++++++- src/api/nw_app_api.h | 14 +++ src/browser/nw_content_browser_hooks.cc | 8 ++ src/browser/nw_content_browser_hooks.h | 2 + test/sanity/app-restart/index.html | 60 ++++++++++ test/sanity/app-restart/package.json | 4 + test/sanity/app-restart/test.py | 56 +++++++++ 10 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 test/sanity/app-restart/index.html create mode 100644 test/sanity/app-restart/package.json create mode 100644 test/sanity/app-restart/test.py diff --git a/docs/References/App.md b/docs/References/App.md index 8f8a3b91d5..5b0c0a34a1 100644 --- a/docs/References/App.md +++ b/docs/References/App.md @@ -55,6 +55,12 @@ Mark the Application cache group specified by manifest_url obsolete. This method Send the `close` event to all windows of current app, if no window is blocking the `close` event, then the app will quit after all windows have done shutdown. Use this method to quit an app will give windows a chance to save data. +## App.restart() + +Restart current app. The app is relaunched during shutdown and keeps the current NW.js app path, command line switches, and arguments. + +This method will **not** send `close` event to windows. Use `App.closeAllWindows()` first if windows need a chance to save data. + ## App.crashBrowser() ## App.crashRenderer() diff --git a/src/api/app/app.js b/src/api/app/app.js index 5573d6d4f2..664ffa23ed 100644 --- a/src/api/app/app.js +++ b/src/api/app/app.js @@ -36,6 +36,10 @@ App.prototype.quit = function() { nw.callStaticMethod('App', 'Quit', [ ]); } +App.prototype.restart = function() { + nw.App.restart(); +} + App.prototype.closeAllWindows = function() { nw.callStaticMethod('App', 'CloseAllWindows', [ ]); } diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 120a1aa37b..f5c460eaa6 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -23,6 +23,7 @@ namespace nw.App { static void enableComponent(ComponentExtensions extension_id, ComponentCallback callback); static void updateComponent(ComponentExtensions extension_id, ErrorCallback callback); static void quit(); + static void restart(); static void closeAllWindows(); static void clearCache(); static void clearAppCache(DOMString manifest_url); diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 66ebf0ed83..184ea82cb9 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -1,5 +1,7 @@ #include "content/nw/src/api/nw_app_api.h" +#include + #include "build/build_config.h" #include "third_party/widevine/cdm/buildflags.h" @@ -8,7 +10,13 @@ #include "content/public/common/content_features.h" #include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/functional/bind.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" +#include "base/path_service.h" +#include "base/process/launch.h" #include "base/strings/utf_string_conversions.h" #include "base/task/current_thread.h" #include "content/public/browser/browser_task_traits.h" @@ -26,6 +34,7 @@ #include "components/keep_alive_registry/keep_alive_registry.h" #include "components/keep_alive_registry/keep_alive_types.h" #include "content/nw/src/api/nw_app.h" +#include "content/nw/src/browser/nw_content_browser_hooks.h" #include "content/nw/src/nw_base.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -44,18 +53,113 @@ #include "net/url_request/url_request_context_getter.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#if defined(OS_MAC) +#include "chrome/browser/mac/relauncher.h" +#endif + using namespace extensions::nwapi::nw__app; namespace extensions { -NwAppQuitFunction::NwAppQuitFunction() { +namespace { + +base::FilePath GetAbsolutePackagePath() { + nw::Package* package = nw::package(); + if (!package) + return base::FilePath(); + + base::FilePath path = package->path().NormalizePathSeparators(); + if (path.empty() || path.IsAbsolute()) + return path; + + base::FilePath current_directory; + if (!base::GetCurrentDirectory(¤t_directory)) + return path; + return current_directory.Append(path).NormalizePathSeparators(); } -NwAppQuitFunction::~NwAppQuitFunction() { +void ReplaceNwappSwitch(base::CommandLine::StringVector* argv, + const base::FilePath& package_path) { +#if defined(OS_WIN) + const base::CommandLine::StringType nwapp_switch = L"--nwapp="; + const base::CommandLine::StringType alt_nwapp_switch = L"-nwapp="; + const base::CommandLine::StringType win_nwapp_switch = L"/nwapp="; +#else + const base::CommandLine::StringType nwapp_switch = "--nwapp="; + const base::CommandLine::StringType alt_nwapp_switch = "-nwapp="; +#endif + + for (auto& arg : *argv) { + if (arg.compare(0, nwapp_switch.length(), nwapp_switch) == 0) { + arg = nwapp_switch + package_path.value(); + return; + } + if (arg.compare(0, alt_nwapp_switch.length(), alt_nwapp_switch) == 0) { + arg = alt_nwapp_switch + package_path.value(); + return; + } +#if defined(OS_WIN) + if (arg.compare(0, win_nwapp_switch.length(), win_nwapp_switch) == 0) { + arg = win_nwapp_switch + package_path.value(); + return; + } +#endif + } } -void NwAppQuitFunction::DoJob(extensions::ExtensionRegistrar* registrar, - std::string extension_id) { +base::CommandLine BuildRelaunchCommandLine() { + const base::CommandLine& current_command_line = + *base::CommandLine::ForCurrentProcess(); + base::CommandLine::StringVector argv = current_command_line.original_argv(); + + base::FilePath exe; + if (base::PathService::Get(base::FILE_EXE, &exe) && !argv.empty()) + argv[0] = exe.value(); + + base::FilePath package_path = GetAbsolutePackagePath(); + if (!package_path.empty()) { + ReplaceNwappSwitch(&argv, package_path); + + base::CommandLine::StringVector args = current_command_line.GetArgs(); + if (!args.empty() && argv.size() > 1) { + auto it = std::find(argv.begin() + 1, argv.end(), args[0]); + if (it != argv.end()) + *it = package_path.value(); + } + } + + return base::CommandLine(argv); +} + +void RelaunchCurrentApp() { + base::CommandLine command_line = BuildRelaunchCommandLine(); + base::FilePath exe; + if (base::PathService::Get(base::FILE_EXE, &exe)) + command_line.SetProgram(exe); + +#if defined(OS_MAC) + if (!mac_relauncher::RelaunchApp(command_line.argv())) + LOG(ERROR) << "Failed to relaunch NW app"; +#else + base::LaunchOptions options; +#if defined(OS_WIN) + if (!exe.empty()) { + options.current_directory = exe.DirName(); + options.grant_foreground_privilege = true; + } +#endif + +#if defined(OS_LINUX) + options.allow_new_privs = true; +#endif + + if (!base::LaunchProcess(command_line, options).IsValid()) + LOG(ERROR) << "Failed to relaunch NW app"; +#endif +} + +void QuitApp(extensions::ExtensionRegistrar* registrar, + const std::string& extension_id) { if (base::FeatureList::IsEnabled(::features::kNWNewWin)) { chrome::CloseAllBrowsersAndQuit(true); // trigger BrowserProcessImpl::Unpin() @@ -70,6 +174,20 @@ void NwAppQuitFunction::DoJob(extensions::ExtensionRegistrar* registrar, registrar->GetWeakPtr(), extension_id)); } +} // namespace + +NwAppQuitFunction::NwAppQuitFunction() { + +} + +NwAppQuitFunction::~NwAppQuitFunction() { +} + +void NwAppQuitFunction::DoJob(extensions::ExtensionRegistrar* registrar, + std::string extension_id) { + QuitApp(registrar, extension_id); +} + ExtensionFunction::ResponseAction NwAppQuitFunction::Run() { extensions::ExtensionRegistrar* registrar = @@ -81,6 +199,24 @@ NwAppQuitFunction::Run() { return RespondNow(NoArguments()); } +void NwAppRestartFunction::DoJob(extensions::ExtensionRegistrar* registrar, + std::string extension_id) { + nw::ScheduleRelaunchOnShutdown(&RelaunchCurrentApp); + QuitApp(registrar, extension_id); +} + +ExtensionFunction::ResponseAction +NwAppRestartFunction::Run() { + extensions::ExtensionRegistrar* registrar = + extensions::ExtensionRegistrar::Get(browser_context()); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, + base::BindOnce(&NwAppRestartFunction::DoJob, + registrar, extension_id())); + + return RespondNow(NoArguments()); +} + void NwAppCloseAllWindowsFunction::DoJob(AppWindowRegistry* registry, std::string id) { if (base::FeatureList::IsEnabled(::features::kNWNewWin)) { chrome::CloseAllBrowsers(); diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index 7a07ee32a3..50404cc054 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -29,6 +29,20 @@ class NwAppQuitFunction : public ExtensionFunction { void Callback(); }; +class NwAppRestartFunction : public ExtensionFunction { + public: + NwAppRestartFunction() {} + + static void DoJob(extensions::ExtensionRegistrar* registrar, + std::string extension_id); + protected: + ~NwAppRestartFunction() override {} + + // ExtensionFunction: + ResponseAction Run() override; + DECLARE_EXTENSION_FUNCTION("nw.App.restart", UNKNOWN) +}; + class NwAppCloseAllWindowsFunction : public ExtensionFunction { public: NwAppCloseAllWindowsFunction() {} diff --git a/src/browser/nw_content_browser_hooks.cc b/src/browser/nw_content_browser_hooks.cc index 46a49d555b..a826d49113 100644 --- a/src/browser/nw_content_browser_hooks.cc +++ b/src/browser/nw_content_browser_hooks.cc @@ -58,6 +58,8 @@ bool g_pinning_renderer = true; bool g_mixed_context = false; bool g_in_webview_apply_attr = false; bool g_in_webview_apply_attr_allow_nw = false; +RelaunchCallback g_relaunch_on_shutdown = nullptr; + } //namespace #if defined(OS_MAC) @@ -98,9 +100,15 @@ bool GetPackageImage(nw::Package* package, } void MainPartsPostDestroyThreadsHook() { + if (g_relaunch_on_shutdown) + g_relaunch_on_shutdown(); ReleaseNWPackage(); } +void ScheduleRelaunchOnShutdown(RelaunchCallback relaunch) { + g_relaunch_on_shutdown = relaunch; +} + void RendererProcessTerminatedHook(content::RenderProcessHost* process, const content::ChildProcessTerminationInfo& info) { int exit_code = info.exit_code; diff --git a/src/browser/nw_content_browser_hooks.h b/src/browser/nw_content_browser_hooks.h index 5fcbb5182c..0b6bd2a996 100644 --- a/src/browser/nw_content_browser_hooks.h +++ b/src/browser/nw_content_browser_hooks.h @@ -28,6 +28,7 @@ namespace content { namespace nw { class Package; +using RelaunchCallback = void (*)(); // // implemented in nw_content_browser_hooks.cc @@ -38,6 +39,7 @@ int MainPartsPreCreateThreadsHook(); void MainPartsPreMainMessageLoopRunHook(); // ref in chrome/browser/chrome_browser_main.cc CONTENT_EXPORT void MainPartsPostDestroyThreadsHook(); +CONTENT_EXPORT void ScheduleRelaunchOnShutdown(RelaunchCallback relaunch); // ref in chrome/browser/extensions/extension_service.cc CONTENT_EXPORT void RendererProcessTerminatedHook(content::RenderProcessHost* process, const content::ChildProcessTerminationInfo& info); diff --git a/test/sanity/app-restart/index.html b/test/sanity/app-restart/index.html new file mode 100644 index 0000000000..6dd6bed01d --- /dev/null +++ b/test/sanity/app-restart/index.html @@ -0,0 +1,60 @@ + + + + + + app-restart + + + + + diff --git a/test/sanity/app-restart/package.json b/test/sanity/app-restart/package.json new file mode 100644 index 0000000000..389dce665b --- /dev/null +++ b/test/sanity/app-restart/package.json @@ -0,0 +1,4 @@ +{ + "name": "app-restart", + "main": "index.html" +} diff --git a/test/sanity/app-restart/test.py b/test/sanity/app-restart/test.py new file mode 100644 index 0000000000..2acb49c1f2 --- /dev/null +++ b/test/sanity/app-restart/test.py @@ -0,0 +1,56 @@ +import json +import os +import platform +import shutil +import subprocess +import time + +testdir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(testdir) + +appdir = testdir +datadir = os.path.join(testdir, 'userdata') +state = os.path.join(testdir, 'restart-state.json') +result = os.path.join(testdir, 'restart-result.json') + +try: + shutil.rmtree(datadir) +except OSError: + pass + +for path in [state, result]: + try: + os.remove(path) + except OSError: + pass + +pkg = os.path.dirname(os.environ['CHROMEDRIVER']) + +if platform.system() == 'Darwin': + exe = os.path.join(pkg, 'nwjs.app', 'Contents', 'MacOS', 'nwjs') +elif platform.system() == 'Linux': + exe = os.path.join(pkg, 'nw') +else: + exe = os.path.join(pkg, 'nw.exe') + +ret = subprocess.call([ + exe, + '--nwjs-test-mode2', + '--user-data-dir=' + datadir, + '--restart-test-flag=present', + appdir, + 'plain-restart-arg' +], env=dict(os.environ, APP_RESTART_TEST_DIR=testdir)) +assert ret == 0 + +deadline = time.time() + 30 +while not os.path.exists(result) and time.time() < deadline: + time.sleep(1) + +assert os.path.exists(result) + +with open(result, 'r') as result_file: + data = json.load(result_file) + +print(data) +assert data['success'] From 02159a6c64163046213252f0b09a89909222bd9a Mon Sep 17 00:00:00 2001 From: apple <245524539+apples-kksk@users.noreply.github.com> Date: Thu, 14 May 2026 22:04:44 +0800 Subject: [PATCH 2/2] test: expand app restart coverage --- src/api/app/app.cc | 6 +- src/api/nw_app_api.cc | 50 ++++++++----- test/sanity/app-restart/index.html | 106 +++++++++++++++++++++++----- test/sanity/app-restart/test.py | 109 ++++++++++++++++++++--------- 4 files changed, 200 insertions(+), 71 deletions(-) diff --git a/src/api/app/app.cc b/src/api/app/app.cc index 30f4b6d084..36d15affef 100644 --- a/src/api/app/app.cc +++ b/src/api/app/app.cc @@ -135,10 +135,10 @@ void App::Call(Shell* shell, CommandLine::StringVector args = command_line->GetArgs(); CommandLine::StringVector argv = command_line->original_argv(); - // Ignore first non-switch arg if it's not a standalone package. - bool ignore_arg = !package->self_extract(); + // Ignore the package path only when it was passed as a positional argument. + bool ignore_arg = !package->self_extract() && !command_line->HasSwitch("nwapp"); for (unsigned i = 1; i < argv.size(); ++i) { - if (ignore_arg && argv[i] == args[0]) { + if (ignore_arg && args.size() && argv[i] == args[0]) { ignore_arg = false; continue; } diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 184ea82cb9..07a3b3955e 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -1,7 +1,5 @@ #include "content/nw/src/api/nw_app_api.h" -#include - #include "build/build_config.h" #include "third_party/widevine/cdm/buildflags.h" @@ -78,7 +76,7 @@ base::FilePath GetAbsolutePackagePath() { return current_directory.Append(path).NormalizePathSeparators(); } -void ReplaceNwappSwitch(base::CommandLine::StringVector* argv, +bool ReplaceNwappSwitch(base::CommandLine::StringVector* argv, const base::FilePath& package_path) { #if defined(OS_WIN) const base::CommandLine::StringType nwapp_switch = L"--nwapp="; @@ -92,19 +90,44 @@ void ReplaceNwappSwitch(base::CommandLine::StringVector* argv, for (auto& arg : *argv) { if (arg.compare(0, nwapp_switch.length(), nwapp_switch) == 0) { arg = nwapp_switch + package_path.value(); - return; + return true; } if (arg.compare(0, alt_nwapp_switch.length(), alt_nwapp_switch) == 0) { arg = alt_nwapp_switch + package_path.value(); - return; + return true; } #if defined(OS_WIN) if (arg.compare(0, win_nwapp_switch.length(), win_nwapp_switch) == 0) { arg = win_nwapp_switch + package_path.value(); - return; + return true; } #endif } + + return false; +} + +bool PathReferencesPackage(const base::CommandLine::StringType& arg, + const base::FilePath& package_path) { + base::FilePath arg_path(arg); + if (!arg_path.IsAbsolute()) { + base::FilePath current_directory; + if (!base::GetCurrentDirectory(¤t_directory)) + return false; + arg_path = current_directory.Append(arg_path); + } + + return arg_path.NormalizePathSeparators() == package_path; +} + +void ReplacePackagePathArg(base::CommandLine::StringVector* argv, + const base::FilePath& package_path) { + for (auto it = argv->begin() + 1; it != argv->end(); ++it) { + if (PathReferencesPackage(*it, package_path)) { + *it = package_path.value(); + return; + } + } } base::CommandLine BuildRelaunchCommandLine() { @@ -118,14 +141,9 @@ base::CommandLine BuildRelaunchCommandLine() { base::FilePath package_path = GetAbsolutePackagePath(); if (!package_path.empty()) { - ReplaceNwappSwitch(&argv, package_path); - - base::CommandLine::StringVector args = current_command_line.GetArgs(); - if (!args.empty() && argv.size() > 1) { - auto it = std::find(argv.begin() + 1, argv.end(), args[0]); - if (it != argv.end()) - *it = package_path.value(); - } + bool has_nwapp_switch = ReplaceNwappSwitch(&argv, package_path); + if (!has_nwapp_switch && argv.size() > 1) + ReplacePackagePathArg(&argv, package_path); } return base::CommandLine(argv); @@ -301,8 +319,8 @@ bool NwAppGetArgvSyncFunction::RunNWSync(base::ListValue* response, std::string* base::CommandLine::StringVector args = command_line->GetArgs(); base::CommandLine::StringVector argv = command_line->original_argv(); - // Ignore first non-switch arg if it's not a standalone package. - bool ignore_arg = !package->self_extract(); + // Ignore the package path only when it was passed as a positional argument. + bool ignore_arg = !package->self_extract() && !command_line->HasSwitch("nwapp"); for (unsigned i = 1; i < argv.size(); ++i) { if (ignore_arg && args.size() && argv[i] == args[0]) { ignore_arg = false; diff --git a/test/sanity/app-restart/index.html b/test/sanity/app-restart/index.html index 6dd6bed01d..7ad30557d9 100644 --- a/test/sanity/app-restart/index.html +++ b/test/sanity/app-restart/index.html @@ -10,50 +10,118 @@ const fs = require('fs'); const gui = require('nw.gui'); const path = require('path'); + const testDir = process.env.APP_RESTART_TEST_DIR || process.cwd(); - const statePath = path.join(testDir, 'restart-state.json'); - const resultPath = path.join(testDir, 'restart-result.json'); + const scenario = process.env.APP_RESTART_SCENARIO || 'default'; + const statePath = path.join(testDir, scenario + '-state.json'); + const resultPath = path.join(testDir, scenario + '-result.json'); + const closeEventPath = path.join(testDir, scenario + '-close-event.json'); + const expected = { + appPath: process.env.APP_RESTART_EXPECT_APP_PATH || process.cwd(), + arg: process.env.APP_RESTART_EXPECT_ARG || '', + appPathInArgv: process.env.APP_RESTART_EXPECT_APP_PATH_IN_ARGV === '1', + method: process.env.APP_RESTART_METHOD || 'nw', + switchArg: process.env.APP_RESTART_EXPECT_SWITCH || '' + }; + + nw.Window.get().on('close', function() { + fs.writeFileSync(closeEventPath, JSON.stringify({ + pid: process.pid, + argv: nw.App.argv, + fullArgv: nw.App.fullArgv, + legacyFullArgv: gui.App.fullArgv + }, null, 2)); + this.close(true); + }); + + function normalizePath(value) { + return path.resolve(value).replace(/\\/g, '/').replace(/\/+$/, ''); + } + + function nwappSwitchValue(arg) { + const prefixes = ['--nwapp=', '-nwapp=', '/nwapp=']; + for (let i = 0; i < prefixes.length; ++i) { + if (arg.indexOf(prefixes[i]) === 0) + return arg.substring(prefixes[i].length); + } + return null; + } + + function fullArgvHasAppPath(argv, appPath) { + const normalizedAppPath = normalizePath(appPath); + for (let i = 0; i < argv.length; ++i) { + const switchValue = nwappSwitchValue(argv[i]); + const candidate = switchValue === null ? argv[i] : switchValue; + if (normalizePath(candidate) === normalizedAppPath) + return true; + } + return false; + } + + function fullArgvHasValue(argv, value) { + return value === '' || argv.indexOf(value) !== -1; + } - function writeResult(success, message) { + function writeResult(success, message, details) { fs.writeFileSync(resultPath, JSON.stringify({ success, message, + details, argv: nw.App.argv, - fullArgv: nw.App.fullArgv - })); + fullArgv: nw.App.fullArgv, + legacyFullArgv: gui.App.fullArgv + }, null, 2)); nw.App.quit(); } + function restartApp() { + if (expected.method === 'gui') { + gui.App.restart(); + return; + } + nw.App.restart(); + } + window.onload = function() { if (typeof nw.App.restart !== 'function') { - writeResult(false, 'nw.App.restart is not a function'); + writeResult(false, 'nw.App.restart is not a function', {}); return; } if (typeof gui.App.restart !== 'function') { - writeResult(false, 'gui.App.restart is not a function'); + writeResult(false, 'gui.App.restart is not a function', {}); return; } if (fs.existsSync(statePath)) { const firstRun = JSON.parse(fs.readFileSync(statePath, 'utf8')); - const fullArgv = nw.App.fullArgv.join('\n'); - const hasSwitch = fullArgv.indexOf('--restart-test-flag=present') !== -1; - const hasArg = fullArgv.indexOf('plain-restart-arg') !== -1; - const newProcess = firstRun.pid !== process.pid; - - writeResult( - hasSwitch && hasArg && newProcess, - JSON.stringify({hasSwitch, hasArg, newProcess}) - ); + const checks = { + newProcess: firstRun.pid !== process.pid, + preservedAppPath: !expected.appPathInArgv || + fullArgvHasAppPath(nw.App.fullArgv, expected.appPath), + preservedSwitch: fullArgvHasValue(nw.App.fullArgv, expected.switchArg), + preservedArg: fullArgvHasValue(nw.App.fullArgv, expected.arg), + preservedLegacyAppPath: !expected.appPathInArgv || + fullArgvHasAppPath(gui.App.fullArgv, expected.appPath), + preservedLegacyArg: fullArgvHasValue(gui.App.fullArgv, expected.arg), + noCloseEvent: !fs.existsSync(closeEventPath) + }; + const failures = Object.keys(checks).filter(function(key) { + return !checks[key]; + }); + + writeResult(failures.length === 0, + failures.length ? failures.join(', ') : 'ok', + {checks, firstRun}); return; } fs.writeFileSync(statePath, JSON.stringify({ pid: process.pid, argv: nw.App.argv, - fullArgv: nw.App.fullArgv - })); - nw.App.restart(); + fullArgv: nw.App.fullArgv, + legacyFullArgv: gui.App.fullArgv + }, null, 2)); + restartApp(); }; diff --git a/test/sanity/app-restart/test.py b/test/sanity/app-restart/test.py index 2acb49c1f2..e5f5b6fe95 100644 --- a/test/sanity/app-restart/test.py +++ b/test/sanity/app-restart/test.py @@ -8,22 +8,6 @@ testdir = os.path.dirname(os.path.abspath(__file__)) os.chdir(testdir) -appdir = testdir -datadir = os.path.join(testdir, 'userdata') -state = os.path.join(testdir, 'restart-state.json') -result = os.path.join(testdir, 'restart-result.json') - -try: - shutil.rmtree(datadir) -except OSError: - pass - -for path in [state, result]: - try: - os.remove(path) - except OSError: - pass - pkg = os.path.dirname(os.environ['CHROMEDRIVER']) if platform.system() == 'Darwin': @@ -33,24 +17,83 @@ else: exe = os.path.join(pkg, 'nw.exe') -ret = subprocess.call([ - exe, - '--nwjs-test-mode2', - '--user-data-dir=' + datadir, - '--restart-test-flag=present', - appdir, - 'plain-restart-arg' -], env=dict(os.environ, APP_RESTART_TEST_DIR=testdir)) -assert ret == 0 +workdir = os.path.join(testdir, 'tmp') + +try: + shutil.rmtree(workdir) +except OSError: + pass + +os.mkdir(workdir) + +def copy_app(target): + os.mkdir(target) + for filename in ['index.html', 'package.json']: + shutil.copy2(os.path.join(testdir, filename), + os.path.join(target, filename)) + +def run_restart_scenario(name, method, launch_mode, app_path, cwd=None): + scenario_dir = os.path.join(workdir, name) + datadir = os.path.join(scenario_dir, 'userdata') + result = os.path.join(scenario_dir, name + '-result.json') + switch_arg = '--restart-test-flag=' + name + plain_arg = 'plain-restart-arg-' + name + + os.mkdir(scenario_dir) + + command = [ + exe, + '--nwjs-test-mode2', + '--user-data-dir=' + datadir, + switch_arg, + ] + if launch_mode == 'nwapp-switch': + command.append('--nwapp=' + app_path) + else: + command.append(app_path) + command.append(plain_arg) + + env = dict(os.environ, + APP_RESTART_TEST_DIR=scenario_dir, + APP_RESTART_SCENARIO=name, + APP_RESTART_METHOD=method, + APP_RESTART_EXPECT_APP_PATH=os.path.abspath( + os.path.join(cwd, app_path) if cwd else app_path), + APP_RESTART_EXPECT_APP_PATH_IN_ARGV=( + '1' if launch_mode == 'nwapp-switch' else '0'), + APP_RESTART_EXPECT_SWITCH=switch_arg, + APP_RESTART_EXPECT_ARG=plain_arg) + + print('running %s: %s' % (name, command)) + ret = subprocess.call(command, cwd=cwd, env=env) + assert ret == 0 + + deadline = time.time() + 30 + while not os.path.exists(result) and time.time() < deadline: + time.sleep(1) + + assert os.path.exists(result), '%s did not write a result' % name + + with open(result, 'r') as result_file: + data = json.load(result_file) + + print(data) + assert data['success'], '%s failed: %s' % (name, data['message']) -deadline = time.time() + 30 -while not os.path.exists(result) and time.time() < deadline: - time.sleep(1) +relative_root = os.path.join(workdir, 'relative root') +os.mkdir(relative_root) +relative_app = 'relative app' +copy_app(os.path.join(relative_root, relative_app)) +run_restart_scenario('relative-positional', 'nw', 'positional', + relative_app, cwd=relative_root) -assert os.path.exists(result) +switch_app = os.path.join(workdir, 'switch app with spaces') +copy_app(switch_app) +run_restart_scenario('nwapp-switch', 'gui', 'nwapp-switch', switch_app) -with open(result, 'r') as result_file: - data = json.load(result_file) +absolute_app = os.path.join(workdir, 'absolute-app') +copy_app(absolute_app) +run_restart_scenario('absolute-positional', 'nw', 'positional', + absolute_app) -print(data) -assert data['success'] +shutil.rmtree(workdir)