#![allow(clippy::unwrap_used, clippy::expect_used)]
use iri_rs::{Iri, IriBuf};
use jsonld::{
JsonLdProcessor,
LoadError,
Loader,
RemoteDocument,
syntax::{Parse, Value, native_stack::RED_ZONE},
};
use std::{
hint::black_box,
io,
sync::atomic::{AtomicUsize, Ordering},
thread,
};
const LOADER_ENTRY: usize = 16 * 1024;
const THREAD_STACK: usize = 8 * 1024 * 1024;
const REMOTE_CONTEXT: &str = r#"{"@context": {"p": "http://example.com/vocab#p"}}"#;
const REFERENCING: &str = r#"{"@context": "http://example.com/context.jsonld", "p": "v"}"#;
const IMPORTING: &str = r#"{"@context": {"@version": 1.1, "@import": "http://example.com/context.jsonld"}, "p": "v"}"#;
struct StackRecordingLoader {
least: AtomicUsize,
}
impl Loader for StackRecordingLoader {
type Error = io::Error;
async fn load(&self, url: Iri<&str>) -> Result<RemoteDocument<IriBuf>, LoadError<io::Error>> {
let left = stacker::remaining_stack().expect("the platform reports its stack");
self.least.fetch_min(left, Ordering::Relaxed);
let (json, _) = Value::parse_str(REMOTE_CONTEXT).unwrap();
Ok(RemoteDocument::new(Some(url.into()), None, json))
}
}
fn least_stack_at_the_loader(document: &str) -> usize {
let (json, _) = Value::parse_str(document).unwrap();
let document = RemoteDocument::new(None, None, json);
let loader = StackRecordingLoader {
least: AtomicUsize::new(usize::MAX),
};
let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
runtime.block_on(document.expand(&loader)).unwrap();
loader.least.into_inner()
}
fn with_stack_left(left: usize, run: fn() -> usize) -> usize {
if stacker::remaining_stack().expect("the platform reports its stack") > left {
let pad = black_box([0_u8; 1024]);
let least = with_stack_left(left, run);
black_box(&pad);
least
} else {
run()
}
}
fn on_a_thread_with(left: usize, run: fn() -> usize) -> usize {
thread::Builder::new()
.stack_size(THREAD_STACK)
.spawn(move || with_stack_left(left, run))
.unwrap()
.join()
.unwrap()
}
fn sweep(run: fn() -> usize) -> Vec<(usize, usize)> {
let ample = 4 * 1024 * 1024;
let taken = ample - on_a_thread_with(ample, run);
(0..=16)
.map(|step| taken + RED_ZONE - 64 * 1024 + step * 8 * 1024)
.map(|margin| (margin, on_a_thread_with(margin, run)))
.collect()
}
fn starved(run: fn() -> usize) -> Vec<(usize, usize)> {
sweep(run).into_iter().filter(|(_, least)| *least < RED_ZONE - LOADER_ENTRY).collect()
}
#[test]
fn a_remote_context_is_dereferenced_on_at_least_the_red_zone() {
let starved = starved(|| least_stack_at_the_loader(REFERENCING));
assert!(starved.is_empty(), "(caller margin, stack at the loader): {starved:?}");
}
#[test]
fn an_imported_context_is_dereferenced_on_at_least_the_red_zone() {
let starved = starved(|| least_stack_at_the_loader(IMPORTING));
assert!(starved.is_empty(), "(caller margin, stack at the loader): {starved:?}");
}