environment.rs•5.47 kB
use std::{
collections::BTreeMap,
sync::{
Arc,
LazyLock,
},
time::Duration,
};
use common::{
log_lines::LogLevel,
runtime::{
JoinSet,
Runtime,
UnixTimestamp,
},
types::EnvVarValue,
value::NamespacedTableMapping,
};
use deno_core::{
sourcemap::SourceMap,
v8,
};
use futures::{
future,
FutureExt,
};
use isolate::{
environment::{
crypto_rng::CryptoRng,
AsyncOpRequest,
IsolateEnvironment,
ModuleCodeCacheResult,
},
ConcurrencyPermit,
Timeout,
};
use model::modules::module_versions::FullModuleSource;
use rand::{
Rng,
SeedableRng,
};
use rand_chacha::ChaCha12Rng;
use runtime::testing::TestRuntime;
use serde_json::Value as JsonValue;
// NB: These files are generated by the *isolate* crate's build script.
pub const TEST_SOURCE: &str = include_str!("../../../../../npm-packages/simulation/dist/main.js");
pub const TEST_SOURCE_MAP_STR: &str =
include_str!("../../../../../npm-packages/simulation/dist/main.js.map");
pub static TEST_SOURCE_MAP: LazyLock<SourceMap> = LazyLock::new(|| {
SourceMap::from_slice(TEST_SOURCE_MAP_STR.as_bytes()).expect("Invalid source map")
});
pub struct TestEnvironment {
rt: TestRuntime,
rng: ChaCha12Rng,
next_timer_id: usize,
timers: JoinSet<usize>,
timer_resolvers: BTreeMap<usize, v8::Global<v8::PromiseResolver>>,
}
impl TestEnvironment {
pub fn new(rt: TestRuntime) -> Self {
let rng = ChaCha12Rng::from_seed(rt.rng().random());
Self {
rt,
rng,
next_timer_id: 0,
timers: JoinSet::new(),
timer_resolvers: BTreeMap::new(),
}
}
}
impl IsolateEnvironment<TestRuntime> for TestEnvironment {
async fn lookup_source(
&mut self,
path: &str,
_timeout: &mut Timeout<TestRuntime>,
_permit: &mut Option<ConcurrencyPermit>,
) -> anyhow::Result<Option<(Arc<FullModuleSource>, ModuleCodeCacheResult)>> {
if path != "test.js" {
return Ok(None);
}
Ok(Some((
Arc::new(FullModuleSource {
source: TEST_SOURCE.into(),
source_map: Some(TEST_SOURCE_MAP_STR.to_string()),
}),
ModuleCodeCacheResult::noop(),
)))
}
fn syscall(&mut self, _name: &str, _args: JsonValue) -> anyhow::Result<JsonValue> {
panic!("syscall() unimplemented");
}
fn start_async_syscall(
&mut self,
name: String,
args: JsonValue,
_resolver: v8::Global<v8::PromiseResolver>,
) -> anyhow::Result<()> {
tracing::info!("Ignoring async syscall: {name:?} {args:?}");
Ok(())
}
fn trace(&mut self, level: LogLevel, messages: Vec<String>) -> anyhow::Result<()> {
for message in messages {
match level {
LogLevel::Debug => tracing::debug!("[console] {message}"),
LogLevel::Error => tracing::error!("[console] {message}"),
LogLevel::Warn => tracing::warn!("[console] {message}"),
LogLevel::Info => tracing::info!("[console] {message}"),
LogLevel::Log => tracing::info!("[console] {message}"),
}
}
Ok(())
}
fn rng(&mut self) -> anyhow::Result<&mut ChaCha12Rng> {
Ok(&mut self.rng)
}
fn crypto_rng(&mut self) -> anyhow::Result<CryptoRng> {
anyhow::bail!("CryptoRng not allowed in simulation")
}
fn unix_timestamp(&mut self) -> anyhow::Result<UnixTimestamp> {
Ok(self.rt.unix_timestamp())
}
fn get_environment_variable(
&mut self,
_name: common::types::EnvVarName,
) -> anyhow::Result<Option<EnvVarValue>> {
Ok(None)
}
fn get_all_table_mappings(&mut self) -> anyhow::Result<NamespacedTableMapping> {
panic!("get_all_table_mappings() unimplemented");
}
fn start_async_op(
&mut self,
request: AsyncOpRequest,
resolver: v8::Global<v8::PromiseResolver>,
) -> anyhow::Result<()> {
match request {
AsyncOpRequest::Sleep { until, .. } => {
let id = self.next_timer_id;
self.next_timer_id += 1;
let now = self.rt.unix_timestamp();
let duration = if until > now {
until - now
} else {
Duration::ZERO
};
self.timers
.spawn("timer", tokio::time::sleep(duration).map(move |_| id));
self.timer_resolvers.insert(id, resolver);
},
req => {
tracing::debug!("Ignoring async op request: {req:?}");
},
}
Ok(())
}
fn user_timeout(&self) -> Duration {
Duration::from_secs(60 * 60 * 24)
}
fn system_timeout(&self) -> Duration {
Duration::from_secs(60 * 60 * 24)
}
}
impl TestEnvironment {
pub async fn next_timer(&mut self) -> anyhow::Result<v8::Global<v8::PromiseResolver>> {
let Some(timer) = self.timers.join_next().await else {
return future::pending().await;
};
let timer_id = timer?;
let resolver = self
.timer_resolvers
.remove(&timer_id)
.ok_or_else(|| anyhow::anyhow!("Timer resolver not found"))?;
Ok(resolver)
}
}