1
0
Fork 0
mirror of https://github.com/rubyjs/mini_racer synced 2023-03-27 23:21:28 -04:00

Merge pull request #88 from cataphract/call-ret-improvs

Improvements on returning values
This commit is contained in:
Sam 2018-05-16 10:32:01 +10:00 committed by GitHub
commit 4c104f6a91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 308 additions and 285 deletions

View file

@ -120,7 +120,7 @@ typedef struct {
bool error;
Local<Function> fun;
Local<Value> *argv;
Local<Value> result;
EvalResult result;
} FunctionCall;
enum IsolateFlags {
@ -130,6 +130,7 @@ enum IsolateFlags {
MEM_SOFTLIMIT_REACHED,
};
static VALUE rb_cContext;
static VALUE rb_cSnapshot;
static VALUE rb_cIsolate;
@ -205,6 +206,85 @@ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
}
}
// to be called with active lock and scope
static void prepare_result(MaybeLocal<Value> v8res,
TryCatch& trycatch,
Isolate* isolate,
Local<Context> context,
EvalResult& evalRes /* out */) {
// just don't touch .parsed
evalRes.terminated = false;
evalRes.json = false;
evalRes.value = nullptr;
evalRes.message = nullptr;
evalRes.backtrace = nullptr;
evalRes.executed = !v8res.IsEmpty();
if (evalRes.executed) {
// arrays and objects get converted to json
Local<Value> local_value = v8res.ToLocalChecked();
if ((local_value->IsObject() || local_value->IsArray()) &&
!local_value->IsDate() && !local_value->IsFunction()) {
Local<Object> JSON = context->Global()->Get(
String::NewFromUtf8(isolate, "JSON"))->ToObject();
Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
.As<Function>();
Local<Object> object = local_value->ToObject();
const unsigned argc = 1;
Local<Value> argv[argc] = { object };
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
if (json.IsEmpty()) {
evalRes.executed = false;
} else {
evalRes.json = true;
Persistent<Value>* persistent = new Persistent<Value>();
persistent->Reset(isolate, json.ToLocalChecked());
evalRes.value = persistent;
}
} else {
Persistent<Value>* persistent = new Persistent<Value>();
persistent->Reset(isolate, local_value);
evalRes.value = persistent;
}
}
if (!evalRes.executed || !evalRes.parsed) {
if (trycatch.HasCaught()) {
if (!trycatch.Exception()->IsNull()) {
evalRes.message = new Persistent<Value>();
Local<Message> message = trycatch.Message();
char buf[1000];
int len;
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
*String::Utf8Value(message->GetScriptResourceName()->ToString()),
message->GetLineNumber(),
message->GetStartColumn());
if ((size_t) len >= sizeof(buf)) {
len = sizeof(buf) - 1;
buf[len] = '\0';
}
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
evalRes.message->Reset(isolate, v8_message);
} else if(trycatch.HasTerminated()) {
evalRes.terminated = true;
evalRes.message = new Persistent<Value>();
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
evalRes.message->Reset(isolate, tmp);
}
if (!trycatch.StackTrace().IsEmpty()) {
evalRes.backtrace = new Persistent<Value>();
evalRes.backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
}
}
}
}
void*
nogvl_context_eval(void* arg) {
@ -246,90 +326,30 @@ nogvl_context_eval(void* arg) {
result->json = false;
result->value = NULL;
MaybeLocal<Value> maybe_value;
if (!result->parsed) {
result->message = new Persistent<Value>();
result->message->Reset(isolate, trycatch.Exception());
} else {
// parsing successful
if (eval_params->max_memory > 0) {
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
isolate->AddGCEpilogueCallback(gc_callback);
}
if(eval_params->max_memory > 0) {
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
isolate->AddGCEpilogueCallback(gc_callback);
maybe_value = parsed_script.ToLocalChecked()->Run(context);
}
MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
result->executed = !maybe_value.IsEmpty();
if (result->executed) {
// arrays and objects get converted to json
Local<Value> local_value = maybe_value.ToLocalChecked();
if ((local_value->IsObject() || local_value->IsArray()) &&
!local_value->IsDate() && !local_value->IsFunction()) {
Local<Object> JSON = context->Global()->Get(
String::NewFromUtf8(isolate, "JSON"))->ToObject();
Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
.As<Function>();
Local<Object> object = local_value->ToObject();
const unsigned argc = 1;
Local<Value> argv[argc] = { object };
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
if (json.IsEmpty()) {
result->executed = false;
} else {
result->json = true;
Persistent<Value>* persistent = new Persistent<Value>();
persistent->Reset(isolate, json.ToLocalChecked());
result->value = persistent;
}
} else {
Persistent<Value>* persistent = new Persistent<Value>();
persistent->Reset(isolate, local_value);
result->value = persistent;
}
}
}
if (!result->executed || !result->parsed) {
if (trycatch.HasCaught()) {
if (!trycatch.Exception()->IsNull()) {
result->message = new Persistent<Value>();
Local<Message> message = trycatch.Message();
char buf[1000];
int len;
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
*String::Utf8Value(message->GetScriptResourceName()->ToString()),
message->GetLineNumber(),
message->GetStartColumn());
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
result->message->Reset(isolate, v8_message);
} else if(trycatch.HasTerminated()) {
result->terminated = true;
result->message = new Persistent<Value>();
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
result->message->Reset(isolate, tmp);
}
if (!trycatch.StackTrace().IsEmpty()) {
result->backtrace = new Persistent<Value>();
result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
}
}
}
prepare_result(maybe_value, trycatch, isolate, context, *result);
isolate->SetData(IN_GVL, (void*)true);
return NULL;
}
static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
// assumes isolate locking is in place
static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
Local<Value> value) {
Isolate::Scope isolate_scope(isolate);
HandleScope scope(isolate);
@ -359,7 +379,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
Local<Array> arr = Local<Array>::Cast(value);
for(uint32_t i=0; i < arr->Length(); i++) {
Local<Value> element = arr->Get(i);
VALUE rb_elem = convert_v8_to_ruby(isolate, element);
VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
return rb_elem;
}
@ -383,16 +403,15 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
if (value->IsObject()) {
VALUE rb_hash = rb_hash_new();
TryCatch trycatch(isolate);
Local<Context> context = Context::New(isolate);
Local<Object> object = value->ToObject();
MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
auto maybe_props = object->GetOwnPropertyNames(context);
if (!maybe_props.IsEmpty()) {
Local<Array> props = maybe_props.ToLocalChecked();
for(uint32_t i=0; i < props->Length(); i++) {
Local<Value> key = props->Get(i);
VALUE rb_key = convert_v8_to_ruby(isolate, key);
Local<Value> value = object->Get(key);
VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
Local<Value> prop_value = object->Get(key);
// this may have failed due to Get raising
if (trycatch.HasCaught()) {
@ -401,7 +420,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
}
VALUE rb_value = convert_v8_to_ruby(isolate, value);
VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
rb_hash_aset(rb_hash, rb_key, rb_value);
}
}
@ -412,7 +431,25 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
}
static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
static VALUE convert_v8_to_ruby(Isolate* isolate,
const Persistent<Context>& context,
Local<Value> value) {
HandleScope scope(isolate);
return convert_v8_to_ruby(isolate,
Local<Context>::New(isolate, context),
value);
}
static VALUE convert_v8_to_ruby(Isolate* isolate,
const Persistent<Context>& context,
const Persistent<Value>& value) {
HandleScope scope(isolate);
return convert_v8_to_ruby(isolate,
Local<Context>::New(isolate, context),
Local<Value>::New(isolate, value));
}
static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
EscapableHandleScope scope(isolate);
Local<Array> array;
@ -640,15 +677,103 @@ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
return Qnil;
}
static VALUE convert_result_to_ruby(VALUE self /* context */,
EvalResult& result) {
ContextInfo *context_info;
Data_Get_Struct(self, ContextInfo, context_info);
Isolate *isolate = context_info->isolate_info->isolate;
Persistent<Context> *p_ctx = context_info->context;
VALUE message = Qnil;
VALUE backtrace = Qnil;
{
Locker lock(isolate);
if (result.message) {
message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
result.message->Reset();
delete result.message;
result.message = nullptr;
}
if (result.backtrace) {
backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
result.backtrace->Reset();
delete result.backtrace;
}
}
// NOTE: this is very important, we can not do an rb_raise from within
// a v8 scope, if we do the scope is never cleaned up properly and we leak
if (!result.parsed) {
if(TYPE(message) == T_STRING) {
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
} else {
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
}
}
if (!result.executed) {
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
if (ruby_exception == Qnil) {
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
// If we were terminated or have the memory softlimit flag set
if (result.terminated || mem_softlimit_reached) {
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
} else {
ruby_exception = rb_eScriptRuntimeError;
}
// exception report about what happened
if (TYPE(backtrace) == T_STRING) {
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
} else if(TYPE(message) == T_STRING) {
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
} else {
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
}
} else {
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
}
}
VALUE ret = Qnil;
// New scope for return value
{
Locker lock(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Value> tmp = Local<Value>::New(isolate, *result.value);
if (result.json) {
Local<String> rstr = tmp->ToString();
VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
} else {
ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
}
result.value->Reset();
delete result.value;
}
if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
// TODO try to recover stack trace from the conversion error
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
}
return ret;
}
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
EvalParams eval_params;
EvalResult eval_result;
ContextInfo* context_info;
VALUE result;
VALUE message = Qnil;
VALUE backtrace = Qnil;
Data_Get_Struct(self, ContextInfo, context_info);
Isolate* isolate = context_info->isolate_info->isolate;
@ -699,84 +824,9 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
eval_result.backtrace = NULL;
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
if (eval_result.message != NULL) {
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
message = convert_v8_to_ruby(isolate, tmp);
eval_result.message->Reset();
delete eval_result.message;
}
if (eval_result.backtrace != NULL) {
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
backtrace = convert_v8_to_ruby(isolate, tmp);
eval_result.backtrace->Reset();
delete eval_result.backtrace;
}
}
// NOTE: this is very important, we can not do an rb_raise from within
// a v8 scope, if we do the scope is never cleaned up properly and we leak
if (!eval_result.parsed) {
if(TYPE(message) == T_STRING) {
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
} else {
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
}
}
if (!eval_result.executed) {
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
if (ruby_exception == Qnil) {
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
// If we were terminated or have the memory softlimit flag set
if(eval_result.terminated || mem_softlimit_reached) {
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
} else {
ruby_exception = rb_eScriptRuntimeError;
}
// exception report about what happened
if(TYPE(backtrace) == T_STRING) {
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
} else if(TYPE(message) == T_STRING) {
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
} else {
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
}
} else {
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
}
}
// New scope for return value
{
Locker lock(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
if (eval_result.json) {
Local<String> rstr = tmp->ToString();
VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
result = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
} else {
result = convert_v8_to_ruby(isolate, tmp);
}
eval_result.value->Reset();
delete eval_result.value;
}
if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
// TODO try to recover stack trace from the conversion error
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
}
return result;
return convert_result_to_ruby(self, eval_result);
}
typedef struct {
@ -815,23 +865,30 @@ gvl_ruby_callback(void* data) {
VALUE callback;
VALUE result;
VALUE self;
VALUE parent;
{
HandleScope scope(args->GetIsolate());
Handle<External> external = Handle<External>::Cast(args->Data());
HandleScope scope(args->GetIsolate());
Local<External> external = Local<External>::Cast(args->Data());
VALUE* self_pointer = (VALUE*)(external->Value());
self = *self_pointer;
callback = rb_iv_get(self, "@callback");
self = *(VALUE*)(external->Value());
callback = rb_iv_get(self, "@callback");
parent = rb_iv_get(self, "@parent");
if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
return NULL;
}
ContextInfo* context_info;
Data_Get_Struct(parent, ContextInfo, context_info);
if (length > 0) {
ruby_args = ALLOC_N(VALUE, length);
}
for (int i = 0; i < length; i++) {
Local<Value> value = ((*args)[i]).As<Value>();
ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(),
*context_info->context, value);
}
}
@ -852,7 +909,6 @@ gvl_ruby_callback(void* data) {
(VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
if(callback_data.failed) {
VALUE parent = rb_iv_get(self, "@parent");
rb_iv_set(parent, "@current_exception", result);
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
}
@ -1164,14 +1220,11 @@ nogvl_context_call(void *args) {
Local<Function> fun = call->fun;
EvalResult& eval_res = call->result;
eval_res.parsed = true;
MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
if (res.IsEmpty()) {
// A better error handling should be added, factoring out exception management
// code from nogvl_context_eval
call->error = true;
} else {
call->result = handle_scope.Escape(res.ToLocalChecked());
}
prepare_result(res, trycatch, isolate, context, eval_res);
isolate->SetData(IN_GVL, (void*)true);
@ -1188,7 +1241,6 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
ContextInfo* context_info;
FunctionCall call;
VALUE res = Qnil;
VALUE *call_argv = NULL;
Data_Get_Struct(self, ContextInfo, context_info);
@ -1245,16 +1297,9 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
free(call.argv);
}
if (!call.error) {
res = convert_v8_to_ruby(isolate, call.result);
}
}
if (call.error) {
// TODO - better handling of exceptions
rb_raise(rb_eScriptRuntimeError, "Error calling %s", call.function_name);
}
return res;
return convert_result_to_ruby(self, call.result);
}
static VALUE rb_context_create_isolate_value(VALUE self) {
@ -1275,7 +1320,7 @@ extern "C" {
void Init_mini_racer_extension ( void )
{
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
@ -1303,13 +1348,12 @@ extern "C" {
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
rb_define_alloc_func(rb_cContext, allocate);
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);

View file

@ -2,108 +2,87 @@ require 'test_helper'
require 'timeout'
class MiniRacerFunctionTest < Minitest::Test
def test_fun
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
assert_equal context.eval('f(10)'), 'I need 10 foos'
def test_fun
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
assert_equal context.eval('f(10)'), "I need 10 foos"
assert_raises(ArgumentError) do
context.call
end
assert_raises(ArgumentError) do
context.call
count = 4
res = context.call('f', count)
assert_equal "I need #{count} foos", res
end
def test_non_existing_function
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' galettes' }")
# f is defined, let's call g
assert_raises(MiniRacer::RuntimeError) do
context.call('g')
end
end
def test_throwing_function
context = MiniRacer::Context.new
context.eval('function f(x) { throw new Error("foo bar") }')
# f is defined, let's call g
err = assert_raises(MiniRacer::RuntimeError) do
context.call('f', 1)
end
assert_equal err.message, 'Error: foo bar'
assert_match /1:23/, err.backtrace[0]
end
def test_args_types
context = MiniRacer::Context.new
context.eval("function f(x, y) { return 'I need ' + x + ' ' + y }")
res = context.call('f', 3, 'bars')
assert_equal 'I need 3 bars', res
res = context.call('f', { a: 1 }, 'bars')
assert_equal 'I need [object Object] bars', res
res = context.call('f', [1, 2, 3], 'bars')
assert_equal 'I need 1,2,3 bars', res
end
def test_complex_return
context = MiniRacer::Context.new
context.eval('function f(x, y) { return { vx: x, vy: y, array: [x, y] } }')
h = { 'vx' => 3, 'vy' => 'bars', 'array' => [3, 'bars'] }
res = context.call('f', 3, 'bars')
assert_equal h, res
end
def test_do_not_hang_with_concurrent_calls
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
thread_count = 2
threads = []
thread_count.times do
threads << Thread.new do
10.times do |i|
context.call('f', i)
end
count = 4
res = context.call('f', count)
assert_equal "I need #{count} foos", res
end
end
def test_non_existing_function
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
# f is defined, let's call g
assert_raises(MiniRacer::RuntimeError) do
context.call('g')
end
joined_thread_count = 0
for t in threads do
joined_thread_count += 1
t.join
end
def test_throwing_function
context = MiniRacer::Context.new
context.eval("function f(x) { throw new Error() }")
# f should throw
assert_raises(MiniRacer::RuntimeError) do
context.call('f', 1)
end
end
def test_non_existing_function
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' galettes' }")
# f is defined, let's call g
assert_raises(MiniRacer::RuntimeError) do
context.call('g')
end
end
def test_throwing_function
context = MiniRacer::Context.new
context.eval("function f(x) { throw new Error() }")
# f is defined, let's call g
assert_raises(MiniRacer::RuntimeError) do
context.call('f', 1)
end
end
def test_args_types
context = MiniRacer::Context.new
context.eval("function f(x, y) { return 'I need ' + x + ' ' + y }")
res = context.call('f', 3, "bars")
assert_equal "I need 3 bars", res
res = context.call('f', {a: 1}, "bars")
assert_equal "I need [object Object] bars", res
res = context.call('f', [1,2,3], "bars")
assert_equal "I need 1,2,3 bars", res
end
def test_complex_return
context = MiniRacer::Context.new
context.eval("function f(x, y) { return { vx: x, vy: y, array: [x, y] } }")
h = { "vx" => 3, "vy" => "bars", "array" => [3, "bars"] }
res = context.call('f', 3, "bars")
assert_equal h, res
end
def test_do_not_hang_with_concurrent_calls
context = MiniRacer::Context.new
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
thread_count = 2
threads = []
thread_count.times do
threads << Thread.new do
10.times do |i|
context.call("f", i)
end
end
end
joined_thread_count = 0
for t in threads do
joined_thread_count += 1
t.join
end
# Dummy test, completing should be enough to show we don't hang
self.assert_equal thread_count, joined_thread_count
end
# Dummy test, completing should be enough to show we don't hang
assert_equal thread_count, joined_thread_count
end
end