blob: ba7395440bdf639c8c0592bc16e61a5cf88980eb [file] [log] [blame]
Jakub Koturc72d7202020-12-21 17:28:15 +01001#![allow(dead_code)]
2
3use csv::Reader;
4
5use std::env;
6use std::io::{self, Read, Write};
7use std::path::PathBuf;
8use std::process::{self, Command};
9
10static STRANGE: &'static str = include_str!("../examples/data/strange.csv");
11static USPOP: &'static str = include_str!("../examples/data/uspop.csv");
12static USPOP_NULL: &'static str =
13 include_str!("../examples/data/uspop-null.csv");
14static USPOP_LATIN1: &'static [u8] =
15 include_bytes!("../examples/data/uspop-latin1.csv");
16static WORLDPOP: &'static str =
17 include_str!("../examples/data/bench/worldcitiespop.csv");
18static SMALLPOP: &'static str = include_str!("../examples/data/smallpop.csv");
19static SMALLPOP_COLON: &'static str =
20 include_str!("../examples/data/smallpop-colon.csv");
21static SMALLPOP_NO_HEADERS: &'static str =
22 include_str!("../examples/data/smallpop-no-headers.csv");
23
24#[test]
25fn 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]
32fn 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]
39fn 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]
46fn 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]
53fn 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]
60fn 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]
67fn 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]
74fn 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]
81fn tutorial_error_01_errored() {
82 let data = "\
83header1,header2
84foo,bar
85quux,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]
93fn 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]
100fn tutorial_error_02_errored() {
101 let data = "\
102header1,header2
103foo,bar
104quux,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]
112fn 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]
119fn tutorial_error_03_errored() {
120 let data = "\
121header1,header2
122foo,bar
123quux,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]
131fn 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]
138fn tutorial_error_04_errored() {
139 let data = "\
140header1,header2
141foo,bar
142quux,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]
150fn 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]
158fn 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]
165fn 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]
172fn 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]
179fn 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]
187fn 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]
195fn 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]
203fn 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]
211fn 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]
219fn 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]
226fn 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]
234fn 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]
241fn 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]
249fn 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]
256fn 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]
263fn 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]
271fn 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]
279fn 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]
287fn 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]
295fn 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]
302fn 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]
309fn 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]
316fn 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]
323fn 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]
330fn 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]
337fn 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]
344fn 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.
363fn 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.
374fn example_bin_dir() -> PathBuf {
375 debug_dir().join("examples")
376}
377
378/// Return the repo root directory path.
379fn repo_dir() -> PathBuf {
380 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
381}
382
383/// Return the directory containing the example data.
384fn 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.
391fn 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.
400fn 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.
408fn 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
420struct Output {
421 stdout: String,
422 stderr: String,
423 command: String,
424 status: process::ExitStatus,
425}
426
427impl 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.
507fn 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}