use core::{cell::RefCell, time::Duration};
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use super::{CachedOnDiskCorpus, HasTestcase};
use crate::{
bolts::serdeany::SerdeAnyMap,
corpus::{Corpus, CorpusId, Testcase},
inputs::{Input, UsesInput},
Error,
};
#[cfg(feature = "std")]
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub enum OnDiskMetadataFormat {
Postcard,
Json,
#[default]
JsonPretty,
#[cfg(feature = "gzip")]
JsonGzip,
}
#[cfg(feature = "std")]
#[derive(Debug, Serialize)]
pub struct OnDiskMetadata<'a> {
pub metadata: &'a SerdeAnyMap,
pub exec_time: &'a Option<Duration>,
pub executions: &'a usize,
}
#[cfg(feature = "std")]
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct OnDiskCorpus<I>
where
I: Input,
{
dir_path: PathBuf,
inner: CachedOnDiskCorpus<I>,
}
impl<I> UsesInput for OnDiskCorpus<I>
where
I: Input,
{
type Input = I;
}
impl<I> Corpus for OnDiskCorpus<I>
where
I: Input,
{
#[inline]
fn count(&self) -> usize {
self.inner.count()
}
#[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
self.inner.add(testcase)
}
#[inline]
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
self.inner.replace(idx, testcase)
}
#[inline]
fn remove(&mut self, idx: CorpusId) -> Result<Testcase<I>, Error> {
self.inner.remove(idx)
}
#[inline]
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
self.inner.get(idx)
}
#[inline]
fn current(&self) -> &Option<CorpusId> {
self.inner.current()
}
#[inline]
fn current_mut(&mut self) -> &mut Option<CorpusId> {
self.inner.current_mut()
}
#[inline]
fn next(&self, idx: CorpusId) -> Option<CorpusId> {
self.inner.next(idx)
}
#[inline]
fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
self.inner.prev(idx)
}
#[inline]
fn first(&self) -> Option<CorpusId> {
self.inner.first()
}
#[inline]
fn last(&self) -> Option<CorpusId> {
self.inner.last()
}
#[inline]
fn nth(&self, nth: usize) -> CorpusId {
self.inner.nth(nth)
}
#[inline]
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
self.inner.load_input_into(testcase)
}
#[inline]
fn store_input_from(&self, testcase: &Testcase<Self::Input>) -> Result<(), Error> {
self.inner.store_input_from(testcase)
}
}
impl<I> HasTestcase for OnDiskCorpus<I>
where
I: Input,
{
fn testcase(
&self,
id: CorpusId,
) -> Result<core::cell::Ref<Testcase<<Self as UsesInput>::Input>>, Error> {
Ok(self.get(id)?.borrow())
}
fn testcase_mut(
&self,
id: CorpusId,
) -> Result<core::cell::RefMut<Testcase<<Self as UsesInput>::Input>>, Error> {
Ok(self.get(id)?.borrow_mut())
}
}
impl<I> OnDiskCorpus<I>
where
I: Input,
{
pub fn new<P>(dir_path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
Self::_new(dir_path.as_ref(), OnDiskMetadataFormat::JsonPretty)
}
pub fn with_meta_format<P>(
dir_path: P,
meta_format: OnDiskMetadataFormat,
) -> Result<Self, Error>
where
P: AsRef<Path>,
{
Self::_new(dir_path.as_ref(), meta_format)
}
fn _new(dir_path: &Path, meta_format: OnDiskMetadataFormat) -> Result<Self, Error> {
Ok(OnDiskCorpus {
dir_path: dir_path.into(),
inner: CachedOnDiskCorpus::with_meta_format(dir_path, 1, meta_format)?,
})
}
}
#[cfg(feature = "python")]
pub mod pybind {
use alloc::string::String;
use std::path::PathBuf;
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
corpus::{pybind::PythonCorpus, OnDiskCorpus},
inputs::BytesInput,
};
#[pyclass(unsendable, name = "OnDiskCorpus")]
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PythonOnDiskCorpus {
pub inner: OnDiskCorpus<BytesInput>,
}
#[pymethods]
impl PythonOnDiskCorpus {
#[new]
fn new(path: String) -> Self {
Self {
inner: OnDiskCorpus::new(PathBuf::from(path)).unwrap(),
}
}
fn as_corpus(slf: Py<Self>) -> PythonCorpus {
PythonCorpus::new_on_disk(slf)
}
}
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PythonOnDiskCorpus>()?;
Ok(())
}
}