summaryrefslogtreecommitdiff
path: root/dhall/src/semantics/resolve
diff options
context:
space:
mode:
Diffstat (limited to 'dhall/src/semantics/resolve')
-rw-r--r--dhall/src/semantics/resolve/cache.rs398
1 files changed, 398 insertions, 0 deletions
diff --git a/dhall/src/semantics/resolve/cache.rs b/dhall/src/semantics/resolve/cache.rs
index 5aa9ee2..b725bf3 100644
--- a/dhall/src/semantics/resolve/cache.rs
+++ b/dhall/src/semantics/resolve/cache.rs
@@ -176,6 +176,10 @@ impl AsRef<[u8]> for Hash {
#[cfg(test)]
mod test {
use super::*;
+ use crate::semantics::parse::parse_str;
+ use crate::syntax::{
+ parse_expr, ExprKind, ImportMode, ImportTarget, NumKind, Span,
+ };
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::env::temp_dir;
@@ -286,4 +290,398 @@ mod test {
);
std::fs::remove_dir_all(dir.as_path()).unwrap();
}
+
+ #[test]
+ fn caching_import_should_load_cache() -> Result<(), Error> {
+ let test_id = rand::thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(36)
+ .collect::<String>();
+ let dir = temp_dir().join(test_id);
+
+ std::fs::create_dir_all(dir.as_path())?;
+
+ let cache = Cache::new_with_provider(|_| {
+ Ok(dir.clone().to_str().map(String::from).unwrap())
+ });
+
+ // Create cache file
+ let expr =
+ Expr::new(ExprKind::Num(NumKind::Natural(1)), Span::Artificial);
+ File::create(dir.join("dhall").join("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15"))?
+ .write_all(binary::encode(&expr)?.as_ref())?;
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut resolve_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || panic!("Should not fetch import"),
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok());
+ assert_eq!(resolve_counter, 1);
+
+ std::fs::remove_dir_all(dir.as_path()).unwrap();
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_skip_cache_if_missing_cache_folder(
+ ) -> Result<(), Error> {
+ let cache = Cache::new_with_provider(|_| Err(VarError::NotPresent));
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut resolve_counter = 0;
+ let mut fetcher_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(resolve_counter, 1);
+ assert_eq!(fetcher_counter, 1);
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_skip_cache_on_no_hash_import() -> Result<(), Error>
+ {
+ let cache = Cache::new_with_provider(|_| Err(VarError::NotPresent));
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: None,
+ };
+
+ let mut resolve_counter = 0;
+ let mut fetcher_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(resolve_counter, 1);
+ assert_eq!(fetcher_counter, 1);
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_fetch_import_if_no_cache() -> Result<(), Error> {
+ let test_id = rand::thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(36)
+ .collect::<String>();
+ let dir = temp_dir().join(test_id);
+
+ std::fs::create_dir_all(dir.as_path())?;
+
+ let cache = Cache::new_with_provider(|_| {
+ Ok(dir.clone().to_str().map(String::from).unwrap())
+ });
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut fetcher_counter = 0;
+ let mut resolve_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(resolve_counter, 1);
+ assert_eq!(fetcher_counter, 1);
+
+ std::fs::remove_dir_all(dir.as_path()).unwrap();
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_fetch_import_on_cache_parsed_error(
+ ) -> Result<(), Error> {
+ let test_id = rand::thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(36)
+ .collect::<String>();
+ let dir = temp_dir().join(test_id);
+
+ std::fs::create_dir_all(dir.as_path())?;
+
+ let cache = Cache::new_with_provider(|_| {
+ Ok(dir.clone().to_str().map(String::from).unwrap())
+ });
+
+ File::create(dir.join("dhall").join("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15"))?
+ .write_all("Invalid content".as_bytes())?;
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut fetcher_counter = 0;
+ let mut resolve_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(fetcher_counter, 1, "Should fetch since cache is invalid");
+ assert_eq!(
+ resolve_counter, 1,
+ "Should resolve only 1 time because cache can't be parsed"
+ );
+
+ std::fs::remove_dir_all(dir.as_path()).unwrap();
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_fetch_import_on_cache_resolve_error(
+ ) -> Result<(), Error> {
+ let test_id = rand::thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(36)
+ .collect::<String>();
+ let dir = temp_dir().join(test_id);
+
+ std::fs::create_dir_all(dir.as_path())?;
+
+ let cache = Cache::new_with_provider(|_| {
+ Ok(dir.clone().to_str().map(String::from).unwrap())
+ });
+
+ let expr =
+ Expr::new(ExprKind::Num(NumKind::Natural(2)), Span::Artificial);
+ File::create(dir.join("dhall").join("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15"))?
+ .write_all(binary::encode(&expr)?.as_slice())?;
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut fetcher_counter = 0;
+ let mut resolve_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ match resolve_counter {
+ 1 => Err(Error::new(ErrorKind::Cache(
+ CacheError::CacheHashInvalid,
+ ))),
+ _ => {
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ }
+ }
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(fetcher_counter, 1, "Should fetch since cache is invalid");
+ assert_eq!(
+ resolve_counter, 2,
+ "Should resolve 2 time (one for cache that fail, one for fetch)"
+ );
+
+ std::fs::remove_dir_all(dir.as_path()).unwrap();
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_fetch_import_on_invalid_hash_cache_content(
+ ) -> Result<(), Error> {
+ let test_id = rand::thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(36)
+ .collect::<String>();
+ let dir = temp_dir().join(test_id);
+
+ std::fs::create_dir_all(dir.as_path())?;
+
+ let cache = Cache::new_with_provider(|_| {
+ Ok(dir.clone().to_str().map(String::from).unwrap())
+ });
+
+ let expr =
+ Expr::new(ExprKind::Num(NumKind::Natural(2)), Span::Artificial);
+ File::create(dir.join("dhall").join("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15"))?
+ .write_all(binary::encode(&expr)?.as_slice())?;
+
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut fetcher_counter = 0;
+ let mut resolve_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(fetcher_counter, 1, "Should fetch since cache is invalid");
+ assert_eq!(
+ resolve_counter, 2,
+ "Should resolve 2 time (one for cache, one for fetch)"
+ );
+
+ std::fs::remove_dir_all(dir.as_path()).unwrap();
+ Ok(())
+ }
+
+ #[test]
+ fn caching_import_should_save_import_if_missing() -> Result<(), Error> {
+ let test_id = rand::thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(36)
+ .collect::<String>();
+ let dir = temp_dir().join(test_id);
+
+ std::fs::create_dir_all(dir.as_path())?;
+
+ let cache = Cache::new_with_provider(|_| {
+ Ok(dir.clone().to_str().map(String::from).unwrap())
+ });
+ let import = Import {
+ mode: ImportMode::Code,
+ location: ImportTarget::Missing,
+ hash: Some(Hash::SHA256(hex::decode("d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15").unwrap())),
+ };
+
+ let mut fetcher_counter = 0;
+ let mut resolve_counter = 0;
+
+ let result = cache.caching_import(
+ &import,
+ || {
+ fetcher_counter += 1;
+ parse_str("1")
+ },
+ |parsed| {
+ resolve_counter += 1;
+ let result = parsed.resolve()?.typecheck()?;
+ Ok((result.normalize().to_hir(), result.ty))
+ },
+ );
+
+ assert!(result.is_ok(), "caching_import Should be valid");
+ assert_eq!(fetcher_counter, 1, "Should fetch since cache is mising");
+ assert_eq!(resolve_counter, 1, "Should resolve 1 time");
+
+ let cache_file = dir.join("dhall").join("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15");
+ assert!(cache_file.exists());
+
+ std::fs::remove_dir_all(dir.as_path()).unwrap();
+ Ok(())
+ }
+
+ #[test]
+ fn cache_filename_should_result_for_hash() {
+ let hash =
+ Hash::SHA256(parse_expr("1").unwrap().hash().unwrap().into_vec());
+ assert_eq!("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15".to_string(), cache_filename(hash));
+ }
+
+ #[test]
+ fn check_hash_should_be_ok_for_same_hash() -> Result<(), Error> {
+ let typed = parse_str("1")?.resolve()?.typecheck()?;
+ let hash = Hash::SHA256(parse_expr("1")?.hash()?.into_vec());
+
+ let expected = (typed.normalize().to_hir(), typed.ty);
+ let actual = check_hash(&hash, expected.clone());
+ assert_eq!(actual.unwrap(), expected);
+ Ok(())
+ }
+
+ #[test]
+ fn check_hash_should_be_ok_for_unmatching_hash() -> Result<(), Error> {
+ let typed = parse_str("1")?.resolve()?.typecheck()?;
+ let hash = Hash::SHA256(parse_expr("2")?.hash()?.into_vec());
+
+ let expected = (typed.normalize().to_hir(), typed.ty);
+ let actual = check_hash(&hash, expected);
+ assert!(actual.is_err());
+ Ok(())
+ }
}