From 76a52447bd15513e17128f468ed00d9869f07c42 Mon Sep 17 00:00:00 2001 From: Rajat Arya Date: Fri, 20 Sep 2024 13:12:09 -0700 Subject: [PATCH 1/2] Consolidate CompressionScheme enum - removed from utils crate - moved from cas_types crate to cas_object --- cas_client/src/data_transport.rs | 45 ++++++-- cas_object/src/cas_chunk_format.rs | 4 +- cas_object/src/cas_object_format.rs | 107 ++++++++++++++++-- .../src/compression_scheme.rs | 33 ++---- cas_object/src/lib.rs | 2 + cas_types/src/lib.rs | 1 - utils/src/compression.rs | 98 ---------------- utils/src/lib.rs | 1 - 8 files changed, 145 insertions(+), 146 deletions(-) rename {cas_types => cas_object}/src/compression_scheme.rs (74%) delete mode 100644 utils/src/compression.rs diff --git a/cas_client/src/data_transport.rs b/cas_client/src/data_transport.rs index d8e04613..51db722a 100644 --- a/cas_client/src/data_transport.rs +++ b/cas_client/src/data_transport.rs @@ -4,11 +4,8 @@ use std::time::Duration; use crate::cas_connection_pool::CasConnectionConfig; use anyhow::{anyhow, Result}; -use cas::common::CompressionScheme; -use cas::compression::{ - multiple_accepted_encoding_header_value, CAS_ACCEPT_ENCODING_HEADER, - CAS_CONTENT_ENCODING_HEADER, CAS_INFLATED_SIZE_HEADER, -}; +use cas_object::CompressionScheme; + use error_printer::ErrorPrinter; use http_body_util::{BodyExt, Full}; use hyper::body::Bytes; @@ -33,16 +30,26 @@ use xet_error::Error; use merklehash::MerkleHash; +const CAS_CONTENT_ENCODING_HEADER: &str = "xet-cas-content-encoding"; +const CAS_ACCEPT_ENCODING_HEADER: &str = "xet-cas-content-encoding"; +const CAS_INFLATED_SIZE_HEADER: &str = "xet-cas-inflated-size"; + const HTTP2_POOL_IDLE_TIMEOUT_SECS: u64 = 30; const HTTP2_KEEPALIVE_MILLIS: u64 = 500; const HTTP2_WINDOW_SIZE: u32 = 2147418112; const NUM_RETRIES: usize = 5; const BASE_RETRY_DELAY_MS: u64 = 3000; +// in the header value, we will consider +fn multiple_accepted_encoding_header_value(list: Vec) -> String { + let as_strs: Vec<&str> = list.iter().map(Into::into).collect(); + as_strs.join(";").to_string() +} + lazy_static! { static ref ACCEPTED_ENCODINGS_HEADER_VALUE: HeaderValue = HeaderValue::from_str( multiple_accepted_encoding_header_value(vec![ - CompressionScheme::Lz4, + CompressionScheme::LZ4, CompressionScheme::None ]) .as_str() @@ -284,7 +291,7 @@ impl DataTransport { let bytes = maybe_decode(bytes.as_slice(), encoding, uncompressed_size)?; debug!( "GET; encoding: ({}), uncompressed size: ({}), payload ({}) prefix: ({}), hash: ({})", - encoding.as_str_name(), + encoding, uncompressed_size.unwrap_or_default(), payload_size, prefix, @@ -344,7 +351,7 @@ impl DataTransport { .to_vec(); let payload_size = bytes.len(); let bytes = maybe_decode(bytes.as_slice(), encoding, uncompressed_size)?; - debug!("GET RANGE; encoding: ({}), uncompressed size: ({}), payload ({}) prefix: ({}), hash: ({})", encoding.as_str_name(), uncompressed_size.unwrap_or_default(), payload_size, prefix, hash); + debug!("GET RANGE; encoding: ({}), uncompressed size: ({}), payload ({}) prefix: ({}), hash: ({})", encoding, uncompressed_size.unwrap_or_default(), payload_size, prefix, hash); Ok(bytes.to_vec()) }, is_status_retriable_and_print, @@ -367,7 +374,7 @@ impl DataTransport { let data = maybe_encode(data, encoding)?; debug!( "PUT; encoding: ({}), uncompressed size: ({}), payload: ({}), prefix: ({}), hash: ({})", - encoding.as_str_name(), + encoding, full_size, data.len(), prefix, @@ -423,7 +430,7 @@ fn maybe_decode<'a, T: Into<&'a [u8]>>( encoding: CompressionScheme, uncompressed_size: Option, ) -> Result> { - if let CompressionScheme::Lz4 = encoding { + if let CompressionScheme::LZ4 = encoding { if uncompressed_size.is_none() { return Err(anyhow!( "Missing uncompressed size when attempting to decompress LZ4" @@ -447,7 +454,7 @@ fn get_encoding_info(response: &Response) -> Option<(CompressionScheme, Op } fn maybe_encode<'a, T: Into<&'a [u8]>>(data: T, encoding: CompressionScheme) -> Result> { - if let CompressionScheme::Lz4 = encoding { + if let CompressionScheme::LZ4 = encoding { lz4::block::compress(data.into(), Some(CompressionMode::DEFAULT), false) .log_error("LZ4 compression error") .map_err(|e| anyhow!(e)) @@ -580,4 +587,20 @@ mod tests { assert_eq!(get_header_value(GIT_XET_VERSION_HEADER), git_xet_version); assert_eq!(get_header_value(USER_ID_HEADER), user_id); } + + #[test] + fn test_multiple_accepted_encoding_header_value() { + let multi = vec![CompressionScheme::LZ4, CompressionScheme::None]; + assert_eq!( + multiple_accepted_encoding_header_value(multi), + "lz4;none".to_string() + ); + + let singular = vec![CompressionScheme::LZ4]; + assert_eq!( + multiple_accepted_encoding_header_value(singular), + "lz4".to_string() + ); + } + } diff --git a/cas_object/src/cas_chunk_format.rs b/cas_object/src/cas_chunk_format.rs index 7002ebe7..bd663f40 100644 --- a/cas_object/src/cas_chunk_format.rs +++ b/cas_object/src/cas_chunk_format.rs @@ -6,7 +6,7 @@ use std::{ use crate::error::CasObjectError; use anyhow::anyhow; -use cas_types::compression_scheme::CompressionScheme; +use crate::CompressionScheme; use lz4_flex::frame::{FrameDecoder, FrameEncoder}; pub const CAS_CHUNK_HEADER_LENGTH: u8 = 8; @@ -191,7 +191,7 @@ mod tests { use std::io::Cursor; use super::*; - use cas_types::compression_scheme::CompressionScheme; + use CompressionScheme; use rand::Rng; const COMP_LEN: u32 = 0x010203; diff --git a/cas_object/src/cas_object_format.rs b/cas_object/src/cas_object_format.rs index 258d0f3a..3f1383c3 100644 --- a/cas_object/src/cas_object_format.rs +++ b/cas_object/src/cas_object_format.rs @@ -9,6 +9,7 @@ use std::{ use crate::{ cas_chunk_format::{deserialize_chunk, serialize_chunk}, error::CasObjectError, + CompressionScheme, }; use anyhow::anyhow; @@ -474,11 +475,8 @@ impl CasObject { // now serialize chunk directly to writer (since chunks come first!) // TODO: add compression scheme to this call - let chunk_written_bytes = serialize_chunk( - &chunk_raw_bytes, - writer, - cas_types::compression_scheme::CompressionScheme::None, - )?; + let chunk_written_bytes = + serialize_chunk(&chunk_raw_bytes, writer, CompressionScheme::None)?; total_written_bytes += chunk_written_bytes; let chunk_meta = CasChunkInfo { @@ -554,7 +552,7 @@ mod tests { #[test] fn test_chunk_boundaries_chunk_size_info() { // Arrange - let (c, _cas_data, _raw_data) = build_cas_object(3, 100, false); + let (c, _cas_data, _raw_data) = build_cas_object(3, 100, false, false); // Act & Assert assert_eq!(c.get_chunk_boundaries().len(), 3); assert_eq!(c.get_chunk_boundaries(), [100, 200, 300]); @@ -579,6 +577,7 @@ mod tests { num_chunks: u32, uncompressed_chunk_size: u32, use_random_chunk_size: bool, + use_lz4_compression: bool ) -> (CasObject, Vec, Vec) { let mut c = CasObject::default(); @@ -606,10 +605,15 @@ mod tests { // build chunk, create ChunkInfo and keep going + let compression_scheme = match use_lz4_compression { + true => CompressionScheme::LZ4, + false => CompressionScheme::None + }; + let bytes_written = serialize_chunk( &bytes, &mut writer, - cas_types::compression_scheme::CompressionScheme::None, + compression_scheme, ) .unwrap(); @@ -646,7 +650,7 @@ mod tests { #[test] fn test_basic_serialization_mem() { // Arrange - let (c, _cas_data, raw_data) = build_cas_object(3, 100, false); + let (c, _cas_data, raw_data) = build_cas_object(3, 100, false, false); let mut writer: Cursor> = Cursor::new(Vec::new()); // Act & Assert assert!(CasObject::serialize( @@ -670,7 +674,7 @@ mod tests { #[test] fn test_serialization_deserialization_mem_medium() { // Arrange - let (c, _cas_data, raw_data) = build_cas_object(32, 16384, false); + let (c, _cas_data, raw_data) = build_cas_object(32, 16384, false, false); let mut writer: Cursor> = Cursor::new(Vec::new()); // Act & Assert assert!(CasObject::serialize( @@ -697,7 +701,7 @@ mod tests { #[test] fn test_serialization_deserialization_mem_large_random() { // Arrange - let (c, _cas_data, raw_data) = build_cas_object(32, 65536, true); + let (c, _cas_data, raw_data) = build_cas_object(32, 65536, true, false); let mut writer: Cursor> = Cursor::new(Vec::new()); // Act & Assert assert!(CasObject::serialize( @@ -723,7 +727,87 @@ mod tests { #[test] fn test_serialization_deserialization_file_large_random() { // Arrange - let (c, _cas_data, raw_data) = build_cas_object(256, 65536, true); + let (c, _cas_data, raw_data) = build_cas_object(256, 65536, true, false); + let mut writer: Cursor> = Cursor::new(Vec::new()); + // Act & Assert + assert!(CasObject::serialize( + &mut writer, + &c.info.cashash, + &raw_data, + &c.get_chunk_boundaries(), + ) + .is_ok()); + + let mut reader = writer.clone(); + reader.set_position(0); + let res = CasObject::deserialize(&mut reader); + assert!(res.is_ok()); + + let c2 = res.unwrap(); + assert_eq!(c, c2); + + assert_eq!(c.info.num_chunks, c2.info.num_chunks); + assert_eq!(raw_data, c2.get_all_bytes(&mut reader).unwrap()); + } + + /* + #[test] + fn test_serialization_deserialization_mem_medium_lz4() { + // Arrange + let (c, _cas_data, raw_data) = build_cas_object(32, 16384, false, true); + let mut writer: Cursor> = Cursor::new(Vec::new()); + // Act & Assert + assert!(CasObject::serialize( + &mut writer, + &c.info.cashash, + &raw_data, + &c.get_chunk_boundaries(), + ) + .is_ok()); + + let mut reader = writer.clone(); + reader.set_position(0); + let res = CasObject::deserialize(&mut reader); + assert!(res.is_ok()); + + let c2 = res.unwrap(); + assert_eq!(c, c2); + + let bytes_read = c2.get_all_bytes(&mut reader).unwrap(); + assert_eq!(c.info.num_chunks, c2.info.num_chunks); + assert_eq!(raw_data, bytes_read); + } + + #[test] + fn test_serialization_deserialization_mem_large_random_lz4() { + // Arrange + let (c, _cas_data, raw_data) = build_cas_object(32, 65536, true, true); + let mut writer: Cursor> = Cursor::new(Vec::new()); + // Act & Assert + assert!(CasObject::serialize( + &mut writer, + &c.info.cashash, + &raw_data, + &c.get_chunk_boundaries(), + ) + .is_ok()); + + let mut reader = writer.clone(); + reader.set_position(0); + let res = CasObject::deserialize(&mut reader); + assert!(res.is_ok()); + + let c2 = res.unwrap(); + assert_eq!(c, c2); + + assert_eq!(c.info.num_chunks, c2.info.num_chunks); + assert_eq!(raw_data, c2.get_all_bytes(&mut reader).unwrap()); + } + + #[test] + fn test_serialization_deserialization_file_large_random_lz4() { + // Arrange + let (c, _cas_data, raw_data) = build_cas_object(256, 65536, true, true); let mut writer: Cursor> = Cursor::new(Vec::new()); // Act & Assert assert!(CasObject::serialize( @@ -745,4 +829,5 @@ mod tests { assert_eq!(c.info.num_chunks, c2.info.num_chunks); assert_eq!(raw_data, c2.get_all_bytes(&mut reader).unwrap()); } + */ } diff --git a/cas_types/src/compression_scheme.rs b/cas_object/src/compression_scheme.rs similarity index 74% rename from cas_types/src/compression_scheme.rs rename to cas_object/src/compression_scheme.rs index 440a9dac..ddcd54c1 100644 --- a/cas_types/src/compression_scheme.rs +++ b/cas_object/src/compression_scheme.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use std::str::FromStr; +use std::{fmt::Display, str::FromStr}; #[repr(u8)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] @@ -9,6 +9,15 @@ pub enum CompressionScheme { LZ4 = 1, } +impl Display for CompressionScheme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompressionScheme::None => write!(f, "none"), + CompressionScheme::LZ4 => write!(f, "lz4"), + } + } +} + impl TryFrom for CompressionScheme { type Error = anyhow::Error; @@ -48,15 +57,9 @@ impl FromStr for CompressionScheme { } } -// in the header value, we will consider -pub fn multiple_accepted_encoding_header_value(list: Vec) -> String { - let as_strs: Vec<&str> = list.iter().map(Into::into).collect(); - as_strs.join(";").to_string() -} - #[cfg(test)] mod tests { - use super::{multiple_accepted_encoding_header_value, CompressionScheme}; + use super::CompressionScheme; use std::str::FromStr; #[test] @@ -90,18 +93,4 @@ mod tests { assert_eq!(Into::<&str>::into(CompressionScheme::None), "none"); } - #[test] - fn test_multiple_accepted_encoding_header_value() { - let multi = vec![CompressionScheme::LZ4, CompressionScheme::None]; - assert_eq!( - multiple_accepted_encoding_header_value(multi), - "lz4;none".to_string() - ); - - let singular = vec![CompressionScheme::LZ4]; - assert_eq!( - multiple_accepted_encoding_header_value(singular), - "lz4".to_string() - ); - } } diff --git a/cas_object/src/lib.rs b/cas_object/src/lib.rs index 1f4b9cbe..77beb0eb 100644 --- a/cas_object/src/lib.rs +++ b/cas_object/src/lib.rs @@ -1,6 +1,8 @@ mod cas_chunk_format; mod cas_object_format; +mod compression_scheme; pub mod error; pub use cas_chunk_format::*; pub use cas_object_format::*; +pub use compression_scheme::*; diff --git a/cas_types/src/lib.rs b/cas_types/src/lib.rs index 74458dd2..fc21d52d 100644 --- a/cas_types/src/lib.rs +++ b/cas_types/src/lib.rs @@ -3,7 +3,6 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use merklehash::MerkleHash; use serde::{Deserialize, Serialize}; -pub mod compression_scheme; mod key; pub use key::*; diff --git a/utils/src/compression.rs b/utils/src/compression.rs deleted file mode 100644 index 40f7fbe7..00000000 --- a/utils/src/compression.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::common::CompressionScheme; -use anyhow::anyhow; -use std::str::FromStr; - -pub const CAS_CONTENT_ENCODING_HEADER: &str = "xet-cas-content-encoding"; -pub const CAS_ACCEPT_ENCODING_HEADER: &str = "xet-cas-content-encoding"; -pub const CAS_INFLATED_SIZE_HEADER: &str = "xet-cas-inflated-size"; - -// officially speaking, string representations of the CompressionScheme enum values -// are dictated by prost for generating `as_str_name` and `from_str_name`, and since -// we cannot guarantee no one will use them, we will accept them as the string -// representations instead of CompressionScheme. -// These functions follow protobuf style, so the output strings are uppercase snake case, -// however we will still try to convert lower case/mixed case to CompressionScheme -// as well as count the empty string as CompressionScheme::None. -// we will also attempt to avoid as_str_name/from_str_name in favor of more rusty -// trait usage (From/FromStr) -impl From<&CompressionScheme> for &'static str { - fn from(value: &CompressionScheme) -> Self { - value.as_str_name() - } -} - -impl From for &'static str { - fn from(value: CompressionScheme) -> Self { - From::from(&value) - } -} - -impl FromStr for CompressionScheme { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - if s.is_empty() { - return Ok(CompressionScheme::None); - } - Self::from_str_name(s.to_uppercase().as_str()) - .ok_or_else(|| anyhow!("could not convert &str to CompressionScheme")) - } -} - -// in the header value, we will consider -pub fn multiple_accepted_encoding_header_value(list: Vec) -> String { - let as_strs: Vec<&str> = list.iter().map(Into::into).collect(); - as_strs.join(";").to_string() -} - -#[cfg(test)] -mod tests { - use crate::compression::{multiple_accepted_encoding_header_value, CompressionScheme}; - use std::str::FromStr; - - #[test] - fn test_from_str() { - assert_eq!( - CompressionScheme::from_str("LZ4").unwrap(), - CompressionScheme::Lz4 - ); - assert_eq!( - CompressionScheme::from_str("NONE").unwrap(), - CompressionScheme::None - ); - assert_eq!( - CompressionScheme::from_str("NoNE").unwrap(), - CompressionScheme::None - ); - assert_eq!( - CompressionScheme::from_str("none").unwrap(), - CompressionScheme::None - ); - assert_eq!( - CompressionScheme::from_str("").unwrap(), - CompressionScheme::None - ); - assert!(CompressionScheme::from_str("not-scheme").is_err()); - } - - #[test] - fn test_to_str() { - assert_eq!(Into::<&str>::into(CompressionScheme::Lz4), "LZ4"); - assert_eq!(Into::<&str>::into(CompressionScheme::None), "NONE"); - } - - #[test] - fn test_multiple_accepted_encoding_header_value() { - let multi = vec![CompressionScheme::Lz4, CompressionScheme::None]; - assert_eq!( - multiple_accepted_encoding_header_value(multi), - "LZ4;NONE".to_string() - ); - - let singular = vec![CompressionScheme::Lz4]; - assert_eq!( - multiple_accepted_encoding_header_value(singular), - "LZ4".to_string() - ); - } -} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index f6f1c6b2..dd0b1070 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -31,7 +31,6 @@ pub mod shard { tonic::include_proto!("shard"); } -pub mod compression; pub mod consistenthash; pub mod constants; pub mod errors; From abe67e971500da2ec48a16493bea7a342f44bee0 Mon Sep 17 00:00:00 2001 From: Rajat Arya Date: Fri, 20 Sep 2024 15:09:02 -0700 Subject: [PATCH 2/2] Adds LZ4 compression, with unit-tests --- cas_client/src/local_client.rs | 1 + cas_client/src/remote_client.rs | 1 + cas_object/src/cas_object_format.rs | 46 ++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/cas_client/src/local_client.rs b/cas_client/src/local_client.rs index 49487683..678e2aad 100644 --- a/cas_client/src/local_client.rs +++ b/cas_client/src/local_client.rs @@ -226,6 +226,7 @@ impl Client for LocalClient { hash, &data, &chunk_boundaries.into_iter().map(|x| x as u32).collect(), + cas_object::CompressionScheme::None )?; // flush before persisting writer.flush()?; diff --git a/cas_client/src/remote_client.rs b/cas_client/src/remote_client.rs index 0df41ff6..1391ae55 100644 --- a/cas_client/src/remote_client.rs +++ b/cas_client/src/remote_client.rs @@ -168,6 +168,7 @@ impl CASAPIClient { &key.hash, contents, &chunk_boundaries.into_iter().map(|x| x as u32).collect(), + cas_object::CompressionScheme::LZ4 )?; debug!("Upload: POST to {url:?} for {key:?}"); diff --git a/cas_object/src/cas_object_format.rs b/cas_object/src/cas_object_format.rs index 3f1383c3..ca2b4bc3 100644 --- a/cas_object/src/cas_object_format.rs +++ b/cas_object/src/cas_object_format.rs @@ -7,9 +7,7 @@ use std::{ }; use crate::{ - cas_chunk_format::{deserialize_chunk, serialize_chunk}, - error::CasObjectError, - CompressionScheme, + cas_chunk_format::{deserialize_chunk, serialize_chunk}, error::CasObjectError, CompressionScheme }; use anyhow::anyhow; @@ -454,6 +452,7 @@ impl CasObject { hash: &MerkleHash, data: &[u8], chunk_boundaries: &Vec, + compression_scheme: CompressionScheme, ) -> Result<(Self, usize), CasObjectError> { let mut cas = CasObject::default(); cas.info.cashash.copy_from_slice(hash.as_slice()); @@ -476,7 +475,7 @@ impl CasObject { // now serialize chunk directly to writer (since chunks come first!) // TODO: add compression scheme to this call let chunk_written_bytes = - serialize_chunk(&chunk_raw_bytes, writer, CompressionScheme::None)?; + serialize_chunk(&chunk_raw_bytes, writer, compression_scheme)?; total_written_bytes += chunk_written_bytes; let chunk_meta = CasChunkInfo { @@ -593,7 +592,7 @@ mod tests { for _idx in 0..num_chunks { let chunk_size: u32 = if use_random_chunk_size { let mut rng = rand::thread_rng(); - rng.gen_range(1024..=uncompressed_chunk_size) + rng.gen_range(512..=uncompressed_chunk_size) } else { uncompressed_chunk_size }; @@ -658,6 +657,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::None ) .is_ok()); @@ -682,6 +682,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::None ) .is_ok()); @@ -709,6 +710,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::None ) .is_ok()); @@ -735,6 +737,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::None ) .is_ok()); @@ -749,8 +752,35 @@ mod tests { assert_eq!(c.info.num_chunks, c2.info.num_chunks); assert_eq!(raw_data, c2.get_all_bytes(&mut reader).unwrap()); } + + #[test] + fn test_basic_mem_lz4() { + // Arrange + let (c, _cas_data, raw_data) = build_cas_object(1, 8, false, true); + let mut writer: Cursor> = Cursor::new(Vec::new()); + // Act & Assert + assert!(CasObject::serialize( + &mut writer, + &c.info.cashash, + &raw_data, + &c.get_chunk_boundaries(), + CompressionScheme::LZ4 + ) + .is_ok()); + + let mut reader = writer.clone(); + reader.set_position(0); + let res = CasObject::deserialize(&mut reader); + assert!(res.is_ok()); + + let c2 = res.unwrap(); + assert_eq!(c, c2); + + let bytes_read = c2.get_all_bytes(&mut reader).unwrap(); + assert_eq!(c.info.num_chunks, c2.info.num_chunks); + assert_eq!(raw_data, bytes_read); + } - /* #[test] fn test_serialization_deserialization_mem_medium_lz4() { // Arrange @@ -762,6 +792,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::LZ4 ) .is_ok()); @@ -789,6 +820,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::LZ4 ) .is_ok()); @@ -815,6 +847,7 @@ mod tests { &c.info.cashash, &raw_data, &c.get_chunk_boundaries(), + CompressionScheme::LZ4 ) .is_ok()); @@ -829,5 +862,4 @@ mod tests { assert_eq!(c.info.num_chunks, c2.info.num_chunks); assert_eq!(raw_data, c2.get_all_bytes(&mut reader).unwrap()); } - */ }