From c870cf40823a4900278f8ddf03489338c169878b Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Tue, 15 Jan 2019 09:19:58 -0800 Subject: [PATCH] Add --prefetch flag for deps prefetch without running (#1475) --- libdeno/api.cc | 5 +++-- libdeno/binding.cc | 7 ++++++- libdeno/deno.h | 5 ++++- libdeno/internal.h | 2 +- libdeno/libdeno_test.cc | 32 +++++++++++++++++++++++++++----- src/flags.rs | 5 +++++ src/isolate.rs | 11 +++++++++-- src/libdeno.rs | 1 + src/main.rs | 4 +++- tools/integration_tests.py | 5 +---- tools/prefetch_test.py | 35 +++++++++++++++++++++++++++++++++++ tools/test.py | 3 +++ tools/util.py | 1 + 13 files changed, 99 insertions(+), 17 deletions(-) create mode 100755 tools/prefetch_test.py diff --git a/libdeno/api.cc b/libdeno/api.cc index 2c779fb6863377..5b61551189b121 100644 --- a/libdeno/api.cc +++ b/libdeno/api.cc @@ -127,7 +127,7 @@ int deno_execute(Deno* d_, void* user_data, const char* js_filename, } int deno_execute_mod(Deno* d_, void* user_data, const char* js_filename, - const char* js_source) { + const char* js_source, int resolve_only) { auto* d = unwrap(d_); deno::UserDataScope user_data_scope(d, user_data); auto* isolate = d->isolate_; @@ -136,7 +136,8 @@ int deno_execute_mod(Deno* d_, void* user_data, const char* js_filename, v8::HandleScope handle_scope(isolate); auto context = d->context_.Get(d->isolate_); CHECK(!context.IsEmpty()); - return deno::ExecuteMod(context, js_filename, js_source) ? 1 : 0; + return deno::ExecuteMod(context, js_filename, js_source, resolve_only) ? 1 + : 0; } int deno_respond(Deno* d_, void* user_data, int32_t req_id, deno_buf buf) { diff --git a/libdeno/binding.cc b/libdeno/binding.cc index a1b3f20f1662bd..d292f2638d85e6 100644 --- a/libdeno/binding.cc +++ b/libdeno/binding.cc @@ -590,7 +590,7 @@ void DenoIsolate::ResolveOk(const char* filename, const char* source) { } bool ExecuteMod(v8::Local context, const char* js_filename, - const char* js_source) { + const char* js_source, bool resolve_only) { auto* isolate = context->GetIsolate(); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); @@ -616,6 +616,11 @@ bool ExecuteMod(v8::Local context, const char* js_filename, } CHECK_EQ(v8::Module::kInstantiated, module->GetStatus()); + + if (resolve_only) { + return true; + } + auto result = module->Evaluate(context); if (result.IsEmpty()) { diff --git a/libdeno/deno.h b/libdeno/deno.h index a061f74520b6f9..d80043780544a6 100644 --- a/libdeno/deno.h +++ b/libdeno/deno.h @@ -75,8 +75,11 @@ int deno_execute(Deno* d, void* user_data, const char* js_filename, // when instantiating the Deno object. // Return value: 0 = fail, 1 = success // Get error text with deno_last_exception(). +// If resolve_only is 0, compile and evaluate the module. +// If resolve_only is 1, compile and collect dependencies of the module +// without running the code. int deno_execute_mod(Deno* d, void* user_data, const char* js_filename, - const char* js_source); + const char* js_source, int resolve_only); // deno_respond sends up to one message back for every deno_recv_cb made. // diff --git a/libdeno/internal.h b/libdeno/internal.h index ee783d99845c88..3019bc3648e52b 100644 --- a/libdeno/internal.h +++ b/libdeno/internal.h @@ -145,7 +145,7 @@ void DeleteDataRef(DenoIsolate* d, int32_t req_id); bool Execute(v8::Local context, const char* js_filename, const char* js_source); bool ExecuteMod(v8::Local context, const char* js_filename, - const char* js_source); + const char* js_source, bool resolve_only); } // namespace deno diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc index 9873987ea84c4d..78091be5977007 100644 --- a/libdeno/libdeno_test.cc +++ b/libdeno/libdeno_test.cc @@ -284,7 +284,7 @@ TEST(LibDenoTest, ModuleResolution) { deno_resolve_ok(d, "b.js", mod_b); }; Deno* d = deno_new(deno_config{0, empty, empty, nullptr, resolve_cb}); - EXPECT_TRUE(deno_execute_mod(d, d, "a.js", mod_a)); + EXPECT_TRUE(deno_execute_mod(d, d, "a.js", mod_a, false)); EXPECT_EQ(count, 1); deno_delete(d); } @@ -299,7 +299,7 @@ TEST(LibDenoTest, ModuleResolutionFail) { // Do not call deno_resolve_ok(); }; Deno* d = deno_new(deno_config{0, empty, empty, nullptr, resolve_cb}); - EXPECT_FALSE(deno_execute_mod(d, d, "a.js", mod_a)); + EXPECT_FALSE(deno_execute_mod(d, d, "a.js", mod_a, false)); EXPECT_EQ(count, 1); deno_delete(d); } @@ -309,7 +309,8 @@ TEST(LibDenoTest, ModuleSnapshot) { EXPECT_TRUE(deno_execute_mod(d1, nullptr, "x.js", "const globalEval = eval\n" "const global = globalEval('this')\n" - "global.a = 1 + 2")); + "global.a = 1 + 2", + 0)); deno_buf test_snapshot = deno_get_snapshot(d1); deno_delete(d1); @@ -321,12 +322,32 @@ TEST(LibDenoTest, ModuleSnapshot) { deno_delete(d2); Deno* d3 = deno_new(config); - EXPECT_TRUE(deno_execute_mod(d3, nullptr, "y.js", y_src)); + EXPECT_TRUE(deno_execute_mod(d3, nullptr, "y.js", y_src, false)); deno_delete(d3); delete[] test_snapshot.data_ptr; } +TEST(LibDenoTest, ModuleResolveOnly) { + static int count = 0; + auto resolve_cb = [](void* user_data, const char* specifier, + const char* referrer) { + EXPECT_STREQ(specifier, "b.js"); + EXPECT_STREQ(referrer, "a.js"); + count++; + auto d = reinterpret_cast(user_data); + deno_resolve_ok(d, "b.js", mod_b); + }; + Deno* d = deno_new(deno_config{0, empty, empty, nullptr, resolve_cb}); + // Code should not execute. If executed, the error would be thrown + EXPECT_TRUE(deno_execute_mod(d, d, "a.js", + "import { retb } from 'b.js'\n" + "throw Error('unreachable');", + true)); + EXPECT_EQ(count, 1); + deno_delete(d); +} + TEST(LibDenoTest, BuiltinModules) { static int count = 0; auto resolve_cb = [](void* user_data, const char* specifier, @@ -347,7 +368,8 @@ TEST(LibDenoTest, BuiltinModules) { "import * as deno from 'deno'\n" "if (retb() != 'b') throw Error('retb');\n" // " libdeno.print('deno ' + JSON.stringify(deno));\n" - "if (deno.foo != 'bar') throw Error('foo');\n")); + "if (deno.foo != 'bar') throw Error('foo');\n", + false)); EXPECT_EQ(count, 1); deno_delete(d); } diff --git a/src/flags.rs b/src/flags.rs index f2d0150b161c15..970aceb3c55164 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -28,6 +28,7 @@ pub struct DenoFlags { pub allow_env: bool, pub allow_run: bool, pub types: bool, + pub prefetch: bool, } pub fn get_usage(opts: &Options) -> String { @@ -107,6 +108,9 @@ fn set_recognized_flags( if matches.opt_present("types") { flags.types = true; } + if matches.opt_present("prefetch") { + flags.prefetch = true; + } if !matches.free.is_empty() { rest.extend(matches.free); @@ -142,6 +146,7 @@ pub fn set_flags( opts.optflag("r", "reload", "Reload cached remote resources."); opts.optflag("", "v8-options", "Print V8 command line options."); opts.optflag("", "types", "Print runtime TypeScript declarations."); + opts.optflag("", "prefetch", "Prefetch the dependencies."); let mut flags = DenoFlags::default(); diff --git a/src/isolate.rs b/src/isolate.rs index 9c9e7dd4fd54e3..bbd52c64d6db27 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -254,7 +254,11 @@ impl Isolate { } /// Executes the provided JavaScript module. - pub fn execute_mod(&self, js_filename: &str) -> Result<(), JSError> { + pub fn execute_mod( + &self, + js_filename: &str, + is_prefetch: bool, + ) -> Result<(), JSError> { let out = code_fetch_and_maybe_compile(&self.state, js_filename, ".").unwrap(); @@ -271,6 +275,7 @@ impl Isolate { self.as_raw_ptr(), filename_ptr, js_source_ptr, + if is_prefetch { 1 } else { 0 }, ) }; if r == 0 { @@ -662,7 +667,9 @@ mod tests { let snapshot = libdeno::deno_buf::empty(); let isolate = Isolate::new(snapshot, state, dispatch_sync); tokio_util::init(|| { - isolate.execute_mod(filename).expect("execute_mod error"); + isolate + .execute_mod(filename, false) + .expect("execute_mod error"); isolate.event_loop().ok(); }); } diff --git a/src/libdeno.rs b/src/libdeno.rs index 40a5a51d0231a7..f8f8a61e0a5168 100644 --- a/src/libdeno.rs +++ b/src/libdeno.rs @@ -151,6 +151,7 @@ extern "C" { user_data: *const c_void, js_filename: *const c_char, js_source: *const c_char, + resolve_only: i32, ) -> c_int; pub fn deno_resolve_ok( i: *const isolate, diff --git a/src/main.rs b/src/main.rs index a9c3f42f0e5aec..d6188ef62d4317 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,6 +79,8 @@ fn main() { log::LevelFilter::Warn }); + let should_prefetch = flags.prefetch; + let state = Arc::new(isolate::IsolateState::new(flags, rest_argv, None)); let snapshot = snapshot::deno_snapshot(); let isolate = isolate::Isolate::new(snapshot, state, ops::dispatch); @@ -93,7 +95,7 @@ fn main() { if isolate.state.argv.len() > 1 { let input_filename = &isolate.state.argv[1]; isolate - .execute_mod(input_filename) + .execute_mod(input_filename, should_prefetch) .unwrap_or_else(print_err_and_exit); } diff --git a/tools/integration_tests.py b/tools/integration_tests.py index 67c6852d45d276..86638ce3fdbf3d 100755 --- a/tools/integration_tests.py +++ b/tools/integration_tests.py @@ -11,10 +11,7 @@ import re import sys import subprocess -from util import pattern_match, green_ok, red_failed - -root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -tests_path = os.path.join(root_path, "tests") +from util import root_path, tests_path, pattern_match, green_ok, red_failed def read_test(file_name): diff --git a/tools/prefetch_test.py b/tools/prefetch_test.py new file mode 100755 index 00000000000000..d6de9d3986625d --- /dev/null +++ b/tools/prefetch_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# Copyright 2018 the Deno authors. All rights reserved. MIT license. +import os +import sys +from util import tests_path, run_output, build_path, executable_suffix, green_ok +import tempfile +import shutil + + +def prefetch_test(deno_exe): + sys.stdout.write("prefetch_test...") + sys.stdout.flush() + + # On Windows, set the base directory that mkdtemp() uses explicitly. If not, + # it'll use the short (8.3) path to the temp dir, which triggers the error + # 'TS5009: Cannot find the common subdirectory path for the input files.' + temp_dir = os.environ["TEMP"] if os.name == 'nt' else None + deno_dir = tempfile.mkdtemp(dir=temp_dir) + try: + t = os.path.join(tests_path, "006_url_imports.ts") + output = run_output([deno_exe, "--prefetch", t], + merge_env={"DENO_DIR": deno_dir}) + assert output == "" + # Check that we actually did the prefetch. + os.path.exists( + os.path.join(deno_dir, + "deps/http/localhost_PORT4545/tests/subdir/mod2.ts")) + finally: + shutil.rmtree(deno_dir) + + print green_ok() + + +if __name__ == "__main__": + prefetch_test(sys.argv[1]) diff --git a/tools/test.py b/tools/test.py index 41e811a6d3fb38..9a0b733595b206 100755 --- a/tools/test.py +++ b/tools/test.py @@ -12,6 +12,7 @@ from util_test import util_test from benchmark_test import benchmark_test from repl_test import repl_tests +from prefetch_test import prefetch_test import subprocess import http_server @@ -59,6 +60,8 @@ def main(argv): unit_tests(deno_exe) + prefetch_test(deno_exe) + integration_tests(deno_exe) # TODO We currently skip testing the prompt in Windows completely. diff --git a/tools/util.py b/tools/util.py index 368294f4523363..a7370950bb3f49 100644 --- a/tools/util.py +++ b/tools/util.py @@ -12,6 +12,7 @@ executable_suffix = ".exe" if os.name == "nt" else "" root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +tests_path = os.path.join(root_path, "tests") def make_env(merge_env=None, env=None):