Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core/fuzz): Fix some bugs inside fuzzer #3418

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions core/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ rand = "0.8"
sha2 = { version = "0.10.6" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
tracing-subscriber = { version = "0.3", features = [
"env-filter",
"tracing-log",
] }

[[bin]]
name = "fuzz_reader"
Expand Down
69 changes: 54 additions & 15 deletions core/fuzz/fuzz_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#![no_main]

use std::fmt::{Debug, Formatter};
use std::io::SeekFrom;

use bytes::Bytes;
Expand All @@ -35,20 +36,37 @@ mod utils;

const MAX_DATA_SIZE: usize = 16 * 1024 * 1024;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
enum ReadAction {
Read { size: usize },
Seek(SeekFrom),
Next,
}

#[derive(Debug, Clone)]
#[derive(Clone)]
struct FuzzInput {
path: String,
size: usize,
range: BytesRange,
actions: Vec<ReadAction>,
}

impl Debug for FuzzInput {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut actions = self.actions.clone();
// Remove all Read(0) entry.
let empty = ReadAction::Read { size: 0 };
actions.retain(|e| e != &empty);

f.debug_struct("FuzzInput")
.field("path", &self.path)
.field("size", &self.size)
.field("range", &self.range.to_string())
.field("actions", &actions)
.finish()
}
}

impl Arbitrary<'_> for FuzzInput {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
let total_size = u.int_in_range(1..=MAX_DATA_SIZE)?;
Expand Down Expand Up @@ -109,6 +127,7 @@ impl Arbitrary<'_> for FuzzInput {
}

Ok(FuzzInput {
path: uuid::Uuid::new_v4().to_string(),
size: total_size,
range,
actions,
Expand Down Expand Up @@ -142,17 +161,30 @@ impl ReadChecker {
}
}

fn check_read(&mut self, n: usize, output: &[u8]) {
if n == 0 {
fn check_read(&mut self, buf_size: usize, output: &[u8]) {
if buf_size == 0 {
assert_eq!(
output.len(),
0,
"check read failed: output bs is not empty when read size is 0"
"check read failed: output must be empty if buf_size is 0"
);
return;
}

if buf_size > 0 && output.is_empty() {
assert!(
self.cur >= self.ranged_data.len(),
"check read failed: no data read means cur must outsides of ranged_data",
);
return;
}

let expected = &self.ranged_data[self.cur..self.cur + n];
assert!(
self.cur + output.len() <= self.ranged_data.len(),
"check read failed: cur + output length must be less than ranged_data length, cur: {}, output: {}, ranged_data: {}", self.cur, output.len(), self.ranged_data.len(),
);

let expected = &self.ranged_data[self.cur..self.cur + output.len()];

// Check the read result
assert_eq!(
Expand All @@ -162,7 +194,7 @@ impl ReadChecker {
);

// Update the current position
self.cur += n;
self.cur += output.len();
}

fn check_seek(&mut self, seek_from: SeekFrom, output: Result<u64>) {
Expand Down Expand Up @@ -206,7 +238,8 @@ impl ReadChecker {
"{:x}",
Sha256::digest(&self.ranged_data[self.cur..self.cur + output.len()])
),
"check next failed: output bs is different with expected bs",
"check next failed: output bs is different with expected bs, current: {}, output length: {}",
self.cur, output.len(),
);

// update the current position
Expand All @@ -221,19 +254,20 @@ impl ReadChecker {
}

async fn fuzz_reader(op: Operator, input: FuzzInput) -> Result<()> {
let path = uuid::Uuid::new_v4().to_string();

let mut checker = ReadChecker::new(input.size, input.range);
op.write(&path, checker.raw_data.clone()).await?;
op.write(&input.path, checker.raw_data.clone()).await?;

let mut o = op.reader_with(&path).range(input.range.to_range()).await?;
let mut o = op
.reader_with(&input.path)
.range(input.range.to_range())
.await?;

for action in input.actions {
match action {
ReadAction::Read { size } => {
let mut buf = vec![0; size];
let n = o.read(&mut buf).await?;
checker.check_read(n, &buf[..n]);
checker.check_read(size, &buf[..n]);
}

ReadAction::Seek(seek_from) => {
Expand All @@ -248,20 +282,25 @@ async fn fuzz_reader(op: Operator, input: FuzzInput) -> Result<()> {
}
}

op.delete(&path).await?;
op.delete(&input.path).await?;
Ok(())
}

fuzz_target!(|input: FuzzInput| {
let _ = dotenvy::dotenv();
let _ = tracing_subscriber::fmt()
.pretty()
.with_test_writer()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init();

let runtime = tokio::runtime::Runtime::new().expect("init runtime must succeed");

if let Some(op) = utils::init_service() {
runtime.block_on(async {
fuzz_reader(op, input.clone())
.await
.unwrap_or_else(|_| panic!("fuzz reader must succeed"));
.unwrap_or_else(|err| panic!("fuzz reader must succeed: {err:?}"));
})
}
});
8 changes: 7 additions & 1 deletion core/fuzz/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use opendal::layers::{LoggingLayer, RetryLayer};
use std::env;
use std::str::FromStr;

Expand Down Expand Up @@ -44,5 +45,10 @@ pub fn init_service() -> Option<Operator> {
})
.collect();

Some(Operator::via_map(scheme, envs).unwrap_or_else(|_| panic!("init {} must succeed", scheme)))
Some(
Operator::via_map(scheme, envs)
.unwrap_or_else(|_| panic!("init {} must succeed", scheme))
.layer(LoggingLayer::default())
.layer(RetryLayer::default()),
)
}