From 617c6cb0f0a0cf36fd6d6fab96e5c2d435625199 Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Fri, 15 Dec 2023 13:18:01 +0900 Subject: [PATCH] port Trace definition to rust --- .gitignore | 3 +- crates/Cargo.lock | 223 ++++++++++++++ crates/Cargo.toml | 16 + crates/replay_gen/Cargo.toml | 9 + crates/replay_gen/src/lib.rs | 1 + crates/replay_gen/src/main.rs | 29 ++ crates/replay_gen/src/trace.rs | 519 +++++++++++++++++++++++++++++++++ 7 files changed, 799 insertions(+), 1 deletion(-) create mode 100644 crates/Cargo.lock create mode 100644 crates/Cargo.toml create mode 100644 crates/replay_gen/Cargo.toml create mode 100644 crates/replay_gen/src/lib.rs create mode 100644 crates/replay_gen/src/main.rs create mode 100644 crates/replay_gen/src/trace.rs diff --git a/.gitignore b/.gitignore index 10258717..25f9b1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules/** **/.DS_Store .DS_Store performance.db.ndjson -performance.ndjson \ No newline at end of file +performance.ndjson +target/ \ No newline at end of file diff --git a/crates/Cargo.lock b/crates/Cargo.lock new file mode 100644 index 00000000..7c4df238 --- /dev/null +++ b/crates/Cargo.lock @@ -0,0 +1,223 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "replay_gen" +version = "0.1.0" +dependencies = [ + "tempfile", +] + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/crates/Cargo.toml b/crates/Cargo.toml new file mode 100644 index 00000000..0f6651e5 --- /dev/null +++ b/crates/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +members = ["replay_gen"] +resolver = "2" + +[profile.release] +# Optimize all the things! +opt-level = 3 +lto = "fat" +overflow-checks = true +# Some debug info for profiling. +debug = 1 + +[profile.test] +# Speed-up test execution and avoid stack overflow with deeply-nested ASTs. +opt-level = 2 +lto = "thin" diff --git a/crates/replay_gen/Cargo.toml b/crates/replay_gen/Cargo.toml new file mode 100644 index 00000000..414fd790 --- /dev/null +++ b/crates/replay_gen/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "replay_gen" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tempfile = "3.2.0" diff --git a/crates/replay_gen/src/lib.rs b/crates/replay_gen/src/lib.rs new file mode 100644 index 00000000..11ec9166 --- /dev/null +++ b/crates/replay_gen/src/lib.rs @@ -0,0 +1 @@ +pub mod trace; diff --git a/crates/replay_gen/src/main.rs b/crates/replay_gen/src/main.rs new file mode 100644 index 00000000..42dd5d46 --- /dev/null +++ b/crates/replay_gen/src/main.rs @@ -0,0 +1,29 @@ +use std::env; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; + +use replay_gen::trace; + +fn main() -> io::Result<()> { + // FIXME: use clap to parse args. currently just panics. + let args: Vec = env::args().collect(); + let newline = &args[1]; + let path = Path::new(newline); + let file = File::open(&path)?; + let reader = io::BufReader::new(file); + + let mut lines = reader.lines().peekable(); + + while let Some(line) = lines.next() { + let line = line?; + let event = line.parse::()?; + // hack to print the last event without a newline that matches the current behavior. + if lines.peek().is_some() { + println!("{:?}", event); + } else { + print!("{:?}", event); + } + } + Ok(()) +} diff --git a/crates/replay_gen/src/trace.rs b/crates/replay_gen/src/trace.rs new file mode 100644 index 00000000..433e83f0 --- /dev/null +++ b/crates/replay_gen/src/trace.rs @@ -0,0 +1,519 @@ +use std::fmt; +use std::fmt::Debug; +use std::io::{Error, ErrorKind}; +use std::str::FromStr; + +// FIXME: this is a hack to get around the fact that the trace generated by js. Remove when we discard js based trace. +struct F64(f64); + +impl fmt::Display for F64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.is_infinite() { + write!(f, "Infinity") + } else { + write!(f, "{}", self.0) + } + } +} + +impl std::str::FromStr for F64 { + type Err = std::num::ParseFloatError; + + fn from_str(s: &str) -> Result { + match s { + "Infinity" => Ok(Self(std::f64::INFINITY)), + _ => s.parse::().map(Self), + } + } +} + +pub enum ValType { + I32, + I64, + F32, + F64, + V128, + Anyref, + Externref, +} + +impl Debug for ValType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::I32 => write!(f, "i32"), + Self::I64 => write!(f, "i64"), + Self::F32 => write!(f, "f32"), + Self::F64 => write!(f, "f64"), + Self::V128 => write!(f, "v128"), + Self::Anyref => write!(f, "anyref"), + Self::Externref => write!(f, "externref"), + } + } +} + +impl std::str::FromStr for ValType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "i32" => Ok(Self::I32), + "i64" => Ok(Self::I64), + "f32" => Ok(Self::F32), + "f64" => Ok(Self::F64), + "v128" => Ok(Self::V128), + "anyref" => Ok(Self::Anyref), + "externref" => Ok(Self::Externref), + _ => Err(()), + } + } +} + +pub struct Tracer { + trace: Vec, +} + +impl Tracer { + pub fn new() -> Self { + Self { trace: Vec::new() } + } + pub fn push(&mut self, event: WasmEvent) { + self.trace.push(event); + } +} + +impl Debug for Tracer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for event in &self.trace { + write!(f, "{:?}\n", event)?; + } + Ok(()) + } +} + +pub enum WasmEvent { + Load(Load), + MemGrow(MemGrow), + TableGet(TableGet), + TableGrow(TableGrow), + GlobalGet(GlobalGet), + ExportCall(ExportCall), + TableCall(TableCall), + ExportReturn, + ImportCall(ImportCall), + ImportReturn(ImportReturn), + ImportMemory(ImportMemory), + ImportTable(ImportTable), + ImportGlobal(ImportGlobal), + ImportFunc(ImportFunc), + FuncEntry(FuncEntry), + FuncReturn(FuncReturn), +} + +pub struct Load { + idx: i32, + name: String, + offset: i32, + data: Vec, +} +pub struct MemGrow { + idx: i32, + name: String, + amount: i32, +} +pub struct TableGet { + tableidx: i32, + name: String, + idx: i32, + funcidx: i32, + funcname: String, +} +pub struct TableGrow { + idx: i32, + name: String, + amount: i32, +} +pub struct GlobalGet { + idx: i32, + name: String, + value: F64, + valtype: ValType, +} +pub struct ExportCall { + name: String, + params: Vec, +} +pub struct TableCall { + tablename: String, + funcidx: i32, + params: Vec, +} +pub struct ImportCall { + idx: i32, + name: String, +} +pub struct ImportReturn { + idx: i32, + name: String, + results: Vec, +} +pub struct ImportMemory { + idx: i32, + module: String, + name: String, + initial: F64, + maximum: Option, +} +pub struct ImportTable { + idx: i32, + module: String, + name: String, + initial: F64, + maximum: Option, +} +pub struct ImportGlobal { + idx: i32, + module: String, + name: String, + mutable: bool, + initial: F64, + value: ValType, +} +pub struct ImportFunc { + idx: i32, + module: String, + name: String, +} + +pub struct FuncEntry { + idx: i32, + args: Vec, +} +pub struct FuncReturn { + idx: i32, + values: Vec, +} + +fn join_vec(args: &Vec) -> String { + args.iter() + .map(|x| x.to_string()) + .collect::>() + .join(",") +} + +fn parse_number(s: &str) -> Option { + let s = s.trim(); // Remove leading/trailing whitespace + + match s { + "" | "+" | "-" => None, // Handle empty or only sign character + "Infinity" | "+Infinity" => Some(F64(std::f64::INFINITY)), + "-Infinity" => Some(F64(std::f64::NEG_INFINITY)), + _ => { + if let Ok(num) = s.parse::() { + Some(F64(num)) // Handle floats and scientific notation + } else { + None // Not a number + } + } + } +} + +#[test] +fn test_parse_number() { + // problematic case reading the trace generateed by js + let s = "0.7614822387695312"; + assert_ne!(s, parse_number(s).unwrap().to_string()); +} + +impl FromStr for WasmEvent { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + fn split_list(c: &str) -> Vec { + let list = c + .split(',') + .filter_map(|s| parse_number(s)) + .collect::>(); + if list.is_empty() || (list.len() == 1 && list[0].0.is_nan()) { + vec![] + } else { + list + } + } + + let components: Vec<&str> = s.split(';').collect(); + match components[0] { + "IM" => Ok(WasmEvent::ImportMemory(ImportMemory { + idx: components[1].parse().unwrap(), + module: components[2].to_string(), + name: components[3].to_string(), + initial: components[4].parse().unwrap(), + maximum: if components[5].is_empty() { + None + } else { + Some(components[5].parse().unwrap()) + }, + })), + "EC" => Ok(WasmEvent::ExportCall(ExportCall { + name: components[1].to_string(), + params: split_list(components.get(2).unwrap()), + })), + "TC" => Ok(WasmEvent::TableCall(TableCall { + tablename: components[1].to_string(), + funcidx: components[2].parse().unwrap(), + params: split_list(components.get(3).unwrap()), + })), + "ER" => Ok(WasmEvent::ExportReturn), + "IC" => Ok(WasmEvent::ImportCall(ImportCall { + idx: components[1].parse().unwrap(), + name: components[2].to_string(), + })), + "IR" => Ok(WasmEvent::ImportReturn(ImportReturn { + idx: components[1].parse().unwrap(), + name: components[2].to_string(), + results: split_list(components.get(3).unwrap()), + })), + "L" => Ok(WasmEvent::Load(Load { + idx: components[1].parse().unwrap(), + name: components[2].to_string(), + offset: components[3].parse().unwrap(), + data: split_list(components.get(4).unwrap()), + })), + "MG" => Ok(WasmEvent::MemGrow(MemGrow { + idx: components[1].parse().unwrap(), + name: components[2].to_string(), + amount: components[3].parse().unwrap(), + })), + "T" => Ok(WasmEvent::TableGet(TableGet { + tableidx: components[1].parse().unwrap(), + name: components[2].to_string(), + idx: components[3].parse().unwrap(), + funcidx: components[4].parse().unwrap(), + funcname: components[5].to_string(), + })), + "TG" => Ok(WasmEvent::TableGrow(TableGrow { + idx: components[1].parse().unwrap(), + name: components[2].to_string(), + amount: components[3].parse().unwrap(), + })), + "G" => Ok(WasmEvent::GlobalGet(GlobalGet { + idx: components[1].parse().unwrap(), + name: components[2].to_string(), + value: parse_number(components[3]).unwrap(), + valtype: components[4].parse().unwrap(), + })), + "IG" => Ok(WasmEvent::ImportGlobal(ImportGlobal { + idx: components[1].parse().unwrap(), + module: components[2].to_string(), + name: components[3].to_string(), + value: components[4].parse().unwrap(), + mutable: if components[5] == "1" { true } else { false }, + initial: components[6].parse().unwrap(), + })), + "IF" => Ok(WasmEvent::ImportFunc(ImportFunc { + idx: components[1].parse().unwrap(), + module: components[2].parse().unwrap(), + name: components[3].parse().unwrap(), + })), + "IT" => Ok(WasmEvent::ImportTable(ImportTable { + idx: components[1].parse().unwrap(), + module: components[2].parse().unwrap(), + name: components[3].parse().unwrap(), + initial: components[4].parse().unwrap(), + maximum: if components[5].is_empty() { + None + } else { + Some(components[5].parse().unwrap()) + }, + })), + "FE" => Ok(WasmEvent::FuncEntry(FuncEntry { + idx: components[1].parse().unwrap(), + args: split_list(components.get(2).unwrap()), + })), + "FR" => Ok(WasmEvent::FuncReturn(FuncReturn { + idx: components[1].parse().unwrap(), + values: split_list(components.get(2).unwrap()), + })), + _ => Err(Error::new( + ErrorKind::InvalidData, + format!("Unknown event type: {}", components[0]), + )), + } + } +} + +impl Debug for WasmEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WasmEvent::Load(Load { + idx, + name, + offset, + data, + }) => write!(f, "L;{};{};{};{}", idx, name, offset, join_vec(data)), + WasmEvent::MemGrow(MemGrow { idx, name, amount }) => { + write!(f, "MG;{};{};{}", idx, name, amount) + } + WasmEvent::TableGet(TableGet { + tableidx, + name, + idx, + funcidx, + funcname, + }) => write!( + f, + "T;{};{};{};{};{}", + tableidx, name, idx, funcidx, funcname + ), + WasmEvent::TableGrow(TableGrow { idx, name, amount }) => { + write!(f, "MG;{};{};{}", idx, name, amount) + } + WasmEvent::GlobalGet(GlobalGet { + idx, + name, + value, + valtype, + }) => { + write!(f, "G;{};{};{};{:?}", idx, name, value, valtype) + } + WasmEvent::ExportCall(ExportCall { name, params }) => { + write!(f, "EC;{};{}", name, join_vec(params)) + } + WasmEvent::TableCall(TableCall { + tablename, + funcidx, + params, + }) => write!(f, "TC;{};{};{}", tablename, funcidx, join_vec(params)), + WasmEvent::ExportReturn => write!(f, "ER"), + WasmEvent::ImportCall(ImportCall { idx, name }) => write!(f, "IC;{};{}", idx, name), + WasmEvent::ImportReturn(ImportReturn { idx, name, results }) => { + write!(f, "IR;{};{};{}", idx, name, join_vec(results)) + } + WasmEvent::ImportMemory(ImportMemory { + idx, + module, + name, + initial, + maximum, + }) => { + let temp = match maximum { + Some(f) => f.0.to_string(), + None => "".to_owned(), + }; + write!(f, "IM;{};{};{};{};{}", idx, module, name, initial, temp,) + } + WasmEvent::ImportTable(ImportTable { + idx, + module, + name, + initial, + maximum, + }) => { + let temp = match maximum { + Some(f) => f.0.to_string(), + None => "".to_owned(), + }; + write!( + f, + "IT;{};{};{};{};{};{}", + idx, module, name, initial, temp, "anyfunc" + ) + } + WasmEvent::ImportGlobal(ImportGlobal { + idx, + module, + name, + mutable, + initial, + value, + }) => { + write!( + f, + "IG;{};{};{};{:?};{};{}", + idx, + module, + name, + value, + if *mutable { '1' } else { '0' }, + initial + ) + } + WasmEvent::ImportFunc(ImportFunc { idx, module, name }) => { + write!(f, "IF;{};{};{}", idx, module, name) + } + WasmEvent::FuncEntry(FuncEntry { idx, args }) => { + write!(f, "FE;{};{}", idx, join_vec(args)) + } + WasmEvent::FuncReturn(FuncReturn { idx, values }) => { + write!(f, "FR;{};{}", idx, join_vec(values)) + } + } + } +} + +#[test] +fn test_encode_decode() -> std::io::Result<()> { + use super::*; + use std::fs; + use std::io; + use std::io::Read; + use std::io::Write; + use std::io::{BufRead, Seek, SeekFrom}; + use std::path::Path; + use tempfile::tempfile; + + fn visit_dirs(dir: &Path) -> io::Result<()> { + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + visit_dirs(&path)?; + } else { + if path.extension().and_then(|s| s.to_str()) == Some("r3") { + if path.display().to_string().contains("pathfinding") { + println!("skipping problematic case {}", path.display()); + continue; + } + let mut file = fs::File::open(&path)?; + let mut original_contents = Vec::new(); + file.read_to_end(&mut original_contents)?; + + let file = fs::File::open(&path)?; + let reader = io::BufReader::new(file); + let mut tracer = trace::Tracer::new(); + for line in reader.lines() { + let line = line?; + let event = line.parse()?; + tracer.push(event); + } + let mut newfile = tempfile()?; + write!(newfile, "{:?}", tracer)?; + newfile.seek(SeekFrom::Start(0))?; + + let mut new_contents = Vec::new(); + let mut reader = io::BufReader::new(newfile); + reader.read_to_end(&mut new_contents)?; + if !new_contents.is_empty() { + assert_eq!( + &original_contents[..original_contents.len()], + &new_contents[..new_contents.len() - 1], + "File contents do not match for {}", + path.display() + ); + } else { + assert_eq!( + original_contents, + new_contents, + "File contents do not match for {}", + path.display() + ); + } + } + } + } + } + Ok(()) + } + visit_dirs(Path::new("../../tests"))?; + Ok(()) +}