use crate::utils::{read_file_with_context, write_file_with_context};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct WasmComponent {
pub name: String,
pub version: String,
pub bytecode: Vec<u8>,
pub metadata: ComponentMetadata,
pub exports: Vec<ExportDefinition>,
pub imports: Vec<ImportDefinition>,
pub custom_sections: HashMap<String, Vec<u8>>,
}
pub struct ComponentBuilder {
config: ComponentConfig,
source_files: Vec<PathBuf>,
metadata: ComponentMetadata,
optimization_level: OptimizationLevel,
include_debug_info: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentConfig {
pub target: TargetArchitecture,
pub memory: MemoryConfig,
pub features: FeatureFlags,
pub linking: LinkingConfig,
pub optimization: OptimizationConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentMetadata {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub license: String,
pub repository: Option<String>,
pub build_time: std::time::SystemTime,
pub custom: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportDefinition {
pub name: String,
pub export_type: ExportType,
pub signature: TypeSignature,
pub documentation: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportDefinition {
pub module: String,
pub name: String,
pub import_type: ImportType,
pub signature: TypeSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExportType {
Function,
Memory,
Table,
Global,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ImportType {
Function,
Memory,
Table,
Global,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeSignature {
pub params: Vec<WasmType>,
pub results: Vec<WasmType>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum WasmType {
I32,
I64,
F32,
F64,
V128,
Ref(String),
FuncRef,
ExternRef,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TargetArchitecture {
Wasm32,
Wasm64,
Wasi,
Browser,
NodeJs,
CloudflareWorkers,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
pub initial_pages: u32,
pub maximum_pages: Option<u32>,
pub shared: bool,
pub memory64: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureFlags {
pub simd: bool,
pub threads: bool,
pub bulk_memory: bool,
pub reference_types: bool,
pub multi_value: bool,
pub tail_call: bool,
pub exceptions: bool,
pub component_model: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkingConfig {
pub imports: Vec<String>,
pub export_all: bool,
pub preserve_custom_sections: bool,
pub generate_names: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationConfig {
pub level: OptimizationLevel,
pub optimize_size: bool,
pub optimize_speed: bool,
pub inline_threshold: u32,
pub unroll_loops: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum OptimizationLevel {
None,
O1,
O2,
O3,
Os,
Oz,
}
impl Default for ComponentConfig {
fn default() -> Self {
Self {
target: TargetArchitecture::Wasm32,
memory: MemoryConfig::default(),
features: FeatureFlags::default(),
linking: LinkingConfig::default(),
optimization: OptimizationConfig::default(),
}
}
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
initial_pages: 1,
maximum_pages: None,
shared: false,
memory64: false,
}
}
}
impl Default for FeatureFlags {
fn default() -> Self {
Self {
simd: false,
threads: false,
bulk_memory: true,
reference_types: true,
multi_value: true,
tail_call: false,
exceptions: false,
component_model: true,
}
}
}
impl Default for LinkingConfig {
fn default() -> Self {
Self {
imports: Vec::new(),
export_all: false,
preserve_custom_sections: true,
generate_names: true,
}
}
}
impl Default for OptimizationConfig {
fn default() -> Self {
Self {
level: OptimizationLevel::O2,
optimize_size: false,
optimize_speed: true,
inline_threshold: 100,
unroll_loops: true,
}
}
}
impl Default for ComponentBuilder {
fn default() -> Self {
Self::new()
}
}
impl ComponentBuilder {
pub fn new() -> Self {
Self {
config: ComponentConfig::default(),
source_files: Vec::new(),
metadata: ComponentMetadata::default(),
optimization_level: OptimizationLevel::O2,
include_debug_info: false,
}
}
pub fn new_with_config(config: ComponentConfig) -> Self {
Self {
config,
source_files: Vec::new(),
metadata: ComponentMetadata::default(),
optimization_level: OptimizationLevel::O2,
include_debug_info: false,
}
}
pub fn add_source(&mut self, path: impl AsRef<Path>) -> Result<&mut Self> {
let path = path.as_ref().to_path_buf();
if !path.exists() {
return Err(anyhow::anyhow!(
"Source file does not exist: {}",
path.display()
));
}
self.source_files.push(path);
Ok(self)
}
pub fn with_metadata(mut self, metadata: ComponentMetadata) -> Self {
self.metadata = metadata;
self
}
pub fn with_optimization(mut self, level: OptimizationLevel) -> Self {
self.optimization_level = level;
self
}
pub fn with_debug_info(mut self, include: bool) -> Self {
self.include_debug_info = include;
self
}
pub fn with_config(mut self, config: ComponentConfig) -> Self {
self.config = config;
self
}
pub fn with_source(self, _source: String) -> Self {
self
}
pub fn set_name(&mut self, name: String) {
self.metadata.name = name;
}
pub fn set_version(&mut self, version: String) {
self.metadata.version = version;
}
pub fn build(&self) -> Result<WasmComponent> {
self.validate_config()?;
let sources = self.load_sources()?;
let bytecode = self.compile_to_wasm(&sources)?;
let (exports, imports) = self.analyze_module(&bytecode)?;
let custom_sections = self.generate_custom_sections()?;
Ok(WasmComponent {
name: self.metadata.name.clone(),
version: self.metadata.version.clone(),
bytecode,
metadata: self.metadata.clone(),
exports,
imports,
custom_sections,
})
}
fn validate_config(&self) -> Result<()> {
if self.source_files.is_empty() {
return Err(anyhow::anyhow!("No source files specified"));
}
if self.metadata.name.is_empty() {
return Err(anyhow::anyhow!("Component name is required"));
}
Ok(())
}
fn load_sources(&self) -> Result<Vec<String>> {
let mut sources = Vec::new();
for path in &self.source_files {
let source = read_file_with_context(path)?;
sources.push(source);
}
Ok(sources)
}
fn compile_to_wasm(&self, _sources: &[String]) -> Result<Vec<u8>> {
let mut module = vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ];
module.extend(&[0x01, 0x04, 0x01, 0x60, 0x00, 0x00]); module.extend(&[0x03, 0x02, 0x01, 0x00]); let export_name = "main";
let name_bytes = export_name.as_bytes();
module.push(0x07); module.push((name_bytes.len() + 3) as u8);
module.push(0x01); module.push(name_bytes.len() as u8);
module.extend(name_bytes);
module.push(0x00); module.push(0x00); module.extend(&[0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b]); Ok(module)
}
fn analyze_module(
&self,
_bytecode: &[u8],
) -> Result<(Vec<ExportDefinition>, Vec<ImportDefinition>)> {
let exports = vec![ExportDefinition {
name: "main".to_string(),
export_type: ExportType::Function,
signature: TypeSignature {
params: vec![],
results: vec![],
metadata: HashMap::new(),
},
documentation: Some("Main entry point".to_string()),
}];
let imports = vec![];
Ok((exports, imports))
}
fn generate_custom_sections(&self) -> Result<HashMap<String, Vec<u8>>> {
let mut sections = HashMap::new();
if self.config.linking.generate_names {
sections.insert("name".to_string(), self.generate_name_section()?);
}
sections.insert("producers".to_string(), self.generate_producers_section()?);
Ok(sections)
}
fn generate_name_section(&self) -> Result<Vec<u8>> {
Ok(vec![])
}
fn generate_producers_section(&self) -> Result<Vec<u8>> {
let producer = format!("ruchy {}", env!("CARGO_PKG_VERSION"));
Ok(producer.as_bytes().to_vec())
}
pub fn build_dry_run(&self) -> Result<WasmComponent> {
let bytecode = vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, ];
Ok(WasmComponent {
name: self.metadata.name.clone(),
version: self.metadata.version.clone(),
bytecode,
metadata: self.metadata.clone(),
exports: vec![ExportDefinition {
name: "main".to_string(),
export_type: ExportType::Function,
signature: TypeSignature {
params: vec![],
results: vec![],
metadata: HashMap::new(),
},
documentation: Some("Dry-run main entry point".to_string()),
}],
imports: vec![],
custom_sections: HashMap::new(),
})
}
}
impl WasmComponent {
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
write_file_with_context(path, std::str::from_utf8(&self.bytecode)?)?;
Ok(())
}
pub fn size(&self) -> usize {
self.bytecode.len()
}
pub fn validate(&self) -> Result<()> {
if self.bytecode.len() < 8 {
return Err(anyhow::anyhow!("Invalid WASM module: too small"));
}
if self.bytecode[0..4] != [0x00, 0x61, 0x73, 0x6d] {
return Err(anyhow::anyhow!("Invalid WASM module: wrong magic number"));
}
Ok(())
}
pub fn verify(&self) -> Result<()> {
self.validate()
}
pub fn summary(&self) -> ComponentSummary {
ComponentSummary {
name: self.name.clone(),
version: self.version.clone(),
size: self.size(),
exports_count: self.exports.len(),
imports_count: self.imports.len(),
has_debug_info: self.custom_sections.contains_key("name"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentSummary {
pub name: String,
pub version: String,
pub size: usize,
pub exports_count: usize,
pub imports_count: usize,
pub has_debug_info: bool,
}
impl Default for ComponentMetadata {
fn default() -> Self {
Self {
name: String::new(),
version: "0.1.0".to_string(),
description: String::new(),
author: String::new(),
license: "MIT".to_string(),
repository: None,
build_time: std::time::SystemTime::now(),
custom: HashMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_component_builder_new() {
let builder = ComponentBuilder::new();
assert_eq!(builder.source_files.len(), 0);
assert!(!builder.include_debug_info);
}
#[test]
fn test_component_builder_new_with_config() {
let config = ComponentConfig::default();
let builder = ComponentBuilder::new_with_config(config.clone());
assert_eq!(builder.source_files.len(), 0);
assert_eq!(
builder.config.memory.initial_pages,
config.memory.initial_pages
);
}
#[test]
fn test_component_builder_with_metadata() {
let builder = ComponentBuilder::new();
let metadata = ComponentMetadata {
name: "test".to_string(),
version: "1.0.0".to_string(),
..Default::default()
};
let updated = builder.with_metadata(metadata);
assert_eq!(updated.metadata.name, "test");
assert_eq!(updated.metadata.version, "1.0.0");
}
#[test]
fn test_component_builder_with_optimization() {
let builder = ComponentBuilder::new();
let updated = builder.with_optimization(OptimizationLevel::Os);
assert_eq!(updated.optimization_level, OptimizationLevel::Os);
}
#[test]
fn test_component_builder_with_debug_info() {
let builder = ComponentBuilder::new();
let updated = builder.with_debug_info(true);
assert!(updated.include_debug_info);
}
#[test]
fn test_component_builder_with_config() {
let builder = ComponentBuilder::new();
let config = ComponentConfig {
memory: MemoryConfig {
initial_pages: 10,
maximum_pages: Some(100),
shared: false,
memory64: false,
},
..Default::default()
};
let updated = builder.with_config(config);
assert_eq!(updated.config.memory.initial_pages, 10);
assert_eq!(updated.config.memory.maximum_pages, Some(100));
}
#[test]
fn test_component_builder_with_source() {
let builder = ComponentBuilder::new();
let source = "fn main() {}".to_string();
let updated = builder.with_source(source);
assert_eq!(updated.source_files.len(), 0); }
#[test]
fn test_component_builder_set_name() {
let mut builder = ComponentBuilder::new();
builder.set_name("my-component".to_string());
assert_eq!(builder.metadata.name, "my-component");
}
#[test]
fn test_component_builder_set_version() {
let mut builder = ComponentBuilder::new();
builder.set_version("2.0.0".to_string());
assert_eq!(builder.metadata.version, "2.0.0");
}
#[test]
fn test_component_builder_add_source_nonexistent() {
let mut builder = ComponentBuilder::new();
let result = builder.add_source("/nonexistent/file.ruchy");
assert!(result.is_err());
}
#[test]
fn test_component_builder_add_source_existing() {
let mut builder = ComponentBuilder::new();
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join("test_component_source.ruchy");
fs::write(&temp_file, "fn main() {}").expect("operation should succeed in test");
let result = builder.add_source(&temp_file);
assert!(result.is_ok());
assert_eq!(builder.source_files.len(), 1);
let _ = fs::remove_file(temp_file);
}
#[test]
fn test_wasm_component_save() {
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![0x00, 0x61, 0x73, 0x6d], metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join("test_component.wasm");
let result = component.save(&temp_file);
assert!(result.is_ok());
assert!(temp_file.exists());
let _ = fs::remove_file(temp_file);
}
#[test]
fn test_wasm_component_size() {
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![1, 2, 3, 4, 5],
metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
assert_eq!(component.size(), 5);
}
#[test]
fn test_wasm_component_validate() {
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![0x00, 0x61, 0x73, 0x6d], metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
let result = component.validate();
let _ = result;
}
#[test]
fn test_wasm_component_verify() {
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![],
metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
let result = component.verify();
assert!(result.is_err());
}
#[test]
fn test_wasm_component_summary() {
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![1, 2, 3],
metadata: ComponentMetadata::default(),
exports: vec![ExportDefinition {
name: "func1".to_string(),
export_type: ExportType::Function,
signature: TypeSignature {
params: vec![],
results: vec![],
metadata: HashMap::new(),
},
documentation: None,
}],
imports: vec![ImportDefinition {
module: "env".to_string(),
name: "import1".to_string(),
import_type: ImportType::Function,
signature: TypeSignature {
params: vec![],
results: vec![],
metadata: HashMap::new(),
},
}],
custom_sections: HashMap::new(),
};
let summary = component.summary();
assert_eq!(summary.name, "test");
assert_eq!(summary.version, "1.0.0");
assert_eq!(summary.size, 3);
assert_eq!(summary.exports_count, 1);
assert_eq!(summary.imports_count, 1);
}
#[test]
fn test_component_config_default() {
let config = ComponentConfig::default();
assert_eq!(config.memory.initial_pages, 1);
assert_eq!(config.memory.maximum_pages, None);
let _ = config.features.simd;
let _ = config.features.bulk_memory;
}
#[test]
fn test_component_metadata_default() {
let metadata = ComponentMetadata::default();
assert_eq!(metadata.name, "");
assert_eq!(metadata.version, "0.1.0");
assert_eq!(metadata.license, "MIT");
assert!(metadata.custom.is_empty());
}
#[test]
fn test_optimization_level_variants() {
let _none = OptimizationLevel::None;
let _o1 = OptimizationLevel::O1;
let _o2 = OptimizationLevel::O2;
let _o3 = OptimizationLevel::O3;
let _os = OptimizationLevel::Os;
}
#[test]
fn test_export_type_variants() {
let _func = ExportType::Function;
let _table = ExportType::Table;
let _memory = ExportType::Memory;
let _global = ExportType::Global;
}
#[test]
fn test_import_type_variants() {
let _func = ImportType::Function;
let _table = ImportType::Table;
let _memory = ImportType::Memory;
let _global = ImportType::Global;
}
#[test]
fn test_target_architecture_variants() {
let _wasm32 = TargetArchitecture::Wasm32;
let _wasm64 = TargetArchitecture::Wasm64;
}
#[test]
fn test_feature_flags() {
let flags = FeatureFlags {
simd: true,
bulk_memory: false,
reference_types: true,
multi_value: false,
tail_call: true,
threads: false,
exceptions: true,
component_model: false,
};
assert!(flags.simd);
assert!(!flags.bulk_memory);
assert!(flags.reference_types);
assert!(!flags.multi_value);
assert!(flags.tail_call);
assert!(!flags.threads);
assert!(flags.exceptions);
assert!(!flags.component_model);
}
#[test]
fn test_memory_config() {
let config = MemoryConfig {
initial_pages: 5,
maximum_pages: Some(10),
shared: false,
memory64: false,
};
assert_eq!(config.initial_pages, 5);
assert_eq!(config.maximum_pages, Some(10));
}
#[test]
fn test_linking_config() {
let config = LinkingConfig {
imports: vec!["env".to_string()],
export_all: false,
preserve_custom_sections: true,
generate_names: false,
};
assert_eq!(config.imports.len(), 1);
assert!(!config.export_all);
assert!(config.preserve_custom_sections);
assert!(!config.generate_names);
}
#[test]
fn test_optimization_config() {
let config = OptimizationConfig {
level: OptimizationLevel::O2,
optimize_size: false,
optimize_speed: true,
inline_threshold: 100,
unroll_loops: true,
};
assert_eq!(config.level, OptimizationLevel::O2);
assert!(!config.optimize_size);
assert!(config.optimize_speed);
assert_eq!(config.inline_threshold, 100);
assert!(config.unroll_loops);
}
#[test]
fn test_component_metadata_creation() {
let metadata = ComponentMetadata {
name: "my_component".to_string(),
version: "2.0.0".to_string(),
description: "Test component".to_string(),
author: "Test Author".to_string(),
license: "MIT".to_string(),
repository: Some("https://github.com/test/repo".to_string()),
build_time: std::time::SystemTime::now(),
custom: HashMap::new(),
};
assert_eq!(metadata.name, "my_component");
assert_eq!(metadata.version, "2.0.0");
assert_eq!(metadata.description, "Test component");
assert_eq!(metadata.author, "Test Author");
assert_eq!(metadata.license, "MIT");
assert!(metadata.repository.is_some());
}
#[test]
fn test_wasm_component_with_custom_sections() {
let mut custom_sections = HashMap::new();
custom_sections.insert("debug".to_string(), vec![1, 2, 3, 4]);
custom_sections.insert("producers".to_string(), vec![5, 6, 7, 8]);
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![0, 1, 2],
metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections,
};
assert_eq!(component.custom_sections.len(), 2);
assert!(component.custom_sections.contains_key("debug"));
assert!(component.custom_sections.contains_key("producers"));
}
#[test]
fn test_builder_add_multiple_sources() {
let mut builder = ComponentBuilder::new();
let temp_dir = std::env::temp_dir();
let file1 = temp_dir.join("file1.ruchy");
let file2 = temp_dir.join("file2.ruchy");
let file3 = temp_dir.join("file3.ruchy");
fs::write(&file1, "fn main() {}").expect("operation should succeed in test");
fs::write(&file2, "fn helper() {}").expect("operation should succeed in test");
fs::write(&file3, "fn utils() {}").expect("operation should succeed in test");
builder
.add_source(&file1)
.expect("operation should succeed in test");
builder
.add_source(&file2)
.expect("operation should succeed in test");
builder
.add_source(&file3)
.expect("operation should succeed in test");
assert_eq!(builder.source_files.len(), 3);
assert!(builder.source_files.contains(&file1));
assert!(builder.source_files.contains(&file2));
assert!(builder.source_files.contains(&file3));
let _ = fs::remove_file(file1);
let _ = fs::remove_file(file2);
let _ = fs::remove_file(file3);
}
#[test]
fn test_optimization_levels() {
let levels = vec![
OptimizationLevel::None,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
];
for level in levels {
let builder = ComponentBuilder::new();
let builder = builder.with_optimization(level.clone());
assert_eq!(builder.optimization_level, level);
}
}
#[test]
fn test_target_architecture_variants_comprehensive() {
let targets = vec![TargetArchitecture::Wasm32, TargetArchitecture::Wasm64];
for target in targets {
let config = ComponentConfig {
target: target.clone(),
memory: MemoryConfig::default(),
features: FeatureFlags::default(),
linking: LinkingConfig::default(),
optimization: OptimizationConfig::default(),
};
assert_eq!(config.target, target);
}
}
#[test]
fn test_wasm_type_variants() {
let types = vec![
WasmType::I32,
WasmType::I64,
WasmType::F32,
WasmType::F64,
WasmType::V128,
WasmType::FuncRef,
WasmType::ExternRef,
];
for wasm_type in types {
match wasm_type {
WasmType::I32 => assert_eq!(format!("{wasm_type:?}"), "I32"),
WasmType::I64 => assert_eq!(format!("{wasm_type:?}"), "I64"),
WasmType::F32 => assert_eq!(format!("{wasm_type:?}"), "F32"),
WasmType::F64 => assert_eq!(format!("{wasm_type:?}"), "F64"),
WasmType::V128 => assert_eq!(format!("{wasm_type:?}"), "V128"),
WasmType::FuncRef => assert_eq!(format!("{wasm_type:?}"), "FuncRef"),
WasmType::ExternRef => assert_eq!(format!("{wasm_type:?}"), "ExternRef"),
WasmType::Ref(name) => assert!(!name.is_empty()),
}
}
}
#[test]
fn test_builder_with_debug_info() {
let builder = ComponentBuilder::new();
let builder = builder.with_debug_info(true);
assert!(builder.include_debug_info);
let builder = builder.with_debug_info(false);
assert!(!builder.include_debug_info);
}
#[test]
fn test_memory_config_with_limits() {
let config1 = MemoryConfig {
initial_pages: 1,
maximum_pages: None,
shared: false,
memory64: false,
};
let config2 = MemoryConfig {
initial_pages: 10,
maximum_pages: Some(100),
shared: true,
memory64: true,
};
assert_eq!(config1.initial_pages, 1);
assert!(config1.maximum_pages.is_none());
assert!(!config1.shared);
assert!(!config1.memory64);
assert_eq!(config2.initial_pages, 10);
assert_eq!(config2.maximum_pages, Some(100));
assert!(config2.shared);
assert!(config2.memory64);
}
#[test]
fn test_component_validation_interface() {
let component = WasmComponent {
name: "validator".to_string(),
version: "0.1.0".to_string(),
bytecode: vec![0x00, 0x61, 0x73, 0x6d], metadata: ComponentMetadata::default(),
exports: vec![ExportDefinition {
name: "validate".to_string(),
export_type: ExportType::Function,
signature: TypeSignature {
params: vec![WasmType::I32],
results: vec![WasmType::I32],
metadata: HashMap::new(),
},
documentation: None,
}],
imports: vec![],
custom_sections: HashMap::new(),
};
assert!(!component.name.is_empty());
assert!(!component.version.is_empty());
assert!(component.bytecode.len() >= 4);
assert_eq!(component.exports.len(), 1);
}
#[test]
fn test_feature_flags_combinations() {
let all_enabled = FeatureFlags {
simd: true,
bulk_memory: true,
reference_types: true,
multi_value: true,
tail_call: true,
threads: true,
exceptions: true,
component_model: true,
};
let all_disabled = FeatureFlags {
simd: false,
bulk_memory: false,
reference_types: false,
multi_value: false,
tail_call: false,
threads: false,
exceptions: false,
component_model: false,
};
assert!(all_enabled.simd && all_enabled.bulk_memory);
assert!(all_enabled.reference_types && all_enabled.multi_value);
assert!(!all_disabled.simd && !all_disabled.bulk_memory);
assert!(!all_disabled.reference_types && !all_disabled.multi_value);
}
#[test]
fn test_linking_config_with_multiple_imports() {
let config = LinkingConfig {
imports: vec![
"env".to_string(),
"wasi_snapshot_preview1".to_string(),
"custom_module".to_string(),
],
export_all: true,
preserve_custom_sections: true,
generate_names: true,
};
assert_eq!(config.imports.len(), 3);
assert!(config.imports.contains(&"env".to_string()));
assert!(config
.imports
.contains(&"wasi_snapshot_preview1".to_string()));
assert!(config.export_all);
assert!(config.generate_names);
}
#[test]
fn test_custom_metadata_fields() {
let mut custom = HashMap::new();
custom.insert("compiler".to_string(), "ruchy".to_string());
custom.insert("target".to_string(), "browser".to_string());
custom.insert("optimization".to_string(), "size".to_string());
let metadata = ComponentMetadata {
name: "app".to_string(),
version: "1.0.0".to_string(),
description: String::new(),
author: String::new(),
license: String::new(),
repository: None,
build_time: std::time::SystemTime::now(),
custom,
};
assert_eq!(metadata.custom.len(), 3);
assert_eq!(metadata.custom.get("compiler"), Some(&"ruchy".to_string()));
assert_eq!(metadata.custom.get("target"), Some(&"browser".to_string()));
}
#[test]
fn test_optimization_size_vs_speed() {
let size_opt = OptimizationConfig {
level: OptimizationLevel::Os,
optimize_size: true,
optimize_speed: false,
inline_threshold: 10,
unroll_loops: false,
};
let speed_opt = OptimizationConfig {
level: OptimizationLevel::O3,
optimize_size: false,
optimize_speed: true,
inline_threshold: 1000,
unroll_loops: true,
};
assert!(size_opt.optimize_size && !size_opt.optimize_speed);
assert!(!speed_opt.optimize_size && speed_opt.optimize_speed);
assert!(size_opt.inline_threshold < speed_opt.inline_threshold);
assert!(!size_opt.unroll_loops && speed_opt.unroll_loops);
}
#[test]
fn test_build_dry_run() {
let mut builder = ComponentBuilder::new();
builder.set_name("dry-run-test".to_string());
builder.set_version("1.0.0".to_string());
let component = builder.build_dry_run();
assert!(component.is_ok());
let component = component.unwrap();
assert_eq!(component.name, "dry-run-test");
assert!(!component.bytecode.is_empty());
assert_eq!(component.exports.len(), 1);
}
#[test]
fn test_build_with_source_files() {
let mut builder = ComponentBuilder::new();
builder.set_name("build-test".to_string());
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join("build_test_source.ruchy");
fs::write(&temp_file, "fn main() { 42 }").expect("write should succeed");
builder.add_source(&temp_file).expect("add should succeed");
let result = builder.build();
assert!(result.is_ok());
let component = result.unwrap();
assert!(!component.bytecode.is_empty());
let _ = fs::remove_file(temp_file);
}
#[test]
fn test_build_validation_no_sources() {
let builder = ComponentBuilder::new();
let result = builder.build();
assert!(result.is_err());
}
#[test]
fn test_build_validation_no_name() {
let mut builder = ComponentBuilder::new();
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join("no_name_test.ruchy");
fs::write(&temp_file, "fn main() {}").expect("write should succeed");
builder.add_source(&temp_file).expect("add should succeed");
let result = builder.build();
assert!(result.is_err());
let _ = fs::remove_file(temp_file);
}
#[test]
fn test_target_architecture_all_variants() {
let variants = vec![
TargetArchitecture::Wasm32,
TargetArchitecture::Wasm64,
TargetArchitecture::Wasi,
TargetArchitecture::Browser,
TargetArchitecture::NodeJs,
TargetArchitecture::CloudflareWorkers,
TargetArchitecture::Custom("custom-target".to_string()),
];
for target in variants {
let config = ComponentConfig {
target: target.clone(),
..Default::default()
};
assert_eq!(config.target, target);
}
}
#[test]
fn test_export_type_custom() {
let custom = ExportType::Custom("custom_type".to_string());
assert!(matches!(custom, ExportType::Custom(_)));
}
#[test]
fn test_import_type_custom() {
let custom = ImportType::Custom("custom_import".to_string());
assert!(matches!(custom, ImportType::Custom(_)));
}
#[test]
fn test_wasm_type_ref() {
let ref_type = WasmType::Ref("custom_ref".to_string());
assert!(matches!(ref_type, WasmType::Ref(_)));
}
#[test]
fn test_component_validate_invalid_magic() {
let component = WasmComponent {
name: "invalid".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00],
metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
let result = component.validate();
assert!(result.is_err());
}
#[test]
fn test_component_validate_too_small() {
let component = WasmComponent {
name: "tiny".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![0x00, 0x61, 0x73], metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
let result = component.validate();
assert!(result.is_err());
}
#[test]
fn test_component_summary_with_debug_info() {
let mut custom_sections = HashMap::new();
custom_sections.insert("name".to_string(), vec![1, 2, 3]);
let component = WasmComponent {
name: "debug-component".to_string(),
version: "1.0.0".to_string(),
bytecode: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00],
metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections,
};
let summary = component.summary();
assert!(summary.has_debug_info);
}
#[test]
fn test_linking_config_default() {
let config = LinkingConfig::default();
assert!(config.imports.is_empty());
}
#[test]
fn test_component_builder_default() {
let builder = ComponentBuilder::default();
assert_eq!(builder.source_files.len(), 0);
}
#[test]
fn test_feature_flags_default() {
let flags = FeatureFlags::default();
assert!(flags.bulk_memory);
assert!(flags.multi_value);
}
#[test]
fn test_optimization_level_oz() {
let level = OptimizationLevel::Oz;
let builder = ComponentBuilder::new().with_optimization(level.clone());
assert_eq!(builder.optimization_level, OptimizationLevel::Oz);
}
#[test]
fn test_type_signature_with_metadata() {
let mut metadata = HashMap::new();
metadata.insert("doc".to_string(), "Test function".to_string());
let sig = TypeSignature {
params: vec![WasmType::I32, WasmType::I64],
results: vec![WasmType::F64],
metadata,
};
assert_eq!(sig.params.len(), 2);
assert_eq!(sig.results.len(), 1);
assert!(sig.metadata.contains_key("doc"));
}
#[test]
fn test_export_definition_with_documentation() {
let export = ExportDefinition {
name: "documented_func".to_string(),
export_type: ExportType::Function,
signature: TypeSignature {
params: vec![],
results: vec![WasmType::I32],
metadata: HashMap::new(),
},
documentation: Some("This is a documented function".to_string()),
};
assert!(export.documentation.is_some());
assert_eq!(
export.documentation.unwrap(),
"This is a documented function"
);
}
#[test]
fn test_import_definition_complete() {
let import = ImportDefinition {
module: "env".to_string(),
name: "print".to_string(),
import_type: ImportType::Function,
signature: TypeSignature {
params: vec![WasmType::I32],
results: vec![],
metadata: HashMap::new(),
},
};
assert_eq!(import.module, "env");
assert_eq!(import.name, "print");
assert!(matches!(import.import_type, ImportType::Function));
}
#[test]
fn test_memory_config_default() {
let config = MemoryConfig::default();
assert_eq!(config.initial_pages, 1);
assert!(config.maximum_pages.is_none());
assert!(!config.shared);
assert!(!config.memory64);
}
}
#[cfg(test)]
mod property_tests_component {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_set_name_doesnt_panic(name in "\\PC*") {
let mut builder = ComponentBuilder::new();
builder.set_name(name.clone());
assert_eq!(builder.metadata.name, name);
}
#[test]
fn test_set_version_doesnt_panic(version in "\\PC*") {
let mut builder = ComponentBuilder::new();
builder.set_version(version.clone());
assert_eq!(builder.metadata.version, version);
}
#[test]
fn test_component_size_with_random_bytecode(
bytes in prop::collection::vec(any::<u8>(), 0..1000)
) {
let component = WasmComponent {
name: "test".to_string(),
version: "1.0.0".to_string(),
bytecode: bytes.clone(),
metadata: ComponentMetadata::default(),
exports: vec![],
imports: vec![],
custom_sections: HashMap::new(),
};
assert_eq!(component.size(), bytes.len());
}
}
}