|
11 | 11 |
|
12 | 12 | use crate::{args, config}; |
13 | 13 | use serde::{Deserialize, Serialize}; |
| 14 | +use serde_json::de::IoRead; |
| 15 | +use serde_json::StreamDeserializer; |
14 | 16 | use std::collections::HashMap; |
15 | 17 | use std::path::{Path, PathBuf}; |
16 | 18 | use std::process::{Command, ExitCode}; |
17 | 19 | use std::sync::mpsc::{channel, Receiver, Sender}; |
18 | 20 | use std::sync::Arc; |
19 | | -use std::{env, fmt, thread}; |
| 21 | +use std::{env, fmt, io, thread}; |
20 | 22 |
|
21 | 23 | pub mod tcp; |
22 | 24 |
|
@@ -331,3 +333,143 @@ impl InterceptEnvironment { |
331 | 333 | path.to_str().unwrap_or("").to_string() |
332 | 334 | } |
333 | 335 | } |
| 336 | + |
| 337 | +/// Generate the build events from the file. |
| 338 | +/// |
| 339 | +/// Returns an iterator over the build events. |
| 340 | +/// Any error will interrupt the reading process and the remaining events will be lost. |
| 341 | +pub fn read(reader: impl io::Read) -> impl Iterator<Item = Envelope> { |
| 342 | + let stream = StreamDeserializer::new(IoRead::new(reader)); |
| 343 | + stream.filter_map(|result| match result { |
| 344 | + Ok(value) => Some(value), |
| 345 | + Err(error) => { |
| 346 | + log::error!("Failed to read event: {:?}", error); |
| 347 | + None |
| 348 | + } |
| 349 | + }) |
| 350 | +} |
| 351 | + |
| 352 | +/// Write the build events to the file. |
| 353 | +/// |
| 354 | +/// Can fail if the events cannot be serialized or written to the file. |
| 355 | +/// Any error will interrupt the writing process and the file will be incomplete. |
| 356 | +pub fn write( |
| 357 | + mut writer: impl io::Write, |
| 358 | + envelopes: impl IntoIterator<Item = Envelope>, |
| 359 | +) -> Result<(), anyhow::Error> { |
| 360 | + for envelope in envelopes { |
| 361 | + serde_json::to_writer(&mut writer, &envelope)?; |
| 362 | + writer.write_all(b"\n")?; |
| 363 | + } |
| 364 | + Ok(()) |
| 365 | +} |
| 366 | + |
| 367 | +#[cfg(test)] |
| 368 | +mod test { |
| 369 | + use super::*; |
| 370 | + use crate::vec_of_strings; |
| 371 | + use serde_json::json; |
| 372 | + use std::collections::HashMap; |
| 373 | + use std::path::PathBuf; |
| 374 | + |
| 375 | + #[test] |
| 376 | + fn read_write() { |
| 377 | + let events = expected_values(); |
| 378 | + |
| 379 | + let mut buffer = Vec::new(); |
| 380 | + write(&mut buffer, events.iter().cloned()).unwrap(); |
| 381 | + let mut cursor = io::Cursor::new(buffer); |
| 382 | + let read_events: Vec<_> = read(&mut cursor).collect(); |
| 383 | + |
| 384 | + assert_eq!(events, read_events); |
| 385 | + } |
| 386 | + |
| 387 | + #[test] |
| 388 | + fn read_write_empty() { |
| 389 | + let events = Vec::<Envelope>::new(); |
| 390 | + |
| 391 | + let mut buffer = Vec::new(); |
| 392 | + write(&mut buffer, events.iter().cloned()).unwrap(); |
| 393 | + let mut cursor = io::Cursor::new(buffer); |
| 394 | + let read_events: Vec<_> = read(&mut cursor).collect(); |
| 395 | + |
| 396 | + assert_eq!(events, read_events); |
| 397 | + } |
| 398 | + |
| 399 | + #[test] |
| 400 | + fn read_stops_on_errors() { |
| 401 | + let line1 = json!({ |
| 402 | + "rid": 42, |
| 403 | + "timestamp": 0, |
| 404 | + "event": { |
| 405 | + "pid": 11782, |
| 406 | + "execution": { |
| 407 | + "executable": "/usr/bin/clang", |
| 408 | + "arguments": ["clang", "-c", "main.c"], |
| 409 | + "working_dir": "/home/user", |
| 410 | + "environment": { |
| 411 | + "PATH": "/usr/bin", |
| 412 | + "HOME": "/home/user" |
| 413 | + } |
| 414 | + } |
| 415 | + } |
| 416 | + }); |
| 417 | + let line2 = json!({"rid": 42 }); |
| 418 | + let line3 = json!({ |
| 419 | + "rid": 42, |
| 420 | + "timestamp": 273, |
| 421 | + "event": { |
| 422 | + "pid": 11934, |
| 423 | + "execution": { |
| 424 | + "executable": "/usr/bin/clang", |
| 425 | + "arguments": ["clang", "-c", "output.c"], |
| 426 | + "working_dir": "/home/user", |
| 427 | + "environment": {} |
| 428 | + } |
| 429 | + } |
| 430 | + }); |
| 431 | + let content = format!("{}\n{}\n{}\n", line1, line2, line3); |
| 432 | + |
| 433 | + let mut cursor = io::Cursor::new(content); |
| 434 | + let read_events: Vec<_> = read(&mut cursor).collect(); |
| 435 | + |
| 436 | + // Only the fist event is read, all other lines are ignored. |
| 437 | + assert_eq!(expected_values()[0..1], read_events); |
| 438 | + } |
| 439 | + |
| 440 | + const REPORTER_ID: ReporterId = ReporterId(42); |
| 441 | + |
| 442 | + fn expected_values() -> Vec<Envelope> { |
| 443 | + vec![ |
| 444 | + Envelope { |
| 445 | + rid: REPORTER_ID, |
| 446 | + timestamp: 0, |
| 447 | + event: Event { |
| 448 | + pid: ProcessId(11782), |
| 449 | + execution: Execution { |
| 450 | + executable: PathBuf::from("/usr/bin/clang"), |
| 451 | + arguments: vec_of_strings!["clang", "-c", "main.c"], |
| 452 | + working_dir: PathBuf::from("/home/user"), |
| 453 | + environment: HashMap::from([ |
| 454 | + ("PATH".to_string(), "/usr/bin".to_string()), |
| 455 | + ("HOME".to_string(), "/home/user".to_string()), |
| 456 | + ]), |
| 457 | + }, |
| 458 | + }, |
| 459 | + }, |
| 460 | + Envelope { |
| 461 | + rid: REPORTER_ID, |
| 462 | + timestamp: 273, |
| 463 | + event: Event { |
| 464 | + pid: ProcessId(11934), |
| 465 | + execution: Execution { |
| 466 | + executable: PathBuf::from("/usr/bin/clang"), |
| 467 | + arguments: vec_of_strings!["clang", "-c", "output.c"], |
| 468 | + working_dir: PathBuf::from("/home/user"), |
| 469 | + environment: HashMap::from([]), |
| 470 | + }, |
| 471 | + }, |
| 472 | + }, |
| 473 | + ] |
| 474 | + } |
| 475 | +} |
0 commit comments