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