candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
use tempfile::tempdir;

use super::*;

#[test]
fn fortran_order_is_refused_rather_than_transposed() {
  let dir = tempdir().unwrap();
  let bytes = npy_bytes("<f4", true, &[2, 2], &le_f32(&[1.0, 2.0, 3.0, 4.0]));
  let path = write_file(dir.path(), "fortran.npy", &bytes);

  let err = read(&path).unwrap_err();
  assert!(matches!(err, CanddingError::NpyFortranOrder(p) if p == path));
}

#[test]
fn a_truncated_header_is_refused_by_name() {
  let dir = tempdir().unwrap();
  let full = npy_bytes("<f4", false, &[4], &le_f32(&[1.0, 2.0, 3.0, 4.0]));
  // The magic, the version and v1.0's 2-byte header-length field.
  let prefix_and_length_field = MAGIC.len() + 2 + 2;
  let truncated = &full[..prefix_and_length_field + 5];
  let path = write_file(dir.path(), "truncated.npy", truncated);

  let err = read(&path).unwrap_err();
  match err {
    CanddingError::NpyHeader { file, reason } => {
      assert_eq!(file, path);
      assert!(reason.contains("truncated"), "{reason}");
    }
    other => panic!("expected NpyHeader, got {other:?}"),
  }
}

/// candle has no `I8`, so `i1` has no dtype to read into.
#[test]
fn an_unsupported_dtype_is_refused_by_name() {
  let dir = tempdir().unwrap();
  let bytes = npy_bytes("|i1", false, &[4], &[1, 2, 3, 4]);
  let path = write_file(dir.path(), "int8.npy", &bytes);

  let err = read(&path).unwrap_err();
  match err {
    CanddingError::NpyUnsupportedDtype { file, descr } => {
      assert_eq!(file, path);
      assert_eq!(descr, "|i1");
    }
    other => panic!("expected NpyUnsupportedDtype, got {other:?}"),
  }
}

#[test]
fn big_endian_byte_order_is_refused() {
  let dir = tempdir().unwrap();
  let bytes = npy_bytes(">f4", false, &[1], &1.0f32.to_be_bytes());
  let path = write_file(dir.path(), "big_endian.npy", &bytes);

  let err = read(&path).unwrap_err();
  match err {
    CanddingError::NpyUnsupportedByteOrder { file, order, descr } => {
      assert_eq!(file, path);
      assert_eq!(order, '>');
      assert_eq!(descr, ">f4");
    }
    other => panic!("expected NpyUnsupportedByteOrder, got {other:?}"),
  }
}

#[test]
fn a_shape_data_length_mismatch_is_refused() {
  let dir = tempdir().unwrap();
  let bytes = npy_bytes("<f4", false, &[4], &le_f32(&[1.0, 2.0, 3.0]));
  let path = write_file(dir.path(), "mismatch.npy", &bytes);

  let err = read(&path).unwrap_err();
  match err {
    CanddingError::NpyShapeMismatch {
      file,
      shape,
      expected_bytes,
      actual_bytes,
    } => {
      assert_eq!(file, path);
      assert_eq!(shape, vec![4]);
      assert_eq!(expected_bytes, 16);
      assert_eq!(actual_bytes, 12);
    }
    other => panic!("expected NpyShapeMismatch, got {other:?}"),
  }
}