You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2023/10/27 15:33:06 UTC

(incubator-opendal) 02/14: Add new fuzz test

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

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

commit 3ae3ad9fff4c960a7d7e240c5ca9e109276fce74
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Fri Oct 27 17:26:25 2023 +0800

    Add new fuzz test
    
    Signed-off-by: Xuanwo <gi...@xuanwo.io>
---
 core/src/raw/oio/read/range_read.rs |  32 ++++----
 core/tests/behavior/fuzz.rs         | 154 +++++++++++++++++++++++++++++++++++-
 2 files changed, 166 insertions(+), 20 deletions(-)

diff --git a/core/src/raw/oio/read/range_read.rs b/core/src/raw/oio/read/range_read.rs
index 205b89a6f..28cff072e 100644
--- a/core/src/raw/oio/read/range_read.rs
+++ b/core/src/raw/oio/read/range_read.rs
@@ -230,13 +230,13 @@ where
     R: oio::Read,
 {
     fn poll_read(&mut self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
+        // Sanity check for normal cases.
+        if buf.is_empty() || self.cur >= self.size.unwrap_or(u64::MAX) {
+            return Poll::Ready(Ok(0));
+        }
+
         match &mut self.state {
             State::Idle => {
-                // Sanity check for normal cases.
-                if buf.is_empty() || self.cur >= self.size.unwrap_or(u64::MAX) {
-                    return Poll::Ready(Ok(0));
-                }
-
                 self.state = if self.offset.is_none() {
                     // Offset is none means we are doing tailing reading.
                     // we should stat first to get the correct offset.
@@ -300,6 +300,7 @@ where
             State::Idle => {
                 let (base, amt) = match pos {
                     SeekFrom::Start(n) => (0, n as i64),
+                    SeekFrom::Current(n) => (self.cur as i64, n),
                     SeekFrom::End(n) => {
                         if let Some(size) = self.size {
                             (size as i64, n)
@@ -308,7 +309,6 @@ where
                             return self.poll_seek(cx, pos);
                         }
                     }
-                    SeekFrom::Current(n) => (self.cur as i64, n),
                 };
 
                 let seek_pos = match base.checked_add(amt) {
@@ -359,13 +359,13 @@ where
     }
 
     fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes>>> {
+        // Sanity check for normal cases.
+        if self.cur >= self.size.unwrap_or(u64::MAX) {
+            return Poll::Ready(None);
+        }
+
         match &mut self.state {
             State::Idle => {
-                // Sanity check for normal cases.
-                if self.cur >= self.size.unwrap_or(u64::MAX) {
-                    return Poll::Ready(None);
-                }
-
                 self.state = if self.offset.is_none() {
                     // Offset is none means we are doing tailing reading.
                     // we should stat first to get the correct offset.
@@ -425,13 +425,13 @@ where
     R: oio::BlockingRead,
 {
     fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
+        // Sanity check for normal cases.
+        if buf.is_empty() || self.cur >= self.size.unwrap_or(u64::MAX) {
+            return Ok(0);
+        }
+
         match &mut self.state {
             State::Idle => {
-                // Sanity check for normal cases.
-                if buf.is_empty() || self.cur >= self.size.unwrap_or(u64::MAX) {
-                    return Ok(0);
-                }
-
                 // Offset is none means we are doing tailing reading.
                 // we should stat first to get the correct offset.
                 if self.offset.is_none() {
diff --git a/core/tests/behavior/fuzz.rs b/core/tests/behavior/fuzz.rs
index b8f47fdfb..3ccd51a7d 100644
--- a/core/tests/behavior/fuzz.rs
+++ b/core/tests/behavior/fuzz.rs
@@ -15,18 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::io;
 use std::io::SeekFrom;
 use std::vec;
 
 use anyhow::Result;
-use futures::AsyncSeekExt;
+use bytes::Bytes;
 use log::debug;
+use opendal::raw::oio::ReadExt;
 
 use crate::*;
 
 pub fn behavior_fuzz_tests(op: &Operator) -> Vec<Trial> {
-    async_trials!(op, test_fuzz_issue_2717)
+    async_trials!(
+        op,
+        test_fuzz_issue_2717,
+        test_fuzz_pr_3395_case_1,
+        test_fuzz_pr_3395_case_2
+    )
 }
 
 /// This fuzz test is to reproduce <https://github.com/apache/incubator-opendal/issues/2717>.
@@ -86,7 +91,148 @@ pub async fn test_fuzz_issue_2717(op: Operator) -> Result<()> {
     // Perform a seek
     let result = r.seek(SeekFrom::End(-2)).await;
     assert!(result.is_err());
-    assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
+    assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidInput);
+
+    Ok(())
+}
+
+/// This fuzz test is to reproduce bug inside <https://github.com/apache/incubator-opendal/pull/3395>.
+///
+/// The simplified cases could be seen as:
+///
+/// ```
+/// FuzzInput {
+///     path: "06ae5d93-c0e9-43f2-ae5a-225cfaaa40a0",
+///     size: 1,
+///     range: BytesRange(
+///         Some(
+///             0,
+///         ),
+///         None,
+///     ),
+///     actions: [
+///         Seek(
+///             Current(
+///                 1,
+///             ),
+///         ),
+///         Next,
+///         Seek(
+///             End(
+///                 -1,
+///             ),
+///         ),
+///         Read {
+///             size: 0,
+///         },
+///         Read {
+///             size: 0,
+///         },
+///     ],
+/// }
+/// ```
+pub async fn test_fuzz_pr_3395_case_1(op: Operator) -> Result<()> {
+    let cap = op.info().full_capability();
+
+    if !(cap.read && cap.write & cap.read_with_range) {
+        return Ok(());
+    }
+
+    let path = uuid::Uuid::new_v4().to_string();
+    debug!("Generate a random file: {}", &path);
+    let content = gen_fixed_bytes(1);
+
+    op.write(&path, content.clone())
+        .await
+        .expect("write must succeed");
+
+    let mut r = op.reader_with(&path).range(0..).await?;
+
+    let pos = r.seek(SeekFrom::Current(1)).await?;
+    assert_eq!(pos, 1);
+
+    let bs = r.next().await.transpose()?;
+    assert!(bs.is_none());
+
+    let pos = r.seek(SeekFrom::End(-1)).await?;
+    assert_eq!(pos, 0);
+
+    let mut buf = vec![0; 0];
+    let n = r.read(&mut buf).await?;
+    assert_eq!(n, 0);
+
+    let mut buf = vec![0; 0];
+    let n = r.read(&mut buf).await?;
+    assert_eq!(n, 0);
+
+    Ok(())
+}
+
+/// This fuzz test is to reproduce bug inside <https://github.com/apache/incubator-opendal/pull/3395>.
+///
+/// The simplified cases could be seen as:
+///
+/// ```
+/// FuzzInput {
+///     path: "e6056989-7c7c-4075-b975-5ae380884333",
+///     size: 1,
+///     range: BytesRange(
+///         Some(
+///             0,
+///         ),
+///         None,
+///     ),
+///     actions: [
+///         Next,
+///         Seek(
+///             Current(
+///                 1,
+///             ),
+///         ),
+///         Next,
+///         Seek(
+///             End(
+///                 0,
+///             ),
+///         ),
+///         Read {
+///             size: 0,
+///         },
+///     ],
+/// }
+/// ```
+pub async fn test_fuzz_pr_3395_case_2(op: Operator) -> Result<()> {
+    let cap = op.info().full_capability();
+
+    if !(cap.read && cap.write & cap.read_with_range) {
+        return Ok(());
+    }
+
+    let path = uuid::Uuid::new_v4().to_string();
+    debug!("Generate a random file: {}", &path);
+    let content = gen_fixed_bytes(1);
+
+    op.write(&path, content.clone())
+        .await
+        .expect("write must succeed");
+
+    let mut r = op.reader_with(&path).range(0..).await?;
+
+    let bs = r.next().await.transpose()?;
+    assert_eq!(bs, Some(Bytes::from(content.clone())));
+
+    let pos = r.seek(SeekFrom::Current(1)).await?;
+    assert_eq!(pos, 2);
+
+    let bs = r.next().await.transpose()?;
+    assert!(bs.is_none());
+
+    let pos = r.seek(SeekFrom::End(0)).await?;
+    assert_eq!(pos, 1);
+
+    let mut buf = vec![0; 0];
+    let n = r.read(&mut buf).await?;
+    assert_eq!(n, 0);
 
     Ok(())
 }