disktest.rs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. // -*- coding: utf-8 -*-
  2. //
  3. // disktest - Storage tester
  4. //
  5. // Copyright 2020-2024 Michael Büsch <m@bues.ch>
  6. //
  7. // Licensed under the Apache License version 2.0
  8. // or the MIT license, at your option.
  9. // SPDX-License-Identifier: Apache-2.0 OR MIT
  10. //
  11. use crate::stream_aggregator::{DtStreamAgg, DtStreamAggChunk};
  12. use crate::util::{prettybytes, Hhmmss};
  13. use anyhow as ah;
  14. use chrono::prelude::*;
  15. use disktest_rawio::{RawIo, RawIoResult, DEFAULT_SECTOR_SIZE};
  16. use movavg::MovAvg;
  17. use std::cmp::min;
  18. use std::path::{Path, PathBuf};
  19. use std::sync::atomic::{AtomicBool, Ordering};
  20. use std::sync::Arc;
  21. use std::thread::available_parallelism;
  22. use std::time::Instant;
  23. pub use crate::stream_aggregator::DtStreamType;
  24. const LOG_BYTE_THRES: u64 = 1024 * 1024;
  25. const LOG_SEC_THRES: u64 = 10;
  26. /// Disktest console print verbosity.
  27. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
  28. pub enum DisktestQuiet {
  29. /// Print all messages to the console.
  30. Normal = 0,
  31. /// Be less verbose when printing to the console.
  32. Reduced = 1,
  33. /// Only print warnings to the console.
  34. NoInfo = 2,
  35. /// Do not print anything to the console.
  36. NoWarn = 3,
  37. }
  38. /// Handle for a device or file to write to and/or read from.
  39. pub struct DisktestFile {
  40. path: PathBuf,
  41. read: bool,
  42. write: bool,
  43. io: Option<RawIo>,
  44. drop_offset: u64,
  45. drop_count: u64,
  46. quiet_level: DisktestQuiet,
  47. }
  48. impl DisktestFile {
  49. /// Open a file for use by the Disktest core.
  50. pub fn open(path: &Path, read: bool, write: bool) -> ah::Result<DisktestFile> {
  51. Ok(DisktestFile {
  52. path: path.to_path_buf(),
  53. read,
  54. write,
  55. io: None,
  56. drop_offset: 0,
  57. drop_count: 0,
  58. quiet_level: DisktestQuiet::Normal,
  59. })
  60. }
  61. fn do_open(&mut self) -> ah::Result<()> {
  62. if self.io.is_none() {
  63. self.io = Some(RawIo::new(&self.path, self.write, self.read, self.write)?);
  64. self.drop_offset = 0;
  65. self.drop_count = 0;
  66. }
  67. Ok(())
  68. }
  69. /// Close the file and try to drop all write caches.
  70. fn close(&mut self) -> ah::Result<()> {
  71. let drop_offset = self.drop_offset;
  72. let drop_count = self.drop_count;
  73. self.drop_offset += drop_count;
  74. self.drop_count = 0;
  75. // Take and destruct the RawIo object.
  76. if let Some(mut io) = self.io.take() {
  77. // If bytes have been written, try to drop the operating system caches.
  78. if drop_count > 0 {
  79. if let Err(e) = io.drop_file_caches(drop_offset, drop_count) {
  80. return Err(ah::format_err!("Cache drop error: {}", e));
  81. }
  82. } else {
  83. io.close()?;
  84. }
  85. }
  86. Ok(())
  87. }
  88. /// Get the device's physical sector size.
  89. fn get_sector_size(&mut self) -> ah::Result<Option<u32>> {
  90. self.do_open()?;
  91. let io = self.io.as_ref().expect("get_sector_size: No file.");
  92. Ok(io.get_sector_size())
  93. }
  94. /// Flush written data and seek to a position in the file.
  95. fn seek(&mut self, offset: u64) -> ah::Result<u64> {
  96. if self.drop_count > 0 {
  97. self.close()?;
  98. }
  99. self.do_open()?;
  100. match self.seek_noflush(offset) {
  101. Ok(x) => {
  102. self.drop_offset = offset;
  103. self.drop_count = 0;
  104. Ok(x)
  105. }
  106. other => other,
  107. }
  108. }
  109. /// Seek to a position in the file.
  110. fn seek_noflush(&mut self, offset: u64) -> ah::Result<u64> {
  111. self.do_open()?;
  112. let io = self.io.as_mut().expect("seek: No file.");
  113. io.seek(offset)
  114. }
  115. /// Sync all written data to disk.
  116. fn sync(&mut self) -> ah::Result<()> {
  117. if let Some(io) = self.io.as_mut() {
  118. io.sync()
  119. } else {
  120. Ok(())
  121. }
  122. }
  123. /// Read data from the file.
  124. fn read(&mut self, buffer: &mut [u8]) -> ah::Result<RawIoResult> {
  125. self.do_open()?;
  126. let io = self.io.as_mut().expect("read: No file.");
  127. io.read(buffer)
  128. }
  129. /// Write data to the file.
  130. fn write(&mut self, buffer: &[u8]) -> ah::Result<RawIoResult> {
  131. self.do_open()?;
  132. let io = self.io.as_mut().expect("write: No file.");
  133. match io.write(buffer) {
  134. Ok(res) => {
  135. self.drop_count += buffer.len() as u64;
  136. Ok(res)
  137. }
  138. Err(e) => Err(e),
  139. }
  140. }
  141. /// Get a reference to the PathBuf in use.
  142. fn get_path(&self) -> &PathBuf {
  143. &self.path
  144. }
  145. }
  146. impl Drop for DisktestFile {
  147. fn drop(&mut self) {
  148. if self.io.is_some() {
  149. if self.quiet_level < DisktestQuiet::NoWarn {
  150. eprintln!("WARNING: File not closed. Closing now...");
  151. }
  152. if let Err(e) = self.close() {
  153. panic!("Failed to drop operating system caches: {}", e);
  154. }
  155. }
  156. }
  157. }
  158. /// Disktest core.
  159. pub struct Disktest {
  160. stream_agg: DtStreamAgg,
  161. abort: Option<Arc<AtomicBool>>,
  162. log_count: u64,
  163. log_time: Instant,
  164. rate_count: u64,
  165. rate_count_start_time: Instant,
  166. rate_avg: MovAvg<u64, u64, 5>,
  167. begin_time: Instant,
  168. quiet_level: DisktestQuiet,
  169. }
  170. impl Disktest {
  171. /// Unlimited max_bytes.
  172. pub const UNLIMITED: u64 = u64::MAX;
  173. /// Create a new Disktest instance.
  174. ///
  175. /// Parameters:
  176. ///
  177. /// - algorithm: Select the algorithm to generate the random data stream
  178. /// that is written to disk.
  179. /// If unsure, use `Default::default()`.
  180. /// - seed: The seed for the random data stream generator.
  181. /// - round_id: The disk test round. Every round gets a unique data stream.
  182. /// If unsure, use `0` (first round).
  183. /// - invert_pattern: Bitwise invert the random data stream.
  184. /// If unsure, use `false` (don't invert).
  185. /// - nr_threads: The number of threads to spawn when generating the random data stream.
  186. /// The special value `0` means: Use all CPUs available in the system.
  187. /// If unsure, use `1` (One CPU).
  188. /// - quiet_level: The console verbosity of the Disktest code. See [DisktestQuiet].
  189. /// - abort: If this optional flag is set to `true` (e.g. by another thread) the Disktest
  190. /// process will abort as soon as possible.
  191. /// If unsure, use `None`.
  192. pub fn new(
  193. algorithm: DtStreamType,
  194. seed: &[u8],
  195. round_id: u64,
  196. invert_pattern: bool,
  197. nr_threads: usize,
  198. quiet_level: DisktestQuiet,
  199. abort: Option<Arc<AtomicBool>>,
  200. ) -> Disktest {
  201. let nr_threads = if nr_threads == 0 {
  202. if let Ok(cpus) = available_parallelism() {
  203. cpus.get()
  204. } else {
  205. 1
  206. }
  207. } else {
  208. nr_threads
  209. };
  210. let now = Instant::now();
  211. Disktest {
  212. stream_agg: DtStreamAgg::new(
  213. algorithm,
  214. seed,
  215. round_id,
  216. invert_pattern,
  217. nr_threads,
  218. quiet_level,
  219. ),
  220. abort,
  221. log_count: 0,
  222. log_time: now,
  223. rate_count: 0,
  224. rate_count_start_time: now,
  225. rate_avg: MovAvg::new(),
  226. begin_time: now,
  227. quiet_level,
  228. }
  229. }
  230. /// Abort was requested by user?
  231. fn abort_requested(&self) -> bool {
  232. if let Some(abort) = &self.abort {
  233. abort.load(Ordering::Relaxed)
  234. } else {
  235. false
  236. }
  237. }
  238. /// Reset logging.
  239. fn log_reset(&mut self) {
  240. let now = Instant::now();
  241. self.log_count = 0;
  242. self.log_time = now;
  243. self.rate_count = 0;
  244. self.rate_count_start_time = now;
  245. self.rate_avg.reset();
  246. self.begin_time = now;
  247. }
  248. /// Log progress.
  249. fn log(&mut self, prefix: &str, inc_processed: usize, abs_processed: u64, final_step: bool) {
  250. // Info logging is enabled?
  251. if self.quiet_level < DisktestQuiet::NoInfo {
  252. // Increment byte count.
  253. // Only if byte count is bigger than threshold, then check time.
  254. // This reduces the number of calls to Instant::now.
  255. self.log_count += inc_processed as u64;
  256. self.rate_count += inc_processed as u64;
  257. if (self.log_count >= LOG_BYTE_THRES && self.quiet_level == DisktestQuiet::Normal)
  258. || final_step
  259. {
  260. // Check if it's time to write the next log entry.
  261. let now = Instant::now();
  262. let expired = now.duration_since(self.log_time).as_secs() >= LOG_SEC_THRES;
  263. if (expired && self.quiet_level == DisktestQuiet::Normal) || final_step {
  264. let tod = Local::now().format("%R");
  265. let dur_elapsed = now - self.begin_time;
  266. let rate = if final_step {
  267. let elapsed_ms = dur_elapsed.as_millis();
  268. if elapsed_ms > 0 {
  269. Some(((abs_processed as u128 * 1000) / elapsed_ms) as u64)
  270. } else {
  271. None
  272. }
  273. } else {
  274. let rate_period_ms = (now - self.rate_count_start_time).as_millis();
  275. if rate_period_ms > 0 {
  276. let rate = ((self.rate_count as u128 * 1000) / rate_period_ms) as u64;
  277. Some(self.rate_avg.feed(rate))
  278. } else {
  279. None
  280. }
  281. };
  282. let rate_string = if let Some(rate) = rate {
  283. format!(" @ {}/s", prettybytes(rate, true, false, false))
  284. } else {
  285. "".to_string()
  286. };
  287. let suffix = if final_step { "." } else { " ..." };
  288. println!(
  289. "[{} / {}] {}{}{}{}",
  290. tod,
  291. dur_elapsed.hhmmss(),
  292. prefix,
  293. prettybytes(abs_processed, true, true, final_step),
  294. rate_string,
  295. suffix
  296. );
  297. self.log_time = now;
  298. self.rate_count_start_time = now;
  299. self.rate_count = 0;
  300. }
  301. self.log_count = 0;
  302. }
  303. }
  304. }
  305. /// Initialize disktest.
  306. fn init(
  307. &mut self,
  308. file: &mut DisktestFile,
  309. prefix: &str,
  310. seek: u64,
  311. max_bytes: u64,
  312. ) -> ah::Result<u64> {
  313. file.quiet_level = self.quiet_level;
  314. self.log_reset();
  315. let sector_size = file.get_sector_size().unwrap_or(None);
  316. if self.quiet_level < DisktestQuiet::NoInfo {
  317. let sector_str = if let Some(sector_size) = sector_size.as_ref() {
  318. format!(
  319. " ({} sectors)",
  320. prettybytes(*sector_size as _, true, false, false),
  321. )
  322. } else {
  323. "".to_string()
  324. };
  325. println!(
  326. "{} {}{}, starting at position {}...",
  327. prefix,
  328. file.get_path().display(),
  329. sector_str,
  330. prettybytes(seek, true, true, false)
  331. );
  332. }
  333. let res = self
  334. .stream_agg
  335. .activate(seek, sector_size.unwrap_or(DEFAULT_SECTOR_SIZE))?;
  336. if let Err(e) = file.seek(res.byte_offset) {
  337. return Err(ah::format_err!(
  338. "File seek to {} failed: {}",
  339. seek,
  340. e.to_string()
  341. ));
  342. }
  343. if let Some(sector_size) = sector_size.as_ref() {
  344. if max_bytes < u64::MAX
  345. && max_bytes % *sector_size as u64 != 0
  346. && self.quiet_level < DisktestQuiet::NoWarn
  347. {
  348. #[cfg(target_os = "windows")]
  349. eprintln!("WARNING: The desired byte count of {} is not a multiple of the sector size {}. \
  350. This might result in a write or read error at the very end.",
  351. prettybytes(max_bytes, true, true, true),
  352. prettybytes(*sector_size as u64, true, true, true));
  353. }
  354. }
  355. Ok(res.chunk_size)
  356. }
  357. /// Finalize and flush writing.
  358. fn write_finalize(
  359. &mut self,
  360. file: &mut DisktestFile,
  361. success: bool,
  362. bytes_written: u64,
  363. ) -> ah::Result<()> {
  364. if self.quiet_level < DisktestQuiet::NoInfo {
  365. println!("Writing stopped. Syncing...");
  366. }
  367. if let Err(e) = file.sync() {
  368. return Err(ah::format_err!("Sync failed: {}", e));
  369. }
  370. self.log(
  371. if success { "Done. Wrote " } else { "Wrote " },
  372. 0,
  373. bytes_written,
  374. true,
  375. );
  376. if let Err(e) = file.close() {
  377. return Err(ah::format_err!(
  378. "Failed to drop operating system caches: {}",
  379. e
  380. ));
  381. }
  382. if success && self.quiet_level < DisktestQuiet::NoInfo {
  383. println!("Successfully dropped file caches.");
  384. }
  385. Ok(())
  386. }
  387. /// Run disktest in write mode.
  388. pub fn write(&mut self, file: DisktestFile, seek: u64, max_bytes: u64) -> ah::Result<u64> {
  389. let mut file = file;
  390. let mut bytes_left = max_bytes;
  391. let mut bytes_written = 0u64;
  392. let write_chunk_size = self.init(&mut file, "Writing", seek, max_bytes)?;
  393. loop {
  394. // Get the next data chunk.
  395. let chunk = self.stream_agg.wait_chunk()?;
  396. let write_len = min(write_chunk_size, bytes_left) as usize;
  397. // Write the chunk to disk.
  398. match file.write(&chunk.get_data()[0..write_len]) {
  399. Ok(RawIoResult::Ok(_)) => (),
  400. Ok(RawIoResult::Enospc) => {
  401. if max_bytes == Disktest::UNLIMITED {
  402. self.write_finalize(&mut file, true, bytes_written)?;
  403. break; // End of device. -> Success.
  404. }
  405. let _ = self.write_finalize(&mut file, false, bytes_written);
  406. return Err(ah::format_err!("Write error: Out of disk space."));
  407. }
  408. Err(e) => {
  409. let _ = self.write_finalize(&mut file, false, bytes_written);
  410. return Err(e);
  411. }
  412. }
  413. // Account for the written bytes.
  414. bytes_written += write_len as u64;
  415. bytes_left -= write_len as u64;
  416. if bytes_left == 0 {
  417. self.write_finalize(&mut file, true, bytes_written)?;
  418. break;
  419. }
  420. self.log("Wrote ", write_len, bytes_written, false);
  421. if self.abort_requested() {
  422. let _ = self.write_finalize(&mut file, false, bytes_written);
  423. return Err(ah::format_err!("Aborted by signal!"));
  424. }
  425. }
  426. Ok(bytes_written)
  427. }
  428. /// Finalize verification.
  429. fn verify_finalize(
  430. &mut self,
  431. file: &mut DisktestFile,
  432. success: bool,
  433. bytes_read: u64,
  434. ) -> ah::Result<()> {
  435. self.log(
  436. if success {
  437. "Done. Verified "
  438. } else {
  439. "Verified "
  440. },
  441. 0,
  442. bytes_read,
  443. true,
  444. );
  445. if let Err(e) = file.close() {
  446. return Err(ah::format_err!("Failed to close device: {}", e));
  447. }
  448. Ok(())
  449. }
  450. /// Handle verification failure.
  451. fn verify_failed(
  452. &mut self,
  453. file: &mut DisktestFile,
  454. read_count: usize,
  455. bytes_read: u64,
  456. buffer: &[u8],
  457. chunk: &DtStreamAggChunk,
  458. ) -> ah::Error {
  459. if let Err(e) = self.verify_finalize(file, false, bytes_read) {
  460. if self.quiet_level < DisktestQuiet::NoWarn {
  461. eprintln!("{}", e);
  462. }
  463. }
  464. for (i, buffer_byte) in buffer.iter().enumerate().take(read_count) {
  465. if *buffer_byte != chunk.get_data()[i] {
  466. let pos = bytes_read + i as u64;
  467. if pos >= 1024 {
  468. return ah::format_err!(
  469. "Data MISMATCH at {}!",
  470. prettybytes(pos, true, true, true)
  471. );
  472. } else {
  473. return ah::format_err!("Data MISMATCH at byte {}!", pos);
  474. }
  475. }
  476. }
  477. panic!("Internal error: verify_failed() no mismatch.");
  478. }
  479. /// Run disktest in verify mode.
  480. pub fn verify(&mut self, file: DisktestFile, seek: u64, max_bytes: u64) -> ah::Result<u64> {
  481. let mut file = file;
  482. let mut bytes_left = max_bytes;
  483. let mut bytes_read = 0u64;
  484. let readbuf_len = self.init(&mut file, "Verifying", seek, max_bytes)? as usize;
  485. let mut buffer = vec![0; readbuf_len];
  486. let mut read_count = 0;
  487. let mut read_len = min(readbuf_len as u64, bytes_left) as usize;
  488. loop {
  489. // Read the next chunk from disk.
  490. match file.read(&mut buffer[read_count..read_count + (read_len - read_count)]) {
  491. Ok(RawIoResult::Ok(n)) => {
  492. read_count += n;
  493. // Check if the read buffer is full, or if we are the the end of the disk.
  494. assert!(read_count <= read_len);
  495. if read_count == read_len || (read_count > 0 && n == 0) {
  496. // Calculate and compare the read buffer to the pseudo random sequence.
  497. let chunk = self.stream_agg.wait_chunk()?;
  498. if buffer[..read_count] != chunk.get_data()[..read_count] {
  499. return Err(self.verify_failed(
  500. &mut file, read_count, bytes_read, &buffer, &chunk,
  501. ));
  502. }
  503. // Account for the read bytes.
  504. bytes_read += read_count as u64;
  505. bytes_left -= read_count as u64;
  506. if bytes_left == 0 {
  507. self.verify_finalize(&mut file, true, bytes_read)?;
  508. break;
  509. }
  510. self.log("Verified ", read_count, bytes_read, false);
  511. read_count = 0;
  512. read_len = min(readbuf_len as u64, bytes_left) as usize;
  513. }
  514. // End of the disk?
  515. if n == 0 {
  516. self.verify_finalize(&mut file, true, bytes_read)?;
  517. break;
  518. }
  519. }
  520. Ok(_) => unreachable!(),
  521. Err(e) => {
  522. let _ = self.verify_finalize(&mut file, false, bytes_read);
  523. return Err(ah::format_err!(
  524. "Read error at {}: {}",
  525. prettybytes(bytes_read, true, true, true),
  526. e
  527. ));
  528. }
  529. };
  530. if self.abort_requested() {
  531. let _ = self.verify_finalize(&mut file, false, bytes_read);
  532. return Err(ah::format_err!("Aborted by signal!"));
  533. }
  534. }
  535. Ok(bytes_read)
  536. }
  537. }
  538. #[cfg(test)]
  539. mod tests {
  540. use super::*;
  541. use crate::generator::{GeneratorChaCha12, GeneratorChaCha20, GeneratorChaCha8, GeneratorCrc};
  542. use std::fs::OpenOptions;
  543. use std::io::{Seek, SeekFrom, Write};
  544. use std::path::PathBuf;
  545. use tempfile::tempdir;
  546. fn run_test(algorithm: DtStreamType, base_size: usize, chunk_factor: usize) {
  547. let tdir = tempdir().unwrap();
  548. let tdir_path = tdir.path();
  549. let mut serial = 0;
  550. let seed = vec![42, 43, 44, 45];
  551. let nr_threads = 2;
  552. let mut dt = Disktest::new(
  553. algorithm,
  554. &seed,
  555. 0,
  556. false,
  557. nr_threads,
  558. DisktestQuiet::Normal,
  559. None,
  560. );
  561. let mk_filepath = |num| {
  562. let mut path = PathBuf::from(tdir_path);
  563. path.push(format!("tmp-{}.img", num));
  564. path
  565. };
  566. let mk_file = |num, create| {
  567. let path = mk_filepath(num);
  568. let io = RawIo::new(&path, create, true, true).unwrap();
  569. DisktestFile {
  570. path,
  571. read: true,
  572. write: true,
  573. io: Some(io),
  574. drop_offset: 0,
  575. drop_count: 0,
  576. quiet_level: DisktestQuiet::Normal,
  577. }
  578. };
  579. // Write a couple of bytes and verify them.
  580. {
  581. let nr_bytes = 1000;
  582. assert_eq!(
  583. dt.write(mk_file(serial, true), 0, nr_bytes).unwrap(),
  584. nr_bytes
  585. );
  586. assert_eq!(
  587. dt.verify(mk_file(serial, false), 0, u64::MAX).unwrap(),
  588. nr_bytes
  589. );
  590. serial += 1;
  591. }
  592. // Write a couple of bytes and verify half of them.
  593. {
  594. let nr_bytes = 1000;
  595. assert_eq!(
  596. dt.write(mk_file(serial, true), 0, nr_bytes).unwrap(),
  597. nr_bytes
  598. );
  599. assert_eq!(
  600. dt.verify(mk_file(serial, false), 0, nr_bytes / 2).unwrap(),
  601. nr_bytes / 2
  602. );
  603. serial += 1;
  604. }
  605. // Write a big chunk that is aggregated and verify it.
  606. {
  607. let nr_bytes = (base_size * chunk_factor * nr_threads * 2 + 100) as u64;
  608. assert_eq!(
  609. dt.write(mk_file(serial, true), 0, nr_bytes).unwrap(),
  610. nr_bytes
  611. );
  612. assert_eq!(
  613. dt.verify(mk_file(serial, false), 0, u64::MAX).unwrap(),
  614. nr_bytes
  615. );
  616. serial += 1;
  617. }
  618. // Check whether write rewinds the file.
  619. {
  620. let nr_bytes = 1000;
  621. {
  622. let mut f = mk_file(serial, true);
  623. f.io.as_mut().unwrap().set_len(100).unwrap();
  624. f.io.as_mut().unwrap().seek(10).unwrap();
  625. assert_eq!(dt.write(f, 0, nr_bytes).unwrap(), nr_bytes);
  626. }
  627. assert_eq!(
  628. dt.verify(mk_file(serial, false), 0, u64::MAX).unwrap(),
  629. nr_bytes
  630. );
  631. serial += 1;
  632. }
  633. // Modify the written data and assert failure.
  634. {
  635. let nr_bytes = 1000;
  636. assert_eq!(
  637. dt.write(mk_file(serial, true), 0, nr_bytes).unwrap(),
  638. nr_bytes
  639. );
  640. {
  641. let path = mk_filepath(serial);
  642. let mut file = OpenOptions::new()
  643. .read(true)
  644. .write(true)
  645. .open(path)
  646. .unwrap();
  647. file.seek(SeekFrom::Start(10)).unwrap();
  648. writeln!(&file, "X").unwrap();
  649. }
  650. match dt.verify(mk_file(serial, false), 0, nr_bytes) {
  651. Ok(_) => panic!("Verify of modified data did not fail!"),
  652. Err(e) => assert_eq!(e.to_string(), "Data MISMATCH at byte 10!"),
  653. }
  654. serial += 1;
  655. }
  656. // Check verify with seek.
  657. {
  658. let nr_bytes = (base_size * chunk_factor * nr_threads * 10) as u64;
  659. assert_eq!(
  660. dt.write(mk_file(serial, true), 0, nr_bytes).unwrap(),
  661. nr_bytes
  662. );
  663. for offset in (0..nr_bytes).step_by(base_size * chunk_factor / 2) {
  664. let bytes_verified = dt.verify(mk_file(serial, false), offset, u64::MAX).unwrap();
  665. assert!(bytes_verified > 0 && bytes_verified <= nr_bytes);
  666. }
  667. serial += 1;
  668. }
  669. // Check write with seek.
  670. {
  671. let nr_bytes = (base_size * chunk_factor * nr_threads * 10) as u64;
  672. assert_eq!(
  673. dt.write(mk_file(serial, true), 0, nr_bytes).unwrap(),
  674. nr_bytes
  675. );
  676. let offset = (base_size * chunk_factor * nr_threads * 2) as u64;
  677. assert_eq!(
  678. dt.write(mk_file(serial, false), offset, nr_bytes).unwrap(),
  679. nr_bytes
  680. );
  681. assert_eq!(
  682. dt.verify(mk_file(serial, false), 0, u64::MAX).unwrap(),
  683. nr_bytes + offset
  684. );
  685. //serial += 1;
  686. }
  687. tdir.close().unwrap();
  688. }
  689. #[test]
  690. fn test_chacha8() {
  691. run_test(
  692. DtStreamType::ChaCha8,
  693. GeneratorChaCha8::BASE_SIZE,
  694. GeneratorChaCha8::DEFAULT_CHUNK_FACTOR,
  695. );
  696. }
  697. #[test]
  698. fn test_chacha12() {
  699. run_test(
  700. DtStreamType::ChaCha12,
  701. GeneratorChaCha12::BASE_SIZE,
  702. GeneratorChaCha12::DEFAULT_CHUNK_FACTOR,
  703. );
  704. }
  705. #[test]
  706. fn test_chacha20() {
  707. run_test(
  708. DtStreamType::ChaCha20,
  709. GeneratorChaCha20::BASE_SIZE,
  710. GeneratorChaCha20::DEFAULT_CHUNK_FACTOR,
  711. );
  712. }
  713. #[test]
  714. fn test_crc() {
  715. run_test(
  716. DtStreamType::Crc,
  717. GeneratorCrc::BASE_SIZE,
  718. GeneratorCrc::DEFAULT_CHUNK_FACTOR,
  719. );
  720. }
  721. }
  722. // vim: ts=4 sw=4 expandtab