From 6d850845ad73c0d602104fe12e422421c273d9b4 Mon Sep 17 00:00:00 2001 From: Robert Debug Date: Tue, 27 Apr 2021 19:55:23 +0200 Subject: [PATCH] add tests --- Cargo.lock | 1 + xayn-ai-ffi-wasm/Cargo.toml | 1 + xayn-ai-ffi-wasm/src/ai.rs | 170 +++++++++++++++++++++++++++++++--- xayn-ai-ffi-wasm/src/error.rs | 6 +- xayn-ai/src/analytics.rs | 2 +- xayn-ai/src/data/document.rs | 6 +- 6 files changed, 168 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c82494d83..50e34e080 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1680,6 +1680,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "getrandom", + "itertools 0.10.0", "js-sys", "serde", "wasm-bindgen", diff --git a/xayn-ai-ffi-wasm/Cargo.toml b/xayn-ai-ffi-wasm/Cargo.toml index 022c3b1ab..58a94e125 100644 --- a/xayn-ai-ffi-wasm/Cargo.toml +++ b/xayn-ai-ffi-wasm/Cargo.toml @@ -16,6 +16,7 @@ xayn-ai = { path = "../xayn-ai" } [dev-dependencies] wasm-bindgen-test = "0.3.23" +itertools = "0.10.0" [lib] crate-type = ["cdylib"] diff --git a/xayn-ai-ffi-wasm/src/ai.rs b/xayn-ai-ffi-wasm/src/ai.rs index b527aca5d..27ade4bd7 100644 --- a/xayn-ai-ffi-wasm/src/ai.rs +++ b/xayn-ai-ffi-wasm/src/ai.rs @@ -119,9 +119,15 @@ impl WXaynAi { #[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { + use super::*; + + use std::iter::repeat; + + use itertools::izip; use wasm_bindgen_test::wasm_bindgen_test; + use xayn_ai::{Relevance, UserFeedback}; - use super::*; + use crate::error::ExternError; /// Path to the current vocabulary file. pub const VOCAB: &[u8] = include_bytes!("../../data/rubert_v0000/vocab.txt"); @@ -129,21 +135,161 @@ mod tests { /// Path to the current onnx model file. pub const MODEL: &[u8] = include_bytes!("../../data/rubert_v0000/model.onnx"); + fn test_histories() -> Vec { + let len = 6; + let ids = (0..len).map(|idx| idx.to_string()).collect::>(); + + let relevances = repeat(Relevance::Low) + .take(len / 2) + .chain(repeat(Relevance::High).take(len - len / 2)); + let feedbacks = repeat(UserFeedback::Irrelevant) + .take(len / 2) + .chain(repeat(UserFeedback::Relevant).take(len - len / 2)); + + let history = izip!(ids, relevances, feedbacks) + .map(|(id, relevance, user_feedback)| { + JsValue::from_serde(&DocumentHistory { + id: id.as_str().into(), + relevance, + user_feedback, + }) + .unwrap() + }) + .collect::>(); + + history + } + + fn test_documents() -> Vec { + let len = 10; + let ids = (0..len).map(|idx| idx.to_string()).collect::>(); + + let snippets = (0..len) + .map(|idx| format!("snippet {}", idx)) + .collect::>(); + let ranks = 0..len as usize; + + let document = izip!(ids, snippets, ranks) + .map(|(id, snippet, rank)| { + JsValue::from_serde(&Document { + id: id.as_str().into(), + snippet, + rank, + }) + .unwrap() + }) + .collect::>(); + + document + } + #[wasm_bindgen_test] - fn test_reranker() { + fn test_rerank() { let mut xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + xaynai.rerank(test_histories(), test_documents()).unwrap(); + } - let document = Document { - id: "1".into(), - rank: 0, - snippet: "abc".to_string(), - }; - let js_document = JsValue::from_serde(&document).unwrap(); + #[wasm_bindgen_test] + fn test_serialize() { + let xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + xaynai.serialize().unwrap(); + } + + #[wasm_bindgen_test] + fn test_faults() { + let xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + let faults = xaynai.faults(); + assert!(faults.is_empty()) + } + + #[wasm_bindgen_test] + fn test_analytics() { + let xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + let analytics = xaynai.analytics(); + assert!(analytics.is_null()) + } + + #[wasm_bindgen_test] + fn test_vocab_invalid() { + let error: ExternError = WXaynAi::new(&[], MODEL, None) + .map_err(|e| JsValue::into_serde(&e).unwrap()) + .err() + .unwrap(); - let ranks = xaynai.rerank(vec![], vec![js_document]).unwrap(); - assert_eq!(ranks, [0]); + assert_eq!(error.code, CCode::InitAi as i32); + assert!(error + .message + .contains("Failed to initialize the ai: Failed to build the tokenizer: ")); + } + + // #[wasm_bindgen_test] + // fn test_model_invalid() { + // let error: ExternError = WXaynAi::new(VOCAB, &[], None) + // .map_err(|e| JsValue::into_serde(&e).unwrap()) + // .err() + // .unwrap(); + + // assert_eq!(error.code, CCode::InitAi as i32); + // assert!(error.message.contains("Failed to initialize the ai: ")); + // } + + #[wasm_bindgen_test] + fn test_history_invalid() { + let mut xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + let error: ExternError = xaynai + .rerank(vec![JsValue::from("invalid")], test_documents()) + .map_err(|e| JsValue::into_serde(&e).unwrap()) + .err() + .unwrap(); + + assert_eq!(error.code, CCode::HistoriesDeserialization as i32); + assert!(error + .message + .contains("Failed to deserialize the collection of histories: invalid type: ")); + } + + #[wasm_bindgen_test] + fn test_history_empty() { + let mut xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + xaynai.rerank(vec![], test_documents()).unwrap(); + } + + #[wasm_bindgen_test] + fn test_documents_invalid() { + let mut xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + let error: ExternError = xaynai + .rerank(test_histories(), vec![JsValue::from("invalid")]) + .map_err(|e| JsValue::into_serde(&e).unwrap()) + .err() + .unwrap(); + + assert_eq!(error.code, CCode::DocumentsDeserialization as i32); + assert!(error + .message + .contains("Failed to deserialize the collection of documents: invalid type: ")); + } + + #[wasm_bindgen_test] + fn test_documents_empty() { + let mut xaynai = WXaynAi::new(VOCAB, MODEL, None).unwrap(); + xaynai.rerank(test_histories(), vec![]).unwrap(); + } + + #[wasm_bindgen_test] + fn test_serialized_empty() { + WXaynAi::new(VOCAB, MODEL, Some(Box::new([]))).unwrap(); + } + + #[wasm_bindgen_test] + fn test_serialized_invalid() { + let error: ExternError = WXaynAi::new(VOCAB, MODEL, Some(Box::new([1, 2, 3]))) + .map_err(|e| JsValue::into_serde(&e).unwrap()) + .err() + .unwrap(); - let ser = xaynai.serialize().unwrap(); - assert!(ser.length() != 0) + assert_eq!(error.code, CCode::RerankerDeserialization as i32); + assert!(error.message.contains( + "Failed to deserialize the reranker database: Unsupported serialized data. " + )); } } diff --git a/xayn-ai-ffi-wasm/src/error.rs b/xayn-ai-ffi-wasm/src/error.rs index f69171ea2..4d8594e37 100644 --- a/xayn-ai-ffi-wasm/src/error.rs +++ b/xayn-ai-ffi-wasm/src/error.rs @@ -5,6 +5,7 @@ use crate::utils::IntoJsResult; // placeholder / later we can have a crate that contains common code for c-ffi and wasm #[repr(i32)] +#[cfg_attr(test, derive(Clone, Copy, Debug))] pub enum CCode { /// A warning or uncritical error. Fault = -2, @@ -28,9 +29,10 @@ impl CCode { } #[derive(Serialize)] +#[cfg_attr(test, derive(serde::Deserialize, Debug))] pub struct ExternError { - code: i32, - message: String, + pub(crate) code: i32, + pub(crate) message: String, } impl ExternError { diff --git a/xayn-ai/src/analytics.rs b/xayn-ai/src/analytics.rs index 6927a3f2f..c73f586fc 100644 --- a/xayn-ai/src/analytics.rs +++ b/xayn-ai/src/analytics.rs @@ -2,8 +2,8 @@ use std::{cmp::Ordering, collections::HashMap}; use anyhow::bail; use displaydoc::Display; -use thiserror::Error; use serde::Serialize; +use thiserror::Error; use crate::{ data::{ diff --git a/xayn-ai/src/data/document.rs b/xayn-ai/src/data/document.rs index 819c26e62..ab5e6d166 100644 --- a/xayn-ai/src/data/document.rs +++ b/xayn-ai/src/data/document.rs @@ -20,7 +20,7 @@ pub struct Document { pub snippet: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct DocumentHistory { /// unique identifier of this document pub id: DocumentId, @@ -30,14 +30,14 @@ pub struct DocumentHistory { pub user_feedback: UserFeedback, } -#[derive(Clone, Copy, Debug, PartialEq, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum UserFeedback { Relevant, Irrelevant, None, } -#[derive(Clone, Copy, Debug, PartialEq, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum Relevance { Low, Medium,