diff --git a/Cargo.lock b/Cargo.lock index ffdcdb5..d46aaab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,6 +1321,8 @@ dependencies = [ "judge-apis", "pom", "problem-loader", + "rand 0.8.3", + "rand_chacha 0.3.0", "serde", "serde_json", "strum", diff --git a/processor/Cargo.toml b/processor/Cargo.toml index ebfd99b..aaeca6b 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -21,3 +21,5 @@ serde_json = "1.0.64" valuer-client = { path = "../valuer-client" } strum = { version = "0.20.0", features = ["derive"] } base64 = "0.13.0" +rand = "0.8.3" +rand_chacha = "0.3.0" diff --git a/processor/src/fake.rs b/processor/src/fake.rs new file mode 100644 index 0000000..368a764 --- /dev/null +++ b/processor/src/fake.rs @@ -0,0 +1,105 @@ +//! Pure implementation that returns valid, but fake data + +use crate::{JobProgress, ProtocolSender, Request}; +use judge_apis::judge_log::{JudgeLog, JudgeLogSubtaskRow, JudgeLogTestRow}; +use pom::TestId; +use rand::{ + distributions::{Alphanumeric, Uniform}, + prelude::SliceRandom, + Rng, SeedableRng, +}; +use rand_chacha::ChaChaRng; +use std::hash::{Hash, Hasher}; +use tokio::sync::{mpsc, oneshot}; +use valuer_api::{status_codes, JudgeLogKind, Status, StatusKind, SubtaskId}; + +#[derive(Clone)] +pub struct FakeSettings {} + +pub fn judge(req: Request, settings: FakeSettings) -> JobProgress { + let (done_tx, done_rx) = oneshot::channel(); + let (events_tx, events_rx) = mpsc::channel(1); + tokio::task::spawn(async move { + let mut protocol_sender = ProtocolSender { + sent: Vec::new(), + tx: events_tx, + debug_dump_dir: None, + }; + + do_judge(req, &mut protocol_sender, settings).await; + + done_tx.send(Ok(())).ok(); + }); + JobProgress { events_rx, done_rx } +} + +fn stable_hash(val: &T) -> u64 { + let mut h = std::collections::hash_map::DefaultHasher::new(); + val.hash(&mut h); + h.finish() +} + +fn generate_string((len_lo, len_hi): (usize, usize), rng: &mut ChaChaRng) -> String { + let dist = Uniform::new(len_lo, len_hi); + let len = rng.sample(dist); + + (0..len).map(|_| rng.sample(Alphanumeric) as char).collect() +} + +fn generate_judge_log(kind: JudgeLogKind, rng: &mut ChaChaRng) -> JudgeLog { + let test_count = rng.sample(Uniform::new(3_u32, 20)); + let make_status = |rng: &mut ChaChaRng| Status { + kind: [StatusKind::Accepted, StatusKind::Rejected] + .choose(&mut *rng) + .copied() + .unwrap(), + code: [ + status_codes::WRONG_ANSWER, + status_codes::TEST_PASSED, + status_codes::TIME_LIMIT_EXCEEDED, + status_codes::RUNTIME_ERROR, + ] + .choose(&mut *rng) + .copied() + .unwrap() + .to_owned(), + }; + let tests = (0..test_count) + .map(|id| JudgeLogTestRow { + test_id: TestId::make(id + 1), + status: Some(make_status(&mut *rng)), + test_stdin: Some(generate_string((3, 100), rng)), + test_stdout: Some(generate_string((3, 100), rng)), + test_stderr: Some(generate_string((3, 100), rng)), + test_answer: Some(generate_string((3, 100), rng)), + time_usage: Some(rng.sample(Uniform::new(1_000_000, 1_000_000_000))), + memory_usage: Some(rng.sample(Uniform::new(1_000_000, 1_000_000_000))), + }) + .collect(); + let subtask_count = rng.sample(Uniform::new(1_u32, 10)); + let subtasks = (0..subtask_count) + .map(|id| JudgeLogSubtaskRow { + subtask_id: SubtaskId::make(id + 1), + score: Some(rng.sample(Uniform::new(0, 100))), + }) + .collect(); + JudgeLog { + kind, + tests, + subtasks, + score: rng.sample(Uniform::new(0, 100)), + status: make_status(rng), + compile_log: (generate_string((10, 200), rng)), + is_full: false, + } +} + +async fn do_judge(req: Request, protocol_sender: &mut ProtocolSender, _settings: FakeSettings) { + for kind in JudgeLogKind::list() { + let seed = stable_hash(&(&req.toolchain_name, &req.run_source, kind.as_str())); + tracing::info!(kind = kind.as_str(), seed = seed, "generating judge log"); + let mut rng = ChaChaRng::seed_from_u64(seed); + let log = generate_judge_log(kind, &mut rng); + protocol_sender.send_log(log).await; + } +} diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 4b0105b..7cbf075 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -1,6 +1,7 @@ //! Processor is part of judge that deals with a single run (and it doesn't //! care where have it come from). +pub mod fake; mod compile; mod exec_test; mod request_builder; diff --git a/src/main.rs b/src/main.rs index 6858041..9e60be5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,11 @@ struct Args { /// Directory containing judging logs. Set to `/dev/null` to disable logging #[clap(long, default_value = "/var/log/judges")] logs: PathBuf, + /// Enable fake mode. + /// In this mode judge never loads problems or toolchains and just + /// generates random data for requests + #[clap(long)] + fake: bool, } async fn create_clients(args: &Args) -> anyhow::Result { @@ -54,18 +59,10 @@ async fn create_clients(args: &Args) -> anyhow::Result { }) } -#[tokio::main] -async fn main() -> anyhow::Result<()> { - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .init(); - let args: Args = Clap::parse(); +async fn initialize_normal(args: &Args) -> anyhow::Result { let clients = create_clients(&args) .await .context("failed to initialize dependency clients")?; - tracing::info!("Running REST API"); - let cfg = rest::RestConfig { port: args.port }; - let settings = { let checker_logs = match &args.logs { p if p == Path::new("/dev/null") => (None), @@ -81,6 +78,30 @@ async fn main() -> anyhow::Result<()> { } processor::Settings { checker_logs } }; - rest::serve(cfg, clients, settings).await?; + Ok(rest::ServeKind::Normal { settings, clients }) +} + +fn initialize_fake() -> rest::ServeKind { + rest::ServeKind::Fake { + settings: processor::fake::FakeSettings {}, + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + let args: Args = Clap::parse(); + tracing::info!("Running REST API"); + let cfg = rest::RestConfig { port: args.port }; + + let serve_config = if args.fake { + initialize_fake() + } else { + initialize_normal(&args).await? + }; + + rest::serve(cfg, serve_config).await?; Ok(()) } diff --git a/src/rest.rs b/src/rest.rs index 94433de..b01af3a 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -42,10 +42,19 @@ impl JudgeJob { } } +pub enum ServeKind { + Normal { + clients: processor::Clients, + settings: processor::Settings, + }, + Fake { + settings: processor::fake::FakeSettings, + }, +} + struct State { judge: RwLock>>>, - clients: processor::Clients, - settings: processor::Settings, + kind: ServeKind, } async fn start_job( @@ -58,15 +67,21 @@ async fn start_job( run_source: req.run_source.0, }; let job_id = Uuid::new_v4(); - let mut settings = state.settings.clone(); - { - let mut job_id_s = Uuid::encode_buffer(); - let job_id_s = job_id.to_hyphenated().encode_lower(&mut job_id_s); - if let Some(p) = &mut settings.checker_logs { - p.push(&*job_id_s); + + let mut progress = match &state.kind { + ServeKind::Normal { settings, clients } => { + let mut settings = settings.clone(); + { + let mut job_id_s = Uuid::encode_buffer(); + let job_id_s = job_id.to_hyphenated().encode_lower(&mut job_id_s); + if let Some(p) = &mut settings.checker_logs { + p.push(&*job_id_s); + } + } + processor::judge(proc_request, clients.clone(), settings) } - } - let mut progress = processor::judge(proc_request, state.clients.clone(), settings); + ServeKind::Fake { settings } => processor::fake::judge(proc_request, settings.clone()), + }; let job = JudgeJob { id: job_id, live_test: None, @@ -154,16 +169,11 @@ async fn get_job_judge_log( } /// Serves api -#[tracing::instrument(skip(cfg, clients, settings))] -pub async fn serve( - cfg: RestConfig, - clients: processor::Clients, - settings: processor::Settings, -) -> anyhow::Result<()> { +#[tracing::instrument(skip(cfg, kind))] +pub async fn serve(cfg: RestConfig, kind: ServeKind) -> anyhow::Result<()> { let state = Arc::new(State { judge: RwLock::new(HashMap::new()), - clients, - settings, + kind, }); let state2 = state.clone(); let route_create_job = warp::post()