1use std::fmt;
39use std::path::{Path, PathBuf};
40
41use thiserror::Error;
42use tokio::process::Command;
43use tracing::{debug, info, instrument, trace, warn};
44
45#[derive(Debug, Error)]
47pub enum WasmBuildError {
48 #[error("Could not detect source language in '{path}'")]
50 LanguageNotDetected {
51 path: PathBuf,
53 },
54
55 #[error("Build tool '{tool}' not found: {message}")]
57 ToolNotFound {
58 tool: String,
60 message: String,
62 },
63
64 #[error("Build failed with exit code {exit_code}: {stderr}")]
66 BuildFailed {
67 exit_code: i32,
69 stderr: String,
71 stdout: String,
73 },
74
75 #[error("WASM output not found at expected path: {path}")]
77 OutputNotFound {
78 path: PathBuf,
80 },
81
82 #[error("Configuration error: {message}")]
84 ConfigError {
85 message: String,
87 },
88
89 #[error("IO error: {0}")]
91 Io(#[from] std::io::Error),
92
93 #[error("Failed to read project configuration: {message}")]
95 ProjectConfigError {
96 message: String,
98 },
99}
100
101pub type Result<T, E = WasmBuildError> = std::result::Result<T, E>;
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106pub enum WasmLanguage {
107 Rust,
109 RustComponent,
111 Go,
113 Python,
115 TypeScript,
117 AssemblyScript,
119 C,
121 Zig,
123}
124
125impl WasmLanguage {
126 pub fn all() -> &'static [WasmLanguage] {
128 &[
129 WasmLanguage::Rust,
130 WasmLanguage::RustComponent,
131 WasmLanguage::Go,
132 WasmLanguage::Python,
133 WasmLanguage::TypeScript,
134 WasmLanguage::AssemblyScript,
135 WasmLanguage::C,
136 WasmLanguage::Zig,
137 ]
138 }
139
140 pub fn name(&self) -> &'static str {
142 match self {
143 WasmLanguage::Rust => "Rust",
144 WasmLanguage::RustComponent => "Rust (cargo-component)",
145 WasmLanguage::Go => "Go (TinyGo)",
146 WasmLanguage::Python => "Python",
147 WasmLanguage::TypeScript => "TypeScript",
148 WasmLanguage::AssemblyScript => "AssemblyScript",
149 WasmLanguage::C => "C",
150 WasmLanguage::Zig => "Zig",
151 }
152 }
153
154 pub fn is_component_native(&self) -> bool {
156 matches!(
157 self,
158 WasmLanguage::RustComponent | WasmLanguage::Python | WasmLanguage::TypeScript
159 )
160 }
161}
162
163impl fmt::Display for WasmLanguage {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(f, "{}", self.name())
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
171pub enum WasiTarget {
172 Preview1,
174 #[default]
176 Preview2,
177}
178
179impl WasiTarget {
180 pub fn rust_target(&self) -> &'static str {
182 match self {
183 WasiTarget::Preview1 => "wasm32-wasip1",
184 WasiTarget::Preview2 => "wasm32-wasip2",
185 }
186 }
187
188 pub fn name(&self) -> &'static str {
190 match self {
191 WasiTarget::Preview1 => "WASI Preview 1",
192 WasiTarget::Preview2 => "WASI Preview 2",
193 }
194 }
195}
196
197impl fmt::Display for WasiTarget {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "{}", self.name())
200 }
201}
202
203#[derive(Debug, Clone, Default)]
205pub struct WasmBuildConfig {
206 pub language: Option<WasmLanguage>,
208
209 pub target: WasiTarget,
211
212 pub optimize: bool,
214
215 pub wit_path: Option<PathBuf>,
217
218 pub output_path: Option<PathBuf>,
220}
221
222impl WasmBuildConfig {
223 pub fn new() -> Self {
225 Self::default()
226 }
227
228 pub fn language(mut self, lang: WasmLanguage) -> Self {
230 self.language = Some(lang);
231 self
232 }
233
234 pub fn target(mut self, target: WasiTarget) -> Self {
236 self.target = target;
237 self
238 }
239
240 pub fn optimize(mut self, optimize: bool) -> Self {
242 self.optimize = optimize;
243 self
244 }
245
246 pub fn wit_path(mut self, path: impl Into<PathBuf>) -> Self {
248 self.wit_path = Some(path.into());
249 self
250 }
251
252 pub fn output_path(mut self, path: impl Into<PathBuf>) -> Self {
254 self.output_path = Some(path.into());
255 self
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct WasmBuildResult {
262 pub wasm_path: PathBuf,
264
265 pub language: WasmLanguage,
267
268 pub target: WasiTarget,
270
271 pub size: u64,
273}
274
275#[instrument(level = "debug", skip_all, fields(path = %context.as_ref().display()))]
291pub fn detect_language(context: impl AsRef<Path>) -> Result<WasmLanguage> {
292 let path = context.as_ref();
293 debug!("Detecting WASM source language");
294
295 let cargo_toml = path.join("Cargo.toml");
297 if cargo_toml.exists() {
298 trace!("Found Cargo.toml");
299
300 if is_cargo_component_project(&cargo_toml)? {
302 debug!("Detected Rust (cargo-component) project");
303 return Ok(WasmLanguage::RustComponent);
304 }
305
306 debug!("Detected Rust project");
307 return Ok(WasmLanguage::Rust);
308 }
309
310 if path.join("go.mod").exists() {
312 debug!("Detected Go (TinyGo) project");
313 return Ok(WasmLanguage::Go);
314 }
315
316 if path.join("pyproject.toml").exists()
318 || path.join("requirements.txt").exists()
319 || path.join("setup.py").exists()
320 {
321 debug!("Detected Python project");
322 return Ok(WasmLanguage::Python);
323 }
324
325 let package_json = path.join("package.json");
327 if package_json.exists() {
328 trace!("Found package.json");
329
330 if is_assemblyscript_project(&package_json)? {
332 debug!("Detected AssemblyScript project");
333 return Ok(WasmLanguage::AssemblyScript);
334 }
335
336 debug!("Detected TypeScript project");
337 return Ok(WasmLanguage::TypeScript);
338 }
339
340 if path.join("build.zig").exists() {
342 debug!("Detected Zig project");
343 return Ok(WasmLanguage::Zig);
344 }
345
346 if (path.join("Makefile").exists() || path.join("CMakeLists.txt").exists())
348 && has_c_source_files(path)
349 {
350 debug!("Detected C project");
351 return Ok(WasmLanguage::C);
352 }
353
354 if has_c_source_files(path) {
356 debug!("Detected C project (source files only)");
357 return Ok(WasmLanguage::C);
358 }
359
360 Err(WasmBuildError::LanguageNotDetected {
361 path: path.to_path_buf(),
362 })
363}
364
365fn is_cargo_component_project(cargo_toml: &Path) -> Result<bool> {
367 let content =
368 std::fs::read_to_string(cargo_toml).map_err(|e| WasmBuildError::ProjectConfigError {
369 message: format!("Failed to read Cargo.toml: {}", e),
370 })?;
371
372 if content.contains("[package.metadata.component]") {
376 return Ok(true);
377 }
378
379 if content.contains("wit-bindgen") || content.contains("cargo-component-bindings") {
381 return Ok(true);
382 }
383
384 let component_toml = cargo_toml.parent().map(|p| p.join("cargo-component.toml"));
386 if let Some(ref component_toml) = component_toml {
387 if component_toml.exists() {
388 return Ok(true);
389 }
390 }
391
392 Ok(false)
393}
394
395fn is_assemblyscript_project(package_json: &Path) -> Result<bool> {
397 let content =
398 std::fs::read_to_string(package_json).map_err(|e| WasmBuildError::ProjectConfigError {
399 message: format!("Failed to read package.json: {}", e),
400 })?;
401
402 let json: serde_json::Value =
403 serde_json::from_str(&content).map_err(|e| WasmBuildError::ProjectConfigError {
404 message: format!("Invalid package.json: {}", e),
405 })?;
406
407 let has_assemblyscript = |deps: Option<&serde_json::Value>| -> bool {
409 deps.and_then(|d| d.as_object())
410 .map(|d| d.contains_key("assemblyscript"))
411 .unwrap_or(false)
412 };
413
414 if has_assemblyscript(json.get("dependencies"))
415 || has_assemblyscript(json.get("devDependencies"))
416 {
417 return Ok(true);
418 }
419
420 if let Some(scripts) = json.get("scripts").and_then(|s| s.as_object()) {
422 for script in scripts.values() {
423 if let Some(cmd) = script.as_str() {
424 if cmd.contains("asc ") || cmd.starts_with("asc") {
425 return Ok(true);
426 }
427 }
428 }
429 }
430
431 Ok(false)
432}
433
434fn has_c_source_files(path: &Path) -> bool {
436 if let Ok(entries) = std::fs::read_dir(path) {
437 for entry in entries.flatten() {
438 let file_path = entry.path();
439 if let Some(ext) = file_path.extension() {
440 if ext == "c" || ext == "h" {
441 return true;
442 }
443 }
444 }
445 }
446
447 let src_dir = path.join("src");
449 if src_dir.is_dir() {
450 if let Ok(entries) = std::fs::read_dir(&src_dir) {
451 for entry in entries.flatten() {
452 let file_path = entry.path();
453 if let Some(ext) = file_path.extension() {
454 if ext == "c" || ext == "h" {
455 return true;
456 }
457 }
458 }
459 }
460 }
461
462 false
463}
464
465pub fn get_build_command(language: WasmLanguage, target: WasiTarget, release: bool) -> Vec<String> {
467 match language {
468 WasmLanguage::Rust => {
469 let mut cmd = vec![
470 "cargo".to_string(),
471 "build".to_string(),
472 "--target".to_string(),
473 target.rust_target().to_string(),
474 ];
475 if release {
476 cmd.push("--release".to_string());
477 }
478 cmd
479 }
480
481 WasmLanguage::RustComponent => {
482 let mut cmd = vec![
483 "cargo".to_string(),
484 "component".to_string(),
485 "build".to_string(),
486 ];
487 if release {
488 cmd.push("--release".to_string());
489 }
490 cmd
491 }
492
493 WasmLanguage::Go => {
494 let wasi_target = match target {
496 WasiTarget::Preview1 => "wasip1",
497 WasiTarget::Preview2 => "wasip2",
498 };
499 let mut cmd = vec![
500 "tinygo".to_string(),
501 "build".to_string(),
502 "-target".to_string(),
503 wasi_target.to_string(),
504 "-o".to_string(),
505 "main.wasm".to_string(),
506 ];
507 if release {
508 cmd.push("-opt".to_string());
509 cmd.push("2".to_string());
510 }
511 cmd.push(".".to_string());
512 cmd
513 }
514
515 WasmLanguage::Python => {
516 let _ = release; vec![
521 "componentize-py".to_string(),
522 "-d".to_string(),
523 "wit".to_string(),
524 "-w".to_string(),
525 "world".to_string(),
526 "componentize".to_string(),
527 "app".to_string(),
528 "-o".to_string(),
529 "app.wasm".to_string(),
530 ]
531 }
532
533 WasmLanguage::TypeScript => {
534 vec![
536 "npx".to_string(),
537 "jco".to_string(),
538 "componentize".to_string(),
539 "src/index.js".to_string(),
540 "--wit".to_string(),
541 "wit".to_string(),
542 "-o".to_string(),
543 "dist/component.wasm".to_string(),
544 ]
545 }
546
547 WasmLanguage::AssemblyScript => {
548 let mut cmd = vec![
549 "npx".to_string(),
550 "asc".to_string(),
551 "assembly/index.ts".to_string(),
552 "--target".to_string(),
553 "release".to_string(),
554 "-o".to_string(),
555 "build/release.wasm".to_string(),
556 ];
557 if release {
558 cmd.push("--optimize".to_string());
559 }
560 cmd
561 }
562
563 WasmLanguage::C => {
564 let mut cmd = vec![
566 "clang".to_string(),
567 "--target=wasm32-wasi".to_string(),
568 "-o".to_string(),
569 "main.wasm".to_string(),
570 ];
571 if release {
572 cmd.push("-O2".to_string());
573 }
574 cmd.push("src/main.c".to_string());
575 cmd
576 }
577
578 WasmLanguage::Zig => {
579 let mut cmd = vec![
581 "zig".to_string(),
582 "build".to_string(),
583 "-Dtarget=wasm32-wasi".to_string(),
584 ];
585 if release {
586 cmd.push("-Doptimize=ReleaseFast".to_string());
587 }
588 cmd
589 }
590 }
591}
592
593#[instrument(level = "info", skip_all, fields(
600 context = %context.as_ref().display(),
601 language = ?config.language,
602 target = ?config.target
603))]
604pub async fn build_wasm(
605 context: impl AsRef<Path>,
606 config: WasmBuildConfig,
607) -> Result<WasmBuildResult> {
608 let context = context.as_ref();
609 info!("Building WASM component");
610
611 let language = match config.language {
613 Some(lang) => {
614 debug!("Using specified language: {}", lang);
615 lang
616 }
617 None => {
618 let detected = detect_language(context)?;
619 info!("Auto-detected language: {}", detected);
620 detected
621 }
622 };
623
624 verify_build_tool(language).await?;
626
627 let cmd = get_build_command(language, config.target, config.optimize);
629 debug!("Build command: {:?}", cmd);
630
631 let output = execute_build_command(context, &cmd).await?;
633
634 if !output.status.success() {
636 let exit_code = output.status.code().unwrap_or(-1);
637 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
638 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
639
640 warn!("Build failed with exit code {}", exit_code);
641 trace!("stdout: {}", stdout);
642 trace!("stderr: {}", stderr);
643
644 return Err(WasmBuildError::BuildFailed {
645 exit_code,
646 stderr,
647 stdout,
648 });
649 }
650
651 let wasm_path = find_wasm_output(context, language, config.target, config.optimize)?;
653
654 let final_path = if let Some(ref output_path) = config.output_path {
656 std::fs::copy(&wasm_path, output_path)?;
657 output_path.clone()
658 } else {
659 wasm_path
660 };
661
662 let metadata = std::fs::metadata(&final_path)?;
664 let size = metadata.len();
665
666 info!("Successfully built {} WASM ({} bytes)", language, size);
667
668 Ok(WasmBuildResult {
669 wasm_path: final_path,
670 language,
671 target: config.target,
672 size,
673 })
674}
675
676async fn verify_build_tool(language: WasmLanguage) -> Result<()> {
678 let (tool, check_cmd) = match language {
679 WasmLanguage::Rust | WasmLanguage::RustComponent => ("cargo", vec!["cargo", "--version"]),
680 WasmLanguage::Go => ("tinygo", vec!["tinygo", "version"]),
681 WasmLanguage::Python => ("componentize-py", vec!["componentize-py", "--version"]),
682 WasmLanguage::TypeScript | WasmLanguage::AssemblyScript => {
683 ("npx", vec!["npx", "--version"])
684 }
685 WasmLanguage::C => ("clang", vec!["clang", "--version"]),
686 WasmLanguage::Zig => ("zig", vec!["zig", "version"]),
687 };
688
689 debug!("Checking for tool: {}", tool);
690
691 let result = Command::new(check_cmd[0])
692 .args(&check_cmd[1..])
693 .output()
694 .await;
695
696 match result {
697 Ok(output) if output.status.success() => {
698 trace!("{} is available", tool);
699 Ok(())
700 }
701 Ok(output) => {
702 let stderr = String::from_utf8_lossy(&output.stderr);
703 Err(WasmBuildError::ToolNotFound {
704 tool: tool.to_string(),
705 message: format!("Command failed: {}", stderr),
706 })
707 }
708 Err(e) => Err(WasmBuildError::ToolNotFound {
709 tool: tool.to_string(),
710 message: format!("Not found in PATH: {}", e),
711 }),
712 }
713}
714
715async fn execute_build_command(context: &Path, cmd: &[String]) -> Result<std::process::Output> {
717 let mut command = Command::new(&cmd[0]);
718 command
719 .args(&cmd[1..])
720 .current_dir(context)
721 .stdout(std::process::Stdio::piped())
722 .stderr(std::process::Stdio::piped());
723
724 debug!("Executing: {} in {}", cmd.join(" "), context.display());
725
726 command.output().await.map_err(WasmBuildError::Io)
727}
728
729fn find_wasm_output(
731 context: &Path,
732 language: WasmLanguage,
733 target: WasiTarget,
734 release: bool,
735) -> Result<PathBuf> {
736 let candidates: Vec<PathBuf> = match language {
738 WasmLanguage::Rust => {
739 let profile = if release { "release" } else { "debug" };
740 let target_name = target.rust_target();
741
742 let package_name =
744 get_rust_package_name(context).unwrap_or_else(|_| "output".to_string());
745
746 vec![
747 context
748 .join("target")
749 .join(target_name)
750 .join(profile)
751 .join(format!("{}.wasm", package_name)),
752 context
753 .join("target")
754 .join(target_name)
755 .join(profile)
756 .join(format!("{}.wasm", package_name.replace('-', "_"))),
757 ]
758 }
759
760 WasmLanguage::RustComponent => {
761 let profile = if release { "release" } else { "debug" };
762 let package_name =
763 get_rust_package_name(context).unwrap_or_else(|_| "output".to_string());
764
765 vec![
766 context
768 .join("target")
769 .join("wasm32-wasip1")
770 .join(profile)
771 .join(format!("{}.wasm", package_name)),
772 context
773 .join("target")
774 .join("wasm32-wasip2")
775 .join(profile)
776 .join(format!("{}.wasm", package_name)),
777 context
778 .join("target")
779 .join("wasm32-wasi")
780 .join(profile)
781 .join(format!("{}.wasm", package_name)),
782 ]
783 }
784
785 WasmLanguage::Go => {
786 vec![context.join("main.wasm")]
787 }
788
789 WasmLanguage::Python => {
790 vec![context.join("app.wasm")]
791 }
792
793 WasmLanguage::TypeScript => {
794 vec![
795 context.join("dist").join("component.wasm"),
796 context.join("component.wasm"),
797 ]
798 }
799
800 WasmLanguage::AssemblyScript => {
801 vec![
802 context.join("build").join("release.wasm"),
803 context.join("build").join("debug.wasm"),
804 ]
805 }
806
807 WasmLanguage::C => {
808 vec![context.join("main.wasm")]
809 }
810
811 WasmLanguage::Zig => {
812 vec![
813 context.join("zig-out").join("bin").join("main.wasm"),
814 context.join("zig-out").join("lib").join("main.wasm"),
815 ]
816 }
817 };
818
819 for candidate in &candidates {
821 if candidate.exists() {
822 debug!("Found WASM output at: {}", candidate.display());
823 return Ok(candidate.clone());
824 }
825 }
826
827 if let Some(wasm_path) = find_any_wasm_file(context) {
829 debug!("Found WASM file via search: {}", wasm_path.display());
830 return Ok(wasm_path);
831 }
832
833 Err(WasmBuildError::OutputNotFound {
834 path: candidates
835 .first()
836 .cloned()
837 .unwrap_or_else(|| context.join("output.wasm")),
838 })
839}
840
841fn get_rust_package_name(context: &Path) -> Result<String> {
843 let cargo_toml = context.join("Cargo.toml");
844 let content =
845 std::fs::read_to_string(&cargo_toml).map_err(|e| WasmBuildError::ProjectConfigError {
846 message: format!("Failed to read Cargo.toml: {}", e),
847 })?;
848
849 for line in content.lines() {
851 let line = line.trim();
852 if line.starts_with("name") {
853 if let Some(name) = line
854 .split('=')
855 .nth(1)
856 .map(|s| s.trim().trim_matches('"').trim_matches('\''))
857 {
858 return Ok(name.to_string());
859 }
860 }
861 }
862
863 Err(WasmBuildError::ProjectConfigError {
864 message: "Could not find package name in Cargo.toml".to_string(),
865 })
866}
867
868fn find_any_wasm_file(context: &Path) -> Option<PathBuf> {
870 let search_dirs = [
872 context.to_path_buf(),
873 context.join("target"),
874 context.join("build"),
875 context.join("dist"),
876 context.join("out"),
877 context.join("zig-out"),
878 ];
879
880 for dir in &search_dirs {
881 if let Some(path) = search_wasm_recursive(dir, 3) {
882 return Some(path);
883 }
884 }
885
886 None
887}
888
889fn search_wasm_recursive(dir: &Path, max_depth: usize) -> Option<PathBuf> {
891 if max_depth == 0 || !dir.is_dir() {
892 return None;
893 }
894
895 if let Ok(entries) = std::fs::read_dir(dir) {
896 for entry in entries.flatten() {
897 let path = entry.path();
898
899 if path.is_file() {
900 if let Some(ext) = path.extension() {
901 if ext == "wasm" {
902 return Some(path);
903 }
904 }
905 } else if path.is_dir() {
906 if let Some(found) = search_wasm_recursive(&path, max_depth - 1) {
907 return Some(found);
908 }
909 }
910 }
911 }
912
913 None
914}
915
916#[cfg(test)]
917mod tests {
918 use super::*;
919 use std::fs;
920 use tempfile::TempDir;
921
922 fn create_temp_dir() -> TempDir {
923 TempDir::new().expect("Failed to create temp directory")
924 }
925
926 mod wasm_language_tests {
931 use super::*;
932
933 #[test]
934 fn test_display_all_variants() {
935 assert_eq!(WasmLanguage::Rust.to_string(), "Rust");
936 assert_eq!(
937 WasmLanguage::RustComponent.to_string(),
938 "Rust (cargo-component)"
939 );
940 assert_eq!(WasmLanguage::Go.to_string(), "Go (TinyGo)");
941 assert_eq!(WasmLanguage::Python.to_string(), "Python");
942 assert_eq!(WasmLanguage::TypeScript.to_string(), "TypeScript");
943 assert_eq!(WasmLanguage::AssemblyScript.to_string(), "AssemblyScript");
944 assert_eq!(WasmLanguage::C.to_string(), "C");
945 assert_eq!(WasmLanguage::Zig.to_string(), "Zig");
946 }
947
948 #[test]
949 fn test_debug_formatting() {
950 let debug_str = format!("{:?}", WasmLanguage::Rust);
952 assert_eq!(debug_str, "Rust");
953
954 let debug_str = format!("{:?}", WasmLanguage::RustComponent);
955 assert_eq!(debug_str, "RustComponent");
956
957 let debug_str = format!("{:?}", WasmLanguage::Go);
958 assert_eq!(debug_str, "Go");
959
960 let debug_str = format!("{:?}", WasmLanguage::Python);
961 assert_eq!(debug_str, "Python");
962
963 let debug_str = format!("{:?}", WasmLanguage::TypeScript);
964 assert_eq!(debug_str, "TypeScript");
965
966 let debug_str = format!("{:?}", WasmLanguage::AssemblyScript);
967 assert_eq!(debug_str, "AssemblyScript");
968
969 let debug_str = format!("{:?}", WasmLanguage::C);
970 assert_eq!(debug_str, "C");
971
972 let debug_str = format!("{:?}", WasmLanguage::Zig);
973 assert_eq!(debug_str, "Zig");
974 }
975
976 #[test]
977 fn test_clone() {
978 let lang = WasmLanguage::Rust;
979 let cloned = lang;
980 assert_eq!(lang, cloned);
981
982 let lang = WasmLanguage::Python;
983 let cloned = lang;
984 assert_eq!(lang, cloned);
985 }
986
987 #[test]
988 fn test_copy() {
989 let lang = WasmLanguage::Go;
990 let copied = lang; assert_eq!(lang, copied);
992 assert_eq!(lang, WasmLanguage::Go);
994 }
995
996 #[test]
997 fn test_partial_eq() {
998 assert_eq!(WasmLanguage::Rust, WasmLanguage::Rust);
999 assert_ne!(WasmLanguage::Rust, WasmLanguage::Go);
1000 assert_ne!(WasmLanguage::Rust, WasmLanguage::RustComponent);
1001 assert_eq!(WasmLanguage::TypeScript, WasmLanguage::TypeScript);
1002 assert_ne!(WasmLanguage::TypeScript, WasmLanguage::AssemblyScript);
1003 }
1004
1005 #[test]
1006 fn test_name_method() {
1007 assert_eq!(WasmLanguage::Rust.name(), "Rust");
1008 assert_eq!(WasmLanguage::RustComponent.name(), "Rust (cargo-component)");
1009 assert_eq!(WasmLanguage::Go.name(), "Go (TinyGo)");
1010 assert_eq!(WasmLanguage::Python.name(), "Python");
1011 assert_eq!(WasmLanguage::TypeScript.name(), "TypeScript");
1012 assert_eq!(WasmLanguage::AssemblyScript.name(), "AssemblyScript");
1013 assert_eq!(WasmLanguage::C.name(), "C");
1014 assert_eq!(WasmLanguage::Zig.name(), "Zig");
1015 }
1016
1017 #[test]
1018 fn test_all_returns_all_variants() {
1019 let all = WasmLanguage::all();
1020 assert_eq!(all.len(), 8);
1021 assert!(all.contains(&WasmLanguage::Rust));
1022 assert!(all.contains(&WasmLanguage::RustComponent));
1023 assert!(all.contains(&WasmLanguage::Go));
1024 assert!(all.contains(&WasmLanguage::Python));
1025 assert!(all.contains(&WasmLanguage::TypeScript));
1026 assert!(all.contains(&WasmLanguage::AssemblyScript));
1027 assert!(all.contains(&WasmLanguage::C));
1028 assert!(all.contains(&WasmLanguage::Zig));
1029 }
1030
1031 #[test]
1032 fn test_is_component_native() {
1033 assert!(WasmLanguage::RustComponent.is_component_native());
1035 assert!(WasmLanguage::Python.is_component_native());
1036 assert!(WasmLanguage::TypeScript.is_component_native());
1037
1038 assert!(!WasmLanguage::Rust.is_component_native());
1040 assert!(!WasmLanguage::Go.is_component_native());
1041 assert!(!WasmLanguage::AssemblyScript.is_component_native());
1042 assert!(!WasmLanguage::C.is_component_native());
1043 assert!(!WasmLanguage::Zig.is_component_native());
1044 }
1045
1046 #[test]
1047 fn test_hash() {
1048 use std::collections::HashSet;
1049
1050 let mut set = HashSet::new();
1051 set.insert(WasmLanguage::Rust);
1052 set.insert(WasmLanguage::Go);
1053 set.insert(WasmLanguage::Rust); assert_eq!(set.len(), 2);
1056 assert!(set.contains(&WasmLanguage::Rust));
1057 assert!(set.contains(&WasmLanguage::Go));
1058 }
1059 }
1060
1061 mod wasi_target_tests {
1066 use super::*;
1067
1068 #[test]
1069 fn test_default_returns_preview2() {
1070 let target = WasiTarget::default();
1071 assert_eq!(target, WasiTarget::Preview2);
1072 }
1073
1074 #[test]
1075 fn test_display_preview1() {
1076 assert_eq!(WasiTarget::Preview1.to_string(), "WASI Preview 1");
1077 }
1078
1079 #[test]
1080 fn test_display_preview2() {
1081 assert_eq!(WasiTarget::Preview2.to_string(), "WASI Preview 2");
1082 }
1083
1084 #[test]
1085 fn test_debug_formatting() {
1086 let debug_str = format!("{:?}", WasiTarget::Preview1);
1087 assert_eq!(debug_str, "Preview1");
1088
1089 let debug_str = format!("{:?}", WasiTarget::Preview2);
1090 assert_eq!(debug_str, "Preview2");
1091 }
1092
1093 #[test]
1094 fn test_clone() {
1095 let target = WasiTarget::Preview1;
1096 let cloned = target;
1097 assert_eq!(target, cloned);
1098
1099 let target = WasiTarget::Preview2;
1100 let cloned = target;
1101 assert_eq!(target, cloned);
1102 }
1103
1104 #[test]
1105 fn test_copy() {
1106 let target = WasiTarget::Preview1;
1107 let copied = target; assert_eq!(target, copied);
1109 assert_eq!(target, WasiTarget::Preview1);
1111 }
1112
1113 #[test]
1114 fn test_partial_eq() {
1115 assert_eq!(WasiTarget::Preview1, WasiTarget::Preview1);
1116 assert_eq!(WasiTarget::Preview2, WasiTarget::Preview2);
1117 assert_ne!(WasiTarget::Preview1, WasiTarget::Preview2);
1118 }
1119
1120 #[test]
1121 fn test_rust_target_preview1() {
1122 assert_eq!(WasiTarget::Preview1.rust_target(), "wasm32-wasip1");
1123 }
1124
1125 #[test]
1126 fn test_rust_target_preview2() {
1127 assert_eq!(WasiTarget::Preview2.rust_target(), "wasm32-wasip2");
1128 }
1129
1130 #[test]
1131 fn test_name_method() {
1132 assert_eq!(WasiTarget::Preview1.name(), "WASI Preview 1");
1133 assert_eq!(WasiTarget::Preview2.name(), "WASI Preview 2");
1134 }
1135
1136 #[test]
1137 fn test_hash() {
1138 use std::collections::HashSet;
1139
1140 let mut set = HashSet::new();
1141 set.insert(WasiTarget::Preview1);
1142 set.insert(WasiTarget::Preview2);
1143 set.insert(WasiTarget::Preview1); assert_eq!(set.len(), 2);
1146 assert!(set.contains(&WasiTarget::Preview1));
1147 assert!(set.contains(&WasiTarget::Preview2));
1148 }
1149 }
1150
1151 mod wasm_build_config_tests {
1156 use super::*;
1157
1158 #[test]
1159 fn test_default_trait() {
1160 let config = WasmBuildConfig::default();
1161
1162 assert_eq!(config.language, None);
1163 assert_eq!(config.target, WasiTarget::Preview2); assert!(!config.optimize);
1165 assert_eq!(config.wit_path, None);
1166 assert_eq!(config.output_path, None);
1167 }
1168
1169 #[test]
1170 fn test_new_equals_default() {
1171 let new_config = WasmBuildConfig::new();
1172 let default_config = WasmBuildConfig::default();
1173
1174 assert_eq!(new_config.language, default_config.language);
1175 assert_eq!(new_config.target, default_config.target);
1176 assert_eq!(new_config.optimize, default_config.optimize);
1177 assert_eq!(new_config.wit_path, default_config.wit_path);
1178 assert_eq!(new_config.output_path, default_config.output_path);
1179 }
1180
1181 #[test]
1182 fn test_with_language() {
1183 let config = WasmBuildConfig::new().language(WasmLanguage::Rust);
1184 assert_eq!(config.language, Some(WasmLanguage::Rust));
1185
1186 let config = WasmBuildConfig::new().language(WasmLanguage::Python);
1187 assert_eq!(config.language, Some(WasmLanguage::Python));
1188 }
1189
1190 #[test]
1191 fn test_with_target() {
1192 let config = WasmBuildConfig::new().target(WasiTarget::Preview1);
1193 assert_eq!(config.target, WasiTarget::Preview1);
1194
1195 let config = WasmBuildConfig::new().target(WasiTarget::Preview2);
1196 assert_eq!(config.target, WasiTarget::Preview2);
1197 }
1198
1199 #[test]
1200 fn test_with_optimize_true() {
1201 let config = WasmBuildConfig::new().optimize(true);
1202 assert!(config.optimize);
1203 }
1204
1205 #[test]
1206 fn test_with_optimize_false() {
1207 let config = WasmBuildConfig::new().optimize(false);
1208 assert!(!config.optimize);
1209 }
1210
1211 #[test]
1212 fn test_with_wit_path_string() {
1213 let config = WasmBuildConfig::new().wit_path("/path/to/wit");
1214 assert_eq!(config.wit_path, Some(PathBuf::from("/path/to/wit")));
1215 }
1216
1217 #[test]
1218 fn test_with_wit_path_pathbuf() {
1219 let path = PathBuf::from("/another/wit/path");
1220 let config = WasmBuildConfig::new().wit_path(path.clone());
1221 assert_eq!(config.wit_path, Some(path));
1222 }
1223
1224 #[test]
1225 fn test_with_output_path_string() {
1226 let config = WasmBuildConfig::new().output_path("/output/file.wasm");
1227 assert_eq!(config.output_path, Some(PathBuf::from("/output/file.wasm")));
1228 }
1229
1230 #[test]
1231 fn test_with_output_path_pathbuf() {
1232 let path = PathBuf::from("/custom/output.wasm");
1233 let config = WasmBuildConfig::new().output_path(path.clone());
1234 assert_eq!(config.output_path, Some(path));
1235 }
1236
1237 #[test]
1238 fn test_builder_pattern_chaining() {
1239 let config = WasmBuildConfig::new()
1240 .language(WasmLanguage::Go)
1241 .target(WasiTarget::Preview1)
1242 .optimize(true)
1243 .wit_path("/wit")
1244 .output_path("/out.wasm");
1245
1246 assert_eq!(config.language, Some(WasmLanguage::Go));
1247 assert_eq!(config.target, WasiTarget::Preview1);
1248 assert!(config.optimize);
1249 assert_eq!(config.wit_path, Some(PathBuf::from("/wit")));
1250 assert_eq!(config.output_path, Some(PathBuf::from("/out.wasm")));
1251 }
1252
1253 #[test]
1254 fn test_debug_formatting() {
1255 let config = WasmBuildConfig::new().language(WasmLanguage::Rust);
1256 let debug_str = format!("{:?}", config);
1257
1258 assert!(debug_str.contains("WasmBuildConfig"));
1259 assert!(debug_str.contains("Rust"));
1260 }
1261
1262 #[test]
1263 fn test_clone() {
1264 let config = WasmBuildConfig::new()
1265 .language(WasmLanguage::Python)
1266 .optimize(true);
1267
1268 let cloned = config.clone();
1269
1270 assert_eq!(cloned.language, Some(WasmLanguage::Python));
1271 assert!(cloned.optimize);
1272 }
1273 }
1274
1275 mod wasm_build_result_tests {
1280 use super::*;
1281
1282 #[test]
1283 fn test_struct_creation() {
1284 let result = WasmBuildResult {
1285 wasm_path: PathBuf::from("/path/to/output.wasm"),
1286 language: WasmLanguage::Rust,
1287 target: WasiTarget::Preview2,
1288 size: 1024,
1289 };
1290
1291 assert_eq!(result.wasm_path, PathBuf::from("/path/to/output.wasm"));
1292 assert_eq!(result.language, WasmLanguage::Rust);
1293 assert_eq!(result.target, WasiTarget::Preview2);
1294 assert_eq!(result.size, 1024);
1295 }
1296
1297 #[test]
1298 fn test_struct_creation_all_languages() {
1299 for lang in WasmLanguage::all() {
1300 let result = WasmBuildResult {
1301 wasm_path: PathBuf::from("/test.wasm"),
1302 language: *lang,
1303 target: WasiTarget::Preview1,
1304 size: 512,
1305 };
1306 assert_eq!(result.language, *lang);
1307 }
1308 }
1309
1310 #[test]
1311 fn test_debug_formatting() {
1312 let result = WasmBuildResult {
1313 wasm_path: PathBuf::from("/test.wasm"),
1314 language: WasmLanguage::Go,
1315 target: WasiTarget::Preview1,
1316 size: 2048,
1317 };
1318
1319 let debug_str = format!("{:?}", result);
1320 assert!(debug_str.contains("WasmBuildResult"));
1321 assert!(debug_str.contains("test.wasm"));
1322 assert!(debug_str.contains("Go"));
1323 assert!(debug_str.contains("2048"));
1324 }
1325
1326 #[test]
1327 fn test_clone() {
1328 let result = WasmBuildResult {
1329 wasm_path: PathBuf::from("/original.wasm"),
1330 language: WasmLanguage::Zig,
1331 target: WasiTarget::Preview2,
1332 size: 4096,
1333 };
1334
1335 let cloned = result.clone();
1336
1337 assert_eq!(cloned.wasm_path, result.wasm_path);
1338 assert_eq!(cloned.language, result.language);
1339 assert_eq!(cloned.target, result.target);
1340 assert_eq!(cloned.size, result.size);
1341 }
1342
1343 #[test]
1344 fn test_zero_size() {
1345 let result = WasmBuildResult {
1346 wasm_path: PathBuf::from("/empty.wasm"),
1347 language: WasmLanguage::C,
1348 target: WasiTarget::Preview1,
1349 size: 0,
1350 };
1351 assert_eq!(result.size, 0);
1352 }
1353
1354 #[test]
1355 fn test_large_size() {
1356 let result = WasmBuildResult {
1357 wasm_path: PathBuf::from("/large.wasm"),
1358 language: WasmLanguage::AssemblyScript,
1359 target: WasiTarget::Preview2,
1360 size: u64::MAX,
1361 };
1362 assert_eq!(result.size, u64::MAX);
1363 }
1364 }
1365
1366 mod wasm_build_error_tests {
1371 use super::*;
1372
1373 #[test]
1374 fn test_display_language_not_detected() {
1375 let err = WasmBuildError::LanguageNotDetected {
1376 path: PathBuf::from("/test/path"),
1377 };
1378 let display = err.to_string();
1379 assert!(display.contains("Could not detect source language"));
1380 assert!(display.contains("/test/path"));
1381 }
1382
1383 #[test]
1384 fn test_display_tool_not_found() {
1385 let err = WasmBuildError::ToolNotFound {
1386 tool: "cargo".to_string(),
1387 message: "Not in PATH".to_string(),
1388 };
1389 let display = err.to_string();
1390 assert!(display.contains("Build tool 'cargo' not found"));
1391 assert!(display.contains("Not in PATH"));
1392 }
1393
1394 #[test]
1395 fn test_display_build_failed() {
1396 let err = WasmBuildError::BuildFailed {
1397 exit_code: 1,
1398 stderr: "compilation error".to_string(),
1399 stdout: "some output".to_string(),
1400 };
1401 let display = err.to_string();
1402 assert!(display.contains("Build failed with exit code 1"));
1403 assert!(display.contains("compilation error"));
1404 }
1405
1406 #[test]
1407 fn test_display_output_not_found() {
1408 let err = WasmBuildError::OutputNotFound {
1409 path: PathBuf::from("/expected/output.wasm"),
1410 };
1411 let display = err.to_string();
1412 assert!(display.contains("WASM output not found"));
1413 assert!(display.contains("/expected/output.wasm"));
1414 }
1415
1416 #[test]
1417 fn test_display_config_error() {
1418 let err = WasmBuildError::ConfigError {
1419 message: "Invalid configuration".to_string(),
1420 };
1421 let display = err.to_string();
1422 assert!(display.contains("Configuration error"));
1423 assert!(display.contains("Invalid configuration"));
1424 }
1425
1426 #[test]
1427 fn test_display_project_config_error() {
1428 let err = WasmBuildError::ProjectConfigError {
1429 message: "Failed to parse Cargo.toml".to_string(),
1430 };
1431 let display = err.to_string();
1432 assert!(display.contains("Failed to read project configuration"));
1433 assert!(display.contains("Failed to parse Cargo.toml"));
1434 }
1435
1436 #[test]
1437 fn test_display_io_error() {
1438 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
1439 let err = WasmBuildError::Io(io_err);
1440 let display = err.to_string();
1441 assert!(display.contains("IO error"));
1442 assert!(display.contains("file not found"));
1443 }
1444
1445 #[test]
1446 fn test_debug_formatting_all_variants() {
1447 let errors = vec![
1448 WasmBuildError::LanguageNotDetected {
1449 path: PathBuf::from("/test"),
1450 },
1451 WasmBuildError::ToolNotFound {
1452 tool: "test".to_string(),
1453 message: "msg".to_string(),
1454 },
1455 WasmBuildError::BuildFailed {
1456 exit_code: 0,
1457 stderr: String::new(),
1458 stdout: String::new(),
1459 },
1460 WasmBuildError::OutputNotFound {
1461 path: PathBuf::from("/test"),
1462 },
1463 WasmBuildError::ConfigError {
1464 message: "test".to_string(),
1465 },
1466 WasmBuildError::ProjectConfigError {
1467 message: "test".to_string(),
1468 },
1469 ];
1470
1471 for err in errors {
1472 let debug_str = format!("{:?}", err);
1473 assert!(!debug_str.is_empty());
1474 }
1475 }
1476
1477 #[test]
1478 fn test_from_io_error() {
1479 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
1480 let wasm_err: WasmBuildError = io_err.into();
1481
1482 match wasm_err {
1483 WasmBuildError::Io(e) => {
1484 assert_eq!(e.kind(), std::io::ErrorKind::PermissionDenied);
1485 }
1486 _ => panic!("Expected Io variant"),
1487 }
1488 }
1489
1490 #[test]
1491 fn test_from_io_error_various_kinds() {
1492 let kinds = vec![
1493 std::io::ErrorKind::NotFound,
1494 std::io::ErrorKind::PermissionDenied,
1495 std::io::ErrorKind::AlreadyExists,
1496 std::io::ErrorKind::InvalidData,
1497 ];
1498
1499 for kind in kinds {
1500 let io_err = std::io::Error::new(kind, "test error");
1501 let wasm_err: WasmBuildError = io_err.into();
1502 assert!(matches!(wasm_err, WasmBuildError::Io(_)));
1503 }
1504 }
1505
1506 #[test]
1507 fn test_error_implements_std_error() {
1508 let err = WasmBuildError::ConfigError {
1509 message: "test".to_string(),
1510 };
1511 let _: &dyn std::error::Error = &err;
1513 }
1514 }
1515
1516 mod detect_language_tests {
1521 use super::*;
1522
1523 #[test]
1524 fn test_detect_cargo_toml_rust() {
1525 let dir = create_temp_dir();
1526 fs::write(
1527 dir.path().join("Cargo.toml"),
1528 r#"[package]
1529name = "test"
1530version = "0.1.0"
1531"#,
1532 )
1533 .unwrap();
1534
1535 let lang = detect_language(dir.path()).unwrap();
1536 assert_eq!(lang, WasmLanguage::Rust);
1537 }
1538
1539 #[test]
1540 fn test_detect_cargo_toml_with_cargo_component_metadata() {
1541 let dir = create_temp_dir();
1542 fs::write(
1543 dir.path().join("Cargo.toml"),
1544 r#"[package]
1545name = "test"
1546version = "0.1.0"
1547
1548[package.metadata.component]
1549package = "test:component"
1550"#,
1551 )
1552 .unwrap();
1553
1554 let lang = detect_language(dir.path()).unwrap();
1555 assert_eq!(lang, WasmLanguage::RustComponent);
1556 }
1557
1558 #[test]
1559 fn test_detect_cargo_toml_with_wit_bindgen_dep() {
1560 let dir = create_temp_dir();
1561 fs::write(
1562 dir.path().join("Cargo.toml"),
1563 r#"[package]
1564name = "test"
1565version = "0.1.0"
1566
1567[dependencies]
1568wit-bindgen = "0.20"
1569"#,
1570 )
1571 .unwrap();
1572
1573 let lang = detect_language(dir.path()).unwrap();
1574 assert_eq!(lang, WasmLanguage::RustComponent);
1575 }
1576
1577 #[test]
1578 fn test_detect_cargo_toml_with_cargo_component_bindings() {
1579 let dir = create_temp_dir();
1580 fs::write(
1581 dir.path().join("Cargo.toml"),
1582 r#"[package]
1583name = "test"
1584version = "0.1.0"
1585
1586[dependencies]
1587cargo-component-bindings = "0.1"
1588"#,
1589 )
1590 .unwrap();
1591
1592 let lang = detect_language(dir.path()).unwrap();
1593 assert_eq!(lang, WasmLanguage::RustComponent);
1594 }
1595
1596 #[test]
1597 fn test_detect_cargo_component_toml_file() {
1598 let dir = create_temp_dir();
1599 fs::write(
1600 dir.path().join("Cargo.toml"),
1601 r#"[package]
1602name = "test"
1603version = "0.1.0"
1604"#,
1605 )
1606 .unwrap();
1607 fs::write(
1608 dir.path().join("cargo-component.toml"),
1609 "# cargo-component config",
1610 )
1611 .unwrap();
1612
1613 let lang = detect_language(dir.path()).unwrap();
1614 assert_eq!(lang, WasmLanguage::RustComponent);
1615 }
1616
1617 #[test]
1618 fn test_detect_go_mod() {
1619 let dir = create_temp_dir();
1620 fs::write(
1621 dir.path().join("go.mod"),
1622 "module example.com/test\n\ngo 1.21\n",
1623 )
1624 .unwrap();
1625
1626 let lang = detect_language(dir.path()).unwrap();
1627 assert_eq!(lang, WasmLanguage::Go);
1628 }
1629
1630 #[test]
1631 fn test_detect_pyproject_toml() {
1632 let dir = create_temp_dir();
1633 fs::write(
1634 dir.path().join("pyproject.toml"),
1635 r#"[project]
1636name = "my-package"
1637version = "0.1.0"
1638"#,
1639 )
1640 .unwrap();
1641
1642 let lang = detect_language(dir.path()).unwrap();
1643 assert_eq!(lang, WasmLanguage::Python);
1644 }
1645
1646 #[test]
1647 fn test_detect_requirements_txt() {
1648 let dir = create_temp_dir();
1649 fs::write(
1650 dir.path().join("requirements.txt"),
1651 "flask==2.0.0\nrequests>=2.25.0",
1652 )
1653 .unwrap();
1654
1655 let lang = detect_language(dir.path()).unwrap();
1656 assert_eq!(lang, WasmLanguage::Python);
1657 }
1658
1659 #[test]
1660 fn test_detect_setup_py() {
1661 let dir = create_temp_dir();
1662 fs::write(
1663 dir.path().join("setup.py"),
1664 r#"from setuptools import setup
1665setup(name="mypackage")
1666"#,
1667 )
1668 .unwrap();
1669
1670 let lang = detect_language(dir.path()).unwrap();
1671 assert_eq!(lang, WasmLanguage::Python);
1672 }
1673
1674 #[test]
1675 fn test_detect_package_json_assemblyscript_in_dependencies() {
1676 let dir = create_temp_dir();
1677 fs::write(
1678 dir.path().join("package.json"),
1679 r#"{"name": "test", "dependencies": {"assemblyscript": "^0.27.0"}}"#,
1680 )
1681 .unwrap();
1682
1683 let lang = detect_language(dir.path()).unwrap();
1684 assert_eq!(lang, WasmLanguage::AssemblyScript);
1685 }
1686
1687 #[test]
1688 fn test_detect_package_json_assemblyscript_in_dev_dependencies() {
1689 let dir = create_temp_dir();
1690 fs::write(
1691 dir.path().join("package.json"),
1692 r#"{"name": "test", "devDependencies": {"assemblyscript": "^0.27.0"}}"#,
1693 )
1694 .unwrap();
1695
1696 let lang = detect_language(dir.path()).unwrap();
1697 assert_eq!(lang, WasmLanguage::AssemblyScript);
1698 }
1699
1700 #[test]
1701 fn test_detect_package_json_assemblyscript_in_scripts() {
1702 let dir = create_temp_dir();
1703 fs::write(
1704 dir.path().join("package.json"),
1705 r#"{"name": "test", "scripts": {"build": "asc assembly/index.ts"}}"#,
1706 )
1707 .unwrap();
1708
1709 let lang = detect_language(dir.path()).unwrap();
1710 assert_eq!(lang, WasmLanguage::AssemblyScript);
1711 }
1712
1713 #[test]
1714 fn test_detect_package_json_assemblyscript_asc_command() {
1715 let dir = create_temp_dir();
1716 fs::write(
1717 dir.path().join("package.json"),
1718 r#"{"name": "test", "scripts": {"compile": "asc"}}"#,
1719 )
1720 .unwrap();
1721
1722 let lang = detect_language(dir.path()).unwrap();
1723 assert_eq!(lang, WasmLanguage::AssemblyScript);
1724 }
1725
1726 #[test]
1727 fn test_detect_package_json_typescript() {
1728 let dir = create_temp_dir();
1729 fs::write(
1730 dir.path().join("package.json"),
1731 r#"{"name": "test", "version": "1.0.0", "devDependencies": {"typescript": "^5.0.0"}}"#,
1732 )
1733 .unwrap();
1734
1735 let lang = detect_language(dir.path()).unwrap();
1736 assert_eq!(lang, WasmLanguage::TypeScript);
1737 }
1738
1739 #[test]
1740 fn test_detect_package_json_plain_no_assemblyscript() {
1741 let dir = create_temp_dir();
1742 fs::write(
1743 dir.path().join("package.json"),
1744 r#"{"name": "test", "version": "1.0.0"}"#,
1745 )
1746 .unwrap();
1747
1748 let lang = detect_language(dir.path()).unwrap();
1749 assert_eq!(lang, WasmLanguage::TypeScript);
1750 }
1751
1752 #[test]
1753 fn test_detect_build_zig() {
1754 let dir = create_temp_dir();
1755 fs::write(
1756 dir.path().join("build.zig"),
1757 r#"const std = @import("std");
1758pub fn build(b: *std.build.Builder) void {}
1759"#,
1760 )
1761 .unwrap();
1762
1763 let lang = detect_language(dir.path()).unwrap();
1764 assert_eq!(lang, WasmLanguage::Zig);
1765 }
1766
1767 #[test]
1768 fn test_detect_makefile_with_c_files() {
1769 let dir = create_temp_dir();
1770 fs::write(dir.path().join("Makefile"), "all:\n\t$(CC) main.c -o main").unwrap();
1771 fs::write(dir.path().join("main.c"), "int main() { return 0; }").unwrap();
1772
1773 let lang = detect_language(dir.path()).unwrap();
1774 assert_eq!(lang, WasmLanguage::C);
1775 }
1776
1777 #[test]
1778 fn test_detect_cmakelists_with_c_files() {
1779 let dir = create_temp_dir();
1780 fs::write(
1781 dir.path().join("CMakeLists.txt"),
1782 "cmake_minimum_required(VERSION 3.10)\nproject(test)",
1783 )
1784 .unwrap();
1785 fs::write(dir.path().join("main.c"), "int main() { return 0; }").unwrap();
1786
1787 let lang = detect_language(dir.path()).unwrap();
1788 assert_eq!(lang, WasmLanguage::C);
1789 }
1790
1791 #[test]
1792 fn test_detect_c_header_file_only() {
1793 let dir = create_temp_dir();
1794 fs::write(
1795 dir.path().join("header.h"),
1796 "#ifndef HEADER_H\n#define HEADER_H\n#endif",
1797 )
1798 .unwrap();
1799
1800 let lang = detect_language(dir.path()).unwrap();
1801 assert_eq!(lang, WasmLanguage::C);
1802 }
1803
1804 #[test]
1805 fn test_detect_c_in_src_directory() {
1806 let dir = create_temp_dir();
1807 let src_dir = dir.path().join("src");
1808 fs::create_dir(&src_dir).unwrap();
1809 fs::write(src_dir.join("main.c"), "int main() { return 0; }").unwrap();
1810
1811 let lang = detect_language(dir.path()).unwrap();
1812 assert_eq!(lang, WasmLanguage::C);
1813 }
1814
1815 #[test]
1816 fn test_detect_empty_directory_error() {
1817 let dir = create_temp_dir();
1818 let result = detect_language(dir.path());
1821 assert!(matches!(
1822 result,
1823 Err(WasmBuildError::LanguageNotDetected { .. })
1824 ));
1825 }
1826
1827 #[test]
1828 fn test_detect_unknown_files_error() {
1829 let dir = create_temp_dir();
1830 fs::write(dir.path().join("random.txt"), "some text").unwrap();
1831 fs::write(dir.path().join("data.json"), "{}").unwrap();
1832
1833 let result = detect_language(dir.path());
1834 assert!(matches!(
1835 result,
1836 Err(WasmBuildError::LanguageNotDetected { .. })
1837 ));
1838 }
1839
1840 #[test]
1841 fn test_detect_makefile_without_c_files_error() {
1842 let dir = create_temp_dir();
1843 fs::write(dir.path().join("Makefile"), "all:\n\techo hello").unwrap();
1844
1845 let result = detect_language(dir.path());
1846 assert!(matches!(
1847 result,
1848 Err(WasmBuildError::LanguageNotDetected { .. })
1849 ));
1850 }
1851
1852 #[test]
1853 fn test_detect_priority_rust_over_package_json() {
1854 let dir = create_temp_dir();
1856 fs::write(
1857 dir.path().join("Cargo.toml"),
1858 r#"[package]
1859name = "test"
1860version = "0.1.0"
1861"#,
1862 )
1863 .unwrap();
1864 fs::write(dir.path().join("package.json"), r#"{"name": "test"}"#).unwrap();
1865
1866 let lang = detect_language(dir.path()).unwrap();
1867 assert_eq!(lang, WasmLanguage::Rust);
1868 }
1869
1870 #[test]
1871 fn test_detect_priority_go_over_python() {
1872 let dir = create_temp_dir();
1874 fs::write(dir.path().join("go.mod"), "module test").unwrap();
1875 fs::write(dir.path().join("pyproject.toml"), "[project]").unwrap();
1876
1877 let lang = detect_language(dir.path()).unwrap();
1878 assert_eq!(lang, WasmLanguage::Go);
1879 }
1880 }
1881
1882 mod get_build_command_tests {
1887 use super::*;
1888
1889 #[test]
1890 fn test_rust_preview1_release() {
1891 let cmd = get_build_command(WasmLanguage::Rust, WasiTarget::Preview1, true);
1892 assert_eq!(cmd[0], "cargo");
1893 assert_eq!(cmd[1], "build");
1894 assert!(cmd.contains(&"--target".to_string()));
1895 assert!(cmd.contains(&"wasm32-wasip1".to_string()));
1896 assert!(cmd.contains(&"--release".to_string()));
1897 }
1898
1899 #[test]
1900 fn test_rust_preview2_release() {
1901 let cmd = get_build_command(WasmLanguage::Rust, WasiTarget::Preview2, true);
1902 assert_eq!(cmd[0], "cargo");
1903 assert_eq!(cmd[1], "build");
1904 assert!(cmd.contains(&"--target".to_string()));
1905 assert!(cmd.contains(&"wasm32-wasip2".to_string()));
1906 assert!(cmd.contains(&"--release".to_string()));
1907 }
1908
1909 #[test]
1910 fn test_rust_preview1_debug() {
1911 let cmd = get_build_command(WasmLanguage::Rust, WasiTarget::Preview1, false);
1912 assert_eq!(cmd[0], "cargo");
1913 assert!(cmd.contains(&"wasm32-wasip1".to_string()));
1914 assert!(!cmd.contains(&"--release".to_string()));
1915 }
1916
1917 #[test]
1918 fn test_rust_preview2_debug() {
1919 let cmd = get_build_command(WasmLanguage::Rust, WasiTarget::Preview2, false);
1920 assert_eq!(cmd[0], "cargo");
1921 assert!(cmd.contains(&"wasm32-wasip2".to_string()));
1922 assert!(!cmd.contains(&"--release".to_string()));
1923 }
1924
1925 #[test]
1926 fn test_rust_component_release() {
1927 let cmd = get_build_command(WasmLanguage::RustComponent, WasiTarget::Preview2, true);
1928 assert_eq!(cmd[0], "cargo");
1929 assert_eq!(cmd[1], "component");
1930 assert_eq!(cmd[2], "build");
1931 assert!(cmd.contains(&"--release".to_string()));
1932 }
1933
1934 #[test]
1935 fn test_rust_component_debug() {
1936 let cmd = get_build_command(WasmLanguage::RustComponent, WasiTarget::Preview2, false);
1937 assert_eq!(cmd[0], "cargo");
1938 assert_eq!(cmd[1], "component");
1939 assert_eq!(cmd[2], "build");
1940 assert!(!cmd.contains(&"--release".to_string()));
1941 }
1942
1943 #[test]
1944 fn test_go_preview1() {
1945 let cmd = get_build_command(WasmLanguage::Go, WasiTarget::Preview1, false);
1946 assert_eq!(cmd[0], "tinygo");
1947 assert_eq!(cmd[1], "build");
1948 assert!(cmd.contains(&"-target".to_string()));
1949 assert!(cmd.contains(&"wasip1".to_string()));
1950 }
1951
1952 #[test]
1953 fn test_go_preview2() {
1954 let cmd = get_build_command(WasmLanguage::Go, WasiTarget::Preview2, false);
1955 assert_eq!(cmd[0], "tinygo");
1956 assert!(cmd.contains(&"-target".to_string()));
1957 assert!(cmd.contains(&"wasip2".to_string()));
1958 }
1959
1960 #[test]
1961 fn test_go_release_optimization() {
1962 let cmd = get_build_command(WasmLanguage::Go, WasiTarget::Preview2, true);
1963 assert_eq!(cmd[0], "tinygo");
1964 assert!(cmd.contains(&"-opt".to_string()));
1965 assert!(cmd.contains(&"2".to_string()));
1966 }
1967
1968 #[test]
1969 fn test_go_debug_no_optimization() {
1970 let cmd = get_build_command(WasmLanguage::Go, WasiTarget::Preview2, false);
1971 assert_eq!(cmd[0], "tinygo");
1972 assert!(!cmd.contains(&"-opt".to_string()));
1973 }
1974
1975 #[test]
1976 fn test_tinygo_correct_target_flag() {
1977 let cmd = get_build_command(WasmLanguage::Go, WasiTarget::Preview1, false);
1979 assert!(cmd.contains(&"-target".to_string()));
1980 assert!(!cmd.contains(&"--target".to_string()));
1981 }
1982
1983 #[test]
1984 fn test_python() {
1985 let cmd = get_build_command(WasmLanguage::Python, WasiTarget::Preview2, true);
1986 assert_eq!(cmd[0], "componentize-py");
1987 assert!(cmd.contains(&"-d".to_string()));
1988 assert!(cmd.contains(&"wit".to_string()));
1989 assert!(cmd.contains(&"-w".to_string()));
1990 assert!(cmd.contains(&"world".to_string()));
1991 assert!(cmd.contains(&"componentize".to_string()));
1992 assert!(cmd.contains(&"app".to_string()));
1993 assert!(cmd.contains(&"-o".to_string()));
1994 assert!(cmd.contains(&"app.wasm".to_string()));
1995 }
1996
1997 #[test]
1998 fn test_python_release_same_as_debug() {
1999 let release_cmd = get_build_command(WasmLanguage::Python, WasiTarget::Preview2, true);
2001 let debug_cmd = get_build_command(WasmLanguage::Python, WasiTarget::Preview2, false);
2002 assert_eq!(release_cmd, debug_cmd);
2003 }
2004
2005 #[test]
2006 fn test_componentize_py_arguments() {
2007 let cmd = get_build_command(WasmLanguage::Python, WasiTarget::Preview2, false);
2008 let cmd_str = cmd.join(" ");
2010 assert!(
2011 cmd_str.contains("componentize-py -d wit -w world componentize app -o app.wasm")
2012 );
2013 }
2014
2015 #[test]
2016 fn test_typescript() {
2017 let cmd = get_build_command(WasmLanguage::TypeScript, WasiTarget::Preview2, true);
2018 assert_eq!(cmd[0], "npx");
2019 assert!(cmd.contains(&"jco".to_string()));
2020 assert!(cmd.contains(&"componentize".to_string()));
2021 assert!(cmd.contains(&"--wit".to_string()));
2022 }
2023
2024 #[test]
2025 fn test_assemblyscript_release() {
2026 let cmd = get_build_command(WasmLanguage::AssemblyScript, WasiTarget::Preview2, true);
2027 assert_eq!(cmd[0], "npx");
2028 assert!(cmd.contains(&"asc".to_string()));
2029 assert!(cmd.contains(&"--optimize".to_string()));
2030 }
2031
2032 #[test]
2033 fn test_assemblyscript_debug() {
2034 let cmd = get_build_command(WasmLanguage::AssemblyScript, WasiTarget::Preview2, false);
2035 assert_eq!(cmd[0], "npx");
2036 assert!(cmd.contains(&"asc".to_string()));
2037 assert!(!cmd.contains(&"--optimize".to_string()));
2038 }
2039
2040 #[test]
2041 fn test_c_release() {
2042 let cmd = get_build_command(WasmLanguage::C, WasiTarget::Preview1, true);
2043 assert_eq!(cmd[0], "clang");
2044 assert!(cmd.contains(&"--target=wasm32-wasi".to_string()));
2045 assert!(cmd.contains(&"-O2".to_string()));
2046 }
2047
2048 #[test]
2049 fn test_c_debug() {
2050 let cmd = get_build_command(WasmLanguage::C, WasiTarget::Preview1, false);
2051 assert_eq!(cmd[0], "clang");
2052 assert!(cmd.contains(&"--target=wasm32-wasi".to_string()));
2053 assert!(!cmd.contains(&"-O2".to_string()));
2054 }
2055
2056 #[test]
2057 fn test_zig_release() {
2058 let cmd = get_build_command(WasmLanguage::Zig, WasiTarget::Preview1, true);
2059 assert_eq!(cmd[0], "zig");
2060 assert_eq!(cmd[1], "build");
2061 assert!(cmd.contains(&"-Dtarget=wasm32-wasi".to_string()));
2062 assert!(cmd.contains(&"-Doptimize=ReleaseFast".to_string()));
2063 }
2064
2065 #[test]
2066 fn test_zig_debug() {
2067 let cmd = get_build_command(WasmLanguage::Zig, WasiTarget::Preview1, false);
2068 assert_eq!(cmd[0], "zig");
2069 assert_eq!(cmd[1], "build");
2070 assert!(cmd.contains(&"-Dtarget=wasm32-wasi".to_string()));
2071 assert!(!cmd.contains(&"-Doptimize=ReleaseFast".to_string()));
2072 }
2073
2074 #[test]
2075 fn test_cargo_uses_double_dash_target() {
2076 let cmd = get_build_command(WasmLanguage::Rust, WasiTarget::Preview1, false);
2078 assert!(cmd.contains(&"--target".to_string()));
2079 }
2080
2081 #[test]
2082 fn test_go_output_file() {
2083 let cmd = get_build_command(WasmLanguage::Go, WasiTarget::Preview1, false);
2084 assert!(cmd.contains(&"-o".to_string()));
2085 assert!(cmd.contains(&"main.wasm".to_string()));
2086 }
2087
2088 #[test]
2089 fn test_all_commands_non_empty() {
2090 for lang in WasmLanguage::all() {
2091 for target in [WasiTarget::Preview1, WasiTarget::Preview2] {
2092 for release in [true, false] {
2093 let cmd = get_build_command(*lang, target, release);
2094 assert!(
2095 !cmd.is_empty(),
2096 "Command for {:?}/{:?}/{} should not be empty",
2097 lang,
2098 target,
2099 release
2100 );
2101 assert!(
2102 !cmd[0].is_empty(),
2103 "First command element should not be empty"
2104 );
2105 }
2106 }
2107 }
2108 }
2109 }
2110
2111 mod helper_function_tests {
2116 use super::*;
2117
2118 #[test]
2119 fn test_get_rust_package_name_success() {
2120 let dir = create_temp_dir();
2121 fs::write(
2122 dir.path().join("Cargo.toml"),
2123 r#"[package]
2124name = "my-cool-package"
2125version = "0.1.0"
2126"#,
2127 )
2128 .unwrap();
2129
2130 let name = get_rust_package_name(dir.path()).unwrap();
2131 assert_eq!(name, "my-cool-package");
2132 }
2133
2134 #[test]
2135 fn test_get_rust_package_name_with_single_quotes() {
2136 let dir = create_temp_dir();
2137 fs::write(
2138 dir.path().join("Cargo.toml"),
2139 "[package]\nname = 'single-quoted'\nversion = '0.1.0'\n",
2140 )
2141 .unwrap();
2142
2143 let name = get_rust_package_name(dir.path()).unwrap();
2144 assert_eq!(name, "single-quoted");
2145 }
2146
2147 #[test]
2148 fn test_get_rust_package_name_missing_file() {
2149 let dir = create_temp_dir();
2150 let result = get_rust_package_name(dir.path());
2153 assert!(matches!(
2154 result,
2155 Err(WasmBuildError::ProjectConfigError { .. })
2156 ));
2157 }
2158
2159 #[test]
2160 fn test_get_rust_package_name_no_name_field() {
2161 let dir = create_temp_dir();
2162 fs::write(
2163 dir.path().join("Cargo.toml"),
2164 "[package]\nversion = \"0.1.0\"\n",
2165 )
2166 .unwrap();
2167
2168 let result = get_rust_package_name(dir.path());
2169 assert!(matches!(
2170 result,
2171 Err(WasmBuildError::ProjectConfigError { .. })
2172 ));
2173 }
2174
2175 #[test]
2176 fn test_find_any_wasm_file_in_root() {
2177 let dir = create_temp_dir();
2178 fs::write(dir.path().join("test.wasm"), "wasm content").unwrap();
2179
2180 let found = find_any_wasm_file(dir.path());
2181 assert!(found.is_some());
2182 assert!(found.unwrap().ends_with("test.wasm"));
2183 }
2184
2185 #[test]
2186 fn test_find_any_wasm_file_in_target() {
2187 let dir = create_temp_dir();
2188 let target_dir = dir.path().join("target");
2189 fs::create_dir(&target_dir).unwrap();
2190 fs::write(target_dir.join("output.wasm"), "wasm content").unwrap();
2191
2192 let found = find_any_wasm_file(dir.path());
2193 assert!(found.is_some());
2194 }
2195
2196 #[test]
2197 fn test_find_any_wasm_file_in_build() {
2198 let dir = create_temp_dir();
2199 let build_dir = dir.path().join("build");
2200 fs::create_dir(&build_dir).unwrap();
2201 fs::write(build_dir.join("module.wasm"), "wasm content").unwrap();
2202
2203 let found = find_any_wasm_file(dir.path());
2204 assert!(found.is_some());
2205 }
2206
2207 #[test]
2208 fn test_find_any_wasm_file_in_dist() {
2209 let dir = create_temp_dir();
2210 let dist_dir = dir.path().join("dist");
2211 fs::create_dir(&dist_dir).unwrap();
2212 fs::write(dist_dir.join("bundle.wasm"), "wasm content").unwrap();
2213
2214 let found = find_any_wasm_file(dir.path());
2215 assert!(found.is_some());
2216 }
2217
2218 #[test]
2219 fn test_find_any_wasm_file_nested() {
2220 let dir = create_temp_dir();
2221 let nested = dir
2222 .path()
2223 .join("target")
2224 .join("wasm32-wasip2")
2225 .join("release");
2226 fs::create_dir_all(&nested).unwrap();
2227 fs::write(nested.join("deep.wasm"), "wasm content").unwrap();
2228
2229 let found = find_any_wasm_file(dir.path());
2230 assert!(found.is_some());
2231 }
2232
2233 #[test]
2234 fn test_find_any_wasm_file_none() {
2235 let dir = create_temp_dir();
2236 fs::write(dir.path().join("not_wasm.txt"), "text").unwrap();
2237
2238 let found = find_any_wasm_file(dir.path());
2239 assert!(found.is_none());
2240 }
2241
2242 #[test]
2243 fn test_find_any_wasm_file_respects_depth_limit() {
2244 let dir = create_temp_dir();
2245 let deep = dir.path().join("a").join("b").join("c").join("d").join("e");
2247 fs::create_dir_all(&deep).unwrap();
2248 fs::write(deep.join("too_deep.wasm"), "wasm").unwrap();
2249
2250 let _ = find_any_wasm_file(dir.path());
2253 }
2255
2256 #[test]
2257 fn test_has_c_source_files_true_c() {
2258 let dir = create_temp_dir();
2259 fs::write(dir.path().join("main.c"), "int main() {}").unwrap();
2260
2261 assert!(has_c_source_files(dir.path()));
2262 }
2263
2264 #[test]
2265 fn test_has_c_source_files_true_h() {
2266 let dir = create_temp_dir();
2267 fs::write(dir.path().join("header.h"), "#pragma once").unwrap();
2268
2269 assert!(has_c_source_files(dir.path()));
2270 }
2271
2272 #[test]
2273 fn test_has_c_source_files_in_src() {
2274 let dir = create_temp_dir();
2275 let src = dir.path().join("src");
2276 fs::create_dir(&src).unwrap();
2277 fs::write(src.join("lib.c"), "void foo() {}").unwrap();
2278
2279 assert!(has_c_source_files(dir.path()));
2280 }
2281
2282 #[test]
2283 fn test_has_c_source_files_false() {
2284 let dir = create_temp_dir();
2285 fs::write(dir.path().join("main.rs"), "fn main() {}").unwrap();
2286
2287 assert!(!has_c_source_files(dir.path()));
2288 }
2289
2290 #[test]
2291 fn test_is_cargo_component_project_with_metadata() {
2292 let dir = create_temp_dir();
2293 let cargo_toml = dir.path().join("Cargo.toml");
2294 fs::write(
2295 &cargo_toml,
2296 r#"[package]
2297name = "test"
2298[package.metadata.component]
2299package = "test:component"
2300"#,
2301 )
2302 .unwrap();
2303
2304 assert!(is_cargo_component_project(&cargo_toml).unwrap());
2305 }
2306
2307 #[test]
2308 fn test_is_cargo_component_project_with_wit_bindgen() {
2309 let dir = create_temp_dir();
2310 let cargo_toml = dir.path().join("Cargo.toml");
2311 fs::write(
2312 &cargo_toml,
2313 r#"[package]
2314name = "test"
2315[dependencies]
2316wit-bindgen = "0.20"
2317"#,
2318 )
2319 .unwrap();
2320
2321 assert!(is_cargo_component_project(&cargo_toml).unwrap());
2322 }
2323
2324 #[test]
2325 fn test_is_cargo_component_project_plain_rust() {
2326 let dir = create_temp_dir();
2327 let cargo_toml = dir.path().join("Cargo.toml");
2328 fs::write(
2329 &cargo_toml,
2330 r#"[package]
2331name = "test"
2332version = "0.1.0"
2333"#,
2334 )
2335 .unwrap();
2336
2337 assert!(!is_cargo_component_project(&cargo_toml).unwrap());
2338 }
2339
2340 #[test]
2341 fn test_is_assemblyscript_project_dependencies() {
2342 let dir = create_temp_dir();
2343 let package_json = dir.path().join("package.json");
2344 fs::write(
2345 &package_json,
2346 r#"{"dependencies": {"assemblyscript": "^0.27"}}"#,
2347 )
2348 .unwrap();
2349
2350 assert!(is_assemblyscript_project(&package_json).unwrap());
2351 }
2352
2353 #[test]
2354 fn test_is_assemblyscript_project_dev_dependencies() {
2355 let dir = create_temp_dir();
2356 let package_json = dir.path().join("package.json");
2357 fs::write(
2358 &package_json,
2359 r#"{"devDependencies": {"assemblyscript": "^0.27"}}"#,
2360 )
2361 .unwrap();
2362
2363 assert!(is_assemblyscript_project(&package_json).unwrap());
2364 }
2365
2366 #[test]
2367 fn test_is_assemblyscript_project_script_with_asc() {
2368 let dir = create_temp_dir();
2369 let package_json = dir.path().join("package.json");
2370 fs::write(
2371 &package_json,
2372 r#"{"scripts": {"build": "asc assembly/index.ts"}}"#,
2373 )
2374 .unwrap();
2375
2376 assert!(is_assemblyscript_project(&package_json).unwrap());
2377 }
2378
2379 #[test]
2380 fn test_is_assemblyscript_project_false() {
2381 let dir = create_temp_dir();
2382 let package_json = dir.path().join("package.json");
2383 fs::write(&package_json, r#"{"dependencies": {"react": "^18.0.0"}}"#).unwrap();
2384
2385 assert!(!is_assemblyscript_project(&package_json).unwrap());
2386 }
2387
2388 #[test]
2389 fn test_is_assemblyscript_project_invalid_json() {
2390 let dir = create_temp_dir();
2391 let package_json = dir.path().join("package.json");
2392 fs::write(&package_json, "not valid json").unwrap();
2393
2394 let result = is_assemblyscript_project(&package_json);
2395 assert!(matches!(
2396 result,
2397 Err(WasmBuildError::ProjectConfigError { .. })
2398 ));
2399 }
2400 }
2401
2402 mod find_wasm_output_tests {
2407 use super::*;
2408
2409 #[test]
2410 fn test_find_rust_release_output() {
2411 let dir = create_temp_dir();
2412 fs::write(
2413 dir.path().join("Cargo.toml"),
2414 "[package]\nname = \"myapp\"\nversion = \"0.1.0\"\n",
2415 )
2416 .unwrap();
2417
2418 let output_dir = dir
2419 .path()
2420 .join("target")
2421 .join("wasm32-wasip2")
2422 .join("release");
2423 fs::create_dir_all(&output_dir).unwrap();
2424 fs::write(output_dir.join("myapp.wasm"), "wasm").unwrap();
2425
2426 let result =
2427 find_wasm_output(dir.path(), WasmLanguage::Rust, WasiTarget::Preview2, true);
2428 assert!(result.is_ok());
2429 assert!(result.unwrap().ends_with("myapp.wasm"));
2430 }
2431
2432 #[test]
2433 fn test_find_rust_debug_output() {
2434 let dir = create_temp_dir();
2435 fs::write(
2436 dir.path().join("Cargo.toml"),
2437 "[package]\nname = \"myapp\"\nversion = \"0.1.0\"\n",
2438 )
2439 .unwrap();
2440
2441 let output_dir = dir
2442 .path()
2443 .join("target")
2444 .join("wasm32-wasip1")
2445 .join("debug");
2446 fs::create_dir_all(&output_dir).unwrap();
2447 fs::write(output_dir.join("myapp.wasm"), "wasm").unwrap();
2448
2449 let result =
2450 find_wasm_output(dir.path(), WasmLanguage::Rust, WasiTarget::Preview1, false);
2451 assert!(result.is_ok());
2452 }
2453
2454 #[test]
2455 fn test_find_rust_underscore_name() {
2456 let dir = create_temp_dir();
2457 fs::write(
2458 dir.path().join("Cargo.toml"),
2459 "[package]\nname = \"my-app\"\nversion = \"0.1.0\"\n",
2460 )
2461 .unwrap();
2462
2463 let output_dir = dir
2464 .path()
2465 .join("target")
2466 .join("wasm32-wasip2")
2467 .join("release");
2468 fs::create_dir_all(&output_dir).unwrap();
2469 fs::write(output_dir.join("my_app.wasm"), "wasm").unwrap();
2471
2472 let result =
2473 find_wasm_output(dir.path(), WasmLanguage::Rust, WasiTarget::Preview2, true);
2474 assert!(result.is_ok());
2475 }
2476
2477 #[test]
2478 fn test_find_go_output() {
2479 let dir = create_temp_dir();
2480 fs::write(dir.path().join("main.wasm"), "wasm").unwrap();
2481
2482 let result =
2483 find_wasm_output(dir.path(), WasmLanguage::Go, WasiTarget::Preview1, false);
2484 assert!(result.is_ok());
2485 assert!(result.unwrap().ends_with("main.wasm"));
2486 }
2487
2488 #[test]
2489 fn test_find_python_output() {
2490 let dir = create_temp_dir();
2491 fs::write(dir.path().join("app.wasm"), "wasm").unwrap();
2492
2493 let result =
2494 find_wasm_output(dir.path(), WasmLanguage::Python, WasiTarget::Preview2, true);
2495 assert!(result.is_ok());
2496 assert!(result.unwrap().ends_with("app.wasm"));
2497 }
2498
2499 #[test]
2500 fn test_find_typescript_output() {
2501 let dir = create_temp_dir();
2502 let dist_dir = dir.path().join("dist");
2503 fs::create_dir(&dist_dir).unwrap();
2504 fs::write(dist_dir.join("component.wasm"), "wasm").unwrap();
2505
2506 let result = find_wasm_output(
2507 dir.path(),
2508 WasmLanguage::TypeScript,
2509 WasiTarget::Preview2,
2510 true,
2511 );
2512 assert!(result.is_ok());
2513 }
2514
2515 #[test]
2516 fn test_find_assemblyscript_release_output() {
2517 let dir = create_temp_dir();
2518 let build_dir = dir.path().join("build");
2519 fs::create_dir(&build_dir).unwrap();
2520 fs::write(build_dir.join("release.wasm"), "wasm").unwrap();
2521
2522 let result = find_wasm_output(
2523 dir.path(),
2524 WasmLanguage::AssemblyScript,
2525 WasiTarget::Preview2,
2526 true,
2527 );
2528 assert!(result.is_ok());
2529 }
2530
2531 #[test]
2532 fn test_find_c_output() {
2533 let dir = create_temp_dir();
2534 fs::write(dir.path().join("main.wasm"), "wasm").unwrap();
2535
2536 let result = find_wasm_output(dir.path(), WasmLanguage::C, WasiTarget::Preview1, true);
2537 assert!(result.is_ok());
2538 }
2539
2540 #[test]
2541 fn test_find_zig_output() {
2542 let dir = create_temp_dir();
2543 let zig_out = dir.path().join("zig-out").join("bin");
2544 fs::create_dir_all(&zig_out).unwrap();
2545 fs::write(zig_out.join("main.wasm"), "wasm").unwrap();
2546
2547 let result =
2548 find_wasm_output(dir.path(), WasmLanguage::Zig, WasiTarget::Preview1, true);
2549 assert!(result.is_ok());
2550 }
2551
2552 #[test]
2553 fn test_find_output_not_found() {
2554 let dir = create_temp_dir();
2555 let result =
2558 find_wasm_output(dir.path(), WasmLanguage::Rust, WasiTarget::Preview2, true);
2559 assert!(matches!(result, Err(WasmBuildError::OutputNotFound { .. })));
2560 }
2561
2562 #[test]
2563 fn test_find_output_fallback_search() {
2564 let dir = create_temp_dir();
2565 fs::write(
2566 dir.path().join("Cargo.toml"),
2567 "[package]\nname = \"test\"\n",
2568 )
2569 .unwrap();
2570
2571 let other_dir = dir.path().join("target").join("other");
2573 fs::create_dir_all(&other_dir).unwrap();
2574 fs::write(other_dir.join("unexpected.wasm"), "wasm").unwrap();
2575
2576 let result =
2578 find_wasm_output(dir.path(), WasmLanguage::Rust, WasiTarget::Preview2, true);
2579 assert!(result.is_ok());
2580 }
2581 }
2582}