use std::cell::RefCell;
use std::io;
use std::sync::OnceLock;
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;
use tracing_subscriber::Layer as _;
thread_local! {
static BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
struct ThreadLocalWriter;
impl io::Write for ThreadLocalWriter {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
BUF.with(|buf| buf.borrow_mut().extend_from_slice(bytes));
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[derive(Clone, Copy)]
struct ThreadLocalMakeWriter;
impl<'a> MakeWriter<'a> for ThreadLocalMakeWriter {
type Writer = ThreadLocalWriter;
fn make_writer(&'a self) -> Self::Writer {
ThreadLocalWriter
}
}
static GLOBAL_INSTALLED: OnceLock<()> = OnceLock::new();
fn install_once() {
GLOBAL_INSTALLED.get_or_init(|| {
let layer = tracing_subscriber::fmt::layer()
.with_writer(ThreadLocalMakeWriter)
.with_target(true)
.with_ansi(false)
.without_time()
.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG);
let job_logs = crate::job_log::JobLogLayer::global()
.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG);
let _ = tracing_subscriber::registry()
.with(layer)
.with(job_logs)
.try_init();
});
}
pub fn capture<F: FnOnce() + Send + 'static>(f: F) -> String {
install_once();
tracing::callsite::rebuild_interest_cache();
std::thread::spawn(move || {
BUF.with(|b| b.borrow_mut().clear());
f();
BUF.with(|b| String::from_utf8(b.borrow().clone()).expect("tracing output should be UTF-8"))
})
.join()
.expect("capture thread panicked")
}
pub fn install_job_log_capture() {
install_once();
tracing::callsite::rebuild_interest_cache();
}
pub struct FixedProbe(pub f32);
impl crate::admission::MemoryProbe for FixedProbe {
fn free_gib(&self) -> Option<f32> {
Some(self.0)
}
fn total_gib(&self) -> Option<f32> {
Some(24.0)
}
}
pub struct TestLoaded {
pub id: String,
}
impl crate::host::LoadedModel for TestLoaded {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_chat(&self) -> Option<&dyn crate::host::ChatModel> {
Some(self)
}
fn as_stream(&self) -> Option<&dyn crate::host::StreamingModel> {
Some(self)
}
}
impl crate::host::StreamingModel for TestLoaded {
fn open(
&self,
) -> anyhow::Result<Box<dyn crate::stt_stream::session::StreamingTranscriber + '_>> {
Ok(Box::new(WordPerChunk(0)))
}
}
pub struct WordPerChunk(pub usize);
impl crate::stt_stream::session::StreamingTranscriber for WordPerChunk {
fn chunk_samples(&self) -> usize {
1600
}
fn step(&mut self, chunk: &[f32]) -> anyhow::Result<String> {
if chunk.iter().all(|s| *s == 0.0) {
return Ok(String::new());
}
self.0 += 1;
Ok(format!("\u{2581}w{}", self.0))
}
fn reset(&mut self) {
self.0 = 0;
}
}
impl crate::host::ChatModel for TestLoaded {
fn chat(
&self,
params: crate::types::LlmParams,
_cancelled: &dyn Fn() -> bool,
on_piece: &mut dyn FnMut(&str),
) -> anyhow::Result<serde_json::Value> {
let last = params
.messages
.last()
.map(|m| m.content.clone())
.unwrap_or_default();
on_piece("resident:");
for (i, word) in last.split(' ').enumerate() {
on_piece(&if i == 0 {
word.to_string()
} else {
format!(" {word}")
});
}
Ok(serde_json::json!({
"object": "chat.completion",
"model": self.id,
"choices": [{ "index": 0, "message": { "role": "assistant", "content": format!("resident:{last}") }, "finish_reason": "stop" }],
"kwargs": params.chat_template_kwargs,
"usage": { "prompt_tokens": 3, "completion_tokens": 2, "total_tokens": 5 },
}))
}
fn tokenize(&self, text: &str, _add_special: bool) -> anyhow::Result<Vec<i32>> {
Ok(text.chars().map(|c| c as i32).collect())
}
}
pub struct InstantRuntime;
impl crate::host::ModelRuntime for InstantRuntime {
fn load(
&self,
model: &crate::catalog::CatalogModel,
) -> anyhow::Result<std::sync::Arc<dyn crate::host::LoadedModel>> {
Ok(std::sync::Arc::new(TestLoaded {
id: model.id.clone(),
}))
}
}
pub struct DaemonHarness {
pub url: String,
pub config_path: std::path::PathBuf,
pub control: crate::control::DaemonControl,
pub observers: crate::runtime::WorkerObservers,
pub host: crate::host::ModelHost,
pub catalog: std::sync::Arc<parking_lot::Mutex<crate::catalog::Catalog>>,
engine: std::sync::Arc<dyn crate::engine::Engine>,
_dir: tempfile::TempDir,
handle: Option<std::thread::JoinHandle<()>>,
}
pub const HARNESS_TOKEN: &str = "harness-token-0123456789abcdef";
fn harness_model(id: &str, kind: crate::types::TaskKind) -> crate::catalog::CatalogModel {
use crate::types::{ModelCliDefaults, ModelEngine, ModelSource, TaskKind};
crate::catalog::CatalogModel {
id: id.into(),
display_name: id.into(),
kind,
vram_gb_estimate: 1.0,
description: None,
source: ModelSource {
engine: if kind == TaskKind::Llm {
ModelEngine::LlamaCpp
} else {
ModelEngine::Synthetic
},
files: vec![],
cli_defaults: ModelCliDefaults {
cfg_scale: 1.0,
steps: 4,
width: 64,
height: 64,
..Default::default()
},
},
enabled: true,
origin: "local".into(),
exclusive_group: None,
}
}
impl DaemonHarness {
pub fn start() -> Self {
use std::sync::Arc;
let dir = tempfile::tempdir().expect("tempdir");
let config_path = dir.path().join("config.toml");
let control = crate::control::DaemonControl::new(
crate::config::shared(crate::config::Config::default()),
config_path.clone(),
24.0,
);
let catalog = Arc::new(parking_lot::Mutex::new(crate::catalog::Catalog {
models: vec![
harness_model("chat", crate::types::TaskKind::Llm),
harness_model("img", crate::types::TaskKind::Image),
],
..Default::default()
}));
let host = crate::host::ModelHost::new(
catalog.clone(),
Arc::new(InstantRuntime),
Arc::new(FixedProbe(20.0)),
crate::residency::Residency::load_for_serving(None),
);
let observers = crate::runtime::WorkerObservers::default();
let engine: Arc<dyn crate::engine::Engine> =
Arc::new(crate::engine::SyntheticEngine::new());
let api = crate::local_api::LocalApi::bind(
"127.0.0.1:0",
engine.clone(),
catalog.clone(),
None,
observers.clone(),
HARNESS_TOKEN.to_string(),
crate::job_gate::JobGate::new(),
None,
crate::local_api::ModelServices::new(host.clone()),
)
.expect("bind")
.with_control(control.clone());
let url = api.url();
*observers.local_api_url.lock() = Some(url.clone());
let discovery = crate::config::local_api_discovery_path_for(&config_path).expect("path");
crate::local_api::write_discovery_file(&discovery, &url, HARNESS_TOKEN).expect("discovery");
let stop = control.stop.clone();
let handle = std::thread::spawn(move || api.serve(&stop));
Self {
url,
config_path,
control,
observers,
host,
catalog,
engine,
_dir: dir,
handle: Some(handle),
}
}
pub fn client(&self) -> crate::daemon_client::DaemonClient {
crate::daemon_client::DaemonClient::new(&self.url, HARNESS_TOKEN).expect("client")
}
pub fn wait_state(&self, id: &str, state: &str) {
self.host
.wait_for(id, |s| s.name() == state, std::time::Duration::from_secs(5))
.unwrap_or_else(|| panic!("{id} never reached {state}"));
}
pub fn run_image_job(&self) -> String {
let catalog = self.catalog.lock().clone();
let req = crate::local::LocalImageRequest {
prompt: "a harness fox".into(),
model: Some("img".into()),
..Default::default()
};
crate::local::run_image(self.engine.as_ref(), &catalog, &self.observers, &req)
.expect("image job");
self.observers.local_jobs.lock()[0].job_id.clone()
}
pub fn push_log(&self, message: &str) {
let queue = std::sync::Arc::new(parking_lot::Mutex::new(Vec::new()));
crate::runtime::push_log_with_observers(
&queue,
Some(&self.observers),
"info",
"test",
message,
None,
);
}
}
impl Drop for DaemonHarness {
fn drop(&mut self) {
self.control
.stop
.store(true, std::sync::atomic::Ordering::SeqCst);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capture_collects_events_emitted_inside_the_closure() {
let out = capture(|| {
tracing::info!(target: "studio_worker::test_support_demo", marker = "alpha", "hello");
});
assert!(out.contains("INFO"), "missing INFO level: {out:?}");
assert!(
out.contains("studio_worker::test_support_demo"),
"missing target: {out:?}"
);
assert!(out.contains("marker=\"alpha\""), "missing field: {out:?}");
assert!(out.contains("hello"), "missing message: {out:?}");
}
#[test]
fn capture_isolates_between_invocations() {
let first = capture(|| tracing::info!("first message"));
let second = capture(|| tracing::info!("second message"));
assert!(first.contains("first message") && !first.contains("second message"));
assert!(second.contains("second message") && !second.contains("first message"));
}
#[test]
fn capture_isolates_between_threads() {
let handle = std::thread::spawn(|| {
for _ in 0..50 {
tracing::info!("sibling thread noise");
}
});
let out = capture(|| {
tracing::info!("primary capture message");
});
handle.join().unwrap();
assert!(out.contains("primary capture message"));
assert!(
!out.contains("sibling thread noise"),
"cross-thread leak: {out:?}"
);
}
#[test]
fn capture_works_inside_a_multi_thread_tokio_runtime() {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
let out = rt.block_on(async {
capture(|| {
tracing::info!("emitted from spawned capture thread");
})
});
assert!(out.contains("emitted from spawned capture thread"));
}
}