This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch polish-fuzz
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git

commit 83d8b8427fe9335119b61cda0a5544241bc50ef8
Author: Xuanwo <[email protected]>
AuthorDate: Thu Jul 27 13:39:07 2023 +0800

    refactor: Polish fuzz build time
    
    Signed-off-by: Xuanwo <[email protected]>
---
 .github/workflows/fuzz_test.yml |  31 +++--
 Cargo.lock                      |   1 +
 core/fuzz/Cargo.toml            |  13 +--
 core/fuzz/fuzz_range_reader.rs  | 252 ----------------------------------------
 core/fuzz/fuzz_reader.rs        | 190 +++++++++++++++++-------------
 core/fuzz/fuzz_writer.rs        | 128 +++++++++-----------
 core/fuzz/utils.rs              |  29 +++--
 7 files changed, 205 insertions(+), 439 deletions(-)

diff --git a/.github/workflows/fuzz_test.yml b/.github/workflows/fuzz_test.yml
index 1a11e06aa..b13b12148 100644
--- a/.github/workflows/fuzz_test.yml
+++ b/.github/workflows/fuzz_test.yml
@@ -15,7 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
 name: Fuzz Test
 
 on:
@@ -39,12 +38,16 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-  fuzz-test-build-target:
+  fuzz-build:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3
       - name: Setup Rust toolchain
         uses: ./.github/actions/setup
+      - name: Install libfuzz
+        shell: bash
+        run: sudo apt-get install -y libfuzzer-14-dev
+
       - name: Install cargo fuzz
         shell: bash
         run: rustup install nightly && cargo +nightly install cargo-fuzz
@@ -52,12 +55,16 @@ jobs:
         shell: bash
         working-directory: core/fuzz
         run: cargo +nightly fuzz build
+        env:
+          CUSTOM_LIBFUZZER_PATH: /usr/lib/llvm-14/lib
+
       - name: Upload build artifacts
         uses: actions/upload-artifact@v3
         with:
           name: fuzz_targets
           path: ./target/x86_64-unknown-linux-gnu/release/fuzz_*
-  fuzz-test-run-s3:
+
+  fuzz-test-s3:
     runs-on: ubuntu-latest
     needs: fuzz-test-build-target
     services:
@@ -71,8 +78,10 @@ jobs:
     strategy:
       fail-fast: true
       matrix:
-        fuzz-targets: [ fuzz_reader, fuzz_range_reader, fuzz_writer ]
+        fuzz-targets: [ fuzz_reader, fuzz_writer ]
     steps:
+      - name: Install libfuzzer
+        run: sudo apt-get install -y libfuzzer-14-dev
       - name: Setup Test Bucket
         env:
           AWS_ACCESS_KEY_ID: "minioadmin"
@@ -102,14 +111,17 @@ jobs:
         with:
           name: crash_s3_${{ matrix.fuzz-targets }}_${{ github.event_name 
}}_${{ github.run_attempt }}_${{ github.sha }}
           path: ./crash*
-  fuzz-test-run-fs:
+
+  fuzz-test-fs:
     runs-on: ubuntu-latest
     needs: fuzz-test-build-target
     strategy:
       fail-fast: true
       matrix:
-        fuzz-targets: [ fuzz_reader, fuzz_range_reader, fuzz_writer ]
+        fuzz-targets: [ fuzz_reader, fuzz_writer ]
     steps:
+      - name: Install libfuzzer
+        run: sudo apt-get install -y libfuzzer-14-dev
       - name: Download Fuzz Targets
         uses: actions/download-artifact@v3
         with:
@@ -130,14 +142,17 @@ jobs:
         with:
           name: crash_fs_${{ matrix.fuzz-targets }}_${{ github.event_name 
}}_${{ github.run_attempt }}_${{ github.sha }}
           path: ./crash*
-  fuzz-test-run-memory:
+
+  fuzz-test-memory:
     runs-on: ubuntu-latest
     needs: fuzz-test-build-target
     strategy:
       fail-fast: true
       matrix:
-        fuzz-targets: [ fuzz_reader, fuzz_range_reader, fuzz_writer ]
+        fuzz-targets: [ fuzz_reader, fuzz_writer ]
     steps:
+      - name: Install libfuzzer
+        run: sudo apt-get install -y libfuzzer-14-dev
       - name: Download Fuzz Targets
         uses: actions/download-artifact@v3
         with:
