pub mod array;
pub mod ast;
pub mod compile;
pub mod debug;
#[cfg(feature = "docs")]
pub mod docs;
pub mod driver;
pub mod dummy_env;
pub mod error;
pub mod jet;
pub mod lexer;
pub mod named;
pub mod num;
pub mod parse;
pub mod pattern;
pub mod resolution;
pub mod source;
pub mod unstable;
#[cfg(feature = "serde")]
mod serde;
pub mod str;
#[cfg(test)]
pub mod test_utils;
pub mod tracker;
pub mod types;
pub mod value;
pub mod version;
mod witness;
use std::sync::Arc;
use simplicity::jet::elements::ElementsEnv;
use simplicity::{CommitNode, RedeemNode};
pub extern crate either;
pub extern crate simplicity;
pub use simplicity::elements;
use crate::debug::DebugSymbols;
use crate::driver::{DependencyGraph, SourceMap, MAIN_MODULE};
use crate::error::DiagnosticManager;
use crate::parse::ParseFromStrWithErrors;
use crate::resolution::DependencyMap;
use crate::source::CanonSourceFile;
pub use crate::types::ResolvedType;
pub use crate::unstable::{UnstableFeature, UnstableFeatures};
pub use crate::value::Value;
#[cfg(feature = "serde")]
pub use crate::witness::UnresolvedValues;
pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues};
#[derive(Debug)]
pub struct TemplateProgram {
simfony: ast::Program,
file: Arc<str>,
jet_hinter: Box<dyn ast::JetHinter>,
diagnostics: DiagnosticManager,
resolved_program: parse::Program,
}
impl TemplateProgram {
pub fn flatten(
source: CanonSourceFile,
dependency_map: &DependencyMap,
unstable_features: &UnstableFeatures,
) -> Result<String, DiagnosticManager> {
let (program, diagnostics) = DependencyGraph::build_program(
source,
Arc::from(dependency_map.clone()),
unstable_features,
);
let Some(resolved_program) = program else {
return Err(diagnostics);
};
Ok(resolved_program.to_string())
}
pub fn new_with_dep(
source: CanonSourceFile,
dependency_map: &DependencyMap,
unstable_features: &UnstableFeatures,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, DiagnosticManager> {
let file = source.content();
let (program, mut diagnostics) = DependencyGraph::build_program(
source,
Arc::from(dependency_map.clone()),
unstable_features,
);
let Some(resolved_program) = program else {
return Err(diagnostics);
};
match ast::Program::analyze(&resolved_program, jet_hinter.clone_box()) {
Ok(simfony) => Ok(Self {
simfony,
file,
jet_hinter,
diagnostics,
resolved_program,
}),
Err(e) => {
diagnostics.push(e);
Err(diagnostics)
}
}
}
pub fn new<Str: Into<Arc<str>>>(
s: Str,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, DiagnosticManager> {
Self::new_with_unstable(s, &UnstableFeatures::none(), jet_hinter)
}
pub fn new_with_unstable<Str: Into<Arc<str>>>(
s: Str,
unstable_features: &UnstableFeatures,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, DiagnosticManager> {
let mut diagnostics = DiagnosticManager::default();
let file = s.into();
let Some(resolved_program) = parse::Program::parse_from_str_with_errors(
MAIN_MODULE,
&file,
unstable_features,
&mut diagnostics,
) else {
return Err(diagnostics);
};
match ast::Program::analyze(&resolved_program, jet_hinter.clone_box()) {
Ok(simfony) => Ok(Self {
simfony,
file,
jet_hinter,
diagnostics,
resolved_program,
}),
Err(e) => {
diagnostics.push(e);
Err(diagnostics)
}
}
}
pub fn parameters(&self) -> &Parameters {
self.simfony.parameters()
}
pub fn compiler_version(&self) -> &'static str {
version::SimcDirective::current_version()
}
pub fn witness_types(&self) -> &WitnessTypes {
self.simfony.witness_types()
}
pub fn instantiate(
&self,
arguments: Arguments,
include_debug_symbols: bool,
) -> Result<CompiledProgram, String> {
let mut diagnostics = DiagnosticManager::new();
arguments.is_consistent(self.simfony.parameters(), &mut diagnostics);
if diagnostics.has_errors() {
return Err(diagnostics.to_string());
}
let commit = self.simfony.compile(
arguments,
include_debug_symbols,
self.jet_hinter.clone_box(),
)?;
Ok(CompiledProgram {
debug_symbols: self.simfony.debug_symbols(self.file.as_ref()),
simplicity: commit,
witness_types: self.simfony.witness_types().shallow_clone(),
parameter_types: self.simfony.parameters().shallow_clone(),
})
}
pub fn generate_abi_meta(&self) -> Result<AbiMeta, String> {
Ok(AbiMeta {
witness_types: self.simfony.witness_types().shallow_clone(),
param_types: self.parameters().shallow_clone(),
})
}
pub fn source_map(&self) -> Option<&SourceMap> {
self.diagnostics.sources()
}
pub fn diagnostics(&self) -> &DiagnosticManager {
&self.diagnostics
}
pub fn resolved_program(&self) -> &parse::Program {
&self.resolved_program
}
}
#[derive(Clone, Debug)]
pub struct CompiledProgram {
simplicity: Arc<named::CommitNode>,
witness_types: WitnessTypes,
debug_symbols: DebugSymbols,
parameter_types: Parameters,
}
impl CompiledProgram {
pub fn new_with_dep(
source: CanonSourceFile,
dependency_map: &DependencyMap,
unstable_features: &UnstableFeatures,
arguments: Arguments,
include_debug_symbols: bool,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, String> {
TemplateProgram::new_with_dep(
source,
dependency_map,
unstable_features,
jet_hinter.clone_box(),
)
.map_err(|diagnostics| diagnostics.render_to_string())
.and_then(|template| template.instantiate(arguments, include_debug_symbols))
}
pub fn new<Str: Into<Arc<str>>>(
s: Str,
arguments: Arguments,
include_debug_symbols: bool,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, String> {
Self::new_with_unstable(
s,
&UnstableFeatures::none(),
arguments,
include_debug_symbols,
jet_hinter,
)
}
pub fn new_with_unstable<Str: Into<Arc<str>>>(
s: Str,
unstable_features: &UnstableFeatures,
arguments: Arguments,
include_debug_symbols: bool,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, String> {
TemplateProgram::new_with_unstable(s, unstable_features, jet_hinter.clone_box())
.map_err(|error| error.to_string())
.and_then(|template| template.instantiate(arguments, include_debug_symbols))
}
pub fn debug_symbols(&self) -> &DebugSymbols {
&self.debug_symbols
}
pub fn commit(&self) -> Arc<CommitNode> {
named::forget_names(&self.simplicity)
}
pub fn compiler_version(&self) -> &'static str {
version::SimcDirective::current_version()
}
pub fn witness_types(&self) -> &WitnessTypes {
&self.witness_types
}
pub fn satisfy(&self, witness_values: WitnessValues) -> Result<SatisfiedProgram, String> {
self.satisfy_with_env(witness_values, None)
}
pub fn satisfy_with_env(
&self,
witness_values: WitnessValues,
env: Option<&ElementsEnv<Arc<elements::Transaction>>>,
) -> Result<SatisfiedProgram, String> {
let mut diagnostics = DiagnosticManager::new();
witness_values.is_consistent(&self.witness_types, &mut diagnostics);
if diagnostics.has_errors() {
return Err(diagnostics.to_string());
}
let mut simplicity_redeem = named::populate_witnesses(&self.simplicity, witness_values)?;
if let Some(env) = env {
simplicity_redeem = simplicity_redeem.prune(env).map_err(|e| e.to_string())?;
}
Ok(SatisfiedProgram {
simplicity: simplicity_redeem,
debug_symbols: self.debug_symbols.clone(),
})
}
pub fn generate_abi_meta(&self) -> Result<AbiMeta, String> {
Ok(AbiMeta {
witness_types: self.witness_types.shallow_clone(),
param_types: self.parameter_types.shallow_clone(),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AbiMeta {
pub witness_types: WitnessTypes,
pub param_types: Parameters,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SatisfiedProgram {
simplicity: Arc<RedeemNode>,
debug_symbols: DebugSymbols,
}
impl SatisfiedProgram {
pub fn new<Str: Into<Arc<str>>>(
s: Str,
arguments: Arguments,
witness_values: WitnessValues,
include_debug_symbols: bool,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, String> {
Self::new_with_unstable(
s,
&UnstableFeatures::none(),
arguments,
witness_values,
include_debug_symbols,
jet_hinter,
)
}
pub fn new_with_unstable<Str: Into<Arc<str>>>(
s: Str,
unstable_features: &UnstableFeatures,
arguments: Arguments,
witness_values: WitnessValues,
include_debug_symbols: bool,
jet_hinter: Box<dyn ast::JetHinter>,
) -> Result<Self, String> {
let compiled = CompiledProgram::new_with_unstable(
s,
unstable_features,
arguments,
include_debug_symbols,
jet_hinter,
)?;
compiled.satisfy(witness_values)
}
pub fn redeem(&self) -> &Arc<RedeemNode> {
&self.simplicity
}
pub fn debug_symbols(&self) -> &DebugSymbols {
&self.debug_symbols
}
}
#[macro_export]
macro_rules! impl_eq_hash {
($ty: ident; $($member: ident),*) => {
impl PartialEq for $ty {
fn eq(&self, other: &Self) -> bool {
true $(&& self.$member() == other.$member())*
}
}
impl Eq for $ty {}
impl std::hash::Hash for $ty {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
$(self.$member().hash(state);)*
}
}
};
($ty:ident < $($gen:ident),+ > ; $($member:ident),*) => {
impl<$($gen),+> PartialEq for $ty<$($gen),+>
where
$($gen: PartialEq,)+
{
fn eq(&self, other: &Self) -> bool {
true $(&& self.$member() == other.$member())*
}
}
impl<$($gen),+> Eq for $ty<$($gen),+>
where
$($gen: Eq,)+
{}
impl<$($gen),+> std::hash::Hash for $ty<$($gen),+>
where
$($gen: std::hash::Hash,)+
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
$(self.$member().hash(state);)*
}
}
};
}
#[cfg(feature = "arbitrary")]
trait ArbitraryRec: Sized {
fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result<Self>;
}
#[cfg(feature = "arbitrary")]
pub trait ArbitraryOfType: Sized {
type Type;
fn arbitrary_of_type(
u: &mut arbitrary::Unstructured,
ty: &Self::Type,
) -> arbitrary::Result<Self>;
}
#[cfg(test)]
pub(crate) mod tests {
use crate::ast::{CoreJetHinter, ElementsJetHinter, JetHinter};
use crate::parse::ParseFromStr;
use crate::resolution::tests::{build_map, canon};
use crate::resolution::DependencyMapBuilder;
use crate::source::CanonPath;
use crate::test_utils::TempWorkspace;
use base64::display::Base64Display;
use base64::engine::general_purpose::STANDARD;
use simplicity::BitMachine;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use crate::*;
const FLATTENED: &str = "flattened.simf";
pub(crate) const MAIN: &str = "main.simf";
pub(crate) fn flatten_template_file(
prog_path: &Path,
dependency_map: &DependencyMap,
) -> String {
let program_text = std::fs::read_to_string(prog_path).unwrap();
let source = CanonSourceFile::new(
CanonPath::canonicalize(prog_path).unwrap(),
Arc::from(program_text),
);
match TemplateProgram::flatten(source, dependency_map, &UnstableFeatures::all()) {
Ok(single_file) => single_file,
Err(error) => panic!("{}", &error),
}
}
pub(crate) fn format_program_file(prog_path: &Path) -> String {
let file = Arc::<str>::from(std::fs::read_to_string(prog_path).unwrap());
let mut diagnostics = DiagnosticManager::new();
let parse_program = parse::Program::parse_from_str_with_errors(
MAIN_MODULE,
&file,
&UnstableFeatures::all(),
&mut diagnostics,
)
.unwrap();
parse_program.to_string()
}
pub(crate) fn build_dependency_map<P, I, K>(prog_path: P, dependencies: I) -> DependencyMap
where
P: AsRef<Path>,
I: IntoIterator<Item = (P, K, P)>,
K: Into<String>,
{
let parent = prog_path.as_ref().parent().unwrap();
let canon_root = canon(parent);
let mut builder = DependencyMapBuilder::new();
for (context, alias, target) in dependencies {
let context = canon(context.as_ref());
let target = canon(target.as_ref());
builder.add_dependency(context, alias.into(), target);
}
builder.build(canon_root).unwrap()
}
pub(crate) struct TestCase<T> {
program: T,
lock_time: elements::LockTime,
sequence: elements::Sequence,
include_fee_output: bool,
}
impl TestCase<TemplateProgram> {
pub fn template_file<P: AsRef<Path>>(program_file_path: P) -> Self {
Self::template_file_with_unstable(program_file_path, UnstableFeatures::none())
}
pub fn template_file_with_unstable<P: AsRef<Path>>(
program_file_path: P,
unstable_features: UnstableFeatures,
) -> Self {
let program_text = std::fs::read_to_string(program_file_path).unwrap();
Self::template_text_with_unstable(Cow::Owned(program_text), unstable_features)
}
pub fn template_deps_with_unstable(
prog_path: &Path,
dependency_map: &DependencyMap,
unstable_features: UnstableFeatures,
) -> Self {
let program_text = std::fs::read_to_string(prog_path).unwrap();
let source = CanonSourceFile::new(
CanonPath::canonicalize(prog_path).unwrap(),
Arc::from(program_text),
);
let program = match TemplateProgram::new_with_dep(
source,
dependency_map,
&unstable_features,
Box::new(ElementsJetHinter::new()),
) {
Ok(x) => x,
Err(error) => panic!("{}", &error),
};
Self {
program,
lock_time: elements::LockTime::ZERO,
sequence: elements::Sequence::MAX,
include_fee_output: false,
}
}
pub fn template_text(program_text: Cow<str>) -> Self {
Self::template_text_with_unstable(program_text, UnstableFeatures::none())
}
pub fn template_text_with_unstable(
program_text: Cow<str>,
unstable_features: UnstableFeatures,
) -> Self {
let program = match TemplateProgram::new_with_unstable(
program_text.as_ref(),
&unstable_features,
Box::new(ElementsJetHinter::new()),
) {
Ok(x) => x,
Err(error) => panic!("{}", &error),
};
Self {
program,
lock_time: elements::LockTime::ZERO,
sequence: elements::Sequence::MAX,
include_fee_output: false,
}
}
#[cfg(feature = "serde")]
pub fn with_argument_file<P: AsRef<Path>>(
self,
arguments_file_path: P,
) -> TestCase<CompiledProgram> {
let arguments_text = std::fs::read_to_string(arguments_file_path).unwrap();
let unresolved = match serde_json::from_str::<UnresolvedValues>(&arguments_text) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
let arguments = match unresolved.resolve(self.program.parameters()) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
self.with_arguments(arguments)
}
pub fn with_arguments(self, arguments: Arguments) -> TestCase<CompiledProgram> {
let program = match self.program.instantiate(arguments, true) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
TestCase {
program,
lock_time: self.lock_time,
sequence: self.sequence,
include_fee_output: self.include_fee_output,
}
}
}
impl TestCase<CompiledProgram> {
pub fn program_file<P: AsRef<Path>>(program_file_path: P) -> Self {
TestCase::<TemplateProgram>::template_file(program_file_path)
.with_arguments(Arguments::default())
}
pub fn program_file_with_unstable<P: AsRef<Path>>(
program_file_path: P,
unstable_features: UnstableFeatures,
) -> Self {
TestCase::<TemplateProgram>::template_file_with_unstable(
program_file_path,
unstable_features,
)
.with_arguments(Arguments::default())
}
pub fn program_text(program_text: Cow<str>) -> Self {
TestCase::<TemplateProgram>::template_text(program_text)
.with_arguments(Arguments::default())
}
pub fn program_text_with_unstable(
program_text: Cow<str>,
unstable_features: UnstableFeatures,
) -> Self {
TestCase::<TemplateProgram>::template_text_with_unstable(
program_text,
unstable_features,
)
.with_arguments(Arguments::default())
}
pub fn program_file_with_deps_and_unstable(
prog_path: impl AsRef<Path>,
dependency_map: &DependencyMap,
unstable_features: UnstableFeatures,
) -> Self {
TestCase::<TemplateProgram>::template_deps_with_unstable(
prog_path.as_ref(),
dependency_map,
unstable_features,
)
.with_arguments(Arguments::default())
}
#[cfg(feature = "serde")]
pub fn with_witness_file<P: AsRef<Path>>(
self,
witness_file_path: P,
) -> TestCase<SatisfiedProgram> {
let witness_text = std::fs::read_to_string(witness_file_path).unwrap();
let unresolved = match serde_json::from_str::<UnresolvedValues>(&witness_text) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
let witness_values = match unresolved.resolve(self.program.witness_types()) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
self.with_witness_values(witness_values)
}
pub fn with_witness_values(
self,
witness_values: WitnessValues,
) -> TestCase<SatisfiedProgram> {
let program = match self.program.satisfy(witness_values) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
TestCase {
program,
lock_time: self.lock_time,
sequence: self.sequence,
include_fee_output: self.include_fee_output,
}
}
#[cfg(feature = "serde")]
pub fn get_encoding(self) -> String {
let program_bytes = self.program.commit().to_vec_without_witness();
Base64Display::new(&program_bytes, &STANDARD).to_string()
}
}
impl<T> TestCase<T> {
#[allow(dead_code)]
pub fn with_lock_time(mut self, height: u32) -> Self {
let height = elements::locktime::Height::from_consensus(height).unwrap();
self.lock_time = elements::LockTime::Blocks(height);
if self.sequence.is_final() {
self.sequence = elements::Sequence::ENABLE_LOCKTIME_NO_RBF;
}
self
}
#[allow(dead_code)]
pub fn with_sequence(mut self, distance: u16) -> Self {
self.sequence = elements::Sequence::from_height(distance);
self
}
#[allow(dead_code)]
pub fn print_sighash_all(self) -> Self {
let env = dummy_env::dummy_with(self.lock_time, self.sequence, self.include_fee_output);
dbg!(env.c_tx_env().sighash_all());
self
}
}
impl TestCase<SatisfiedProgram> {
#[allow(dead_code)]
pub fn print_encoding(self) -> Self {
let (program_bytes, witness_bytes) = self.program.redeem().to_vec_with_witness();
println!(
"Program:\n{}",
Base64Display::new(&program_bytes, &STANDARD)
);
println!(
"Witness:\n{}",
Base64Display::new(&witness_bytes, &STANDARD)
);
self
}
fn run(self) -> Result<(), simplicity::bit_machine::ExecutionError> {
let env = dummy_env::dummy_with(self.lock_time, self.sequence, self.include_fee_output);
let pruned = self.program.redeem().prune(&env)?;
let mut mac = BitMachine::for_program(&pruned)
.expect("program should be within reasonable bounds");
mac.exec(&pruned, &env).map(|_| ())
}
pub fn assert_run_success(self) {
match self.run() {
Ok(()) => {}
Err(error) => panic!("Unexpected error: {error}"),
}
}
#[cfg(feature = "serde")]
pub fn get_encoding_with_witness(self) -> (String, String) {
let (program_bytes, witness_bytes) = self.program.redeem().to_vec_with_witness();
(
Base64Display::new(&program_bytes, &STANDARD).to_string(),
Base64Display::new(&witness_bytes, &STANDARD).to_string(),
)
}
}
pub(crate) fn run_dependency_test(root_path: &str, lib_alias: &str) {
let root_path = PathBuf::from(root_path);
let lib_path = root_path.join(lib_alias);
let main_path = root_path.join(MAIN);
let dependency_map = build_dependency_map(&main_path, [(&root_path, lib_alias, &lib_path)]);
TestCase::program_file_with_deps_and_unstable(
&main_path,
&dependency_map,
UnstableFeatures::all(),
)
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
pub(crate) fn flatten_dependency_test(root_path: &str, lib_alias: &str) {
let root_path = PathBuf::from(root_path);
let lib_path = root_path.join(lib_alias);
let main_path = root_path.join(MAIN);
let flattened_path = root_path.join(FLATTENED);
let dependency_map = build_dependency_map(&main_path, [(&root_path, lib_alias, &lib_path)]);
let expected = format_program_file(&flattened_path);
let actual = flatten_template_file(&main_path, &dependency_map);
assert_eq!(expected, actual);
}
pub(crate) fn run_multidep_test(root_path: &str, deps: &[(&str, &str, &str)]) {
let root_path = PathBuf::from(root_path);
let main_path = root_path.join(MAIN);
let mapped_deps: Vec<(PathBuf, &str, PathBuf)> = deps
.iter()
.map(|(ctx, alias, target)| {
let ctx_path = if *ctx == "." {
root_path.clone()
} else {
root_path.join(ctx)
};
let target_path = root_path.join(target);
(ctx_path, *alias, target_path)
})
.collect();
let ref_deps = mapped_deps.iter().map(|(c, a, t)| (c, *a, t));
let dependency_map = build_dependency_map(&main_path, ref_deps);
TestCase::program_file_with_deps_and_unstable(
&main_path,
&dependency_map,
UnstableFeatures::all(),
)
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
pub(crate) fn flatten_multidep_test(root_path: &str, deps: &[(&str, &str, &str)]) {
let root_path = PathBuf::from(root_path);
let main_path = root_path.join(MAIN);
let flattened_path = root_path.join(FLATTENED);
let mapped_deps: Vec<(PathBuf, &str, PathBuf)> = deps
.iter()
.map(|(ctx, alias, target)| {
let ctx_path = if *ctx == "." {
root_path.clone()
} else {
root_path.join(ctx)
};
let target_path = root_path.join(target);
(ctx_path, *alias, target_path)
})
.collect();
let ref_deps = mapped_deps.iter().map(|(c, a, t)| (c, *a, t));
let dependency_map = build_dependency_map(&main_path, ref_deps);
let expected = format_program_file(&flattened_path);
let actual = flatten_template_file(&main_path, &dependency_map);
assert_eq!(expected, actual);
}
#[test]
fn single_dep() {
run_dependency_test("./examples/single_dep", "temp");
}
#[test]
fn flatten_single_dep() {
flatten_dependency_test("./examples/single_dep", "temp");
}
#[test]
fn simple_multidep() {
run_multidep_test(
"./examples/simple_multidep",
&[(".", "math", "math"), (".", "crypto", "crypto")],
);
}
#[test]
fn flatten_simple_multidep() {
flatten_multidep_test(
"./examples/simple_multidep",
&[(".", "math", "math"), (".", "crypto", "crypto")],
);
}
#[test]
fn multiple_deps() {
run_multidep_test(
"./examples/multiple_deps",
&[
(".", "merkle", "merkle"),
(".", "base_math", "math"),
("merkle", "math", "math"),
],
);
}
#[test]
fn flatten_multiple_deps() {
flatten_multidep_test(
"./examples/multiple_deps",
&[
(".", "merkle", "merkle"),
(".", "base_math", "math"),
("merkle", "math", "math"),
],
);
}
#[test]
fn local_crate() {
run_multidep_test("./examples/local_crate", &[]);
}
#[test]
fn test_crate_keyword_compilation_success() {
let ws = TempWorkspace::new("crate_success");
let root = ws.create_dir("workspace");
ws.create_file(
format!("workspace/{MAIN}").as_str(),
"use crate::utils::add;\nfn main() { assert!(jet::eq_32(add(2, 2), 4)); }",
);
ws.create_file(
"workspace/utils.simf",
"pub fn add(a: u32, b: u32) -> u32 { let (_, sum): (bool, u32) = jet::add_32(a, b); sum }",
);
let main_path = root.join(MAIN);
let canon_root = CanonPath::canonicalize(&root).unwrap();
let dependency_map = build_map(&canon_root, &[]).unwrap();
TestCase::<TemplateProgram>::template_deps_with_unstable(
&main_path,
&dependency_map,
UnstableFeatures::all(),
)
.with_arguments(Arguments::default())
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn test_anonymous_source_compiles_without_dependencies() {
let code = "fn main() { assert!(true); }";
let program = TemplateProgram::new(code, Box::new(ElementsJetHinter::new()));
assert!(
program.is_ok(),
"TemplateProgram::new should successfully compile anonymous source files without requiring canonical paths"
);
}
#[test]
fn cat() {
TestCase::program_file("./examples/cat.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn modules() {
TestCase::program_file_with_unstable("./examples/modules.simf", UnstableFeatures::all())
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn ctv() {
TestCase::program_file("./examples/ctv.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn regression_153() {
TestCase::program_file("./examples/array_fold_2n.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn pattern_matching() {
TestCase::program_file("./examples/pattern_matching.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_non_interactive_fee_bump() {
let mut t = TestCase::program_file("./examples/non_interactive_fee_bump.simf")
.with_witness_file("./examples/non_interactive_fee_bump.wit");
t.sequence = elements::Sequence::ENABLE_LOCKTIME_NO_RBF;
t.lock_time = elements::LockTime::from_time(1734967235 + 600).unwrap();
t.include_fee_output = true;
t.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn escrow_with_delay_timeout() {
TestCase::program_file("./examples/escrow_with_delay.simf")
.with_sequence(1000)
.print_sighash_all()
.with_witness_file("./examples/escrow_with_delay.timeout.wit")
.assert_run_success();
}
#[test]
fn hash_loop() {
TestCase::program_file("./examples/hash_loop.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn hodl_vault() {
TestCase::program_file("./examples/hodl_vault.simf")
.with_lock_time(1000)
.print_sighash_all()
.with_witness_file("./examples/hodl_vault.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn htlc_complete() {
TestCase::program_file("./examples/htlc.simf")
.print_sighash_all()
.with_witness_file("./examples/htlc.complete.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn last_will_inherit() {
TestCase::program_file_with_unstable("./examples/last_will.simf", UnstableFeatures::all())
.with_sequence(25920)
.print_sighash_all()
.with_witness_file("./examples/last_will.inherit.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn p2ms() {
TestCase::program_file("./examples/p2ms.simf")
.print_sighash_all()
.with_witness_file("./examples/p2ms.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn p2pk() {
TestCase::template_file("./examples/p2pk.simf")
.with_argument_file("./examples/p2pk.args")
.print_sighash_all()
.with_witness_file("./examples/p2pk.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn p2pkh() {
TestCase::program_file("./examples/p2pkh.simf")
.print_sighash_all()
.with_witness_file("./examples/p2pkh.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn presigned_vault_complete() {
TestCase::program_file("./examples/presigned_vault.simf")
.with_sequence(1000)
.print_sighash_all()
.with_witness_file("./examples/presigned_vault.complete.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_all_anyonecanpay() {
TestCase::program_file("./examples/sighash_all_anyonecanpay.simf")
.with_witness_file("./examples/sighash_all_anyonecanpay.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_all_anyprevout() {
TestCase::program_file("./examples/sighash_all_anyprevout.simf")
.with_witness_file("./examples/sighash_all_anyprevout.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_all_anyprevoutanyscript() {
TestCase::program_file("./examples/sighash_all_anyprevoutanyscript.simf")
.with_witness_file("./examples/sighash_all_anyprevoutanyscript.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_none() {
TestCase::program_file("./examples/sighash_none.simf")
.with_witness_file("./examples/sighash_none.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_single() {
TestCase::program_file("./examples/sighash_single.simf")
.with_witness_file("./examples/sighash_single.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn transfer_with_timeout_transfer() {
TestCase::program_file("./examples/transfer_with_timeout.simf")
.print_sighash_all()
.with_witness_file("./examples/transfer_with_timeout.transfer.wit")
.assert_run_success();
}
#[test]
fn redefined_variable() {
let prog_text = r#"fn main() {
let beefbabe: (u16, u16) = (0xbeef, 0xbabe);
let beefbabe: u32 = <(u16, u16)>::into(beefbabe);
}
"#;
TestCase::program_text(Cow::Borrowed(prog_text))
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn empty_function_body_nonempty_return() {
let prog_text = r#"fn my_true() -> bool {
// function body is empty, although function must return `bool`
}
fn main() {
assert!(my_true());
}
"#;
match SatisfiedProgram::new(
prog_text,
Arguments::default(),
WitnessValues::default(),
false,
Box::new(ElementsJetHinter::new()),
) {
Ok(_) => panic!("Accepted faulty program"),
Err(error) => {
assert!(
error.contains("Expected expression of type `bool`, found type `()`"),
"Unexpected error: {error}",
);
}
}
}
#[test]
fn fuzz_regression_2() {
parse::Program::parse_from_str("fn dbggscas(h: bool, asyxhaaaa: a) {\nfalse}\n\n").unwrap();
}
#[test]
fn fuzz_slow_unit_1() {
parse::Program::parse_from_str("fn fnnfn(MMet:(((sssss,((((((sssss,ssssss,ss,((((((sssss,ss,((((((sssss,ssssss,ss,((((((sssss,ssssss,((((((sssss,sssssssss,(((((((sssss,sssssssss,(((((ssss,((((((sssss,sssssssss,(((((((sssss,ssss,((((((sssss,ss,((((((sssss,ssssss,ss,((((((sssss,ssssss,((((((sssss,sssssssss,(((((((sssss,sssssssss,(((((ssss,((((((sssss,sssssssss,(((((((sssss,sssssssssssss,(((((((((((u|(").unwrap_err();
}
#[test]
fn type_alias() {
let prog_text = r#"type MyAlias = u32;
fn main() {
let x: MyAlias = 32;
assert!(jet::eq_32(x, 32));
}"#;
TestCase::program_text(Cow::Borrowed(prog_text))
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn type_error_regression() {
let prog_text = r#"fn main() {
let (a, b): (u32, u32) = (0, 1);
assert!(jet::eq_32(a, 0));
let (c, d): (u32, u32) = (2, 3);
assert!(jet::eq_32(c, 2));
assert!(jet::eq_32(d, 3));
}"#;
TestCase::program_text(Cow::Borrowed(prog_text))
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn test_compilation_against_different_jet_hinters() {
let code = r#"fn main() {
let (_, sum): (bool, u32) = jet::add_32(10, 20);
assert!(jet::eq_32(sum, 30));
let and_result: u32 = jet::and_32(0xFF00FF00, 0x0F0F0F0F);
assert!(jet::eq_32(and_result, 0x0F000F00));
}"#;
let hinters: Vec<Box<dyn JetHinter>> = vec![
Box::new(CoreJetHinter::new()),
Box::new(ElementsJetHinter::new()),
];
for hinter in hinters {
let program = TemplateProgram::new(code, hinter);
assert!(
program.is_ok(),
"TemplateProgram::new should successfully compile the same program with different jet hinters: {:?}",
program.err(),
);
}
}
#[test]
fn test_fail_with_different_jet_hinters() {
let code = r#"fn main() {
let v: u32 = jet::version();
let idx: u32 = jet::current_index();
assert!(jet::eq_32(v, v));
assert!(jet::eq_32(idx, idx));
}"#;
let elements_result = TemplateProgram::new(code, Box::new(ElementsJetHinter::new()));
assert!(
elements_result.is_ok(),
"ElementsJetHinter should compile Elements-specific jets: {:?}",
elements_result.err(),
);
let core_result = TemplateProgram::new(code, Box::new(CoreJetHinter::new()));
assert!(
core_result.is_err(),
"CoreJetHinter should fail to compile Elements-specific jets",
);
}
#[cfg(feature = "serde")]
mod regression {
use super::TestCase;
#[derive(serde::Deserialize)]
struct Program {
program: String,
witness: Option<String>,
}
fn regression_test(name: &str) {
regression_test_with_features(name, crate::UnstableFeatures::none());
}
fn regression_test_with_features(name: &str, features: crate::UnstableFeatures) {
let program = serde_json::from_str::<Program>(
std::fs::read_to_string(format!("./test-data/{}.json", name))
.unwrap()
.as_str(),
)
.unwrap();
let test_case =
TestCase::program_file_with_unstable(format!("./examples/{}.simf", name), features);
match program.witness {
Some(wit) => {
let (new_program, new_witness) = test_case
.with_witness_file(format!("./examples/{}.wit", name))
.get_encoding_with_witness();
assert_eq!(
program.program, new_program,
"Byte code of programs should be the same"
);
assert_eq!(
wit, new_witness,
"Byte code of witnesses should be the same"
);
}
None => {
let new_program = test_case.get_encoding();
assert_eq!(
program.program, new_program,
"Byte code of programs should be the same"
)
}
}
}
#[test]
fn array_fold_2n_regression() {
regression_test("array_fold_2n");
}
#[test]
fn array_fold_regression() {
regression_test("array_fold");
}
#[test]
fn cat_regression() {
regression_test("cat");
}
#[test]
fn ctv_regression() {
regression_test("ctv");
}
#[test]
fn escrow_with_delay_regression() {
regression_test("escrow_with_delay");
}
#[test]
fn hash_loop_regression() {
regression_test("hash_loop");
}
#[test]
fn hodl_vault_regression() {
regression_test("hodl_vault");
}
#[test]
fn htlc_regression() {
regression_test("htlc");
}
#[test]
fn last_will_regression() {
regression_test_with_features("last_will", crate::UnstableFeatures::all());
}
#[test]
fn non_interactive_fee_bump_regression() {
regression_test("non_interactive_fee_bump");
}
#[test]
fn p2ms_regression() {
regression_test("p2ms");
}
#[test]
fn p2pkh_regression() {
regression_test("p2pkh");
}
#[test]
fn presigned_vault_regression() {
regression_test("presigned_vault");
}
#[test]
fn reveal_collision_regression() {
regression_test("reveal_collision");
}
#[test]
fn reveal_fix_point_regression() {
regression_test("reveal_fix_point");
}
#[test]
fn sighash_all_anyonecanpay_regression() {
regression_test("sighash_all_anyonecanpay");
}
#[test]
fn sighash_all_anyprevoutanyscript_regression() {
regression_test("sighash_all_anyprevoutanyscript");
}
#[test]
fn sighash_all_anyprevout_regression() {
regression_test("sighash_all_anyprevout");
}
#[test]
fn sighash_none_regression() {
regression_test("sighash_none");
}
#[test]
fn sighash_single_regression() {
regression_test("sighash_single");
}
#[test]
fn transfer_with_timeout_regression() {
regression_test("transfer_with_timeout");
}
}
#[test]
fn compatible_directive_compiles() {
let version = crate::version::SimcDirective::current_version()
.split('-')
.next()
.unwrap();
let compatible = format!("simc \"{version}\";\nfn main() {{}}");
assert!(
TemplateProgram::new(compatible, Box::new(crate::ast::ElementsJetHinter::new()))
.is_ok()
);
}
#[test]
fn compiler_version_accessor() {
let template = TemplateProgram::new(
"fn main() {}",
Box::new(crate::ast::ElementsJetHinter::new()),
)
.unwrap();
assert_eq!(template.compiler_version(), env!("CARGO_PKG_VERSION"));
let compiled = template.instantiate(Arguments::default(), false).unwrap();
assert_eq!(compiled.compiler_version(), env!("CARGO_PKG_VERSION"));
}
#[test]
fn incompatible_directive_aborts() {
let too_old = "simc \">= 99.99.99\";\nfn main() {}";
let err = TemplateProgram::new(too_old, Box::new(crate::ast::ElementsJetHinter::new()))
.unwrap_err()
.to_string();
assert!(
err.contains("Incompatible compiler version"),
"Expected 'Incompatible compiler version', got: {}",
err
);
}
#[test]
fn enum_construction_compiles_and_runs() {
let src = "enum Action { Refresh(u32, bool), Cold, }
fn pick() -> Action {
Action::Refresh(7, true)
}
fn main() {
let a: Action = pick();
match a {
Action::Refresh(n: u32, b: bool) => {
assert!(jet::eq_32(n, 7));
assert!(b);
},
Action::Cold => assert!(false),
}
}";
TestCase::program_text_with_unstable(Cow::Borrowed(src), UnstableFeatures::all())
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn enum_unit_construction_compiles_and_runs() {
let src = "enum Action { Hot, Cold, }
fn main() {
let a: Action = Action::Cold;
match a {
Action::Hot => assert!(false),
Action::Cold => {},
}
}";
TestCase::program_text_with_unstable(Cow::Borrowed(src), UnstableFeatures::all())
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn enum_match_witness_file_variant_name() {
use crate::str::WitnessName;
let src = "enum Action { Hot, Cold, }
fn main() {
let x: u32 = match witness::ACT {
Action::Hot => 1,
Action::Cold => 2,
};
assert!(jet::eq_32(x, 2));
}";
let compiled = CompiledProgram::new_with_unstable(
src,
&UnstableFeatures::all(),
Arguments::default(),
false,
Box::new(ElementsJetHinter::new()),
)
.unwrap();
let unresolved: UnresolvedValues =
serde_json::from_str(r#"{ "ACT": "Action::Cold" }"#).unwrap();
let witness: WitnessValues = unresolved.resolve(compiled.witness_types()).unwrap();
assert!(witness
.get(&WitnessName::from_str_unchecked("ACT"))
.is_some());
TestCase::program_text_with_unstable(Cow::Borrowed(src), UnstableFeatures::all())
.with_witness_values(witness)
.assert_run_success();
}
#[test]
fn strict_satisfy_rejects_missing_witness() {
use crate::str::{Identifier, WitnessName};
use crate::value::ValueConstructible;
use std::collections::HashMap;
let src = r#"
enum Branch { A, B }
fn main() {
match witness::SELECTOR {
Branch::A => assert!(jet::is_zero_32(witness::A)),
Branch::B => assert!(jet::is_zero_32(witness::B)),
}
}
"#;
let compiled = CompiledProgram::new_with_unstable(
src,
&UnstableFeatures::all(),
Arguments::default(),
false,
Box::new(ElementsJetHinter::new()),
)
.unwrap();
let selector_ty = compiled
.witness_types()
.get(&WitnessName::from_str_unchecked("SELECTOR"))
.unwrap()
.clone();
let mut map: HashMap<WitnessName, Value> = HashMap::new();
map.insert(
WitnessName::from_str_unchecked("SELECTOR"),
Value::enum_variant(&selector_ty, &Identifier::from_str_unchecked("A"), vec![])
.unwrap(),
);
map.insert(WitnessName::from_str_unchecked("A"), Value::u32(0));
let err = compiled
.satisfy(WitnessValues::from(map.clone()))
.expect_err("satisfy must reject a missing witness");
assert!(
err.contains('B'),
"error should mention the missing witness B, got: {err}"
);
}
#[test]
fn enum_match_dispatches_every_variant() {
use crate::str::{Identifier, WitnessName};
use std::collections::HashMap;
for (variants, arms) in [
("A,", vec!["A"]),
("A, B, C,", vec!["A", "B", "C"]),
("A, B, C, D, E,", vec!["A", "B", "C", "D", "E"]),
] {
let arm_lines: String = arms
.iter()
.enumerate()
.map(|(i, name)| format!("Action::{} => {},\n", name, (i + 1) * 10))
.collect();
let src = format!(
"enum Action {{ {variants} }}
fn main() {{
let selected: u32 = match witness::ACT {{
{arm_lines}
}};
assert!(jet::eq_32(selected, witness::EXPECTED));
}}"
);
let compiled = CompiledProgram::new_with_unstable(
src.as_str(),
&UnstableFeatures::all(),
Arguments::default(),
false,
Box::new(ElementsJetHinter::new()),
)
.unwrap();
let action_ty = compiled
.witness_types()
.get(&WitnessName::from_str_unchecked("ACT"))
.expect("ACT is declared")
.clone();
for (i, name) in arms.iter().enumerate() {
let action =
Value::enum_variant(&action_ty, &Identifier::from_str_unchecked(name), vec![])
.expect("declared variant");
let expected = u32::try_from((i + 1) * 10).unwrap();
let map = HashMap::from([
(WitnessName::from_str_unchecked("ACT"), action),
(
WitnessName::from_str_unchecked("EXPECTED"),
crate::value::ValueConstructible::u32(expected),
),
]);
TestCase::program_text_with_unstable(
Cow::Owned(src.clone()),
UnstableFeatures::all(),
)
.with_witness_values(WitnessValues::from(map))
.assert_run_success();
}
}
}
}
#[cfg(test)]
mod error_tests {
use std::path::Path;
use super::tests::MAIN;
use super::*;
use crate::ast::ElementsJetHinter;
use crate::resolution::tests::{build_map, canon};
use crate::source::CanonPath;
use crate::test_utils::TempWorkspace;
fn dependency_map(root_dir: &Path, drp: &str, lib_dir: &Path) -> DependencyMap {
let context = CanonPath::canonicalize(root_dir).unwrap();
let target = CanonPath::canonicalize(lib_dir).unwrap();
build_map(&context, &[(&context, drp, &target)]).unwrap()
}
fn source_file(path: &Path) -> CanonSourceFile {
let content = std::fs::read_to_string(path).expect("Failed to read test file");
CanonSourceFile::new(canon(path), Arc::from(content))
}
#[test]
#[ignore = "TODO: Bug in Error Handler. Expected to be fixed in a future update to correctly point to dependency source files."]
fn dependency_ast_errors_use_dependency_source_file() {
let ws = TempWorkspace::new("dependency_ast_error_source");
let root_dir = ws.create_dir("workspace");
let lib_dir = ws.create_dir("workspace/lib");
let main_path = ws.create_file(
format!("workspace/{MAIN}").as_str(),
"use lib::bad::f;\nfn main() { f(); }\n",
);
let bad_path = ws.create_file(
"workspace/lib/bad.simf",
"pub fn f() { let x: u32 = true; }\n",
);
let dependencies = dependency_map(&root_dir, "lib", &lib_dir);
let err = TemplateProgram::new_with_dep(
source_file(&main_path),
&dependencies,
&UnstableFeatures::all(),
Box::new(ElementsJetHinter::new()),
)
.expect_err("dependency body has a type error");
let dependency_source = canon(&bad_path).as_path().display().to_string();
assert!(
err.to_string().contains(&dependency_source),
"expected diagnostic to point at dependency source {dependency_source}, got:\n{}",
err
);
}
#[test]
fn omitted_context_dependency_applies_inside_dependency_files() {
let ws = TempWorkspace::new("omitted_context_dependency");
let root_dir = ws.create_dir("workspace");
let lib_dir = ws.create_dir("workspace/lib");
let main_path = ws.create_file(
format!("workspace/{MAIN}").as_str(),
"use lib::nested::two;\nfn main() { assert!(jet::eq_32(two(), 2)); }\n",
);
ws.create_file(
"workspace/lib/nested.simf",
"use lib::base::one;\npub fn two() -> u32 {\n let (_, out): (bool, u32) = jet::add_32(one(), 1);\n out\n}\n",
);
ws.create_file("workspace/lib/base.simf", "pub fn one() -> u32 { 1 }\n");
let dependencies = dependency_map(&root_dir, "lib", &lib_dir);
let _err = TemplateProgram::new_with_dep(
source_file(&main_path),
&dependencies,
&UnstableFeatures::none(),
Box::new(ElementsJetHinter::new()),
)
.expect_err("omitted-context dependencies");
}
#[test]
fn missing_mapped_module_is_reported_as_file_not_found() {
let ws = TempWorkspace::new("missing_mapped_module");
let root_dir = ws.create_dir("workspace");
let lib_dir = ws.create_dir("workspace/lib");
let main_path = ws.create_file(
format!("workspace/{MAIN}").as_str(),
"use lib::missing::Thing;\nfn main() {}\n",
);
let dependencies = dependency_map(&root_dir, "lib", &lib_dir);
let err = TemplateProgram::new_with_dep(
source_file(&main_path),
&dependencies,
&UnstableFeatures::all(),
Box::new(ElementsJetHinter::new()),
)
.expect_err("missing imported module should fail");
assert!(
err.to_string().contains("missing.simf"),
"diagnostic should mention the missing module path, got:\n{}",
err
);
}
}
#[cfg(test)]
mod functional_tests {
use crate::ast::ElementsJetHinter;
use crate::resolution::tests::build_map;
use crate::resolution::DependencyMap;
use crate::source::{CanonPath, CanonSourceFile};
use crate::tests::{flatten_multidep_test, run_dependency_test, run_multidep_test};
use crate::{Arguments, CompiledProgram};
use std::path::{Path, PathBuf};
use std::sync::Arc;
const VALID_TESTS_DIR: &str = "./functional-tests/valid-test-cases";
const ERROR_TESTS_DIR: &str = "./functional-tests/error-test-cases";
#[test]
fn module_simple() {
run_dependency_test(format!("{}/module-simple", VALID_TESTS_DIR).as_str(), "lib");
}
#[test]
fn module_name_simple() {
run_dependency_test(
format!("{}/module-name-collision", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn diamond_dependency_resolution() {
run_dependency_test(
format!("{}/diamond-dependency-resolution", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn deep_reexport_chain() {
run_dependency_test(
format!("{}/deep-reexport-chain", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn leaky_signature() {
run_dependency_test(
format!("{}/leaky-signature", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn reexport_diamond() {
run_dependency_test(
format!("{}/reexport-diamond", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn multi_lib_facade_resolution() {
run_multidep_test(
format!("{}/multi-lib-facade", VALID_TESTS_DIR).as_str(),
&[
(".", "api", "api"),
("crypto", "math", "math"),
("api", "crypto", "crypto"),
("api", "math", "math"),
],
);
}
#[test]
fn interleaved_waterfall() {
run_multidep_test(
format!("{}/interleaved-waterfall", VALID_TESTS_DIR).as_str(),
&[
(".", "orch", "orch"),
("orch", "db", "db"),
("orch", "auth", "auth"),
("orch", "types", "types"),
("db", "types", "types"),
("auth", "types", "types"),
("auth", "db", "db"),
],
);
}
#[test]
#[should_panic(expected = "Circular dependency detected:")]
fn cyclic_dependency_error() {
run_dependency_test(
format!("{}/cyclic-dependency", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "DependencyPathNotFound")]
fn file_not_found_error() {
run_dependency_test(
format!("{}/file-not-found", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "DependencyPathNotFound")]
fn lib_not_found_error() {
run_dependency_test(format!("{}/lib-not-found", ERROR_TESTS_DIR).as_str(), "lib");
}
#[test]
#[should_panic(expected = "Item `SecretType` is private")]
fn private_type_visibility_error() {
run_dependency_test(
format!("{}/private-visibility", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "Item `add` was defined multiple times")]
fn name_collision_error() {
run_dependency_test(
format!("{}/name-collision", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "Type alias `A` was defined multiple times")]
fn type_alias_duplication_error() {
run_dependency_test(
format!("{}/type-alias-duplication", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn local_crate_resolution() {
run_multidep_test(format!("{}/local-crate", VALID_TESTS_DIR).as_str(), &[]);
}
#[test]
fn local_crate_nested_resolution() {
run_multidep_test(
format!("{}/local-crate-nested", VALID_TESTS_DIR).as_str(),
&[],
);
}
#[test]
fn external_library_uses_crate() {
run_multidep_test(
format!("{}/external-library-uses-crate", VALID_TESTS_DIR).as_str(),
&[(".", "ext_lib", "ext_lib")],
);
}
#[test]
#[should_panic(expected = "not found")]
fn crate_file_not_found_error() {
run_multidep_test(
format!("{}/crate-file-not-found", ERROR_TESTS_DIR).as_str(),
&[],
);
}
#[test]
#[should_panic(
expected = "is part of the local project and must be imported using the `crate::` prefix"
)]
fn local_file_as_external_error() {
run_multidep_test(
format!("{}/local-file-as-external", ERROR_TESTS_DIR).as_str(),
&[(".", "ext", ".")],
);
}
fn compile_with_deps(path: &Path, dependency_map: &DependencyMap) -> CompiledProgram {
let program_text = std::fs::read_to_string(path)
.unwrap_or_else(|_| panic!("Failed to read source file: {}", path.display()));
let source = CanonSourceFile::new(
CanonPath::canonicalize(path).expect("Failed to canonicalize path"),
Arc::from(program_text),
);
CompiledProgram::new_with_dep(
source,
dependency_map,
&crate::UnstableFeatures::all(),
Arguments::default(),
false,
Box::new(ElementsJetHinter::new()),
)
.expect("Failed to compile program in test")
}
#[test]
fn identical_crate_uses_in_different_package_roots_do_not_poison_resolution_cache() {
let base_dir = PathBuf::from(format!("{}/use-statement-collision", VALID_TESTS_DIR));
let lib_dir = base_dir.join("libs/lib");
let poisoned_main = base_dir.join("main.simf");
let control_main = base_dir.join("control.simf");
let root_canon = CanonPath::canonicalize(&base_dir).unwrap();
let lib_canon = CanonPath::canonicalize(&lib_dir).unwrap();
let dependency_map = build_map(&root_canon, &[(&root_canon, "lib", &lib_canon)]).unwrap();
let no_dependency_map = build_map(&root_canon, &[]).unwrap();
let poisoned = compile_with_deps(&poisoned_main, &dependency_map);
let control = compile_with_deps(&control_main, &no_dependency_map);
assert_eq!(
poisoned.commit().cmr(),
control.commit().cmr(),
"Resolving an identical `use crate::...` inside a dependency must not change \
what `crate::...` means in the entry package"
);
flatten_multidep_test(&base_dir.to_string_lossy(), &[(".", "lib", "libs/lib")]);
}
}