├── .gitignore ├── Cargo.toml ├── Cargo.lock ├── .vscode └── launch.json ├── README.md └── src ├── main.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "espflash-coredump" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "espflash-coredump" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'espflash-coredump'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=espflash-coredump", 15 | "--package=espflash-coredump" 16 | ], 17 | "filter": { 18 | "name": "espflash-coredump", 19 | "kind": "bin" 20 | } 21 | }, 22 | "args": [], 23 | "cwd": "${workspaceFolder}" 24 | }, 25 | { 26 | "type": "lldb", 27 | "request": "launch", 28 | "name": "Debug unit tests in executable 'espflash-coredump'", 29 | "cargo": { 30 | "args": [ 31 | "test", 32 | "--no-run", 33 | "--bin=espflash-coredump", 34 | "--package=espflash-coredump" 35 | ], 36 | "filter": { 37 | "name": "espflash-coredump", 38 | "kind": "bin" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # espflash-coredump 2 | 3 | This is a convenience espflash log-processor for the `coredump*` feature in `esp-backtrace`. (see https://github.com/esp-rs/esp-hal/pull/2672) 4 | 5 | You need `espflash` commit `4f8a526e23cf16223bd06e391807ae5fb7f18913` or later. (i.e. a version later than `3.2.0`) 6 | 7 | ## Example 8 | 9 | ```text 10 | ❯ espflash flash --monitor --processors=espflash-coredump examples\target\riscv32imac-unknown-none-elf\release\gpio_interrupt 11 | [2024-12-04T12:25:19Z INFO ] Serial port: 'COM13' 12 | [2024-12-04T12:25:19Z INFO ] Connecting... 13 | [2024-12-04T12:25:19Z INFO ] Using flash stub 14 | [2024-12-04T12:25:20Z WARN ] Setting baud rate higher than 115,200 can cause issues 15 | Chip type: esp32c6 (revision v0.0) 16 | Crystal frequency: 40 MHz 17 | Flash size: 4MB 18 | Features: WiFi 6, BT 5 19 | MAC address: 60:55:f9:f6:01:78 20 | App/part. size: 31,536/4,128,768 bytes, 0.76% 21 | [2024-12-04T12:25:21Z INFO ] Segment at address '0x0' has not changed, skipping write 22 | [2024-12-04T12:25:21Z INFO ] Segment at address '0x8000' has not changed, skipping write 23 | [2024-12-04T12:25:21Z INFO ] Segment at address '0x10000' has not changed, skipping write 24 | [2024-12-04T12:25:21Z INFO ] Flashing has completed! 25 | Commands: 26 | CTRL+R Reset chip 27 | CTRL+C Exit 28 | 29 | ESP-ROM:esp32c6-20220919 30 | Build:Sep 19 2022 31 | rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT) 32 | SPIWP:0xee 33 | mode:DIO, clock div:2 34 | load:0x4086c410,len:0xd48 35 | load:0x4086e610,len:0x2d68 36 | load:0x40875720,len:0x1800 37 | entry 0x4086c410 38 | I (23) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader 39 | I (24) boot: compile time Jun 7 2023 08:02:08 40 | I (25) boot: chip revision: v0.0 41 | I (29) boot.esp32c6: SPI Speed : 40MHz 42 | I (33) boot.esp32c6: SPI Mode : DIO 43 | I (38) boot.esp32c6: SPI Flash Size : 4MB 44 | I (43) boot: Enabling RNG early entropy source... 45 | I (49) boot: Partition Table: 46 | I (52) boot: ## Label Usage Type ST Offset Length 47 | I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000 48 | I (67) boot: 1 phy_init RF data 01 01 0000f000 00001000 49 | I (74) boot: 2 factory factory app 00 00 00010000 003f0000 50 | I (82) boot: End of partition table 51 | I (86) esp_image: segment 0: paddr=00010020 vaddr=42000020 size=058e8h ( 22760) map 52 | I (99) esp_image: segment 1: paddr=00015910 vaddr=40800000 size=0057ch ( 1404) load 53 | I (103) esp_image: segment 2: paddr=00015e94 vaddr=42005e94 size=01124h ( 4388) map 54 | I (112) esp_image: segment 3: paddr=00016fc0 vaddr=4080057c size=00b48h ( 2888) load 55 | I (120) boot: Loaded app from partition at offset 0x10000 56 | I (125) boot: Disabling RNG early entropy source... 57 | 58 | 59 | ====================== PANIC ====================== 60 | panicked at src\bin\gpio_interrupt.rs:55:5: 61 | Panic message here! 62 | 63 | 64 | 65 | 66 | 67 | Receiving coredump ... 68 | Got coredump 69 | Run `riscv32-esp-elf-gdb examples\target\riscv32imac-unknown-none-elf\release\gpio_interrupt coredump.elf` 70 | ``` 71 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::io::{self}; 3 | use std::sync::mpsc::{self, Receiver, Sender}; 4 | use std::thread; 5 | 6 | mod utils; 7 | 8 | use utils::*; 9 | 10 | fn main() { 11 | let args: Vec = std::env::args().collect(); 12 | let elf_file = if args.len() >= 2 { 13 | args[1].clone() 14 | } else { 15 | "".to_string() 16 | }; 17 | 18 | let mut coredump: Vec = Vec::new(); 19 | let mut state = State::Wait; 20 | let mut utf8fixer = Utf8Fixer::new(); 21 | let mut pipe = Tokker::new(vec!["@COREDUMP\n".to_string(), "@ENDCOREDUMP".to_string()]); 22 | let mut pushed_back = None; 23 | 24 | let receiver = spawn_stdin_stream(); 25 | loop { 26 | if let Ok(v) = receiver.try_recv() { 27 | utf8fixer.push(&v); 28 | pipe.push(&utf8fixer.poll()); 29 | } 30 | 31 | let received = pipe.poll(); 32 | 33 | match received { 34 | TokkerPollResult::None => (), 35 | TokkerPollResult::Data(to_print) => match state { 36 | State::Wait => { 37 | print!("{to_print}"); 38 | } 39 | State::Done => (), 40 | State::Receiving => { 41 | let to_convert = if let Some(pushed_back) = pushed_back { 42 | format!("{}{}", pushed_back as char, to_print) 43 | } else { 44 | to_print 45 | }; 46 | pushed_back = None; 47 | 48 | for b in to_convert.chars().collect::>().chunks(2) { 49 | if b.len() != 2 { 50 | pushed_back = Some(b[0]); 51 | break; 52 | } 53 | 54 | let b = 55 | u8::from_str_radix(&format!("{}{}", b[0] as char, b[1] as char), 16) 56 | .unwrap(); 57 | coredump.push(b); 58 | } 59 | } 60 | State::Idle => (), 61 | }, 62 | TokkerPollResult::Token(token) => { 63 | if token == "@COREDUMP\n" { 64 | state = State::Receiving; 65 | println!("\n\nReceiving coredump ..."); 66 | } else if token == "@ENDCOREDUMP" { 67 | state = State::Done; 68 | println!("Got coredump"); 69 | } 70 | } 71 | } 72 | 73 | if matches!(state, State::Done) { 74 | state = State::Idle; 75 | 76 | std::fs::write("./coredump.elf", &coredump).unwrap(); 77 | if coredump[0] != 0x7f || coredump[1] != 0x45 || coredump[2] != 0x4c { 78 | println!("Coredump corrupted"); 79 | } 80 | 81 | if elf_file != "" { 82 | let gdb = if elf_file.contains("-esp32-") { 83 | "xtensa-esp32-elf-gdb" 84 | } else if elf_file.contains("-esp32s2-") { 85 | "xtensa-esp32s2-elf-gdb" 86 | } else if elf_file.contains("-esp32s3-") { 87 | "xtensa-esp32s3-elf-gdb" 88 | } else { 89 | "riscv32-esp-elf-gdb" 90 | }; 91 | 92 | println!("Run `{} {} coredump.elf`", gdb, elf_file); 93 | } else { 94 | println!("Use `riscv32-esp-elf-gdb` or `xtensa-esp32[s2/s3]-elf-gdb` to make use of the coredump."); 95 | } 96 | } 97 | } 98 | } 99 | 100 | #[derive(Debug, PartialEq)] 101 | enum State { 102 | Wait, 103 | Receiving, 104 | Done, 105 | Idle, 106 | } 107 | 108 | pub fn spawn_stdin_stream() -> Receiver> { 109 | let (tx, rx): (Sender>, Receiver>) = mpsc::channel(); 110 | 111 | thread::spawn(move || { 112 | let stdin = io::stdin(); 113 | let mut stdin_lock = stdin.lock(); 114 | 115 | loop { 116 | let mut buffer = [0u8; 1024]; 117 | match stdin_lock.read(&mut buffer) { 118 | Ok(len) => { 119 | let _ = tx.send(Vec::from(&buffer[..len])); 120 | } 121 | Err(_) => (), // Read failure 122 | } 123 | } 124 | }); 125 | 126 | rx 127 | } 128 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq)] 2 | pub enum TokkerPollResult { 3 | None, 4 | Data(String), 5 | Token(String), 6 | } 7 | 8 | #[derive(Debug)] 9 | pub struct Tokker { 10 | in_flight: String, 11 | data_end: usize, 12 | token: Vec, 13 | } 14 | 15 | impl Tokker { 16 | pub fn new(token: Vec) -> Self { 17 | let mut token = token; 18 | token.sort_by_key(|v| v.len() as isize * -1); 19 | 20 | Self { 21 | in_flight: String::new(), 22 | data_end: 0, 23 | token, 24 | } 25 | } 26 | 27 | pub fn push(&mut self, data: &[u8]) { 28 | self.in_flight.push_str(std::str::from_utf8(data).unwrap()); 29 | self.data_end += data.len(); 30 | 31 | let mut keep_end = 0; 32 | for token in &self.token { 33 | for l in 1..token.len() { 34 | if self.in_flight.ends_with(&token[..l]) { 35 | keep_end = usize::max(l, keep_end); 36 | } 37 | } 38 | } 39 | self.data_end = self.in_flight.len() - keep_end; 40 | } 41 | 42 | pub fn poll(&mut self) -> TokkerPollResult { 43 | if self.data_end > 0 { 44 | let res = self.in_flight[..self.data_end].to_string(); 45 | for token in &self.token { 46 | if res.starts_with(token) { 47 | self.in_flight.drain(..token.len()); 48 | self.data_end -= token.len(); 49 | return TokkerPollResult::Token(token.to_string()); 50 | } 51 | } 52 | 53 | let mut found_at_idx = None; 54 | for token in &self.token { 55 | if let Some(index) = res.find(token) { 56 | if let Some(prev) = found_at_idx { 57 | if index < prev { 58 | found_at_idx = Some(index); 59 | } 60 | } else { 61 | found_at_idx = Some(index); 62 | } 63 | } 64 | } 65 | 66 | if let Some(index) = found_at_idx { 67 | let head = self.in_flight[..index].to_string(); 68 | self.in_flight.drain(..index); 69 | self.data_end -= head.len(); 70 | return TokkerPollResult::Data(head); 71 | } 72 | 73 | self.in_flight.drain(..self.data_end); 74 | self.data_end = 0; 75 | TokkerPollResult::Data(res) 76 | } else { 77 | TokkerPollResult::None 78 | } 79 | } 80 | } 81 | 82 | #[derive(Debug)] 83 | pub struct Utf8Fixer { 84 | data: Vec, 85 | } 86 | 87 | impl Utf8Fixer { 88 | pub fn new() -> Self { 89 | Self { data: Vec::new() } 90 | } 91 | 92 | pub fn push(&mut self, data: &[u8]) { 93 | self.data.extend_from_slice(data); 94 | } 95 | 96 | pub fn poll(&mut self) -> Vec { 97 | loop { 98 | if self.data.len() == 0 { 99 | break; 100 | } 101 | 102 | if self.data[0] & 0b1100_0000 == 0b1000_0000 { 103 | self.data.drain(..1); 104 | } else { 105 | break; 106 | } 107 | } 108 | 109 | if self.data.len() == 0 { 110 | return vec![]; 111 | } 112 | 113 | let mut valid = 0; 114 | loop { 115 | if self.data[valid] & 0b1000_0000 == 0b0000_0000 { 116 | // ok - ASCII 117 | valid += 1; 118 | } else if self.data[valid] & 0b1100_0000 == 0b1100_0000 { 119 | // UTF multi byte char first byte 120 | let skip = self.data[valid].leading_ones() as usize; 121 | 122 | if valid + skip <= self.data.len() { 123 | valid += skip as usize; 124 | } else { 125 | break; 126 | } 127 | } else if self.data[valid] & 0b1100_0000 == 0b1000_0000 { 128 | // UTF multi byte char w/o start - will get discarded by the next `poll` 129 | break; 130 | } 131 | 132 | if valid >= self.data.len() { 133 | break; 134 | } 135 | } 136 | 137 | let mut res = Vec::new(); 138 | res.extend_from_slice(&self.data[..valid]); 139 | self.data.drain(..valid); 140 | res 141 | } 142 | } 143 | 144 | #[cfg(test)] 145 | mod test { 146 | use super::*; 147 | 148 | #[test] 149 | fn test0() { 150 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 151 | 152 | stream.push(b"diwjeodj wefijweoifj weoifjweiojf owefiojwo"); 153 | assert_eq!( 154 | TokkerPollResult::Data("diwjeodj wefijweoifj weoifjweiojf owefiojwo".to_string()), 155 | stream.poll() 156 | ); 157 | assert_eq!(TokkerPollResult::None, stream.poll()); 158 | } 159 | 160 | #[test] 161 | fn test1() { 162 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 163 | 164 | stream.push(b"abcd"); 165 | stream.push(b"@CORE"); 166 | 167 | assert_eq!(TokkerPollResult::Data("abcd".to_string()), stream.poll()); 168 | assert_eq!(TokkerPollResult::None, stream.poll()); 169 | assert_eq!(TokkerPollResult::None, stream.poll()); 170 | assert_eq!(TokkerPollResult::None, stream.poll()); 171 | 172 | stream.push(b"DUMP"); 173 | assert_eq!(TokkerPollResult::None, stream.poll()); 174 | 175 | stream.push(b"\n"); 176 | assert_eq!( 177 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 178 | stream.poll() 179 | ); 180 | 181 | stream.push(b"foo"); 182 | assert_eq!(TokkerPollResult::Data("foo".to_string()), stream.poll()); 183 | } 184 | 185 | #[test] 186 | fn test2() { 187 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 188 | 189 | stream.push(b"abcd"); 190 | stream.push(b"@CORE"); 191 | 192 | assert_eq!(TokkerPollResult::Data("abcd".to_string()), stream.poll()); 193 | assert_eq!(TokkerPollResult::None, stream.poll()); 194 | assert_eq!(TokkerPollResult::None, stream.poll()); 195 | assert_eq!(TokkerPollResult::None, stream.poll()); 196 | 197 | stream.push(b"DUMP"); 198 | stream.push(b"\n"); 199 | stream.push(b"foo"); 200 | 201 | assert_eq!( 202 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 203 | stream.poll() 204 | ); 205 | assert_eq!(TokkerPollResult::Data("foo".to_string()), stream.poll()); 206 | } 207 | 208 | #[test] 209 | fn test3() { 210 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 211 | 212 | stream.push(b"abcd"); 213 | stream.push(b"@CORE"); 214 | 215 | assert_eq!(TokkerPollResult::Data("abcd".to_string()), stream.poll()); 216 | assert_eq!(TokkerPollResult::None, stream.poll()); 217 | assert_eq!(TokkerPollResult::None, stream.poll()); 218 | assert_eq!(TokkerPollResult::None, stream.poll()); 219 | 220 | stream.push(b"abcd"); 221 | assert_eq!( 222 | TokkerPollResult::Data("@COREabcd".to_string()), 223 | stream.poll() 224 | ); 225 | } 226 | 227 | #[test] 228 | fn test4() { 229 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 230 | 231 | stream.push(b"wehdihweuidhweuihduiwehduiwehduiweh@COREDUMP\nabcdwejdjweiodjoiwejdiowejiowjiodjweiojdiowjediwejwd"); 232 | assert_eq!( 233 | TokkerPollResult::Data("wehdihweuidhweuihduiwehduiwehduiweh".to_string()), 234 | stream.poll() 235 | ); 236 | assert_eq!( 237 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 238 | stream.poll() 239 | ); 240 | assert_eq!( 241 | TokkerPollResult::Data( 242 | "abcdwejdjweiodjoiwejdiowejiowjiodjweiojdiowjediwejwd".to_string() 243 | ), 244 | stream.poll() 245 | ); 246 | assert_eq!(TokkerPollResult::None, stream.poll()); 247 | } 248 | 249 | #[test] 250 | fn test5() { 251 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 252 | 253 | stream.push(b"wehdihweuidhweuihduiwehduiwehduiweh@COREDUMP\nabcdwejdjweiodjoiwejdiowejiowjiodjweiojd@FOOiowjediwejwd"); 254 | assert_eq!( 255 | TokkerPollResult::Data("wehdihweuidhweuihduiwehduiwehduiweh".to_string()), 256 | stream.poll() 257 | ); 258 | assert_eq!( 259 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 260 | stream.poll() 261 | ); 262 | assert_eq!( 263 | TokkerPollResult::Data("abcdwejdjweiodjoiwejdiowejiowjiodjweiojd".to_string()), 264 | stream.poll() 265 | ); 266 | assert_eq!(TokkerPollResult::Token("@FOO".to_string()), stream.poll()); 267 | assert_eq!( 268 | TokkerPollResult::Data("iowjediwejwd".to_string()), 269 | stream.poll() 270 | ); 271 | assert_eq!(TokkerPollResult::None, stream.poll()); 272 | } 273 | 274 | #[test] 275 | fn test6() { 276 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@FOO".to_string()]); 277 | 278 | stream.push(b"wehdihweuidhweuihduiwehduiwehduiweh@FOOabcdwejdjweiodjoiwejdiowejiowjiodjweiojd@COREDUMP\niowjediwejwd"); 279 | assert_eq!( 280 | TokkerPollResult::Data("wehdihweuidhweuihduiwehduiwehduiweh".to_string()), 281 | stream.poll() 282 | ); 283 | assert_eq!(TokkerPollResult::Token("@FOO".to_string()), stream.poll()); 284 | assert_eq!( 285 | TokkerPollResult::Data("abcdwejdjweiodjoiwejdiowejiowjiodjweiojd".to_string()), 286 | stream.poll() 287 | ); 288 | assert_eq!( 289 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 290 | stream.poll() 291 | ); 292 | assert_eq!( 293 | TokkerPollResult::Data("iowjediwejwd".to_string()), 294 | stream.poll() 295 | ); 296 | assert_eq!(TokkerPollResult::None, stream.poll()); 297 | } 298 | 299 | #[test] 300 | fn test7() { 301 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@ENDCOREDUMP".to_string()]); 302 | 303 | stream.push(b"wehdihweuidhweuihduiwehduiwehduiweh@COREDUMP\nabcdwejdjweiodjoiwejdiowejiowjiodjweiojd"); 304 | assert_eq!( 305 | TokkerPollResult::Data("wehdihweuidhweuihduiwehduiwehduiweh".to_string()), 306 | stream.poll() 307 | ); 308 | 309 | stream.push(b"@END"); 310 | 311 | assert_eq!( 312 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 313 | stream.poll() 314 | ); 315 | 316 | stream.push(b"COREDUMP"); 317 | 318 | assert_eq!( 319 | TokkerPollResult::Data("abcdwejdjweiodjoiwejdiowejiowjiodjweiojd".to_string()), 320 | stream.poll() 321 | ); 322 | assert_eq!( 323 | TokkerPollResult::Token("@ENDCOREDUMP".to_string()), 324 | stream.poll() 325 | ); 326 | assert_eq!(TokkerPollResult::None, stream.poll()); 327 | } 328 | 329 | #[test] 330 | fn test8() { 331 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@ENDCOREDUMP".to_string()]); 332 | 333 | stream.push(&[b'A'; 1024]); 334 | stream.push(b"@COREDUMP\n"); 335 | stream.push(&[b'A'; 1024]); 336 | 337 | assert_eq!( 338 | TokkerPollResult::Data(std::str::from_utf8(&[b'A'; 1024]).unwrap().to_string()), 339 | stream.poll() 340 | ); 341 | 342 | assert_eq!( 343 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 344 | stream.poll() 345 | ); 346 | 347 | assert_eq!( 348 | TokkerPollResult::Data(std::str::from_utf8(&[b'A'; 1024]).unwrap().to_string()), 349 | stream.poll() 350 | ); 351 | 352 | stream.push(b"@ENDCOREDUMP\n"); 353 | 354 | assert_eq!( 355 | TokkerPollResult::Token("@ENDCOREDUMP".to_string()), 356 | stream.poll() 357 | ); 358 | 359 | assert_eq!(TokkerPollResult::Data("\n".to_string()), stream.poll()); 360 | } 361 | 362 | #[test] 363 | fn test9() { 364 | let mut stream = Tokker::new(vec!["@COREDUMP\n".to_string(), "@ENDCOREDUMP".to_string()]); 365 | 366 | stream.push(b"diwjeodj wefijweoifj weoifjweiojf owefiojwo"); 367 | assert_eq!( 368 | TokkerPollResult::Data("diwjeodj wefijweoifj weoifjweiojf owefiojwo".to_string()), 369 | stream.poll() 370 | ); 371 | 372 | stream.push(b"@COREDUMP\n1234567890"); 373 | assert_eq!( 374 | TokkerPollResult::Token("@COREDUMP\n".to_string()), 375 | stream.poll() 376 | ); 377 | 378 | assert_eq!( 379 | TokkerPollResult::Data("1234567890".to_string()), 380 | stream.poll() 381 | ); 382 | 383 | assert_eq!(TokkerPollResult::None, stream.poll()); 384 | 385 | stream.push(b"000000000000000a54000420a00000005000000000000000000000000000000"); 386 | assert_eq!( 387 | TokkerPollResult::Data("000000000000000a54000420a00000005000000000000000000000000000000".to_string()), 388 | stream.poll() 389 | ); 390 | 391 | assert_eq!(TokkerPollResult::None, stream.poll()); 392 | 393 | 394 | stream.push(b"0"); 395 | assert_eq!( 396 | TokkerPollResult::Data("0".to_string()), 397 | stream.poll() 398 | ); 399 | 400 | assert_eq!(TokkerPollResult::None, stream.poll()); 401 | 402 | 403 | stream.push(b"0000000000000000000000000000000@ENDCOREDUMP\n"); 404 | assert_eq!( 405 | TokkerPollResult::Data("0000000000000000000000000000000".to_string()), 406 | stream.poll() 407 | ); 408 | 409 | assert_eq!( 410 | TokkerPollResult::Token("@ENDCOREDUMP".to_string()), 411 | stream.poll() 412 | ); 413 | 414 | assert_eq!( 415 | TokkerPollResult::Data("\n".to_string()), 416 | stream.poll() 417 | ); 418 | 419 | assert_eq!(TokkerPollResult::None, stream.poll()); 420 | } 421 | 422 | #[test] 423 | fn test0_utf() { 424 | let mut v = Utf8Fixer::new(); 425 | 426 | v.push(&[240, 159, 146, 150]); 427 | assert!(v.poll() == &[240, 159, 146, 150]); 428 | 429 | v.push(&[240]); 430 | assert!(v.poll() == &[]); 431 | v.push(&[159, 146, 150]); 432 | assert!(v.poll() == &[240, 159, 146, 150]); 433 | 434 | v.push(&[240]); 435 | assert!(v.poll() == &[]); 436 | v.push(&[159]); 437 | assert!(v.poll() == &[]); 438 | v.push(&[146]); 439 | assert!(v.poll() == &[]); 440 | v.push(&[150]); 441 | 442 | assert!(v.poll() == &[240, 159, 146, 150]); 443 | } 444 | 445 | #[test] 446 | fn test1_utf() { 447 | let mut v = Utf8Fixer::new(); 448 | 449 | v.push(&[159, 146, 150]); 450 | assert!(v.poll() == &[]); 451 | 452 | v.push(b"Hello World"); 453 | assert!(v.poll() == b"Hello World"); 454 | assert!(v.poll() == &[]); 455 | assert!(v.poll() == &[]); 456 | assert!(v.poll() == &[]); 457 | } 458 | } 459 | --------------------------------------------------------------------------------