assetpack-core 0.3.0

Content-addressed asset packing, chunking, recipe, and SQLite storage primitives.
Documentation
use std::{
  io::{BufRead, Write},
  sync::Arc,
};

use super::*;
use crate::hash::Hash32;

#[derive(Debug, Clone)]
struct DummyTransform;

impl crate::TransformDecoder for DummyTransform {
  fn id(&self) -> u16 {
    0xFFFF
  }

  fn version(&self) -> u16 {
    1
  }

  fn decode(&self, input: &mut dyn BufRead, out: &mut dyn Write) -> crate::Result<()> {
    std::io::copy(input, out)?;
    Ok(())
  }
}

impl FileTransform for DummyTransform {
  fn quick_check(&self, _hint: &FileHint) -> bool {
    true
  }

  fn encode(&self, input: &mut dyn BufRead, out: &mut dyn Write) -> crate::Result<TransformStats> {
    let bytes = std::io::copy(input, out)?;
    Ok(TransformStats {
      input_bytes: bytes,
      output_bytes: bytes,
    })
  }
}

fn dummy_spec() -> TransformSpec {
  fn enabled(_config: &FileTransformConfig) -> bool {
    true
  }

  fn build(_config: &FileTransformConfig) -> Arc<dyn FileTransform> {
    Arc::new(DummyTransform)
  }

  TransformSpec {
    id: 0xFFFF,
    name: "dummy",
    selectable: true,
    use_quick_check: false,
    enabled,
    build,
  }
}

#[test]
fn spool_overflow_falls_back() {
  let mut spool = StoredStreamSpool::new_memory(4);
  assert!(spool.write_all(&[1, 2, 3]).is_ok());
  assert!(spool.write_all(&[4, 5]).is_err());
}

#[test]
fn precomp2_config_default_matches_expected() {
  let config = FileTransformPrecomp2Config::default();
  assert!(config.enabled);
  assert_eq!(config.max_depth, 4);
  assert_eq!(config.max_expand_ratio, 16.0);
  assert_eq!(config.max_total_output, 1 << 32);
  assert!(!config.recover);
}

#[test]
fn selector_skips_too_large() {
  let config = FileTransformConfig {
    min_size: 0,
    max_transform_bytes: 4,
    allow_ext: vec!["bin".into()],
    ..Default::default()
  };
  let selector = TransformSelector::new(config, None, vec![dummy_spec()]);
  let data = vec![0u8; 8];
  let hint = FileHint {
    size: data.len() as u64,
    extension: Some("bin".into()),
    head: Some(data[..4].to_vec()),
  };
  let selection = selector.select_bytes(data.clone(), &hint, Hash32::sha3_256(&data)).unwrap();
  assert_eq!(selection.transform_id, TRANSFORM_ID_NONE);
}

#[test]
fn selector_respects_no_spill() {
  let config = FileTransformConfig {
    min_size: 0,
    allow_ext: vec!["bin".into()],
    max_in_memory_bytes: 4,
    spill_to_disk: false,
    ..Default::default()
  };
  let selector = TransformSelector::new(config, None, vec![dummy_spec()]);
  let data = vec![1u8; 8];
  let hint = FileHint {
    size: data.len() as u64,
    extension: Some("bin".into()),
    head: Some(data[..4].to_vec()),
  };
  let selection = selector.select_bytes(data.clone(), &hint, Hash32::sha3_256(&data)).unwrap();
  assert_eq!(selection.transform_id, TRANSFORM_ID_NONE);
}

#[test]
fn selector_falls_back_when_temp_dir_invalid() {
  let config = FileTransformConfig {
    min_size: 0,
    allow_ext: vec!["bin".into()],
    max_in_memory_bytes: 1,
    spill_to_disk: true,
    ..Default::default()
  };
  let bad_dir = tempfile::NamedTempFile::new().unwrap();
  let selector = TransformSelector::new(config, Some(bad_dir.path().to_path_buf()), vec![dummy_spec()]);
  let data = vec![2u8; 8];
  let hint = FileHint {
    size: data.len() as u64,
    extension: Some("bin".into()),
    head: Some(data[..4].to_vec()),
  };
  let selection = selector.select_bytes(data.clone(), &hint, Hash32::sha3_256(&data)).unwrap();
  assert_eq!(selection.transform_id, TRANSFORM_ID_NONE);
}