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(())
}