| Jakub Kotur | c72d720 | 2020-12-21 17:28:15 +0100 | [diff] [blame] | 1 | #![allow(dead_code)] |
| 2 | |
| 3 | use csv::Reader; |
| 4 | |
| 5 | use std::env; |
| 6 | use std::io::{self, Read, Write}; |
| 7 | use std::path::PathBuf; |
| 8 | use std::process::{self, Command}; |
| 9 | |
| 10 | static STRANGE: &'static str = include_str!("../examples/data/strange.csv"); |
| 11 | static USPOP: &'static str = include_str!("../examples/data/uspop.csv"); |
| 12 | static USPOP_NULL: &'static str = |
| 13 | include_str!("../examples/data/uspop-null.csv"); |
| 14 | static USPOP_LATIN1: &'static [u8] = |
| 15 | include_bytes!("../examples/data/uspop-latin1.csv"); |
| 16 | static WORLDPOP: &'static str = |
| 17 | include_str!("../examples/data/bench/worldcitiespop.csv"); |
| 18 | static SMALLPOP: &'static str = include_str!("../examples/data/smallpop.csv"); |
| 19 | static SMALLPOP_COLON: &'static str = |
| 20 | include_str!("../examples/data/smallpop-colon.csv"); |
| 21 | static SMALLPOP_NO_HEADERS: &'static str = |
| 22 | include_str!("../examples/data/smallpop-no-headers.csv"); |
| 23 | |
| 24 | #[test] |
| 25 | fn cookbook_read_basic() { |
| 26 | let mut cmd = cmd_for_example("cookbook-read-basic"); |
| 27 | let out = cmd_output_with(&mut cmd, SMALLPOP.as_bytes()); |
| 28 | assert_eq!(out.stdout().lines().count(), 10); |
| 29 | } |
| 30 | |
| 31 | #[test] |
| 32 | fn cookbook_read_serde() { |
| 33 | let mut cmd = cmd_for_example("cookbook-read-serde"); |
| 34 | let out = cmd_output_with(&mut cmd, SMALLPOP.as_bytes()); |
| 35 | assert_eq!(out.stdout().lines().count(), 10); |
| 36 | } |
| 37 | |
| 38 | #[test] |
| 39 | fn cookbook_read_colon() { |
| 40 | let mut cmd = cmd_for_example("cookbook-read-colon"); |
| 41 | let out = cmd_output_with(&mut cmd, SMALLPOP_COLON.as_bytes()); |
| 42 | assert_eq!(out.stdout().lines().count(), 10); |
| 43 | } |
| 44 | |
| 45 | #[test] |
| 46 | fn cookbook_read_no_headers() { |
| 47 | let mut cmd = cmd_for_example("cookbook-read-no-headers"); |
| 48 | let out = cmd_output_with(&mut cmd, SMALLPOP_NO_HEADERS.as_bytes()); |
| 49 | assert_eq!(out.stdout().lines().count(), 10); |
| 50 | } |
| 51 | |
| 52 | #[test] |
| 53 | fn cookbook_write_basic() { |
| 54 | let mut cmd = cmd_for_example("cookbook-write-basic"); |
| 55 | let out = cmd_output(&mut cmd); |
| 56 | assert_eq!(out.stdout().lines().count(), 3); |
| 57 | } |
| 58 | |
| 59 | #[test] |
| 60 | fn cookbook_write_serde() { |
| 61 | let mut cmd = cmd_for_example("cookbook-write-serde"); |
| 62 | let out = cmd_output(&mut cmd); |
| 63 | assert_eq!(out.stdout().lines().count(), 3); |
| 64 | } |
| 65 | |
| 66 | #[test] |
| 67 | fn tutorial_setup_01() { |
| 68 | let mut cmd = cmd_for_example("tutorial-setup-01"); |
| 69 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 70 | assert_eq!(out.stdout().lines().count(), 100); |
| 71 | } |
| 72 | |
| 73 | #[test] |
| 74 | fn tutorial_error_01() { |
| 75 | let mut cmd = cmd_for_example("tutorial-error-01"); |
| 76 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 77 | assert_eq!(out.stdout().lines().count(), 100); |
| 78 | } |
| 79 | |
| 80 | #[test] |
| 81 | fn tutorial_error_01_errored() { |
| 82 | let data = "\ |
| 83 | header1,header2 |
| 84 | foo,bar |
| 85 | quux,baz,foobar |
| 86 | "; |
| 87 | let mut cmd = cmd_for_example("tutorial-error-01"); |
| 88 | let out = cmd_output_with(&mut cmd, data.as_bytes()); |
| 89 | assert!(out.stderr().contains("thread 'main' panicked")); |
| 90 | } |
| 91 | |
| 92 | #[test] |
| 93 | fn tutorial_error_02() { |
| 94 | let mut cmd = cmd_for_example("tutorial-error-02"); |
| 95 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 96 | assert_eq!(out.stdout().lines().count(), 100); |
| 97 | } |
| 98 | |
| 99 | #[test] |
| 100 | fn tutorial_error_02_errored() { |
| 101 | let data = "\ |
| 102 | header1,header2 |
| 103 | foo,bar |
| 104 | quux,baz,foobar |
| 105 | "; |
| 106 | let mut cmd = cmd_for_example("tutorial-error-02"); |
| 107 | let out = cmd_output_with(&mut cmd, data.as_bytes()); |
| 108 | assert!(out.stdout_failed().contains("error reading CSV from <stdin>")); |
| 109 | } |
| 110 | |
| 111 | #[test] |
| 112 | fn tutorial_error_03() { |
| 113 | let mut cmd = cmd_for_example("tutorial-error-03"); |
| 114 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 115 | assert_eq!(out.stdout().lines().count(), 100); |
| 116 | } |
| 117 | |
| 118 | #[test] |
| 119 | fn tutorial_error_03_errored() { |
| 120 | let data = "\ |
| 121 | header1,header2 |
| 122 | foo,bar |
| 123 | quux,baz,foobar |
| 124 | "; |
| 125 | let mut cmd = cmd_for_example("tutorial-error-03"); |
| 126 | let out = cmd_output_with(&mut cmd, data.as_bytes()); |
| 127 | assert!(out.stdout_failed().contains("CSV error:")); |
| 128 | } |
| 129 | |
| 130 | #[test] |
| 131 | fn tutorial_error_04() { |
| 132 | let mut cmd = cmd_for_example("tutorial-error-04"); |
| 133 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 134 | assert_eq!(out.stdout().lines().count(), 100); |
| 135 | } |
| 136 | |
| 137 | #[test] |
| 138 | fn tutorial_error_04_errored() { |
| 139 | let data = "\ |
| 140 | header1,header2 |
| 141 | foo,bar |
| 142 | quux,baz,foobar |
| 143 | "; |
| 144 | let mut cmd = cmd_for_example("tutorial-error-04"); |
| 145 | let out = cmd_output_with(&mut cmd, data.as_bytes()); |
| 146 | assert!(out.stdout_failed().contains("CSV error:")); |
| 147 | } |
| 148 | |
| 149 | #[test] |
| 150 | fn tutorial_read_01() { |
| 151 | let mut cmd = cmd_for_example("tutorial-read-01"); |
| 152 | cmd.arg(data_dir().join("uspop.csv")); |
| 153 | let out = cmd_output(&mut cmd); |
| 154 | assert_eq!(out.stdout().lines().count(), 100); |
| 155 | } |
| 156 | |
| 157 | #[test] |
| 158 | fn tutorial_read_headers_01() { |
| 159 | let mut cmd = cmd_for_example("tutorial-read-headers-01"); |
| 160 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 161 | assert_eq!(out.stdout().lines().count(), 101); |
| 162 | } |
| 163 | |
| 164 | #[test] |
| 165 | fn tutorial_read_headers_02() { |
| 166 | let mut cmd = cmd_for_example("tutorial-read-headers-02"); |
| 167 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 168 | assert_eq!(out.stdout().lines().count(), 102); |
| 169 | } |
| 170 | |
| 171 | #[test] |
| 172 | fn tutorial_read_delimiter_01() { |
| 173 | let mut cmd = cmd_for_example("tutorial-read-delimiter-01"); |
| 174 | let out = cmd_output_with(&mut cmd, STRANGE.as_bytes()); |
| 175 | assert_eq!(out.stdout().lines().count(), 6); |
| 176 | } |
| 177 | |
| 178 | #[test] |
| 179 | fn tutorial_read_serde_01() { |
| 180 | let mut cmd = cmd_for_example("tutorial-read-serde-01"); |
| 181 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 182 | assert_eq!(out.stdout().lines().count(), 100); |
| 183 | assert!(out.stdout().lines().all(|x| x.contains("pop:"))); |
| 184 | } |
| 185 | |
| 186 | #[test] |
| 187 | fn tutorial_read_serde_02() { |
| 188 | let mut cmd = cmd_for_example("tutorial-read-serde-02"); |
| 189 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 190 | assert_eq!(out.stdout().lines().count(), 100); |
| 191 | assert!(out.stdout().lines().all(|x| x.starts_with("("))); |
| 192 | } |
| 193 | |
| 194 | #[test] |
| 195 | fn tutorial_read_serde_03() { |
| 196 | let mut cmd = cmd_for_example("tutorial-read-serde-03"); |
| 197 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 198 | assert_eq!(out.stdout().lines().count(), 100); |
| 199 | assert!(out.stdout().lines().all(|x| x.contains("\"City\":"))); |
| 200 | } |
| 201 | |
| 202 | #[test] |
| 203 | fn tutorial_read_serde_04() { |
| 204 | let mut cmd = cmd_for_example("tutorial-read-serde-04"); |
| 205 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 206 | assert_eq!(out.stdout().lines().count(), 100); |
| 207 | assert!(out.stdout().lines().all(|x| x.starts_with("Record { latitude:"))); |
| 208 | } |
| 209 | |
| 210 | #[test] |
| 211 | fn tutorial_read_serde_05_invalid() { |
| 212 | let mut cmd = cmd_for_example("tutorial-read-serde-invalid-01"); |
| 213 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 214 | assert_eq!(out.stdout().lines().count(), 100); |
| 215 | assert!(out.stdout().lines().all(|x| x.starts_with("Record { latitude:"))); |
| 216 | } |
| 217 | |
| 218 | #[test] |
| 219 | fn tutorial_read_serde_05_invalid_errored() { |
| 220 | let mut cmd = cmd_for_example("tutorial-read-serde-invalid-01"); |
| 221 | let out = cmd_output_with(&mut cmd, USPOP_NULL.as_bytes()); |
| 222 | assert!(out.stdout_failed().contains("CSV deserialize error:")); |
| 223 | } |
| 224 | |
| 225 | #[test] |
| 226 | fn tutorial_read_serde_invalid_06() { |
| 227 | let mut cmd = cmd_for_example("tutorial-read-serde-invalid-02"); |
| 228 | let out = cmd_output_with(&mut cmd, USPOP_NULL.as_bytes()); |
| 229 | assert_eq!(out.stdout().lines().count(), 100); |
| 230 | assert!(out.stdout().lines().all(|x| x.starts_with("Record { latitude:"))); |
| 231 | } |
| 232 | |
| 233 | #[test] |
| 234 | fn tutorial_write_01() { |
| 235 | let mut cmd = cmd_for_example("tutorial-write-01"); |
| 236 | let out = cmd_output(&mut cmd); |
| 237 | assert_eq!(out.stdout().lines().count(), 4); |
| 238 | } |
| 239 | |
| 240 | #[test] |
| 241 | fn tutorial_write_delimiter_01() { |
| 242 | let mut cmd = cmd_for_example("tutorial-write-delimiter-01"); |
| 243 | let out = cmd_output(&mut cmd); |
| 244 | assert_eq!(out.stdout().lines().count(), 4); |
| 245 | assert!(out.stdout().lines().all(|x| x.contains('\t'))); |
| 246 | } |
| 247 | |
| 248 | #[test] |
| 249 | fn tutorial_write_serde_01() { |
| 250 | let mut cmd = cmd_for_example("tutorial-write-serde-01"); |
| 251 | let out = cmd_output(&mut cmd); |
| 252 | assert_eq!(out.stdout().lines().count(), 4); |
| 253 | } |
| 254 | |
| 255 | #[test] |
| 256 | fn tutorial_write_serde_02() { |
| 257 | let mut cmd = cmd_for_example("tutorial-write-serde-02"); |
| 258 | let out = cmd_output(&mut cmd); |
| 259 | assert_eq!(out.stdout().lines().count(), 4); |
| 260 | } |
| 261 | |
| 262 | #[test] |
| 263 | fn tutorial_pipeline_search_01() { |
| 264 | let mut cmd = cmd_for_example("tutorial-pipeline-search-01"); |
| 265 | cmd.arg("MA"); |
| 266 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 267 | assert_eq!(out.stdout().lines().count(), 2); |
| 268 | } |
| 269 | |
| 270 | #[test] |
| 271 | fn tutorial_pipeline_search_01_errored() { |
| 272 | let mut cmd = cmd_for_example("tutorial-pipeline-search-01"); |
| 273 | cmd.arg("MA"); |
| 274 | let out = cmd_output_with(&mut cmd, USPOP_LATIN1); |
| 275 | assert!(out.stdout_failed().contains("invalid utf-8")); |
| 276 | } |
| 277 | |
| 278 | #[test] |
| 279 | fn tutorial_pipeline_search_02() { |
| 280 | let mut cmd = cmd_for_example("tutorial-pipeline-search-02"); |
| 281 | cmd.arg("MA"); |
| 282 | let out = cmd_output_with(&mut cmd, USPOP_LATIN1); |
| 283 | assert_eq!(out.stdout().lines().count(), 2); |
| 284 | } |
| 285 | |
| 286 | #[test] |
| 287 | fn tutorial_pipeline_pop_01() { |
| 288 | let mut cmd = cmd_for_example("tutorial-pipeline-pop-01"); |
| 289 | cmd.arg("100000"); |
| 290 | let out = cmd_output_with(&mut cmd, USPOP.as_bytes()); |
| 291 | assert_eq!(out.stdout().lines().count(), 4); |
| 292 | } |
| 293 | |
| 294 | #[test] |
| 295 | fn tutorial_perf_alloc_01() { |
| 296 | let mut cmd = cmd_for_example("tutorial-perf-alloc-01"); |
| 297 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 298 | assert_eq!(out.stdout(), "11\n"); |
| 299 | } |
| 300 | |
| 301 | #[test] |
| 302 | fn tutorial_perf_alloc_02() { |
| 303 | let mut cmd = cmd_for_example("tutorial-perf-alloc-02"); |
| 304 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 305 | assert_eq!(out.stdout(), "11\n"); |
| 306 | } |
| 307 | |
| 308 | #[test] |
| 309 | fn tutorial_perf_alloc_03() { |
| 310 | let mut cmd = cmd_for_example("tutorial-perf-alloc-03"); |
| 311 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 312 | assert_eq!(out.stdout(), "11\n"); |
| 313 | } |
| 314 | |
| 315 | #[test] |
| 316 | fn tutorial_perf_serde_01() { |
| 317 | let mut cmd = cmd_for_example("tutorial-perf-serde-01"); |
| 318 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 319 | assert_eq!(out.stdout(), "11\n"); |
| 320 | } |
| 321 | |
| 322 | #[test] |
| 323 | fn tutorial_perf_serde_02() { |
| 324 | let mut cmd = cmd_for_example("tutorial-perf-serde-02"); |
| 325 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 326 | assert_eq!(out.stdout(), "11\n"); |
| 327 | } |
| 328 | |
| 329 | #[test] |
| 330 | fn tutorial_perf_serde_03() { |
| 331 | let mut cmd = cmd_for_example("tutorial-perf-serde-03"); |
| 332 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 333 | assert_eq!(out.stdout(), "11\n"); |
| 334 | } |
| 335 | |
| 336 | #[test] |
| 337 | fn tutorial_perf_core_01() { |
| 338 | let mut cmd = cmd_for_example("tutorial-perf-core-01"); |
| 339 | let out = cmd_output_with(&mut cmd, WORLDPOP.as_bytes()); |
| 340 | assert_eq!(out.stdout(), "11\n"); |
| 341 | } |
| 342 | |
| 343 | #[test] |
| 344 | fn no_infinite_loop_on_io_errors() { |
| 345 | struct FailingRead; |
| 346 | impl Read for FailingRead { |
| 347 | fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> { |
| 348 | Err(io::Error::new(io::ErrorKind::Other, "Broken reader")) |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | let mut record_results = Reader::from_reader(FailingRead).into_records(); |
| 353 | let first_result = record_results.next(); |
| 354 | assert!( |
| 355 | matches!(&first_result, Some(Err(e)) if matches!(e.kind(), csv::ErrorKind::Io(_))) |
| 356 | ); |
| 357 | assert!(record_results.next().is_none()); |
| 358 | } |
| 359 | |
| 360 | // Helper functions follow. |
| 361 | |
| 362 | /// Return the target/debug directory path. |
| 363 | fn debug_dir() -> PathBuf { |
| 364 | env::current_exe() |
| 365 | .expect("test binary path") |
| 366 | .parent() |
| 367 | .expect("test binary directory") |
| 368 | .parent() |
| 369 | .expect("example binary directory") |
| 370 | .to_path_buf() |
| 371 | } |
| 372 | |
| 373 | /// Return the directory containing the example test binaries. |
| 374 | fn example_bin_dir() -> PathBuf { |
| 375 | debug_dir().join("examples") |
| 376 | } |
| 377 | |
| 378 | /// Return the repo root directory path. |
| 379 | fn repo_dir() -> PathBuf { |
| 380 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) |
| 381 | } |
| 382 | |
| 383 | /// Return the directory containing the example data. |
| 384 | fn data_dir() -> PathBuf { |
| 385 | repo_dir().join("examples").join("data") |
| 386 | } |
| 387 | |
| 388 | /// Return a command ready to execute the given example test binary. |
| 389 | /// |
| 390 | /// The command's current directory is set to the repo root. |
| 391 | fn cmd_for_example(name: &str) -> Command { |
| 392 | let mut cmd = Command::new(example_bin_dir().join(name)); |
| 393 | cmd.current_dir(repo_dir()); |
| 394 | cmd |
| 395 | } |
| 396 | |
| 397 | /// Return the (stdout, stderr) of running the command as a string. |
| 398 | /// |
| 399 | /// If the command has a non-zero exit code, then this function panics. |
| 400 | fn cmd_output(cmd: &mut Command) -> Output { |
| 401 | cmd.stdout(process::Stdio::piped()); |
| 402 | cmd.stderr(process::Stdio::piped()); |
| 403 | let child = cmd.spawn().expect("command spawns successfully"); |
| 404 | Output::new(cmd, child) |
| 405 | } |
| 406 | |
| 407 | /// Like cmd_output, but sends the given data as stdin to the given child. |
| 408 | fn cmd_output_with(cmd: &mut Command, data: &[u8]) -> Output { |
| 409 | cmd.stdin(process::Stdio::piped()); |
| 410 | cmd.stdout(process::Stdio::piped()); |
| 411 | cmd.stderr(process::Stdio::piped()); |
| 412 | let mut child = cmd.spawn().expect("command spawns successfully"); |
| 413 | { |
| 414 | let stdin = child.stdin.as_mut().expect("failed to get stdin"); |
| 415 | stdin.write_all(data).expect("failed to write to stdin"); |
| 416 | } |
| 417 | Output::new(cmd, child) |
| 418 | } |
| 419 | |
| 420 | struct Output { |
| 421 | stdout: String, |
| 422 | stderr: String, |
| 423 | command: String, |
| 424 | status: process::ExitStatus, |
| 425 | } |
| 426 | |
| 427 | impl Output { |
| 428 | /// Return the (stdout, stderr) of running the given child as a string. |
| 429 | /// |
| 430 | /// If the command has a non-zero exit code, then this function panics. |
| 431 | fn new(cmd: &mut Command, child: process::Child) -> Output { |
| 432 | let out = child.wait_with_output().expect("command runs successfully"); |
| 433 | let stdout = |
| 434 | String::from_utf8(out.stdout).expect("valid utf-8 (stdout)"); |
| 435 | let stderr = |
| 436 | String::from_utf8(out.stderr).expect("valid utf-8 (stderr)"); |
| 437 | Output { |
| 438 | stdout: stdout, |
| 439 | stderr: stderr, |
| 440 | command: format!("{:?}", cmd), |
| 441 | status: out.status, |
| 442 | } |
| 443 | } |
| 444 | |
| 445 | fn stdout(&self) -> &str { |
| 446 | if !self.status.success() { |
| 447 | panic!( |
| 448 | "\n\n==== {:?} ====\n\ |
| 449 | command failed but expected success!\ |
| 450 | \n\ncwd: {}\ |
| 451 | \n\nstatus: {}\ |
| 452 | \n\nstdout: {}\ |
| 453 | \n\nstderr: {}\ |
| 454 | \n\n=====\n", |
| 455 | self.command, |
| 456 | repo_dir().display(), |
| 457 | self.status, |
| 458 | self.stdout, |
| 459 | self.stderr |
| 460 | ); |
| 461 | } |
| 462 | &self.stdout |
| 463 | } |
| 464 | |
| 465 | fn stdout_failed(&self) -> &str { |
| 466 | if self.status.success() { |
| 467 | panic!( |
| 468 | "\n\n==== {:?} ====\n\ |
| 469 | command succeeded but expected failure!\ |
| 470 | \n\ncwd: {}\ |
| 471 | \n\nstatus: {}\ |
| 472 | \n\nstdout: {}\ |
| 473 | \n\nstderr: {}\ |
| 474 | \n\n=====\n", |
| 475 | self.command, |
| 476 | repo_dir().display(), |
| 477 | self.status, |
| 478 | self.stdout, |
| 479 | self.stderr |
| 480 | ); |
| 481 | } |
| 482 | &self.stdout |
| 483 | } |
| 484 | |
| 485 | fn stderr(&self) -> &str { |
| 486 | if self.status.success() { |
| 487 | panic!( |
| 488 | "\n\n==== {:?} ====\n\ |
| 489 | command succeeded but expected failure!\ |
| 490 | \n\ncwd: {}\ |
| 491 | \n\nstatus: {}\ |
| 492 | \n\nstdout: {}\ |
| 493 | \n\nstderr: {}\ |
| 494 | \n\n=====\n", |
| 495 | self.command, |
| 496 | repo_dir().display(), |
| 497 | self.status, |
| 498 | self.stdout, |
| 499 | self.stderr |
| 500 | ); |
| 501 | } |
| 502 | &self.stderr |
| 503 | } |
| 504 | } |
| 505 | |
| 506 | /// Consume the reader given into a string. |
| 507 | fn read_to_string<R: io::Read>(mut rdr: R) -> String { |
| 508 | let mut s = String::new(); |
| 509 | rdr.read_to_string(&mut s).unwrap(); |
| 510 | s |
| 511 | } |