From d82c1991cf0919c312b87501bc588cf17781b32f Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Tue, 11 Jun 2019 15:34:39 +0100 Subject: [PATCH] Add --seed for setting RNG seed (#2483) --- cli/flags.rs | 68 ++++++++++++++++++++++++++++++++++++++++ cli/ops.rs | 11 +++++-- cli/state.rs | 9 ++++++ tests/seed_random.js | 11 +++++++ tests/seed_random.js.out | 12 +++++++ tests/seed_random.test | 2 ++ website/manual.md | 1 + 7 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tests/seed_random.js create mode 100644 tests/seed_random.js.out create mode 100644 tests/seed_random.test diff --git a/cli/flags.rs b/cli/flags.rs index 9f37fb18..fa86df59 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -29,6 +29,7 @@ pub struct DenoFlags { pub allow_hrtime: bool, pub no_prompts: bool, pub no_fetch: bool, + pub seed: Option, pub v8_flags: Option>, pub xeval_replvar: Option, pub xeval_delim: Option, @@ -145,6 +146,19 @@ To get help on the another subcommands (run in this case): .help("Load compiler configuration file") .takes_value(true) .global(true), + ).arg( + Arg::with_name("seed") + .long("seed") + .value_name("NUMBER") + .help("Seed Math.random()") + .takes_value(true) + .validator(|val: String| { + match val.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Seed should be a number".to_string()) + } + }) + .global(true), ).arg( Arg::with_name("v8-options") .long("v8-options") @@ -379,6 +393,22 @@ pub fn parse_flags(matches: &ArgMatches) -> DenoFlags { v8_flags.insert(0, "deno".to_string()); flags.v8_flags = Some(v8_flags); } + if matches.is_present("seed") { + let seed_string = matches.value_of("seed").unwrap(); + let seed = seed_string.parse::().unwrap(); + flags.seed = Some(seed); + + let v8_seed_flag = format!("--random-seed={}", seed); + + match flags.v8_flags { + Some(ref mut v8_flags) => { + v8_flags.push(v8_seed_flag); + } + None => { + flags.v8_flags = Some(svec!["deno", v8_seed_flag]); + } + } + } flags = parse_run_args(flags, matches); // flags specific to "run" subcommand @@ -1112,4 +1142,42 @@ mod tests { assert_eq!(subcommand, DenoSubcommand::Run); assert_eq!(argv, svec!["deno", "script.ts"]); } + + #[test] + fn test_flags_from_vec_28() { + let (flags, subcommand, argv) = + flags_from_vec(svec!["deno", "--seed", "250", "run", "script.ts"]); + assert_eq!( + flags, + DenoFlags { + seed: Some(250 as u64), + v8_flags: Some(svec!["deno", "--random-seed=250"]), + ..DenoFlags::default() + } + ); + assert_eq!(subcommand, DenoSubcommand::Run); + assert_eq!(argv, svec!["deno", "script.ts"]) + } + + #[test] + fn test_flags_from_vec_29() { + let (flags, subcommand, argv) = flags_from_vec(svec![ + "deno", + "--seed", + "250", + "--v8-flags=--expose-gc", + "run", + "script.ts" + ]); + assert_eq!( + flags, + DenoFlags { + seed: Some(250 as u64), + v8_flags: Some(svec!["deno", "--expose-gc", "--random-seed=250"]), + ..DenoFlags::default() + } + ); + assert_eq!(subcommand, DenoSubcommand::Run); + assert_eq!(argv, svec!["deno", "script.ts"]) + } } diff --git a/cli/ops.rs b/cli/ops.rs index f39daaab..61f29ac9 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -2211,10 +2211,17 @@ fn op_host_post_message( } fn op_get_random_values( - _state: &ThreadSafeState, + state: &ThreadSafeState, _base: &msg::Base<'_>, data: Option, ) -> Box { - thread_rng().fill(&mut data.unwrap()[..]); + if let Some(ref seeded_rng) = state.seeded_rng { + let mut rng = seeded_rng.lock().unwrap(); + rng.fill(&mut data.unwrap()[..]); + } else { + let mut rng = thread_rng(); + rng.fill(&mut data.unwrap()[..]); + } + Box::new(ok_future(empty_buf())) } diff --git a/cli/state.rs b/cli/state.rs index 4a2db65c..255a8814 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -23,6 +23,8 @@ use deno::PinnedBuf; use futures::future::Either; use futures::future::Shared; use futures::Future; +use rand::rngs::StdRng; +use rand::SeedableRng; use std; use std::collections::HashMap; use std::collections::HashSet; @@ -82,6 +84,7 @@ pub struct State { pub dispatch_selector: ops::OpSelector, /// Reference to global progress bar. pub progress: Progress, + pub seeded_rng: Option>, /// Set of all URLs that have been compiled. This is a hacky way to work /// around the fact that --reload will force multiple compilations of the same @@ -295,6 +298,11 @@ impl ThreadSafeState { } } + let mut seeded_rng = None; + if let Some(seed) = flags.seed { + seeded_rng = Some(Mutex::new(StdRng::seed_from_u64(seed))); + }; + ThreadSafeState(Arc::new(State { main_module, dir, @@ -312,6 +320,7 @@ impl ThreadSafeState { resource, dispatch_selector, progress, + seeded_rng, compiled: Mutex::new(HashSet::new()), })) } diff --git a/tests/seed_random.js b/tests/seed_random.js new file mode 100644 index 00000000..7f6e336d --- /dev/null +++ b/tests/seed_random.js @@ -0,0 +1,11 @@ +for (let i = 0; i < 10; ++i) { + console.log(Math.random()); +} + +const arr = new Uint8Array(32); + +crypto.getRandomValues(arr); +console.log(arr); + +crypto.getRandomValues(arr); +console.log(arr); diff --git a/tests/seed_random.js.out b/tests/seed_random.js.out new file mode 100644 index 00000000..06993cb1 --- /dev/null +++ b/tests/seed_random.js.out @@ -0,0 +1,12 @@ +0.858562739044346 +0.8973397944553141 +0.15335012655691727 +0.36867387434349963 +0.3591039342838782 +0.7044499748617652 +0.7461423057751548 +0.3824611207183364 +0.5950178237266042 +0.22440633214343908 +Uint8Array [ 31, 147, 233, 143, 64, 159, 189, 114, 137, 153, 196, 156, 133, 210, 78, 4, 125, 255, 147, 234, 169, 149, 228, 46, 166, 246, 137, 49, 50, 182, 106, 219 ] +Uint8Array [ 220, 209, 104, 94, 239, 165, 8, 254, 123, 163, 160, 177, 229, 105, 171, 232, 236, 71, 107, 28, 132, 143, 113, 44, 86, 251, 159, 102, 20, 119, 174, 230 ] diff --git a/tests/seed_random.test b/tests/seed_random.test new file mode 100644 index 00000000..20a3b3c8 --- /dev/null +++ b/tests/seed_random.test @@ -0,0 +1,2 @@ +args: run --seed=100 tests/seed_random.js +output: tests/seed_random.js.out diff --git a/website/manual.md b/website/manual.md index 684d44c2..8b3d3752 100644 --- a/website/manual.md +++ b/website/manual.md @@ -635,6 +635,7 @@ OPTIONS: --allow-write= Allow file system write access -c, --config Load compiler configuration file --importmap Load import map file + --seed Seed Math.random() and crypto.getRandomValues() --v8-flags= Set V8 command line options SUBCOMMANDS: -- GitLab