diff --git a/Cargo.lock b/Cargo.lock
index cb0b29ed6..29292833f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3194,6 +3194,7 @@ dependencies = [
  "dotenvy",
  "libfuzzer-sys",
  "opendal",
+ "rand 0.8.5",
  "sha2",
  "tokio",
  "uuid",
diff --git a/core/fuzz/Cargo.toml b/core/fuzz/Cargo.toml
index 37184ff5a..8020dfaf4 100644
--- a/core/fuzz/Cargo.toml
+++ b/core/fuzz/Cargo.toml
@@ -31,24 +31,15 @@ bytes = "1.2"
 dotenvy = "0.15.6"
 libfuzzer-sys = "0.4"
 opendal = { path = ".." }
+rand = "0.8"
 sha2 = { version = "0.10.6" }
 tokio = { version = "1", features = ["full"] }
-uuid = { version = "1.3.0", features = ["v4"] }
+uuid = { version = "1", features = ["v4"] }
 
 [[bin]]
-doc = false
 name = "fuzz_reader"
 path = "fuzz_reader.rs"
-test = false
 
 [[bin]]
-doc = false
 name = "fuzz_writer"
 path = "fuzz_writer.rs"
-test = false
-
-[[bin]]
-doc = false
-name = "fuzz_range_reader"
-path = "fuzz_range_reader.rs"
-test = false
diff --git a/core/fuzz/fuzz_range_reader.rs b/core/fuzz/fuzz_range_reader.rs
deleted file mode 100644
index ba7ade043..000000000
--- a/core/fuzz/fuzz_range_reader.rs
+++ /dev/null
@@ -1,252 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-#![no_main]
-
-use std::io::SeekFrom;
-
-use bytes::Bytes;
-use libfuzzer_sys::arbitrary::Arbitrary;
-use libfuzzer_sys::arbitrary::Result;
-use libfuzzer_sys::arbitrary::Unstructured;
-use libfuzzer_sys::fuzz_target;
-use sha2::Digest;
-use sha2::Sha256;
-
-use opendal::raw::oio::ReadExt;
-use opendal::Operator;
-
-mod utils;
-
-const MAX_DATA_SIZE: usize = 16 * 1024 * 1024;
-
-#[derive(Debug, Clone)]
-enum ReaderAction {
-    Read { size: usize },
-    Seek(SeekFrom),
-    Next,
-}
-
-#[derive(Debug, Clone)]
-struct FuzzInput {
-    actions: Vec<ReaderAction>,
-    data: Vec<u8>,
-
-    range: (u64, u64),
-}
-
-impl Arbitrary<'_> for FuzzInput {
-    fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
-        let data_len = u.int_in_range(1..=MAX_DATA_SIZE)?;
-        let data: Vec<u8> = u.bytes(data_len)?.to_vec();
-
-        let range_start = u.int_in_range(0..=data_len as u64 - 1)?;
-        let range_end = u.int_in_range(range_start + 1..=data_len as u64)?;
-
-        let range = (range_start, range_end);
-
-        let mut actions = vec![];
-        let mut action_count = u.int_in_range(128..=1024)?;
-
-        while action_count != 0 {
-            action_count -= 1;
-            match u.int_in_range(0..=2)? {
-                0 => {
-                    let size = u.int_in_range(0..=data_len * 2)?;
-                    actions.push(ReaderAction::Read { size });
-                }
-                1 => {
-                    let offset: i64 = u.int_in_range(-(data_len as 
i64)..=(data_len as i64))?;
-                    let seek_from = match u.int_in_range(0..=2)? {
-                        0 => SeekFrom::Start(offset.unsigned_abs()),
-                        1 => SeekFrom::End(offset),
-                        _ => SeekFrom::Current(offset),
-                    };
-                    actions.push(ReaderAction::Seek(seek_from));
-                }
-                _ => actions.push(ReaderAction::Next),
-            }
-        }
-        Ok(FuzzInput {
-            actions,
-            data,
-            range,
-        })
-    }
-}
-
-struct ReaderFuzzerChecker {
-    data: Vec<u8>,
-    size: usize,
-    cur: usize,
-    start: usize,
-}
-
-impl ReaderFuzzerChecker {
-    fn new(data: Vec<u8>, start: usize, end: usize) -> Self {
-        Self {
-            size: end - start,
-            data,
-            cur: 0,
-            start,
-        }
-    }
-
-    fn check_read(&mut self, n: usize, output: &[u8]) {
-        if n == 0 {
-            return;
-        }
-
-        let current = self.cur + self.start;
-        let expected = &self.data[current..current + n];
-
-        // Check the read result
-        assert_eq!(
-            format!("{:x}", Sha256::digest(output)),
-            format!("{:x}", Sha256::digest(expected)),
-            "check read failed: output bs is different with expected bs",
-        );
-
-        // Update the current position
-        self.cur += n;
-    }
-
-    fn check_seek(&mut self, seek_from: SeekFrom, output: 
opendal::Result<u64>) {
-        let expected = match seek_from {
-            SeekFrom::Start(offset) => offset as i64,
-            SeekFrom::End(offset) => self.size as i64 + offset,
-            SeekFrom::Current(offset) => self.cur as i64 + offset,
-        };
-
-        if expected < 0 {
-            assert!(output.is_err(), "check seek failed: seek should fail");
-            assert_eq!(
-                output.unwrap_err().kind(),
-                opendal::ErrorKind::InvalidInput,
-                "check seek failed: seek result is different with expected 
result",
-            );
-        } else {
-            assert_eq!(
-                output.unwrap(),
-                expected as u64,
-                "check seek failed: seek result is different with expected 
result",
-            );
-
-            self.cur = expected as usize;
-        }
-    }
-
-    fn check_next(&mut self, output: Option<Bytes>) {
-        if let Some(output) = output {
-            assert!(
-                self.cur + output.len() <= self.size,
-                "check next failed: output bs is larger than remaining bs",
-            );
-
-            let current = self.cur + self.start;
-            let expected = &self.data[current..current + output.len()];
-
-            assert_eq!(
-                format!("{:x}", Sha256::digest(&output)),
-                format!("{:x}", Sha256::digest(expected)),
-                "check next failed: output bs is different with expected bs",
-            );
-
-            // update the current position
-            self.cur += output.len();
-        } else {
-            assert!(
-                self.cur >= self.size,
-                "check next failed: output bs is None, we still have bytes to 
read",
-            )
-        }
-    }
-}
-
-async fn fuzz_range_reader_process(input: FuzzInput, op: &Operator, name: 
&str) -> Result<()> {
-    let path = uuid::Uuid::new_v4().to_string();
-
-    let mut checker = ReaderFuzzerChecker::new(
-        input.data.clone(),
-        input.range.0 as usize,
-        input.range.1 as usize,
-    );
-
-    op.write(&path, input.data)
-        .await
-        .unwrap_or_else(|_| panic!("{} write must succeed", name));
-
-    let mut o = op
-        .range_reader(&path, input.range.0..input.range.1)
-        .await
-        .unwrap_or_else(|_| panic!("{} init reader must succeed", name));
-
-    for action in input.actions {
-        match action {
-            ReaderAction::Read { size } => {
-                let mut buf = vec![0; size];
-                let n = o
-                    .read(&mut buf)
-                    .await
-                    .unwrap_or_else(|_| panic!("{} read must succeed", name));
-                checker.check_read(n, &buf[..n]);
-            }
-
-            ReaderAction::Seek(seek_from) => {
-                let res = o.seek(seek_from).await;
-                checker.check_seek(seek_from, res);
-            }
-
-            ReaderAction::Next => {
-                let res = o
-                    .next()
-                    .await
-                    .map(|v| v.unwrap_or_else(|_| panic!("{} next should not 
return error", name)));
-                checker.check_next(res);
-            }
-        }
-    }
-
-    op.delete(&path)
-        .await
-        .unwrap_or_else(|_| panic!("{} delete must succeed", name));
-    Ok(())
-}
-
-fn fuzz_reader(name: &str, op: &Operator, input: FuzzInput) {
-    let runtime = tokio::runtime::Runtime::new().unwrap();
-
-    runtime.block_on(async {
-        fuzz_range_reader_process(input, op, name)
-            .await
-            .unwrap_or_else(|_| panic!("{} fuzz range reader must succeed", 
name));
-    });
-}
-
-fuzz_target!(|input: FuzzInput| {
-    let _ = dotenvy::dotenv();
-
-    for service in utils::init_services() {
-        if service.1.is_none() {
-            continue;
-        }
-
-        let op = service.1.unwrap();
-
-        fuzz_reader(service.0, &op, input.clone());
-    }
-});
diff --git a/core/fuzz/fuzz_reader.rs b/core/fuzz/fuzz_reader.rs
index 8ec87f591..26b6323fc 100644
--- a/core/fuzz/fuzz_reader.rs
+++ b/core/fuzz/fuzz_reader.rs
@@ -21,21 +21,23 @@ use std::io::SeekFrom;
 
 use bytes::Bytes;
 use libfuzzer_sys::arbitrary::Arbitrary;
-use libfuzzer_sys::arbitrary::Result;
 use libfuzzer_sys::arbitrary::Unstructured;
 use libfuzzer_sys::fuzz_target;
+use rand::prelude::*;
 use sha2::Digest;
 use sha2::Sha256;
 
 use opendal::raw::oio::ReadExt;
+use opendal::raw::BytesRange;
 use opendal::Operator;
+use opendal::Result;
 
 mod utils;
 
 const MAX_DATA_SIZE: usize = 16 * 1024 * 1024;
 
 #[derive(Debug, Clone)]
-enum ReaderAction {
+enum ReadAction {
     Read { size: usize },
     Seek(SeekFrom),
     Next,
@@ -43,58 +45,103 @@ enum ReaderAction {
 
 #[derive(Debug, Clone)]
 struct FuzzInput {
-    actions: Vec<ReaderAction>,
-    data: Vec<u8>,
+    size: usize,
+    range: BytesRange,
+    actions: Vec<ReadAction>,
 }
 
 impl Arbitrary<'_> for FuzzInput {
-    fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
-        let data_len = u.int_in_range(1..=MAX_DATA_SIZE)?;
-        let data: Vec<u8> = u.bytes(data_len)?.to_vec();
+    fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
+        let total_size = u.int_in_range(1..=MAX_DATA_SIZE)?;
+
+        // TODO: it's valid that size is larger than total_size.
+        let (offset, size) = match u.int_in_range(0..=3)? {
+            // Full range
+            0 => (None, None),
+            1 => {
+                let offset = u.int_in_range(0..=total_size as u64 - 1)?;
+                (Some(offset), None)
+            }
+            2 => {
+                let size = u.int_in_range(1..=total_size as u64)?;
+                (None, Some(size))
+            }
+            3 => {
+                let offset = u.int_in_range(0..=total_size as u64 - 1)?;
+                let size = u.int_in_range(1..=total_size as u64 - offset)?;
+                (Some(offset), Some(size))
+            }
+            _ => unreachable!("invalid int generated by arbitrary"),
+        };
+        let range = BytesRange::new(offset, size);
 
+        let count = u.int_in_range(128..=1024)?;
         let mut actions = vec![];
-        let mut action_count = u.int_in_range(128..=1024)?;
 
-        while action_count != 0 {
-            action_count -= 1;
-            match u.int_in_range(0..=2)? {
+        for _ in 0..count {
+            let action = match u.int_in_range(0..=4)? {
+                // Read
                 0 => {
-                    let size = u.int_in_range(0..=data_len * 2)?;
-                    actions.push(ReaderAction::Read { size });
+                    let size = u.int_in_range(0..=total_size * 2)?;
+                    ReadAction::Read { size }
                 }
-                1 => {
-                    let offset: i64 = u.int_in_range(-(data_len as 
i64)..=(data_len as i64))?;
-                    let seek_from = match u.int_in_range(0..=2)? {
-                        0 => SeekFrom::Start(offset.unsigned_abs()),
-                        1 => SeekFrom::End(offset),
-                        _ => SeekFrom::Current(offset),
-                    };
-                    actions.push(ReaderAction::Seek(seek_from));
+                // Next
+                1 => ReadAction::Next,
+                // Seek Start
+                2 => {
+                    // NOTE: seek out of the end of file is valid.
+                    let offset = u.int_in_range(0..=total_size * 2)?;
+                    ReadAction::Seek(SeekFrom::Start(offset as u64))
                 }
-                _ => actions.push(ReaderAction::Next),
-            }
+                // Seek Current
+                3 => {
+                    let offset = u.int_in_range(-(total_size as 
i64)..=(total_size as i64))?;
+                    ReadAction::Seek(SeekFrom::Current(offset))
+                }
+                // Seek End
+                4 => {
+                    let offset = u.int_in_range(-(total_size as 
i64)..=(total_size as i64))?;
+                    ReadAction::Seek(SeekFrom::End(offset))
+                }
+                _ => unreachable!("invalid int generated by arbitrary"),
+            };
+
+            actions.push(action);
         }
-        Ok(FuzzInput { actions, data })
+
+        Ok(FuzzInput {
+            size: total_size,
+            range,
+            actions,
+        })
     }
 }
 
-struct ReaderFuzzerChecker {
-    data: Vec<u8>,
+struct ReadChecker {
     size: usize,
+
+    data: Bytes,
     cur: usize,
 }
 
-impl ReaderFuzzerChecker {
-    fn new(data: Vec<u8>) -> Self {
-        Self {
-            size: data.len(),
-            data,
-            cur: 0,
-        }
+impl ReadChecker {
+    fn new(size: usize, range: BytesRange) -> Self {
+        let mut rng = thread_rng();
+        let mut data = vec![0; size];
+        rng.fill_bytes(&mut data);
+
+        let data = range.apply_on_bytes(Bytes::from(data));
+
+        Self { size, data, cur: 0 }
     }
 
     fn check_read(&mut self, n: usize, output: &[u8]) {
         if n == 0 {
+            assert_eq!(
+                output.len(),
+                0,
+                "check read failed: output bs is not empty when read size is 0"
+            );
             return;
         }
 
@@ -111,7 +158,7 @@ impl ReaderFuzzerChecker {
         self.cur += n;
     }
 
-    fn check_seek(&mut self, seek_from: SeekFrom, output: 
opendal::Result<u64>) {
+    fn check_seek(&mut self, seek_from: SeekFrom, output: Result<u64>) {
         let expected = match seek_from {
             SeekFrom::Start(offset) => offset as i64,
             SeekFrom::End(offset) => self.size as i64 + offset,
@@ -125,16 +172,18 @@ impl ReaderFuzzerChecker {
                 opendal::ErrorKind::InvalidInput,
                 "check seek failed: seek result is different with expected 
result"
             );
-        } else {
-            assert_eq!(
-                output.unwrap(),
-                expected as u64,
-                "check seek failed: seek result is different with expected 
result",
-            );
 
-            // only update the current position when seek succeed
-            self.cur = expected as usize;
+            return;
         }
+
+        assert_eq!(
+            output.unwrap(),
+            expected as u64,
+            "check seek failed: seek result is different with expected result",
+        );
+
+        // only update the current position when seek succeed
+        self.cur = expected as usize;
     }
 
     fn check_next(&mut self, output: Option<Bytes>) {
@@ -164,71 +213,48 @@ impl ReaderFuzzerChecker {
     }
 }
 
-async fn fuzz_reader_process(input: FuzzInput, op: &Operator, name: &str) -> 
Result<()> {
+async fn fuzz_reader(op: Operator, input: FuzzInput) -> Result<()> {
     let path = uuid::Uuid::new_v4().to_string();
 
-    let mut checker = ReaderFuzzerChecker::new(input.data.clone());
-    op.write(&path, input.data)
-        .await
-        .unwrap_or_else(|_| panic!("{} write must succeed", name));
+    let mut checker = ReadChecker::new(input.size, input.range);
+    op.write(&path, checker.data.clone()).await?;
 
-    let mut o = op
-        .reader(&path)
-        .await
-        .unwrap_or_else(|_| panic!("{} init reader must succeed", name));
+    let mut o = op.range_reader(&path, input.range.to_range()).await?;
 
     for action in input.actions {
         match action {
-            ReaderAction::Read { size } => {
+            ReadAction::Read { size } => {
                 let mut buf = vec![0; size];
-                let n = o
-                    .read(&mut buf)
-                    .await
-                    .unwrap_or_else(|_| panic!("{} read must succeed", name));
+                let n = o.read(&mut buf).await?;
                 checker.check_read(n, &buf[..n]);
             }
 
-            ReaderAction::Seek(seek_from) => {
+            ReadAction::Seek(seek_from) => {
                 let res = o.seek(seek_from).await;
                 checker.check_seek(seek_from, res);
             }
 
-            ReaderAction::Next => {
-                let res = o
-                    .next()
-                    .await
-                    .map(|v| v.unwrap_or_else(|_| panic!("{} next should not 
return error", name)));
+            ReadAction::Next => {
+                let res = o.next().await.transpose()?;
                 checker.check_next(res);
             }
         }
     }
 
-    op.delete(&path)
-        .await
-        .unwrap_or_else(|_| panic!("{} delete must succeed", name));
+    op.delete(&path).await?;
     Ok(())
 }
 
-fn fuzz_reader(name: &str, op: &Operator, input: FuzzInput) {
-    let runtime = tokio::runtime::Runtime::new().unwrap();
-
-    runtime.block_on(async {
-        fuzz_reader_process(input, op, name)
-            .await
-            .unwrap_or_else(|_| panic!("{} fuzz reader must succeed", name));
-    });
-}
-
 fuzz_target!(|input: FuzzInput| {
     let _ = dotenvy::dotenv();
 
-    for service in utils::init_services() {
-        if service.1.is_none() {
-            continue;
-        }
-
-        let op = service.1.unwrap();
+    let runtime = tokio::runtime::Runtime::new().expect("init runtime must 
succeed");
 
-        fuzz_reader(service.0, &op, input.clone());
+    for op in utils::init_services() {
+        runtime.block_on(async {
+            fuzz_reader(op, input.clone())
+                .await
+                .unwrap_or_else(|_| panic!("fuzz reader must succeed"));
+        })
     }
 });
diff --git a/core/fuzz/fuzz_writer.rs b/core/fuzz/fuzz_writer.rs
index 261ffedf5..63c3ebb3a 100644
--- a/core/fuzz/fuzz_writer.rs
+++ b/core/fuzz/fuzz_writer.rs
@@ -17,15 +17,16 @@
 
 #![no_main]
 
-use bytes::Bytes;
+use bytes::{Bytes, BytesMut};
 use libfuzzer_sys::arbitrary::Arbitrary;
-use libfuzzer_sys::arbitrary::Result;
 use libfuzzer_sys::arbitrary::Unstructured;
 use libfuzzer_sys::fuzz_target;
+use rand::prelude::*;
 use sha2::Digest;
 use sha2::Sha256;
 
 use opendal::Operator;
+use opendal::Result;
 
 mod utils;
 
@@ -33,7 +34,7 @@ const MAX_DATA_SIZE: usize = 16 * 1024 * 1024;
 
 #[derive(Debug, Clone)]
 enum WriterAction {
-    Write { data: Bytes },
+    Write { size: usize },
 }
 
 #[derive(Debug, Clone)]
@@ -42,40 +43,46 @@ struct FuzzInput {
 }
 
 impl Arbitrary<'_> for FuzzInput {
-    fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
+    fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
         let mut actions = vec![];
-        let mut action_count = u.int_in_range(128..=1024)?;
-
-        while action_count != 0 {
-            action_count -= 1;
-            let data_len = u.int_in_range(1..=MAX_DATA_SIZE)?;
-            let data: Vec<u8> = u.bytes(data_len)?.to_vec();
-            actions.push(WriterAction::Write {
-                data: Bytes::from(data),
-            });
+
+        let count = u.int_in_range(128..=1024)?;
+
+        for _ in 0..count {
+            let size = u.int_in_range(1..=MAX_DATA_SIZE)?;
+            actions.push(WriterAction::Write { size });
         }
 
         Ok(FuzzInput { actions })
     }
 }
 
-struct WriterFuzzChecker {
-    data: Vec<u8>,
+struct WriteChecker {
+    chunks: Vec<Bytes>,
+    data: Bytes,
 }
 
-impl WriterFuzzChecker {
-    fn new(input: FuzzInput) -> Self {
-        let mut data = vec![];
+impl WriteChecker {
+    fn new(size: Vec<usize>) -> Self {
+        let mut rng = thread_rng();
 
-        for action in input.actions {
-            match action {
-                WriterAction::Write { data: d } => {
-                    data.extend_from_slice(&d);
-                }
-            }
+        let mut chunks = Vec::with_capacity(size.len());
+
+        for i in size {
+            let mut bs = vec![0u8; i];
+            rng.fill_bytes(&mut bs);
+            chunks.push(Bytes::from(bs));
         }
 
-        WriterFuzzChecker { data }
+        let data = chunks.iter().fold(BytesMut::new(), |mut acc, x| {
+            acc.extend_from_slice(x);
+            acc
+        });
+
+        WriteChecker {
+            chunks,
+            data: data.freeze(),
+        }
     }
 
     fn check(&self, actual: &[u8]) {
@@ -87,64 +94,45 @@ impl WriterFuzzChecker {
     }
 }
 
-async fn fuzz_writer_process(input: FuzzInput, op: &Operator, name: &str) -> 
Result<()> {
+async fn fuzz_writer(op: Operator, input: FuzzInput) -> Result<()> {
     let path = uuid::Uuid::new_v4().to_string();
 
-    let checker = WriterFuzzChecker::new(input.clone());
-
-    let mut writer = op
-        .writer(&path)
-        .await
-        .unwrap_or_else(|_| panic!("{} create must succeed", name));
-
-    for action in input.actions {
-        match action {
-            WriterAction::Write { data } => {
-                writer
-                    .write(data)
-                    .await
-                    .unwrap_or_else(|_| panic!("{} write must succeed", name));
-            }
-        }
+    let total_size = input
+        .actions
+        .iter()
+        .map(|a| match a {
+            WriterAction::Write { size } => *size,
+        })
+        .collect();
+
+    let checker = WriteChecker::new(total_size);
+
+    let mut writer = op.writer(&path).await?;
+
+    for chunk in &checker.chunks {
+        writer.write(chunk.clone()).await?;
     }
-    writer
-        .close()
-        .await
-        .unwrap_or_else(|_| panic!("{} close must succeed", name));
 
-    let result = op
-        .read(&path)
-        .await
-        .unwrap_or_else(|_| panic!("{} read must succeed", name));
+    writer.close().await?;
+
+    let result = op.read(&path).await?;
 
     checker.check(&result);
 
-    op.delete(&path)
-        .await
-        .unwrap_or_else(|_| panic!("{} delete must succeed", name));
+    op.delete(&path).await?;
     Ok(())
 }
 
-fn fuzz_writer(name: &str, op: &Operator, input: FuzzInput) {
-    let runtime = tokio::runtime::Runtime::new().unwrap();
-
-    runtime.block_on(async {
-        fuzz_writer_process(input, op, name)
-            .await
-            .unwrap_or_else(|_| panic!("{} fuzz writer must succeed", name));
-    });
-}
-
 fuzz_target!(|input: FuzzInput| {
     let _ = dotenvy::dotenv();
 
-    for service in utils::init_services() {
-        if service.1.is_none() {
-            continue;
-        }
-
-        let op = service.1.unwrap();
+    let runtime = tokio::runtime::Runtime::new().expect("init runtime must 
succeed");
 
-        fuzz_writer(service.0, &op, input.clone());
+    for op in utils::init_services() {
+        runtime.block_on(async {
+            fuzz_writer(op, input.clone())
+                .await
+                .unwrap_or_else(|_| panic!("fuzz reader must succeed"));
+        })
     }
 });
diff --git a/core/fuzz/utils.rs b/core/fuzz/utils.rs
index 83b708a9c..adc301557 100644
--- a/core/fuzz/utils.rs
+++ b/core/fuzz/utils.rs
@@ -17,17 +17,16 @@
 
 use std::env;
 
-use opendal::services;
-use opendal::Builder;
 use opendal::Operator;
+use opendal::Scheme;
 
-fn service<B: Builder>() -> Option<Operator> {
-    let test_key = format!("opendal_{}_test", B::SCHEME).to_uppercase();
+fn service(scheme: Scheme) -> Option<Operator> {
+    let test_key = format!("opendal_{}_test", scheme).to_uppercase();
     if env::var(test_key).unwrap_or_default() != "on" {
         return None;
     }
 
-    let prefix = format!("opendal_{}_", B::SCHEME);
+    let prefix = format!("opendal_{}_", scheme);
     let envs = env::vars()
         .filter_map(move |(k, v)| {
             k.to_lowercase()
@@ -36,17 +35,15 @@ fn service<B: Builder>() -> Option<Operator> {
         })
         .collect();
 
-    Some(
-        Operator::from_map::<B>(envs)
-            .unwrap_or_else(|_| panic!("init {} must succeed", B::SCHEME))
-            .finish(),
-    )
+    Some(Operator::via_map(scheme, envs).unwrap_or_else(|_| panic!("init {} 
must succeed", scheme)))
 }
 
-pub fn init_services() -> Vec<(&'static str, Option<Operator>)> {
-    vec![
-        ("fs", service::<services::Fs>()),
-        ("memory", service::<services::Memory>()),
-        ("s3", service::<services::S3>()),
-    ]
+pub fn init_services() -> Vec<Operator> {
+    let ops = vec![
+        service(Scheme::Memory),
+        service(Scheme::Fs),
+        service(Scheme::S3),
+    ];
+
+    ops.into_iter().flatten().collect()
 }

Reply via email to