diff options
author | Kevin (Kun) "Kassimo" Qian <kevinkassimo@gmail.com> | 2019-02-09 13:55:40 -0800 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2019-02-09 16:55:40 -0500 |
commit | 1d36eb47eb882cb9305a6338019fa2a2b375d7b1 (patch) | |
tree | 4584aff9da784f3e05c4fb5314d5911772b4d655 | |
parent | 1502051453bf16787a59f43004b24d553d39bd26 (diff) |
Support scoped variables, unblock REPL async op, and REPL error colors (#1721)
-rw-r--r-- | BUILD.gn | 1 | ||||
-rw-r--r-- | js/console.ts | 5 | ||||
-rw-r--r-- | js/console_test.ts | 5 | ||||
-rw-r--r-- | js/format_error.ts | 21 | ||||
-rw-r--r-- | js/libdeno.ts | 47 | ||||
-rw-r--r-- | js/repl.ts | 19 | ||||
-rw-r--r-- | libdeno/api.cc | 1 | ||||
-rw-r--r-- | libdeno/binding.cc | 96 | ||||
-rw-r--r-- | libdeno/exceptions.cc | 28 | ||||
-rw-r--r-- | libdeno/exceptions.h | 7 | ||||
-rw-r--r-- | libdeno/internal.h | 7 | ||||
-rw-r--r-- | libdeno/libdeno_test.cc | 14 | ||||
-rw-r--r-- | libdeno/libdeno_test.js | 47 | ||||
-rw-r--r-- | src/libdeno.rs | 1 | ||||
-rw-r--r-- | src/msg.fbs | 10 | ||||
-rw-r--r-- | src/ops.rs | 40 | ||||
-rw-r--r-- | src/resources.rs | 13 | ||||
-rw-r--r-- | tools/repl_test.py | 23 |
18 files changed, 330 insertions, 55 deletions
@@ -68,6 +68,7 @@ ts_sources = [ "js/event.ts", "js/event_target.ts", "js/fetch.ts", + "js/format_error.ts", "js/dom_file.ts", "js/file_info.ts", "js/files.ts", diff --git a/js/console.ts b/js/console.ts index 0797ab8e9..e5dafceee 100644 --- a/js/console.ts +++ b/js/console.ts @@ -3,6 +3,8 @@ import { isTypedArray } from "./util"; import { TextEncoder } from "./text_encoding"; import { File, stdout } from "./files"; import { cliTable } from "./console_table"; +import { formatError } from "./format_error"; +import { libdeno } from "./libdeno"; // tslint:disable-next-line:no-any type ConsoleContext = Set<any>; @@ -263,7 +265,8 @@ function createObjectString( ...args: [ConsoleContext, number, number] ): string { if (value instanceof Error) { - return value.stack! || ""; + const errorJSON = libdeno.errorToJSON(value); + return formatError(errorJSON); } else if (Array.isArray(value)) { return createArrayString(value, ...args); } else if (value instanceof Number) { diff --git a/js/console_test.ts b/js/console_test.ts index 577c8bf5d..f84dc247a 100644 --- a/js/console_test.ts +++ b/js/console_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import { Console, libdeno, stringifyArgs, inspect, write, stdout } from "deno"; -import { test, assertEqual } from "./test_util.ts"; +import { test, assertEqual, assert } from "./test_util.ts"; const console = new Console(libdeno.print); @@ -245,7 +245,8 @@ test(function consoleTestError() { try { throw new MyError("This is an error"); } catch (e) { - assertEqual(stringify(e).split("\n")[0], "MyError: This is an error"); + assert(stringify(e).split("\n")[3] + .includes("MyError: This is an error")); } }); diff --git a/js/format_error.ts b/js/format_error.ts new file mode 100644 index 000000000..ebc579355 --- /dev/null +++ b/js/format_error.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import * as msg from "gen/msg_generated"; +import * as flatbuffers from "./flatbuffers"; +import { sendSync } from "./dispatch"; +import { assert } from "./util"; + +export function formatError(errString: string): string { + const builder = flatbuffers.createBuilder(); + const errString_ = builder.createString(errString); + msg.FormatError.startFormatError(builder); + msg.FormatError.addError(builder, errString_); + const offset = msg.FormatError.endFormatError(builder); + const baseRes = sendSync(builder, msg.Any.FormatError, offset); + assert(baseRes != null); + assert(msg.Any.FormatErrorRes === baseRes!.innerType()); + const formatErrorResMsg = new msg.FormatErrorRes(); + assert(baseRes!.inner(formatErrorResMsg) != null); + const formattedError = formatErrorResMsg.error(); + assert(formatError != null); + return formattedError!; +} diff --git a/js/libdeno.ts b/js/libdeno.ts index 401ffae40..7d2bc3ede 100644 --- a/js/libdeno.ts +++ b/js/libdeno.ts @@ -3,11 +3,17 @@ import { globalEval } from "./global_eval"; // The libdeno functions are moved so that users can't access them. type MessageCallback = (msg: Uint8Array) => void; -export type PromiseRejectEvent = - | "RejectWithNoHandler" - | "HandlerAddedAfterReject" - | "ResolveAfterResolved" - | "RejectAfterResolved"; + +interface EvalErrorInfo { + // Is the object thrown a native Error? + isNativeError: boolean; + // Was the error happened during compilation? + isCompileError: boolean; + // The actual thrown entity + // (might be an Error or anything else thrown by the user) + // If isNativeError is true, this is an Error + thrown: any; // tslint:disable-line:no-any +} interface Libdeno { recv(cb: MessageCallback): void; @@ -20,26 +26,17 @@ interface Libdeno { builtinModules: { [s: string]: object }; - setGlobalErrorHandler: ( - handler: ( - message: string, - source: string, - line: number, - col: number, - error: Error - ) => void - ) => void; - - setPromiseRejectHandler: ( - handler: ( - error: Error | string, - event: PromiseRejectEvent, - /* tslint:disable-next-line:no-any */ - promise: Promise<any> - ) => void - ) => void; - - setPromiseErrorExaminer: (handler: () => boolean) => void; + /** Evaluate provided code in the current context. + * It differs from eval(...) in that it does not create a new context. + * Returns an array: [output, errInfo]. + * If an error occurs, `output` becomes null and `errInfo` is non-null. + */ + evalContext( + code: string + ): [any, EvalErrorInfo | null] /* tslint:disable-line:no-any */; + + // tslint:disable-next-line:no-any + errorToJSON: (e: Error) => string; } const window = globalEval("this"); diff --git a/js/repl.ts b/js/repl.ts index 6fb395e1b..6676721fc 100644 --- a/js/repl.ts +++ b/js/repl.ts @@ -7,6 +7,8 @@ import { close } from "./files"; import * as dispatch from "./dispatch"; import { exit } from "./os"; import { globalEval } from "./global_eval"; +import { libdeno } from "./libdeno"; +import { formatError } from "./format_error"; const window = globalEval("this"); @@ -96,14 +98,19 @@ export async function replLoop(): Promise<void> { } function evaluate(code: string): void { - try { - const result = eval.call(window, code); // FIXME use a new scope. + if (code.trim() === "") { + return; + } + const [result, errInfo] = libdeno.evalContext(code); + if (!errInfo) { console.log(result); - } catch (err) { - if (err instanceof Error) { - console.error(`${err.constructor.name}: ${err.message}`); + } else { + if (errInfo.isNativeError) { + const formattedError = formatError( + libdeno.errorToJSON(errInfo.thrown as Error)); + console.error(formattedError); } else { - console.error("Thrown:", err); + console.error("Thrown:", errInfo.thrown); } } } diff --git a/libdeno/api.cc b/libdeno/api.cc index e540c95fc..21ece13a6 100644 --- a/libdeno/api.cc +++ b/libdeno/api.cc @@ -9,6 +9,7 @@ #include "third_party/v8/src/base/logging.h" #include "deno.h" +#include "exceptions.h" #include "internal.h" extern "C" { diff --git a/libdeno/binding.cc b/libdeno/binding.cc index 78e4cad29..f7ee977e8 100644 --- a/libdeno/binding.cc +++ b/libdeno/binding.cc @@ -120,6 +120,16 @@ void Print(const v8::FunctionCallbackInfo<v8::Value>& args) { fflush(file); } +void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args) { + CHECK_EQ(args.Length(), 1); + auto* isolate = args.GetIsolate(); + DenoIsolate* d = DenoIsolate::FromIsolate(isolate); + auto context = d->context_.Get(d->isolate_); + v8::HandleScope handle_scope(isolate); + auto json_string = EncodeExceptionAsJSON(context, args[0]); + args.GetReturnValue().Set(v8_str(json_string.c_str())); +} + v8::Local<v8::Uint8Array> ImportBuf(DenoIsolate* d, deno_buf buf) { if (buf.alloc_ptr == nullptr) { // If alloc_ptr isn't set, we memcpy. @@ -368,6 +378,80 @@ bool Execute(v8::Local<v8::Context> context, const char* js_filename, return true; } +static inline v8::Local<v8::Boolean> v8_bool(bool v) { + return v8::Boolean::New(v8::Isolate::GetCurrent(), v); +} + +void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Isolate* isolate = args.GetIsolate(); + DenoIsolate* d = DenoIsolate::FromIsolate(isolate); + v8::EscapableHandleScope handleScope(isolate); + auto context = d->context_.Get(isolate); + v8::Context::Scope context_scope(context); + + CHECK(args[0]->IsString()); + auto source = args[0].As<v8::String>(); + + auto output = v8::Array::New(isolate, 2); + /** + * output[0] = result + * output[1] = ErrorInfo | null + * ErrorInfo = { + * thrown: Error | any, + * isNativeError: boolean, + * isCompileError: boolean, + * } + */ + + v8::TryCatch try_catch(isolate); + + auto name = v8_str("<unknown>"); + v8::ScriptOrigin origin(name); + auto script = v8::Script::Compile(context, source, &origin); + + if (script.IsEmpty()) { + DCHECK(try_catch.HasCaught()); + auto exception = try_catch.Exception(); + + output->Set(0, v8::Null(isolate)); + + auto errinfo_obj = v8::Object::New(isolate); + errinfo_obj->Set(v8_str("isCompileError"), v8_bool(true)); + errinfo_obj->Set(v8_str("isNativeError"), + v8_bool(exception->IsNativeError())); + errinfo_obj->Set(v8_str("thrown"), exception); + + output->Set(1, errinfo_obj); + + args.GetReturnValue().Set(output); + return; + } + + auto result = script.ToLocalChecked()->Run(context); + + if (result.IsEmpty()) { + DCHECK(try_catch.HasCaught()); + auto exception = try_catch.Exception(); + + output->Set(0, v8::Null(isolate)); + + auto errinfo_obj = v8::Object::New(isolate); + errinfo_obj->Set(v8_str("isCompileError"), v8_bool(false)); + errinfo_obj->Set(v8_str("isNativeError"), + v8_bool(exception->IsNativeError())); + errinfo_obj->Set(v8_str("thrown"), exception); + + output->Set(1, errinfo_obj); + + args.GetReturnValue().Set(output); + return; + } + + output->Set(0, result.ToLocalChecked()); + output->Set(1, v8::Null(isolate)); + args.GetReturnValue().Set(output); +} + void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) { v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); @@ -389,6 +473,18 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) { auto send_val = send_tmpl->GetFunction(context).ToLocalChecked(); CHECK(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust()); + auto eval_context_tmpl = v8::FunctionTemplate::New(isolate, EvalContext); + auto eval_context_val = + eval_context_tmpl->GetFunction(context).ToLocalChecked(); + CHECK(deno_val->Set(context, deno::v8_str("evalContext"), eval_context_val) + .FromJust()); + + auto error_to_json_tmpl = v8::FunctionTemplate::New(isolate, ErrorToJSON); + auto error_to_json_val = + error_to_json_tmpl->GetFunction(context).ToLocalChecked(); + CHECK(deno_val->Set(context, deno::v8_str("errorToJSON"), error_to_json_val) + .FromJust()); + CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared) .FromJust()); diff --git a/libdeno/exceptions.cc b/libdeno/exceptions.cc index 0d7bbed8b..51f81bfce 100644 --- a/libdeno/exceptions.cc +++ b/libdeno/exceptions.cc @@ -4,10 +4,10 @@ namespace deno { -std::string EncodeMessageAsJSON(v8::Local<v8::Context> context, - v8::Local<v8::Message> message) { +v8::Local<v8::Object> EncodeMessageAsObject(v8::Local<v8::Context> context, + v8::Local<v8::Message> message) { auto* isolate = context->GetIsolate(); - v8::HandleScope handle_scope(isolate); + v8::EscapableHandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); auto stack_trace = message->GetStackTrace(); @@ -134,12 +134,33 @@ std::string EncodeMessageAsJSON(v8::Local<v8::Context> context, } CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust()); + json_obj = handle_scope.Escape(json_obj); + return json_obj; +} +std::string EncodeMessageAsJSON(v8::Local<v8::Context> context, + v8::Local<v8::Message> message) { + auto* isolate = context->GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context); + auto json_obj = EncodeMessageAsObject(context, message); auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked(); v8::String::Utf8Value json_string_(isolate, json_string); return std::string(ToCString(json_string_)); } +v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context, + v8::Local<v8::Value> exception) { + auto* isolate = context->GetIsolate(); + v8::EscapableHandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context); + + auto message = v8::Exception::CreateMessage(isolate, exception); + auto json_obj = EncodeMessageAsObject(context, message); + json_obj = handle_scope.Escape(json_obj); + return json_obj; +} + std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context, v8::Local<v8::Value> exception) { auto* isolate = context->GetIsolate(); @@ -167,5 +188,4 @@ void HandleExceptionMessage(v8::Local<v8::Context> context, CHECK_NOT_NULL(d); d->last_exception_ = json_str; } - } // namespace deno diff --git a/libdeno/exceptions.h b/libdeno/exceptions.h index 362bbc0e6..e07ff183a 100644 --- a/libdeno/exceptions.h +++ b/libdeno/exceptions.h @@ -2,10 +2,17 @@ #ifndef EXCEPTIONS_H_ #define EXCEPTIONS_H_ +#include <string> #include "third_party/v8/include/v8.h" namespace deno { +v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context, + v8::Local<v8::Value> exception); + +std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context, + v8::Local<v8::Value> exception); + void HandleException(v8::Local<v8::Context> context, v8::Local<v8::Value> exception); diff --git a/libdeno/internal.h b/libdeno/internal.h index 0cd50162c..a87ec0fdc 100644 --- a/libdeno/internal.h +++ b/libdeno/internal.h @@ -133,6 +133,8 @@ static inline v8::Local<v8::String> v8_str(const char* x) { void Print(const v8::FunctionCallbackInfo<v8::Value>& args); void Recv(const v8::FunctionCallbackInfo<v8::Value>& args); void Send(const v8::FunctionCallbackInfo<v8::Value>& args); +void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args); +void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args); void Shared(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info); void BuiltinModules(v8::Local<v8::Name> property, @@ -142,6 +144,8 @@ static intptr_t external_references[] = { reinterpret_cast<intptr_t>(Print), reinterpret_cast<intptr_t>(Recv), reinterpret_cast<intptr_t>(Send), + reinterpret_cast<intptr_t>(EvalContext), + reinterpret_cast<intptr_t>(ErrorToJSON), reinterpret_cast<intptr_t>(Shared), reinterpret_cast<intptr_t>(BuiltinModules), reinterpret_cast<intptr_t>(MessageCallback), @@ -153,9 +157,6 @@ Deno* NewFromSnapshot(void* user_data, deno_recv_cb cb); void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context); -void HandleException(v8::Local<v8::Context> context, - v8::Local<v8::Value> exception); - void DeserializeInternalFields(v8::Local<v8::Object> holder, int index, v8::StartupData payload, void* data); diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc index 0936c53b4..3193e7677 100644 --- a/libdeno/libdeno_test.cc +++ b/libdeno/libdeno_test.cc @@ -290,6 +290,20 @@ TEST(LibDenoTest, Utf8Bug) { deno_delete(d); } +TEST(LibDenoTest, LibDenoEvalContext) { + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + deno_execute(d, nullptr, "a.js", "LibDenoEvalContext();"); + EXPECT_EQ(nullptr, deno_last_exception(d)); + deno_delete(d); +} + +TEST(LibDenoTest, LibDenoEvalContextError) { + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + deno_execute(d, nullptr, "a.js", "LibDenoEvalContextError();"); + EXPECT_EQ(nullptr, deno_last_exception(d)); + deno_delete(d); +} + TEST(LibDenoTest, SharedAtomics) { int32_t s[] = {0, 1, 2}; deno_buf shared = {nullptr, 0, reinterpret_cast<uint8_t*>(s), sizeof s}; diff --git a/libdeno/libdeno_test.js b/libdeno/libdeno_test.js index 50f5c03d4..d6ea5f983 100644 --- a/libdeno/libdeno_test.js +++ b/libdeno/libdeno_test.js @@ -147,3 +147,50 @@ global.Shared = () => { ui8[1] = 43; ui8[2] = 44; }; + +global.LibDenoEvalContext = () => { + const [result, errInfo] = libdeno.evalContext("let a = 1; a"); + assert(result === 1); + assert(!errInfo); + const [result2, errInfo2] = libdeno.evalContext("a = a + 1; a"); + assert(result2 === 2); + assert(!errInfo2); +}; + +global.LibDenoEvalContextError = () => { + const [result, errInfo] = libdeno.evalContext("not_a_variable"); + assert(!result); + assert(!!errInfo); + assert(errInfo.isNativeError); // is a native error (ReferenceError) + assert(!errInfo.isCompileError); // is NOT a compilation error + assert(errInfo.thrown.message === "not_a_variable is not defined"); + + const [result2, errInfo2] = libdeno.evalContext("throw 1"); + assert(!result2); + assert(!!errInfo2); + assert(!errInfo2.isNativeError); // is NOT a native error + assert(!errInfo2.isCompileError); // is NOT a compilation error + assert(errInfo2.thrown === 1); + + const [result3, errInfo3] = + libdeno.evalContext("class AError extends Error {}; throw new AError('e')"); + assert(!result3); + assert(!!errInfo3); + assert(errInfo3.isNativeError); // extend from native error, still native error + assert(!errInfo3.isCompileError); // is NOT a compilation error + assert(errInfo3.thrown.message === "e"); + + const [result4, errInfo4] = libdeno.evalContext("{"); + assert(!result4); + assert(!!errInfo4); + assert(errInfo4.isNativeError); // is a native error (SyntaxError) + assert(errInfo4.isCompileError); // is a compilation error! (braces not closed) + assert(errInfo4.thrown.message === "Unexpected end of input"); + + const [result5, errInfo5] = libdeno.evalContext("eval('{')"); + assert(!result5); + assert(!!errInfo5); + assert(errInfo5.isNativeError); // is a native error (SyntaxError) + assert(!errInfo5.isCompileError); // is NOT a compilation error! (just eval) + assert(errInfo5.thrown.message === "Unexpected end of input"); +}; diff --git a/src/libdeno.rs b/src/libdeno.rs index ddf025aba..a3a711b65 100644 --- a/src/libdeno.rs +++ b/src/libdeno.rs @@ -178,5 +178,4 @@ extern "C" { user_data: *const c_void, id: deno_mod, ); - } diff --git a/src/msg.fbs b/src/msg.fbs index de69c31ad..7d8b89175 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -1,6 +1,8 @@ union Any { Start, StartRes, + FormatError, + FormatErrorRes, WorkerGetMessage, WorkerGetMessageRes, WorkerPostMessage, @@ -163,6 +165,14 @@ table StartRes { no_color: bool; } +table FormatError { + error: string; +} + +table FormatErrorRes { + error: string; +} + table WorkerGetMessage { unused: int8; } diff --git a/src/ops.rs b/src/ops.rs index 2b4136208..a0646e923 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -10,6 +10,7 @@ use crate::isolate::Buf; use crate::isolate::Isolate; use crate::isolate::IsolateState; use crate::isolate::Op; +use crate::js_errors::JSError; use crate::libdeno; use crate::msg; use crate::msg_util; @@ -97,6 +98,7 @@ pub fn dispatch( msg::Any::Environ => op_env, msg::Any::Exit => op_exit, msg::Any::Fetch => op_fetch, + msg::Any::FormatError => op_format_error, msg::Any::Listen => op_listen, msg::Any::MakeTempDir => op_make_temp_dir, msg::Any::Metrics => op_metrics, @@ -283,6 +285,41 @@ fn op_start( )) } +fn op_format_error( + state: &Arc<IsolateState>, + base: &msg::Base<'_>, + data: libdeno::deno_buf, +) -> Box<Op> { + assert_eq!(data.len(), 0); + let inner = base.inner_as_format_error().unwrap(); + let orig_error = String::from(inner.error().unwrap()); + + let js_error = JSError::from_v8_exception(&orig_error).unwrap(); + let js_error_mapped = js_error.apply_source_map(&state.dir); + let js_error_string = js_error_mapped.to_string(); + + let mut builder = FlatBufferBuilder::new(); + let new_error = builder.create_string(&js_error_string); + + let inner = msg::FormatErrorRes::create( + &mut builder, + &msg::FormatErrorResArgs { + error: Some(new_error), + ..Default::default() + }, + ); + + ok_future(serialize_response( + base.cmd_id(), + &mut builder, + msg::BaseArgs { + inner_type: msg::Any::FormatErrorRes, + inner: Some(inner.as_union_value()), + ..Default::default() + }, + )) +} + fn serialize_response( cmd_id: u32, builder: &mut FlatBufferBuilder<'_>, @@ -1271,7 +1308,8 @@ fn op_repl_readline( debug!("op_repl_readline {} {}", rid, prompt); blocking(base.sync(), move || -> OpResult { - let line = resources::readline(rid, &prompt)?; + let repl = resources::get_repl(rid)?; + let line = repl.lock().unwrap().readline(&prompt)?; let builder = &mut FlatBufferBuilder::new(); let line_off = builder.create_string(&line); diff --git a/src/resources.rs b/src/resources.rs index 1f5a121de..59167275b 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -35,7 +35,7 @@ use std::net::{Shutdown, SocketAddr}; use std::process::ExitStatus; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use tokio; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::net::TcpStream; @@ -95,7 +95,7 @@ enum Repr { TcpListener(tokio::net::TcpListener, Option<futures::task::Task>), TcpStream(tokio::net::TcpStream), HttpBody(HttpBody), - Repl(Repl), + Repl(Arc<Mutex<Repl>>), // Enum size is bounded by the largest variant. // Use `Box` around large `Child` struct. // https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant @@ -334,7 +334,7 @@ pub fn add_hyper_body(body: hyper::Body) -> Resource { pub fn add_repl(repl: Repl) -> Resource { let rid = new_rid(); let mut tg = RESOURCE_TABLE.lock().unwrap(); - let r = tg.insert(rid, Repr::Repl(repl)); + let r = tg.insert(rid, Repr::Repl(Arc::new(Mutex::new(repl)))); assert!(r.is_none()); Resource { rid } } @@ -462,14 +462,11 @@ pub fn child_status(rid: ResourceId) -> DenoResult<ChildStatus> { } } -pub fn readline(rid: ResourceId, prompt: &str) -> DenoResult<String> { +pub fn get_repl(rid: ResourceId) -> DenoResult<Arc<Mutex<Repl>>> { let mut table = RESOURCE_TABLE.lock().unwrap(); let maybe_repr = table.get_mut(&rid); match maybe_repr { - Some(Repr::Repl(ref mut r)) => { - let line = r.readline(&prompt)?; - Ok(line) - } + Some(Repr::Repl(ref mut r)) => Ok(r.clone()), _ => Err(bad_resource()), } } diff --git a/tools/repl_test.py b/tools/repl_test.py index 1b589de67..2ce692b99 100644 --- a/tools/repl_test.py +++ b/tools/repl_test.py @@ -19,7 +19,7 @@ class Repl(object): def input(self, *lines, **kwargs): exit_ = kwargs.pop("exit", True) sleep_ = kwargs.pop("sleep", 0) - p = Popen([self.deno_exe], stdout=PIPE, stderr=PIPE, stdin=PIPE) + p = Popen([self.deno_exe, "-A"], stdout=PIPE, stderr=PIPE, stdin=PIPE) try: # Note: The repl takes a >100ms until it's ready. time.sleep(sleep_) @@ -87,7 +87,7 @@ class Repl(object): def test_reference_error(self): out, err, code = self.input("not_a_variable") assertEqual(out, '') - assertEqual(err, 'ReferenceError: not_a_variable is not defined\n') + assert "not_a_variable is not defined" in err assertEqual(code, 0) def test_set_timeout(self): @@ -108,16 +108,25 @@ class Repl(object): assertEqual(err, '') assertEqual(code, 0) + def test_async_op(self): + out, err, code = self.input( + "fetch('http://localhost:4545/tests/001_hello.js')" + + ".then(res => res.text()).then(console.log)", + sleep=1) + assertEqual(out, 'Promise {}\nconsole.log("Hello World");\n\n') + assertEqual(err, '') + assertEqual(code, 0) + def test_syntax_error(self): out, err, code = self.input("syntax error") assertEqual(out, '') - assertEqual(err, "SyntaxError: Unexpected identifier\n") + assert "Unexpected identifier" in err assertEqual(code, 0) def test_type_error(self): out, err, code = self.input("console()") assertEqual(out, '') - assertEqual(err, 'TypeError: console is not a function\n') + assert "console is not a function" in err assertEqual(code, 0) def test_variable(self): @@ -126,6 +135,12 @@ class Repl(object): assertEqual(err, '') assertEqual(code, 0) + def test_lexical_scoped_variable(self): + out, err, code = self.input("let a = 123;", "a") + assertEqual(out, 'undefined\n123\n') + assertEqual(err, '') + assertEqual(code, 0) + def assertEqual(left, right): if left != right: |