зеркало из https://github.com/microsoft/napajs.git
Polish zone.execute: transported function will inherit __dirname from caller. (#40)
This change addresses issue to make zone.execute on anonymous function more fluent. Also it made transported functions more secure, which can only access their own v8 contexts. Detailed changes Support require(path, script) to create module from string in Napa zone. Introduce napa.v8.currentStack to get current stack. Replace eval with creating a new module for transported function. Use caller JS file + function hash as module id for transported function. As a result, transported function will use __dirname from caller. Updated test files. Notable comments We always assume that the function passed to zone.execute without property origin is to use caller file as origin. This assumption makes most fluent usage for most cases, but if functions are defined in different file other than caller, this assumption will be broken. User can explicit specify `f.origin = __filename' in the file where f is defined to avoid issue. Bug fixes Issue: require.resolve should throw on non-existed path.
This commit is contained in:
Родитель
bf1cef52ae
Коммит
3211319d77
|
@ -5,6 +5,7 @@
|
|||
- [Transportable types](#transportable-types)
|
||||
- [Constructor ID](#constructor-id)
|
||||
- [Transport context](#transport-context)
|
||||
- [Transporting functions](#transporting-functions)
|
||||
- API
|
||||
- [`isTransportable(jsValue: any): boolean`](#istransportable)
|
||||
- [`register(transportableClass: new(...args: any[]) => any): void`](#register)
|
||||
|
@ -38,6 +39,7 @@ Transportable types are:
|
|||
- JavaScript primitive types: undefined, null, boolean, number, string
|
||||
- Object (TypeScript class) that implement [`Transportable`](#transportable) interface
|
||||
- Array or plain JavaScript object that is composite pattern of above.
|
||||
- Function without referencing closures.
|
||||
|
||||
### <a name="constructor-id"></a> Constructor ID (cid)
|
||||
For user classes that implement [`Transportable`](#transportable) interface, Napa uses Constructor ID (`cid`) to lookup constructors for creating a right object from a string payload. `cid` is marshalled as a part of the payload. During unmarshalling, transport layer will extract the `cid`, create an object instance using the constructor associated with it, and then call unmarshall on the object.
|
||||
|
@ -47,6 +49,14 @@ It's class developer's responsibility to choose the right `cid` for your class.
|
|||
### <a name="transport-context"></a> Transport context
|
||||
There are states that cannot be saved or loaded in serialized form (like std::shared_ptr), or it's very inefficient to serialize (like JavaScript function). Transport context is introduced to help in these scenarios. TransportContext objects can be passed from one JavaScript VM to another, or stored in native world, so lifecycle of shared native objects extended by using TransportContext. An example of `Transportable` implementation using TransportContext is [`ShareableWrap`](..\..\inc\napa\module\shareable-wrap.h).
|
||||
|
||||
### <a name="transporting-functions"></a> Transporting functions
|
||||
JavaScript function is a special transportable type, through marshalling its definition into a [store](./store.md#intro), and generate a new function from its definition on target thread.
|
||||
|
||||
Highlights on transporting functions are:
|
||||
- For the same function, marshall/unmarshall is an one-time cost on each JavaScript thread. Once a function is transported for the first time, later transportation of the same function to previous JavaScript thread can be regarded as free.
|
||||
- Closure cannot be transported, but you won't get error when transporting a function. Instead, you will get runtime error complaining a variable (from closure) is undefined when you can the function later.
|
||||
- `__dirname` / `__filename` can be accessed in transported function, which is determined by `origin` property of function. By default `origin` property is set to current working directory.
|
||||
|
||||
## <a name="api"></a> API
|
||||
|
||||
### <a name="istransportable"></a> isTransportable(jsValue: any): boolean
|
||||
|
|
|
@ -158,7 +158,12 @@ zone.execute(
|
|||
|
||||
### <a name="execute-anonymous-function"></a> zone.execute(function: (...args: any[]) => any, args?: any[], options?: CallOptions): Promise\<any\>
|
||||
|
||||
Execute an anonymous function asynchronously on arbitrary worker. Arguments can be of any JavaScript type that is [transportable](transport.md#transportable-types). It returns a Promise of [`Result`](#result). If error happens, either bad code, user exception, or timeout is reached, promise will be rejected.
|
||||
Execute a function object asynchronously on arbitrary worker. Arguments can be of any JavaScript type that is [transportable](transport.md#transportable-types). It returns a Promise of [`Result`](#result). If error happens, either bad code, user exception, or timeout is reached, promise will be rejected.
|
||||
|
||||
Here are a few restricitions on executing a function object:
|
||||
|
||||
- The function object cannot access variables from closure
|
||||
- Unless the function object has `origin` property, it will use current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions))
|
||||
|
||||
Example:
|
||||
```js
|
||||
|
@ -172,6 +177,20 @@ zone.execute((a: number, b: string, c: object) => {
|
|||
console.log('execute failed:', error);
|
||||
});
|
||||
|
||||
```
|
||||
Output:
|
||||
```
|
||||
execute succeeded: 1hello{"field1":1}
|
||||
|
||||
```
|
||||
Another example demonstrates accessing `__filename` when executing an anonymous function:
|
||||
```js
|
||||
// File: /usr/file1.js
|
||||
zone.execute(() => { console.log(__filename);});
|
||||
```
|
||||
Output:
|
||||
```
|
||||
/usr/file1.js
|
||||
```
|
||||
## <a name="call-options"></a> Interface `CallOptions`
|
||||
Interface for options to call function in `zone.execute`.
|
||||
|
|
|
@ -7,9 +7,10 @@ import * as metric from './metric';
|
|||
import * as runtime from './runtime';
|
||||
import * as store from './store';
|
||||
import * as transport from './transport';
|
||||
import * as v8 from './v8';
|
||||
import * as zone from './zone';
|
||||
|
||||
export { log, memory, metric, runtime, store, transport, zone };
|
||||
export { log, memory, metric, runtime, store, transport, v8, zone };
|
||||
|
||||
// Add execute proxy to global context.
|
||||
import { call } from './zone/function-call';
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { Store } from '../store/store';
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
/// <summary> Function hash to function cache. </summary>
|
||||
let _hashToFunctionCache: {[hash: string]: (...args: any[]) => any} = {};
|
||||
|
@ -17,6 +18,15 @@ let _functionToHashCache: any = {};
|
|||
/// <summary> Marshalled function body cache. </summary>
|
||||
let _store: Store;
|
||||
|
||||
/// <summary> Interface for function definition that will be saved in store. </summary>
|
||||
interface FunctionDef {
|
||||
/// <summary> From which file name the function is defined. </summary>
|
||||
origin: string;
|
||||
|
||||
/// <summary> Function body. </summary>
|
||||
body: string;
|
||||
}
|
||||
|
||||
/// <summary> Get underlying store to save marshall function body across isolates. </summary>
|
||||
function getStore(): Store {
|
||||
if (_store == null) {
|
||||
|
@ -34,9 +44,15 @@ export function save(func: (...args: any[]) => any): string {
|
|||
let hash = _functionToHashCache[(<any>(func))];
|
||||
if (hash == null) {
|
||||
// Should happen only on first marshall of input function in current isolate.
|
||||
let origin = (<any>func).origin || '';
|
||||
let body = func.toString();
|
||||
hash = getFunctionHash(body);
|
||||
getStore().set(hash, body);
|
||||
let fullContent = origin + ":" + body;
|
||||
hash = getFunctionHash(fullContent);
|
||||
let def: FunctionDef = {
|
||||
origin: origin,
|
||||
body: body
|
||||
};
|
||||
getStore().set(hash, def);
|
||||
cacheFunction(hash, func);
|
||||
}
|
||||
return hash;
|
||||
|
@ -47,11 +63,11 @@ export function load(hash: string): (...args: any[]) => any {
|
|||
let func = _hashToFunctionCache[hash];
|
||||
if (func == null) {
|
||||
// Should happen only on first unmarshall of given hash in current isolate..
|
||||
let body = getStore().get(hash);
|
||||
if (body == null) {
|
||||
let def: FunctionDef = getStore().get(hash);
|
||||
if (def == null) {
|
||||
throw new Error(`Function hash cannot be found: ${hash}`);
|
||||
}
|
||||
func = eval(`(${body})`);
|
||||
func = loadFunction(def);
|
||||
cacheFunction(hash, func);
|
||||
}
|
||||
return func;
|
||||
|
@ -66,10 +82,10 @@ function cacheFunction(hash: string, func: (...args: any[]) => any) {
|
|||
/// <summary> Generate hash for function definition using DJB2 algorithm.
|
||||
/// See: https://en.wikipedia.org/wiki/DJB2
|
||||
/// </summary>
|
||||
function getFunctionHash(functionDef: string): string {
|
||||
function getFunctionHash(signature: string): string {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < functionDef.length; ++i) {
|
||||
hash = (hash * 33) ^ functionDef.charCodeAt(i);
|
||||
for (let i = 0; i < signature.length; ++i) {
|
||||
hash = (hash * 33) ^ signature.charCodeAt(i);
|
||||
}
|
||||
|
||||
/* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
|
||||
|
@ -77,3 +93,31 @@ function getFunctionHash(functionDef: string): string {
|
|||
* signed int to an unsigned by doing an unsigned bitshift. */
|
||||
return (hash >>> 0).toString(16);
|
||||
}
|
||||
|
||||
declare var __in_napa: boolean;
|
||||
|
||||
/// <summary> Load function from definition. </summary>
|
||||
function loadFunction(def: FunctionDef): (...args: any[]) => any {
|
||||
let moduleId = def.origin;
|
||||
let script = "module.exports = " + def.body + ";";
|
||||
let func: any = null;
|
||||
|
||||
if (typeof __in_napa === 'undefined') {
|
||||
// In node, we create a sandbox using Module
|
||||
let Module: any = null;
|
||||
if (Module == null) {
|
||||
Module = require('module');
|
||||
}
|
||||
let module = new Module(moduleId);
|
||||
module.filename = moduleId;
|
||||
module.paths = Module._nodeModulePaths(path.dirname(def.origin));
|
||||
module._compile(script, moduleId);
|
||||
func = module.exports;
|
||||
|
||||
} else {
|
||||
// In napa, we create a sandbox using require(path, script);
|
||||
func = (<any>require)(moduleId, script);
|
||||
}
|
||||
func.origin = def.origin;
|
||||
return func;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export * from './v8/stack-trace';
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/// Reference: https://github.com/v8/v8/wiki/Stack-Trace-API
|
||||
|
||||
/// <summary> Represents a stack frame. </summary>
|
||||
export interface CallSite {
|
||||
|
||||
/// <summary> Returns the value of this. </summary>
|
||||
getThis(): any;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of this as a string.
|
||||
/// This is the name of the function stored in the constructor field of this, if available,
|
||||
/// otherwise the object's [[Class]] internal property.
|
||||
/// </summary>
|
||||
getTypeName(): string;
|
||||
|
||||
/// <summary> Returns the current function. </summary>
|
||||
getFunction(): any;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the current function, typically its name property.
|
||||
/// If a name property is not available an attempt will be made to try to infer a name from the function's context.
|
||||
/// </summary>
|
||||
getFunctionName(): string;
|
||||
|
||||
/// <summary> Returns the name of the property of this or one of its prototypes that holds the current function. </summary>
|
||||
getMethodName(): string;
|
||||
|
||||
/// <summary> If this function was defined in a script returns the name of the script. </summary>
|
||||
getFileName(): string;
|
||||
|
||||
/// <summary> If this function was defined in a script returns the current line number. </summary>
|
||||
getLineNumber(): number;
|
||||
|
||||
/// <summary> If this function was defined in a script returns the current column number. </summary>
|
||||
getColumnNumber(): number;
|
||||
|
||||
/// <summary> If this function was created using a call to eval returns a CallSite object representing the location where eval was called. </summary>
|
||||
getEvalOrigin(): CallSite;
|
||||
|
||||
/// <summary> Is this a toplevel invocation, that is, is this the global object. </summary>
|
||||
isToplevel(): boolean;
|
||||
|
||||
/// <summary> Is this call in native V8 code. </summary>
|
||||
isNative(): boolean;
|
||||
|
||||
/// <summary> Is this a constructor call. </summary>
|
||||
isConstructor(): boolean;
|
||||
|
||||
/// <summary> Does this call take place in code defined by a call to eval. </summary>
|
||||
isEval(): boolean;
|
||||
}
|
||||
|
||||
/// <summary> Get current stack. </summary>
|
||||
/// <param name="stackTraceLimit"> Max stack depth to trace. </param>
|
||||
/// <returns> Array of CallSite. </returns>
|
||||
export function currentStack(stackTraceLimit: number = 0): CallSite[] {
|
||||
let e: any = Error;
|
||||
|
||||
const originPrepare = e.prepareStackTrace;
|
||||
const originLimit = e.stackTraceLimit;
|
||||
|
||||
if (stackTraceLimit > 0) {
|
||||
e.stackTraceLimit = stackTraceLimit + 1;
|
||||
}
|
||||
|
||||
e.prepareStackTrace = (prepare: any, stack: any) => stack;
|
||||
// We remove stack at top since it's always this function.
|
||||
let stack = new e().stack.slice(1);
|
||||
e.prepareStackTrace = originPrepare;
|
||||
|
||||
if (stackTraceLimit > 0) {
|
||||
e.stackTraceLimit = originLimit;
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
/// <summary> Format stack trace. </summary>
|
||||
export function formatStackTrace(trace: CallSite[]): string {
|
||||
let s = '';
|
||||
for (let site of trace) {
|
||||
s += 'at '
|
||||
+ site.getFunctionName()
|
||||
+ '('
|
||||
+ site.getFileName()
|
||||
+ ':'
|
||||
+ site.getLineNumber()
|
||||
+ ':'
|
||||
+ site.getColumnNumber()
|
||||
+ ")\n";
|
||||
}
|
||||
return s;
|
||||
}
|
|
@ -100,14 +100,23 @@ function callFunction(
|
|||
options: CallOptions): any {
|
||||
|
||||
let module: any = null;
|
||||
if (moduleName == null || moduleName.length === 0) {
|
||||
let useAnonymousFunction: boolean = false;
|
||||
|
||||
if (moduleName == null || moduleName.length === 0 || moduleName === 'global') {
|
||||
module = global;
|
||||
} else if (moduleName !== '__function') {
|
||||
} else if (moduleName === '__function') {
|
||||
useAnonymousFunction = true;
|
||||
} else {
|
||||
module = require(moduleName);
|
||||
}
|
||||
|
||||
let func = null;
|
||||
if (module != null) {
|
||||
if (useAnonymousFunction) {
|
||||
func = transport.loadFunction(functionName);
|
||||
} else {
|
||||
if (module == null) {
|
||||
throw new Error(`Cannot load module \"${moduleName}\".`);
|
||||
}
|
||||
func = module;
|
||||
if (functionName != null && functionName.length != 0) {
|
||||
var path = functionName.split('.');
|
||||
|
@ -121,9 +130,6 @@ function callFunction(
|
|||
if (typeof func !== 'function') {
|
||||
throw new Error("'" + functionName + "' in module '" + moduleName + "' is not a function");
|
||||
}
|
||||
} else {
|
||||
// Anonymous function.
|
||||
func = transport.loadFunction(functionName);
|
||||
}
|
||||
|
||||
let args = marshalledArgs.map((arg) => { return transport.unmarshall(arg, transportContext); });
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as path from 'path';
|
||||
import * as zone from './zone';
|
||||
import * as transport from '../transport';
|
||||
import * as v8 from '../v8';
|
||||
|
||||
interface FunctionSpec {
|
||||
module: string;
|
||||
|
@ -125,12 +127,28 @@ export class ZoneImpl implements zone.Zone {
|
|||
|
||||
if (typeof arg1 === 'function') {
|
||||
moduleName = "__function";
|
||||
if (arg1.origin == null) {
|
||||
// We get caller stack at index 2.
|
||||
// <caller> -> execute -> createExecuteRequest
|
||||
// 2 1 0
|
||||
arg1.origin = v8.currentStack(3)[2].getFileName();
|
||||
}
|
||||
|
||||
functionName = transport.saveFunction(arg1);
|
||||
args = arg2;
|
||||
options = arg3;
|
||||
}
|
||||
else {
|
||||
moduleName = arg1;
|
||||
// If module name is relative path, try to deduce from call site.
|
||||
if (moduleName != null
|
||||
&& moduleName.length != 0
|
||||
&& !path.isAbsolute(moduleName)) {
|
||||
|
||||
moduleName = path.resolve(
|
||||
path.dirname(v8.currentStack(3)[2].getFileName()),
|
||||
moduleName);
|
||||
}
|
||||
functionName = arg2;
|
||||
args = arg3;
|
||||
options = arg4;
|
||||
|
|
|
@ -14,7 +14,7 @@ using namespace napa::module;
|
|||
BinaryModuleLoader::BinaryModuleLoader(BuiltInModulesSetter builtInModulesSetter)
|
||||
: _builtInModulesSetter(std::move(builtInModulesSetter)) {}
|
||||
|
||||
bool BinaryModuleLoader::TryGet(const std::string& path, v8::Local<v8::Object>& module) {
|
||||
bool BinaryModuleLoader::TryGet(const std::string& path, v8::Local<v8::Value> /*arg*/, v8::Local<v8::Object>& module) {
|
||||
auto isolate = v8::Isolate::GetCurrent();
|
||||
v8::EscapableHandleScope scope(isolate);
|
||||
|
||||
|
|
|
@ -23,9 +23,10 @@ namespace module {
|
|||
|
||||
/// <summary> It loads a module from binary file. </summary>
|
||||
/// <param name="path"> Module path called by require(). </param>
|
||||
/// <param name="arg"> Argument for loading the file. Passed through as arg1 from require. </param>
|
||||
/// <param name="module"> Loaded binary module if successful. </param>
|
||||
/// <returns> True if binary module is loaded, false otherwise. </returns>
|
||||
bool TryGet(const std::string& path, v8::Local<v8::Object>& module) override;
|
||||
bool TryGet(const std::string& path, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) override;
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ CoreModuleLoader::CoreModuleLoader(BuiltInModulesSetter builtInModulesSetter,
|
|||
ModuleCache& bindingCache)
|
||||
: JavascriptModuleLoader(std::move(builtInModulesSetter), moduleCache), _bindingCache(bindingCache) {}
|
||||
|
||||
bool CoreModuleLoader::TryGet(const std::string& name, v8::Local<v8::Object>& module) {
|
||||
bool CoreModuleLoader::TryGet(const std::string& name, v8::Local<v8::Value> /*arg*/, v8::Local<v8::Object>& module) {
|
||||
filesystem::Path basePath(module_loader_helpers::GetNapaRuntimeDirectory());
|
||||
auto fileName = name + CORE_MODULE_EXTENSION;
|
||||
|
||||
|
@ -33,13 +33,13 @@ bool CoreModuleLoader::TryGet(const std::string& name, v8::Local<v8::Object>& mo
|
|||
auto fullPath = basePath / CORE_MODULE_DIRECTORY / fileName;
|
||||
if (filesystem::IsRegularFile(fullPath)) {
|
||||
// Load javascript core module from a file at ./lib directory.
|
||||
return JavascriptModuleLoader::TryGet(fullPath.String(), module);
|
||||
return JavascriptModuleLoader::TryGet(fullPath.String(), v8::Local<v8::Value>(), module);
|
||||
}
|
||||
|
||||
fullPath = (basePath.Parent() / CORE_MODULE_DIRECTORY / fileName).Normalize();
|
||||
if (filesystem::IsRegularFile(fullPath)) {
|
||||
// Load javascript core module from a file at ../lib directory.
|
||||
return JavascriptModuleLoader::TryGet(fullPath.String(), module);
|
||||
return JavascriptModuleLoader::TryGet(fullPath.String(), v8::Local<v8::Value>(), module);
|
||||
}
|
||||
|
||||
// Return binary core module if exists.
|
||||
|
|
|
@ -24,9 +24,10 @@ namespace module {
|
|||
|
||||
/// <summary> It loads a core module. </summary>
|
||||
/// <param name="name"> Core module name. </param>
|
||||
/// <param name="arg"> Argument for loading the file. Passed through as arg1 from require. </param>
|
||||
/// <param name="module"> Loaded core module if successful. </param>
|
||||
/// <returns> True if core module is loaded, false otherwise. </returns>
|
||||
bool TryGet(const std::string& name, v8::Local<v8::Object>& module) override;
|
||||
bool TryGet(const std::string& name, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) override;
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -13,12 +13,20 @@ using namespace napa::module;
|
|||
JavascriptModuleLoader::JavascriptModuleLoader(BuiltInModulesSetter builtInModulesSetter, ModuleCache& moduleCache)
|
||||
: _builtInModulesSetter(std::move(builtInModulesSetter)) , _moduleCache(moduleCache) {}
|
||||
|
||||
bool JavascriptModuleLoader::TryGet(const std::string& path, v8::Local<v8::Object>& module) {
|
||||
bool JavascriptModuleLoader::TryGet(const std::string& path, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) {
|
||||
auto isolate = v8::Isolate::GetCurrent();
|
||||
v8::EscapableHandleScope scope(isolate);
|
||||
|
||||
auto source = module_loader_helpers::ReadModuleFile(path);
|
||||
JS_ENSURE_WITH_RETURN(isolate, !source.IsEmpty(), false, "Can't read Javascript module: \"%s\"", path.c_str());
|
||||
bool fromContent = !arg.IsEmpty();
|
||||
v8::Local<v8::String> source;
|
||||
|
||||
if (!fromContent) {
|
||||
source = module_loader_helpers::ReadModuleFile(path);
|
||||
JS_ENSURE_WITH_RETURN(isolate, !source.IsEmpty(), false, "Can't read Javascript module: \"%s\"", path.c_str());
|
||||
} else {
|
||||
JS_ENSURE_WITH_RETURN(isolate, arg->IsString(), false, "The 2nd argument of 'require' must be content of string type.");
|
||||
source = v8::Local<v8::String>::Cast(arg);
|
||||
}
|
||||
|
||||
auto context = isolate->GetCurrentContext();
|
||||
|
||||
|
@ -34,7 +42,9 @@ bool JavascriptModuleLoader::TryGet(const std::string& path, v8::Local<v8::Objec
|
|||
_builtInModulesSetter(moduleContext);
|
||||
|
||||
// To prevent cycle, cache unloaded module first.
|
||||
_moduleCache.Upsert(path, module_loader_helpers::ExportModule(moduleContext->Global(), nullptr));
|
||||
if (!fromContent) {
|
||||
_moduleCache.Upsert(path, module_loader_helpers::ExportModule(moduleContext->Global(), nullptr));
|
||||
}
|
||||
|
||||
v8::TryCatch tryCatch;
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace module {
|
|||
// forward declaration.
|
||||
class ModuleCache;
|
||||
|
||||
/// <summary> It loads a module from javascript file. </summary>
|
||||
/// <summary> It loads a module from javascript file or content from arg. </summary>
|
||||
class JavascriptModuleLoader : public ModuleFileLoader {
|
||||
public:
|
||||
|
||||
|
@ -24,9 +24,10 @@ namespace module {
|
|||
|
||||
/// <summary> It loads a module from javascript file. </summary>
|
||||
/// <param name="path"> Module path called by require(). </param>
|
||||
/// <param name="arg"> Argument for loading the file. Passed through as arg1 from require. </param>
|
||||
/// <param name="module"> Loaded javascript module if successful. </param>
|
||||
/// <returns> True if the javascript module is loaded, false otherwise. </returns>
|
||||
bool TryGet(const std::string& path, v8::Local<v8::Object>& module) override;
|
||||
bool TryGet(const std::string& path, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) override;
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
using namespace napa;
|
||||
using namespace napa::module;
|
||||
|
||||
bool JsonModuleLoader::TryGet(const std::string& path, v8::Local<v8::Object>& module) {
|
||||
bool JsonModuleLoader::TryGet(const std::string& path, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) {
|
||||
auto isolate = v8::Isolate::GetCurrent();
|
||||
v8::EscapableHandleScope scope(isolate);
|
||||
|
||||
|
|
|
@ -16,9 +16,10 @@ namespace module {
|
|||
|
||||
/// <summary> It loads an object from json file. </summary>
|
||||
/// <param name="path"> Module path called by require(). </param>
|
||||
/// <param name="arg"> Argument for loading the file. Passed through as arg1 from require. </param>
|
||||
/// <param name="module"> Loaded object if successful. </param>
|
||||
/// <returns> True if the object is loaded, false otherwise. </returns>
|
||||
bool TryGet(const std::string& path, v8::Local<v8::Object>& module) override;
|
||||
bool TryGet(const std::string& path, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) override;
|
||||
};
|
||||
|
||||
} // End of namespace module.
|
||||
|
|
|
@ -21,10 +21,10 @@ namespace module {
|
|||
|
||||
/// <summary> It loads a module from file. </summary>
|
||||
/// <param name="path"> Module path called by require(). </param>
|
||||
/// <param name="arg"> Optional argument for loading module file. Passed through as arg1 from require. </param>
|
||||
/// <param name="module"> Loaded module if successful. </param>
|
||||
/// <returns> True if the module was loaded, false otherwise. </returns>
|
||||
virtual bool TryGet(const std::string& path, v8::Local<v8::Object>& module) = 0;
|
||||
virtual bool TryGet(const std::string& path, v8::Local<v8::Value> arg, v8::Local<v8::Object>& module) = 0;
|
||||
};
|
||||
|
||||
} // End of namespace module.
|
||||
} // End of namespace napa.
|
|
@ -12,6 +12,7 @@
|
|||
#include "module-resolver.h"
|
||||
|
||||
#include <module/core-modules/core-modules.h>
|
||||
#include <platform/filesystem.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
// TODO: decouple dependencies between module-loader and zone.
|
||||
|
@ -175,7 +176,7 @@ void ModuleLoader::ModuleLoaderImpl::Bootstrap() {
|
|||
// Override built-in modules with javascript file if exists.
|
||||
for (const auto& name : _builtInNames) {
|
||||
v8::Local<v8::Object> module;
|
||||
if (coreModuleLoader->TryGet(name, module)) {
|
||||
if (coreModuleLoader->TryGet(name, v8::Local<v8::Value>(), module)) {
|
||||
// If javascript core module exists, replace the existing one.
|
||||
_moduleCache.Upsert(name, module);
|
||||
(void)context->Global()->Set(context,
|
||||
|
@ -213,8 +214,10 @@ void ModuleLoader::ModuleLoaderImpl::ResolveCallback(const v8::FunctionCallbackI
|
|||
v8::String::Utf8Value path(args[0]);
|
||||
auto contextDir = module_loader_helpers::GetCurrentContextDirectory();
|
||||
|
||||
auto resolvedPath = moduleLoader->_impl->_resolver.Resolve(*path, contextDir.c_str());
|
||||
args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, resolvedPath.fullPath));
|
||||
auto moduleInfo = moduleLoader->_impl->_resolver.Resolve(*path, contextDir.c_str());
|
||||
JS_ENSURE(isolate, moduleInfo.type != ModuleType::NONE, "Cannot find module \"%s\"", *path);
|
||||
|
||||
args.GetReturnValue().Set(v8_helpers::MakeV8String(isolate, moduleInfo.fullPath));
|
||||
}
|
||||
|
||||
void ModuleLoader::ModuleLoaderImpl::BindingCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
|
@ -247,6 +250,10 @@ void ModuleLoader::ModuleLoaderImpl::RequireModule(const char* path, const v8::F
|
|||
auto isolate = v8::Isolate::GetCurrent();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
// Set optional argument for module file loader.
|
||||
auto arg = args.Length() == 1 ? v8::Local<v8::Value>() : args[1];
|
||||
bool fromContent = !arg.IsEmpty() && arg->IsString();
|
||||
|
||||
// If require is called with a module receiver, use module.filename to deduce context directory.
|
||||
std::string contextDir;
|
||||
if (!args.Holder().IsEmpty()) {
|
||||
|
@ -257,34 +264,48 @@ void ModuleLoader::ModuleLoaderImpl::RequireModule(const char* path, const v8::F
|
|||
contextDir = module_loader_helpers::GetCurrentContextDirectory();
|
||||
}
|
||||
|
||||
auto moduleInfo = _resolver.Resolve(path, contextDir.c_str());
|
||||
if (moduleInfo.type == ModuleType::NONE) {
|
||||
NAPA_DEBUG("ModuleLoader", "Cannot resolve module path \"%s\".", path);
|
||||
|
||||
args.GetReturnValue().SetUndefined();
|
||||
return;
|
||||
ModuleInfo moduleInfo;
|
||||
if (fromContent) {
|
||||
moduleInfo = ModuleInfo {
|
||||
ModuleType::JAVASCRIPT,
|
||||
(filesystem::Path(contextDir) / path).Normalize().String(), // Module id
|
||||
"" // No package.json
|
||||
};
|
||||
} else {
|
||||
moduleInfo = _resolver.Resolve(path, contextDir.c_str());
|
||||
if (moduleInfo.type == ModuleType::NONE) {
|
||||
NAPA_DEBUG("ModuleLoader", "Cannot resolve module path \"%s\".", path);
|
||||
args.GetReturnValue().SetUndefined();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> module;
|
||||
if (_moduleCache.TryGet(moduleInfo.fullPath, module)) {
|
||||
NAPA_DEBUG("ModuleLoader", "Retrieved module from cache: \"%s\".", path);
|
||||
args.GetReturnValue().Set(module);
|
||||
return;
|
||||
|
||||
// Module from content script is not cached.
|
||||
if (!fromContent) {
|
||||
if (_moduleCache.TryGet(moduleInfo.fullPath, module)) {
|
||||
NAPA_DEBUG("ModuleLoader", "Retrieved module from cache: \"%s\".", path);
|
||||
args.GetReturnValue().Set(module);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto& loader = _loaders[static_cast<size_t>(moduleInfo.type)];
|
||||
JS_ENSURE(isolate, loader != nullptr, "No proper module loader is defined");
|
||||
|
||||
auto succeeded = loader->TryGet(moduleInfo.fullPath, module);
|
||||
auto succeeded = loader->TryGet(moduleInfo.fullPath, arg, module);
|
||||
if (!succeeded) {
|
||||
NAPA_DEBUG("ModuleLoader", "Cannot load module \"%s\".", path);
|
||||
|
||||
args.GetReturnValue().SetUndefined();
|
||||
return;
|
||||
}
|
||||
|
||||
NAPA_DEBUG("ModuleLoader", "Loaded module from file (first time): \"%s\".", path);
|
||||
_moduleCache.Upsert(moduleInfo.fullPath, module);
|
||||
|
||||
if (!fromContent) {
|
||||
_moduleCache.Upsert(moduleInfo.fullPath, module);
|
||||
}
|
||||
args.GetReturnValue().Set(module);
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ ModuleInfo ModuleResolver::ModuleResolverImpl::Resolve(const char* name, const c
|
|||
// Normalize module context path.
|
||||
filesystem::Path basePath =
|
||||
(path == nullptr) ? filesystem::CurrentDirectory() : filesystem::Path(path);
|
||||
|
||||
|
||||
// Look up from the given path.
|
||||
RETURN_IF_NOT_EMPTY(ResolveFromPath(name, basePath));
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ import * as napa from "../lib/index";
|
|||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
let NAPA_ZONE_TEST_MODULE = path.resolve(__dirname, 'napa-zone/test');
|
||||
|
||||
describe('napajs/memory', function() {
|
||||
this.timeout(0);
|
||||
let napaZone = napa.zone.create('zone5');
|
||||
|
@ -35,7 +33,7 @@ describe('napajs/memory', function() {
|
|||
});
|
||||
|
||||
it('@napa: crtAllocator', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "crtAllocatorTest");
|
||||
napaZone.execute('./napa-zone/test', "crtAllocatorTest");
|
||||
});
|
||||
|
||||
it('@node: defaultAllocator', () => {
|
||||
|
@ -45,7 +43,7 @@ describe('napajs/memory', function() {
|
|||
});
|
||||
|
||||
it('@napa: defaultAllocator', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "defaultAllocatorTest");
|
||||
napaZone.execute('./napa-zone/test', "defaultAllocatorTest");
|
||||
});
|
||||
|
||||
it('@node: debugAllocator', () => {
|
||||
|
@ -63,7 +61,7 @@ describe('napajs/memory', function() {
|
|||
});
|
||||
|
||||
it('@napa: debugAllocator', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "debugAllocatorTest");
|
||||
napaZone.execute('./napa-zone/test', "debugAllocatorTest");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -13,40 +13,54 @@ describe('napajs/module', function () {
|
|||
|
||||
describe('load', function () {
|
||||
it('javascript module', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var jsmodule = require(thisFilePath + '/module/jsmodule');
|
||||
var jsmodule = require('./module/jsmodule');
|
||||
|
||||
assert.notEqual(jsmodule, undefined);
|
||||
assert.equal(jsmodule.wasLoaded, true);
|
||||
}, [__dirname]);
|
||||
});
|
||||
});
|
||||
|
||||
it('javascript module from string', () => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var path = require('path');
|
||||
|
||||
var jsmodule = (<any>require)(
|
||||
'./module/jsmodule-from-string',
|
||||
"module.exports = function() { return __filename;}");
|
||||
|
||||
assert.notEqual(jsmodule, undefined);
|
||||
assert.equal(jsmodule(), path.resolve(__dirname, 'module/jsmodule-from-string'));
|
||||
});
|
||||
});
|
||||
|
||||
it('json module', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var jsonModule = require(thisFilePath + '/module/test.json');
|
||||
var jsonModule = require('./module/test.json');
|
||||
|
||||
assert.notEqual(jsonModule, undefined);
|
||||
assert.equal(jsonModule.prop1, "val1");
|
||||
assert.equal(jsonModule.prop2, "val2");
|
||||
}, [__dirname]);
|
||||
});
|
||||
});
|
||||
|
||||
it('napa module', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var napaModule = require(thisFilePath + '/module/addon/build/simple-napa-addon.napa');
|
||||
var napaModule = require('./module/addon/build/simple-napa-addon.napa');
|
||||
|
||||
assert.notEqual(napaModule, undefined);
|
||||
assert.equal(napaModule.getModuleName(), "simple-napa-addon");
|
||||
}, [__dirname]);
|
||||
});
|
||||
});
|
||||
|
||||
it('object wrap module', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var napaModule = require(thisFilePath + '/module/addon/build/simple-napa-addon.napa');
|
||||
var napaModule = require('./module/addon/build/simple-napa-addon.napa');
|
||||
|
||||
var obj = napaModule.createSimpleObjectWrap();
|
||||
assert.notEqual(obj, undefined);
|
||||
|
@ -56,11 +70,11 @@ describe('napajs/module', function () {
|
|||
});
|
||||
|
||||
it('circular dependencies', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
|
||||
var cycle_a = require(thisFilePath + '/module/cycle-a.js');
|
||||
var cycle_b = require(thisFilePath + '/module/cycle-b.js');
|
||||
var cycle_a = require('./module/cycle-a.js');
|
||||
var cycle_b = require('./module/cycle-b.js');
|
||||
|
||||
assert(cycle_a.done);
|
||||
assert(cycle_b.done);
|
||||
|
@ -71,7 +85,7 @@ describe('napajs/module', function () {
|
|||
describe('resolve', function () {
|
||||
// TODO: support correct __dirname in anonymous function and move tests from 'resolution-tests.js' here.
|
||||
it('require.resolve', () => {
|
||||
return napaZone.execute(__dirname + "/module/resolution-tests.js", "run");
|
||||
return napaZone.execute("./module/resolution-tests.js", "run");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -148,34 +162,34 @@ describe('napajs/module', function () {
|
|||
|
||||
describe('fs', function () {
|
||||
it('existsSync', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var fs = require('fs');
|
||||
|
||||
assert(fs.existsSync(thisFilePath + '/module/jsmodule.js'));
|
||||
assert.ifError(fs.existsSync('non-existing-file.txt'));
|
||||
}, [__dirname]);
|
||||
assert(fs.existsSync(__dirname + '/module/jsmodule.js'));
|
||||
assert.ifError(fs.existsSync(__dirname + '/non-existing-file.txt'));
|
||||
});
|
||||
});
|
||||
|
||||
it('readFileSync', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var fs = require('fs');
|
||||
|
||||
var content = JSON.parse(fs.readFileSync(thisFilePath + '/module/test.json'));
|
||||
var content = JSON.parse(fs.readFileSync(__dirname + '/module/test.json'));
|
||||
assert.equal(content.prop1, 'val1');
|
||||
assert.equal(content.prop2, 'val2');
|
||||
}, [__dirname]);
|
||||
});
|
||||
});
|
||||
|
||||
it('mkdirSync', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var fs = require('fs');
|
||||
|
||||
fs.mkdirSync(thisFilePath + '/module/test-dir');
|
||||
assert(fs.existsSync(thisFilePath + '/module/test-dir'));
|
||||
}, [__dirname]).then(()=> {
|
||||
fs.mkdirSync(__dirname + '/module/test-dir');
|
||||
assert(fs.existsSync(__dirname + '/module/test-dir'));
|
||||
}).then(()=> {
|
||||
// Cleanup
|
||||
var fs = require('fs');
|
||||
if (fs.existsSync('./module/test-dir')) {
|
||||
|
@ -185,12 +199,12 @@ describe('napajs/module', function () {
|
|||
});
|
||||
|
||||
it('writeFileSync', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var fs = require('fs');
|
||||
|
||||
fs.writeFileSync(thisFilePath + '/module/test-file', 'test');
|
||||
assert.equal(fs.readFileSync(thisFilePath + '/module/test-file'), 'test');
|
||||
fs.writeFileSync(__dirname + '/module/test-file', 'test');
|
||||
assert.equal(fs.readFileSync(__dirname + '/module/test-file'), 'test');
|
||||
}, [__dirname]).then(()=> {
|
||||
// Cleanup
|
||||
var fs = require('fs');
|
||||
|
@ -201,17 +215,17 @@ describe('napajs/module', function () {
|
|||
});
|
||||
|
||||
it('readFileSync', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var fs = require('fs');
|
||||
|
||||
var testDir = thisFilePath + '/module/test-dir';
|
||||
var testDir = __dirname + '/module/test-dir';
|
||||
fs.mkdirSync(testDir);
|
||||
fs.writeFileSync(testDir + '/1', 'test');
|
||||
fs.writeFileSync(testDir + '/2', 'test');
|
||||
|
||||
assert.deepEqual(fs.readdirSync(testDir).sort(), ['1', '2']);
|
||||
}, [__dirname]).then(()=> {
|
||||
}).then(()=> {
|
||||
// Cleanup
|
||||
var fs = require('fs');
|
||||
if (fs.existsSync('./module/test-dir')) {
|
||||
|
@ -395,9 +409,9 @@ describe('napajs/module', function () {
|
|||
|
||||
describe('async', function () {
|
||||
it('post async work', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var napaModule = require(thisFilePath + '/module/addon/build/simple-napa-addon.napa');
|
||||
var napaModule = require('./module/addon/build/simple-napa-addon.napa');
|
||||
|
||||
var obj = napaModule.createSimpleObjectWrap();
|
||||
obj.setValue(3);
|
||||
|
@ -412,15 +426,15 @@ describe('napajs/module', function () {
|
|||
assert.equal(obj.getValue(), 3);
|
||||
|
||||
return promise;
|
||||
}, [__dirname]).then((result: napa.zone.Result) => {
|
||||
}).then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 4);
|
||||
});
|
||||
});
|
||||
|
||||
it('do async work', () => {
|
||||
return napaZone.execute((thisFilePath: string) => {
|
||||
return napaZone.execute(() => {
|
||||
var assert = require("assert");
|
||||
var napaModule = require(thisFilePath + '/module/addon/build/simple-napa-addon.napa');
|
||||
var napaModule = require('./module/addon/build/simple-napa-addon.napa');
|
||||
|
||||
var obj = napaModule.createSimpleObjectWrap();
|
||||
obj.setValue(8);
|
||||
|
@ -435,7 +449,7 @@ describe('napajs/module', function () {
|
|||
assert.equal(obj.getValue(), 9);
|
||||
|
||||
return promise;
|
||||
}, [__dirname]).then((result: napa.zone.Result) => {
|
||||
}).then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 9);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,11 +31,10 @@ function run() {
|
|||
// From node_modules without extension
|
||||
assert(require.resolve('file'), __dirname + '/node_modules/file.js');
|
||||
|
||||
// TODO: Fix this issue: require.resolve should throw an exception if file doesn't exist.
|
||||
// Resolving non-existing file should throw
|
||||
// assert.throws(() => {
|
||||
// require.resolve('./sub-folder/non-existing-file.js');
|
||||
// });
|
||||
assert.throws(() => {
|
||||
require.resolve('./sub-folder/non-existing-file.js');
|
||||
});
|
||||
}
|
||||
|
||||
exports.run = run;
|
||||
|
|
|
@ -5,8 +5,6 @@ import * as napa from "../lib/index";
|
|||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
let NAPA_ZONE_TEST_MODULE = path.resolve(__dirname, 'napa-zone/test');
|
||||
|
||||
describe('napajs/store', function () {
|
||||
this.timeout(0);
|
||||
|
||||
|
@ -32,7 +30,7 @@ describe('napajs/store', function () {
|
|||
let store2CreationComplete: Promise<napa.zone.Result>;
|
||||
|
||||
it('@napa: store.getOrCreate', () => {
|
||||
store2CreationComplete = napaZone.execute(NAPA_ZONE_TEST_MODULE, "getOrCreateStoreTest");
|
||||
store2CreationComplete = napaZone.execute('./napa-zone/test', "getOrCreateStoreTest");
|
||||
});
|
||||
|
||||
it('@node: store.get', async () => {
|
||||
|
@ -46,7 +44,7 @@ describe('napajs/store', function () {
|
|||
});
|
||||
|
||||
it('@napa: store.get', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "getStoreTest");
|
||||
napaZone.execute('./napa-zone/test', "getStoreTest");
|
||||
});
|
||||
|
||||
it('simple types: set in node, get in node', () => {
|
||||
|
@ -56,16 +54,16 @@ describe('napajs/store', function () {
|
|||
|
||||
it('simple types: set in node, get in napa', () => {
|
||||
store1.set('b', 'hi');
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyGet", ['store1', 'b', 'hi']);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'b', 'hi']);
|
||||
});
|
||||
|
||||
it('simple types: set in napa, get in napa', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeSet", ['store1', 'c', 1]);
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyGet", ['store1', 'c', 1]);
|
||||
napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'c', 1]);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'c', 1]);
|
||||
});
|
||||
|
||||
it('simple types: set in napa, get in node', async () => {
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeSet", ['store1', 'd', { a: 1, b: 1}]);
|
||||
await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'd', { a: 1, b: 1}]);
|
||||
assert.deepEqual(store1.get('d'), {
|
||||
a: 1,
|
||||
b: 1
|
||||
|
@ -79,18 +77,18 @@ describe('napajs/store', function () {
|
|||
|
||||
it('transportable types: set in node, get in napa', () => {
|
||||
store1.set('b', napa.memory.defaultAllocator);
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyGet", ['store1', 'b', napa.memory.defaultAllocator]);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'b', napa.memory.defaultAllocator]);
|
||||
});
|
||||
|
||||
it('transportable types: set in napa, get in napa', async () => {
|
||||
// We have to compare handle in this case, since napa.memory.defaultAllocator retrieved from napa zone will have 2+ refCount.
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeSet", ['store1', 'e', napa.memory.defaultAllocator]);
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeGetCompareHandle", ['store1', 'e', napa.memory.defaultAllocator.handle]);
|
||||
await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'e', napa.memory.defaultAllocator]);
|
||||
napaZone.execute('./napa-zone/test', "storeGetCompareHandle", ['store1', 'e', napa.memory.defaultAllocator.handle]);
|
||||
});
|
||||
|
||||
it('transportable types: set in napa, get in node', async () => {
|
||||
let debugAllocator = napa.memory.debugAllocator(napa.memory.defaultAllocator);
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeSet", ['store1', 'f', debugAllocator]);
|
||||
await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'f', debugAllocator]);
|
||||
assert.deepEqual(store1.get('f'), debugAllocator);
|
||||
});
|
||||
|
||||
|
@ -101,16 +99,16 @@ describe('napajs/store', function () {
|
|||
|
||||
it('function type: set in node, get in napa', () => {
|
||||
store1.set('h', () => { return 0; });
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyGet", ['store1', 'h', () => { return 0; }]);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'h', () => { return 0; }]);
|
||||
});
|
||||
|
||||
it('function type: set in napa, get in napa', async () => {
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeSet", ['store1', 'i', () => { return 0; }]);
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyGet", ['store1', 'i', () => { return 0; }]);
|
||||
await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'i', () => { return 0; }]);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyGet", ['store1', 'i', () => { return 0; }]);
|
||||
});
|
||||
|
||||
it('function type: set in napa, get in node', async () => {
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeSet", ['store1', 'j', () => { return 0; }]);
|
||||
await napaZone.execute('./napa-zone/test', "storeSet", ['store1', 'j', () => { return 0; }]);
|
||||
assert.deepEqual(store1.get('j').toString(), (() => { return 0; }).toString());
|
||||
});
|
||||
|
||||
|
@ -125,16 +123,16 @@ describe('napajs/store', function () {
|
|||
it('delete in node, check in napa', () => {
|
||||
assert(store1.has('b'));
|
||||
store1.delete('b');
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyNotExist", ['store1', 'b']);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyNotExist", ['store1', 'b']);
|
||||
});
|
||||
|
||||
it('delete in napa, check in napa', async () => {
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeDelete", ['store1', 'c']);
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeVerifyNotExist", ['store1', 'c']);
|
||||
await napaZone.execute('./napa-zone/test', "storeDelete", ['store1', 'c']);
|
||||
napaZone.execute('./napa-zone/test', "storeVerifyNotExist", ['store1', 'c']);
|
||||
})
|
||||
|
||||
it('delete in napa, check in node', async () => {
|
||||
await napaZone.execute(NAPA_ZONE_TEST_MODULE, "storeDelete", ['store1', 'd']);
|
||||
await napaZone.execute('./napa-zone/test', "storeDelete", ['store1', 'd']);
|
||||
assert(!store1.has('d'));
|
||||
assert(store1.get('d') === undefined);
|
||||
});
|
||||
|
|
|
@ -6,8 +6,6 @@ import * as assert from 'assert';
|
|||
import * as path from 'path';
|
||||
import * as t from './napa-zone/test';
|
||||
|
||||
let NAPA_ZONE_TEST_MODULE = path.resolve(__dirname, 'napa-zone/test');
|
||||
|
||||
describe('napajs/transport', () => {
|
||||
let napaZone = napa.zone.create('zone10');
|
||||
describe('TransportContext', () => {
|
||||
|
@ -50,7 +48,7 @@ describe('napajs/transport', () => {
|
|||
});
|
||||
|
||||
it('@napa: simple types', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "simpleTypeTransportTest");
|
||||
napaZone.execute('./napa-zone/test', "simpleTypeTransportTest");
|
||||
}).timeout(3000);
|
||||
|
||||
it('@node: JS transportable', () => {
|
||||
|
@ -58,7 +56,7 @@ describe('napajs/transport', () => {
|
|||
});
|
||||
|
||||
it('@napa: JS transportable', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "jsTransportTest");
|
||||
napaZone.execute('./napa-zone/test', "jsTransportTest");
|
||||
});
|
||||
|
||||
it('@node: addon transportable', () => {
|
||||
|
@ -66,7 +64,7 @@ describe('napajs/transport', () => {
|
|||
});
|
||||
|
||||
it('@napa: addon transportable', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "addonTransportTest");
|
||||
napaZone.execute('./napa-zone/test', "addonTransportTest");
|
||||
});
|
||||
|
||||
it('@node: function transportable', () => {
|
||||
|
@ -74,7 +72,7 @@ describe('napajs/transport', () => {
|
|||
});
|
||||
|
||||
it('@napa: function transportable', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "functionTransportTest");
|
||||
napaZone.execute('./napa-zone/test', "functionTransportTest");
|
||||
});
|
||||
|
||||
it('@node: composite transportable', () => {
|
||||
|
@ -82,7 +80,7 @@ describe('napajs/transport', () => {
|
|||
});
|
||||
|
||||
it('@napa: composite transportable', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "compositeTransportTest");
|
||||
napaZone.execute('./napa-zone/test', "compositeTransportTest");
|
||||
});
|
||||
|
||||
it('@node: non-transportable', () => {
|
||||
|
@ -90,7 +88,7 @@ describe('napajs/transport', () => {
|
|||
});
|
||||
|
||||
it('@napa: non-transportable', () => {
|
||||
napaZone.execute(NAPA_ZONE_TEST_MODULE, "nontransportableTest");
|
||||
napaZone.execute('./napa-zone/test', "nontransportableTest");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -25,8 +25,7 @@ describe('napajs/zone', function () {
|
|||
let napaZone1: Zone = napa.zone.create('napa-zone1');
|
||||
let napaZone2: Zone = napa.zone.create('napa-zone2');
|
||||
let napaLibPath: string = path.resolve(__dirname, '../lib');
|
||||
let napaZoneTestModule: string = path.resolve(__dirname, 'napa-zone/test');
|
||||
|
||||
|
||||
describe('create', () => {
|
||||
it('@node: default settings', () => {
|
||||
assert(napaZone1 != null);
|
||||
|
@ -101,7 +100,7 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa', async () => {
|
||||
let result = await napaZone1.execute(napaZoneTestModule, "getCurrentZone");
|
||||
let result = await napaZone1.execute('./napa-zone/test', "getCurrentZone");
|
||||
assert.strictEqual(result.value.id, 'napa-zone1');
|
||||
});
|
||||
});
|
||||
|
@ -116,15 +115,15 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa: -> napa zone with JavaScript code', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcast", ["napa-zone2", "var state = 0;"]);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone2", "var state = 0;"]);
|
||||
});
|
||||
|
||||
it('@napa: -> napa zone with JavaScript code', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcast", ["napa-zone1", "var state = 0;"]);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone1", "var state = 0;"]);
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with JavaScript code', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcast", ["node", "var state = 0;"]);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcast", ["node", "var state = 0;"]);
|
||||
});
|
||||
|
||||
it('@node: bad JavaScript code', () => {
|
||||
|
@ -135,7 +134,7 @@ describe('napajs/zone', function () {
|
|||
|
||||
it('@napa: bad JavaScript code', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcast", ["napa-zone2", "var state() = 0;"]);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone2", "var state() = 0;"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -153,13 +152,13 @@ describe('napajs/zone', function () {
|
|||
|
||||
it('@napa: -> napa zone throw runtime error', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcast", ["napa-zone2", "throw new Error();"]);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcast", ["napa-zone2", "throw new Error();"]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone throw runtime error', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcast", ["node", "throw new Error();"]);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcast", ["node", "throw new Error();"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -176,11 +175,11 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa: -> napa zone with anonymous function', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcastTestFunction", ['napa-zone2']);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcastTestFunction", ['napa-zone2']);
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with anonymous function', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcastTestFunction", ['node']);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcastTestFunction", ['node']);
|
||||
});
|
||||
|
||||
// TODO #4: support transportable args in broadcast.
|
||||
|
@ -199,12 +198,12 @@ describe('napajs/zone', function () {
|
|||
|
||||
// Blocked by TODO #4.
|
||||
it.skip('@napa: -> napa zone with transportable args', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcastTransportable", ['napa-zone2']);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcastTransportable", ['napa-zone2']);
|
||||
});
|
||||
|
||||
// Blocked by TODO #4.
|
||||
it.skip('@napa: -> node zone with transportable args', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcastTransportable", ['node']);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcastTransportable", ['node']);
|
||||
});
|
||||
|
||||
it('@node: -> node zone with anonymous function having closure (should fail)', () => {
|
||||
|
@ -225,13 +224,13 @@ describe('napajs/zone', function () {
|
|||
|
||||
it('@napa: -> napa zone with anonymous function having closure (should fail)', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcastClosure", ['napa-zone2']);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcastClosure", ['napa-zone2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with anonymous function having closure (should fail)', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, "broadcastClosure", ['node']);
|
||||
return napaZone1.execute('./napa-zone/test', "broadcastClosure", ['node']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -270,14 +269,14 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa: -> napa zone with global function name', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["napa-zone2", "", "foo", ['hello world']])
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", "", "foo", ['hello world']])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with global function name', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["node", "", "foo", ['hello world']])
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["node", "", "foo", ['hello world']])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
|
@ -304,46 +303,46 @@ describe('napajs/zone', function () {
|
|||
|
||||
it('@napa: -> napa zone with global function name not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["napa-zone2", "", "foo1", []]);
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", "", "foo1", []]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with global function name not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["node", "", "foo1", []]);
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["node", "", "foo1", []]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@node: -> node zone with module function name', () => {
|
||||
return napa.zone.current.execute(napaZoneTestModule, "bar", ['hello world'])
|
||||
return napa.zone.current.execute('./napa-zone/test', "bar", ['hello world'])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('@node: -> napa zone with module function name', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "bar", ['hello world'])
|
||||
return napaZone1.execute('./napa-zone/test', "bar", ['hello world'])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> napa zone with module function name', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["napa-zone2", napaZoneTestModule, "bar", ['hello world']])
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", path.resolve(__dirname, './napa-zone/test'), "bar", ['hello world']])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with module function name', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["node", napaZoneTestModule, "bar", ['hello world']])
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["node", path.resolve(__dirname, './napa-zone/test'), "bar", ['hello world']])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('@node: -> napa zone with module function name: function with namespaces', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "ns1.ns2.foo", ['hello world'])
|
||||
return napaZone1.execute('./napa-zone/test', "ns1.ns2.foo", ['hello world'])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
|
@ -370,37 +369,37 @@ describe('napajs/zone', function () {
|
|||
|
||||
it('@napa: -> napa zone with module not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["napa-zone2", "abc", ".foo", []]);
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone2", "abc", ".foo", []]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with module not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["node", "abc", "foo.", []]);
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["node", "abc", "foo.", []]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@node: -> node zone with module function not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napa.zone.current.execute(napaZoneTestModule, "foo1", ['hello world']);
|
||||
return napa.zone.current.execute('./napa-zone/test', "foo1", ['hello world']);
|
||||
});
|
||||
});
|
||||
|
||||
it('@node: -> napa zone with module function not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, "foo1", ['hello world'])
|
||||
return napaZone1.execute('./napa-zone/test', "foo1", ['hello world'])
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> napa zone with module function not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["napa-zone1", napaZoneTestModule, "foo1", []]);
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["napa-zone1", './napa-zone/test', "foo1", []]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with module function not exists', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'execute', ["node", napaZoneTestModule, "foo1", []]);
|
||||
return napaZone1.execute('./napa-zone/test', 'execute', ["node", './napa-zone/test', "foo1", []]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -423,14 +422,14 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa: -> napa zone with anonymous function', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'executeTestFunction', ["napa-zone2"])
|
||||
return napaZone1.execute('./napa-zone/test', 'executeTestFunction', ["napa-zone2"])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with anonymous function', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'executeTestFunction', ["node"])
|
||||
return napaZone1.execute('./napa-zone/test', 'executeTestFunction', ["node"])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.equal(result.value, 'hello world');
|
||||
});
|
||||
|
@ -448,13 +447,13 @@ describe('napajs/zone', function () {
|
|||
|
||||
it('@napa: -> napa zone with anonymous function having closure (should fail)', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'executeTestFunctionWithClosure', ["napa-zone2"]);
|
||||
return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithClosure', ["napa-zone2"]);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with anonymous function having closure (should fail)', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'executeTestFunctionWithClosure', ["node"]);
|
||||
return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithClosure', ["node"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -473,11 +472,11 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa: -> napa zone with transportable args', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "executeWithTransportableArgs", ['napa-zone2']);
|
||||
return napaZone1.execute('./napa-zone/test', "executeWithTransportableArgs", ['napa-zone2']);
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with transportable args', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "executeWithTransportableArgs", ['node']);
|
||||
return napaZone1.execute('./napa-zone/test', "executeWithTransportableArgs", ['node']);
|
||||
});
|
||||
|
||||
it('@node: -> node zone with transportable returns', () => {
|
||||
|
@ -499,36 +498,36 @@ describe('napajs/zone', function () {
|
|||
});
|
||||
|
||||
it('@napa: -> napa zone with transportable returns', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "executeWithTransportableReturns", ['napa-zone2'])
|
||||
return napaZone1.execute('./napa-zone/test', "executeWithTransportableReturns", ['napa-zone2'])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.deepEqual(result.value.handle, napa.memory.crtAllocator.handle);
|
||||
});
|
||||
});
|
||||
|
||||
it('@napa: -> node zone with transportable returns', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, "executeWithTransportableReturns", ['node'])
|
||||
return napaZone1.execute('./napa-zone/test', "executeWithTransportableReturns", ['node'])
|
||||
.then((result: napa.zone.Result) => {
|
||||
assert.deepEqual(result.value.handle, napa.memory.crtAllocator.handle);
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('@node: -> napa zone with timeout and succeed', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'waitMS', [1], {timeout: 100});
|
||||
return napaZone1.execute('./napa-zone/test', 'waitMS', [1], {timeout: 100});
|
||||
});
|
||||
|
||||
it.skip('@napa: -> napa zone with timeout and succeed', () => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'executeTestFunctionWithTimeout', ["napa-zone2", 1], {timeout: 100});
|
||||
return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithTimeout', ["napa-zone2", 1], {timeout: 100});
|
||||
});
|
||||
|
||||
it.skip('@node: -> napa zone with timed out in JavaScript', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'waitMS', [100], {timeout: 1});
|
||||
return napaZone1.execute('./napa-zone/test', 'waitMS', [100], {timeout: 1});
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('@napa: -> napa zone with timed out in JavaScript', () => {
|
||||
return shouldFail(() => {
|
||||
return napaZone1.execute(napaZoneTestModule, 'executeTestFunctionWithTimeout', ["napa-zone2", 100], {timeout: 1});
|
||||
return napaZone1.execute('./napa-zone/test', 'executeTestFunctionWithTimeout', ["napa-zone2", 100], {timeout: 1});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче