Repository: mun-lang/mun (language: rust)

Issue:
Implement `mun new` and `mun init`
Now that [projects have landed](#223) it would be good if we can create a barebones project by invoking `mun new` or `mun init` similar to [`cargo new`](https://doc.rust-lang.org/cargo/commands/cargo-new.html) and [`cargo init`](https://doc.rust-lang.org/cargo/commands/cargo-init.html).

**Good first issue process:**

- [x] [read our contributing guidelines](https://github.com/mun-lang/mun/blob/master/CONTRIBUTING.md)
- [x] claim this issue (assign yourself or comment below)
- [x] implement the feature and start a Pull Request with in the description: closes #237. 

If this is your first PR, welcome 🎉 😄

Related to #223 


Discussion / hints:
Hi, I would like to try doing this. It would be my first PR on Mun.
Welcome! I've assigned the issue to you 🙂 

If you need any help, feel free to reach out to us here or on our Discord!

Fix patch:
diff --git a/crates/mun/Cargo.toml b/crates/mun/Cargo.toml
index c91524d03..5ba645f84 100644
--- a/crates/mun/Cargo.toml
+++ b/crates/mun/Cargo.toml
@@ -31,4 +31,5 @@ default-features = false # Disable features which are enabled by default
 features = ["precommit-hook", "run-cargo-test", "run-cargo-fmt", "run-cargo-clippy", "run-for-all"]
 
 [dev-dependencies]
-tempdir = "0.3"
+tempfile = "3.1"
+serial_test = "0.4"
diff --git a/crates/mun/src/lib.rs b/crates/mun/src/lib.rs
index d7a8feabe..a07a858b4 100644
--- a/crates/mun/src/lib.rs
+++ b/crates/mun/src/lib.rs
@@ -1,15 +1,12 @@
-use std::cell::RefCell;
+mod ops;
+
 use std::env;
-use std::rc::Rc;
-use std::time::Duration;
+use std::ffi::OsString;
 
-use anyhow::anyhow;
-use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
-use mun_compiler::{Config, DisplayColor, Target};
+use clap::{App, AppSettings, Arg, SubCommand};
 use mun_project::MANIFEST_FILENAME;
-use mun_runtime::{invoke_fn, ReturnTypeReflection, Runtime, RuntimeBuilder};
-use std::ffi::OsString;
-use std::path::{Path, PathBuf};
+
+use ops::{build, init, language_server, new, start};
 
 #[derive(Copy, Debug, Clone, PartialEq, Eq)]
 pub enum ExitStatus {
@@ -94,6 +91,13 @@ where
                         .help("how much to delay received filesystem events (in ms). This allows bundling of identical events, e.g. when several writes to the same file are detected. A high delay will make hot reloading less responsive. (defaults to 10 ms)"),
                 ),
         )
+        .subcommand(
+            SubCommand::with_name("new")
+                .arg(Arg::with_name("path").help("the path to create a new project").required(true).index(1))
+        )
+        .subcommand(
+            SubCommand::with_name("init")
+        )
         .subcommand(
             SubCommand::with_name("language-server")
         )
@@ -104,6 +108,8 @@ where
             ("build", Some(matches)) => build(matches),
             ("language-server", Some(matches)) => language_server(matches),
             ("start", Some(matches)) => start(matches).map(|_| ExitStatus::Success),
+            ("new", Some(matches)) => new(matches),
+            ("init", Some(_)) => init(),
             _ => unreachable!(),
         },
         Err(e) => {
@@ -112,169 +118,3 @@ where
         }
     }
 }
-
-/// Find a Mun manifest file in the specified directory or one of its parents.
-fn find_manifest(directory: &Path) -> Option<PathBuf> {
-    let mut current_dir = Some(directory);
-    while let Some(dir) = current_dir {
-        let manifest_path = dir.join(MANIFEST_FILENAME);
-        if manifest_path.exists() {
-            return Some(manifest_path);
-        }
-        current_dir = dir.parent();
-    }
-    None
-}
-
-/// This method is invoked when the executable is run with the `build` argument indicating that a
-/// user requested us to build a project in the current directory or one of its parent directories.
-fn build(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
-    log::trace!("starting build");
-
-    let options = compiler_options(matches)?;
-
-    // Locate the manifest
-    let manifest_path = match matches.value_of("manifest-path") {
-        None => {
-            let current_dir =
-                std::env::current_dir().expect("could not determine currrent working directory");
-            find_manifest(&current_dir).ok_or_else(|| {
-                anyhow::anyhow!(
-                    "could not find {} in '{}' or a parent directory",
-                    MANIFEST_FILENAME,
-                    current_dir.display()
-                )
-            })?
-        }
-        Some(path) => std::fs::canonicalize(Path::new(path))
-            .map_err(|_| anyhow::anyhow!("'{}' does not refer to a valid manifest path", path))?,
-    };
-
-    log::info!("located build manifest at: {}", manifest_path.display());
-
-    if matches.is_present("watch") {
-        mun_compiler_daemon::compile_and_watch_manifest(&manifest_path, options)
-    } else {
-        mun_compiler::compile_manifest(&manifest_path, options)
-    }
-    .map(Into::into)
-}
-
-/// Starts the runtime with the specified library and invokes function `entry`.
-fn start(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
-    let runtime = runtime(matches)?;
-
-    let borrowed = runtime.borrow();
-    let entry_point = matches.value_of("entry").unwrap_or("main");
-    let fn_definition = borrowed
-        .get_function_definition(entry_point)
-        .ok_or_else(|| {
-            std::io::Error::new(
-                std::io::ErrorKind::InvalidInput,
-                format!("Failed to obtain entry point '{}'", entry_point),
-            )
-        })?;
-
-    if let Some(ret_type) = fn_definition.prototype.signature.return_type() {
-        let type_guid = &ret_type.guid;
-        if *type_guid == bool::type_guid() {
-            let result: bool = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
-
-            println!("{}", result)
-        } else if *type_guid == f64::type_guid() {
-            let result: f64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
-
-            println!("{}", result)
-        } else if *type_guid == i64::type_guid() {
-            let result: i64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
-
-            println!("{}", result)
-        } else {
-            return Err(anyhow!(
-                "Only native Mun return types are supported for entry points. Found: {}",
-                ret_type.name()
-            ));
-        };
-        Ok(ExitStatus::Success)
-    } else {
-        #[allow(clippy::unit_arg)]
-        invoke_fn!(runtime, entry_point)
-            .map(|_: ()| ExitStatus::Success)
-            .map_err(|e| anyhow!("{}", e))
-    }
-}
-
-fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::Config, anyhow::Error> {
-    let optimization_lvl = match matches.value_of("opt-level") {
-        Some("0") => mun_compiler::OptimizationLevel::None,
-        Some("1") => mun_compiler::OptimizationLevel::Less,
-        None | Some("2") => mun_compiler::OptimizationLevel::Default,
-        Some("3") => mun_compiler::OptimizationLevel::Aggressive,
-        _ => return Err(anyhow!("Only optimization levels 0-3 are supported")),
-    };
-
-    let display_color = matches
-        .value_of("color")
-        .map(ToOwned::to_owned)
-        .or_else(|| env::var("MUN_TERMINAL_COLOR").ok())
-        .map(|value| match value.as_str() {
-            "disable" => DisplayColor::Disable,
-            "enable" => DisplayColor::Enable,
-            _ => DisplayColor::Auto,
-        })
-        .unwrap_or(DisplayColor::Auto);
-
-    Ok(Config {
-        target: matches
-            .value_of("target")
-            .map_or_else(Target::host_target, Target::search)?,
-        optimization_lvl,
-        out_dir: None,
-        display_color,
-    })
-}
-
-fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, anyhow::Error> {
-    let builder = RuntimeBuilder::new(
-        matches.value_of("LIBRARY").unwrap(), // Safe because its a required arg
-    );
-
-    let builder = if let Some(delay) = matches.value_of("delay") {
-        let delay: u64 = delay.parse()?;
-        builder.set_delay(Duration::from_millis(delay))
-    } else {
-        builder
-    };
-
-    builder.spawn()
-}
-
-/// This function is invoked when the executable is invoked with the `language-server` argument. A
-/// Mun language server is started ready to serve language information about one or more projects.
-fn language_server(_matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
-    mun_language_server::run_server().map_err(|e| anyhow::anyhow!("{}", e))?;
-    Ok(ExitStatus::Success)
-}
-
-#[cfg(test)]
-mod test {
-    use crate::find_manifest;
-    use mun_project::MANIFEST_FILENAME;
-    use tempdir::TempDir;
-
-    #[test]
-    fn test_find_manifest() {
-        let dir = TempDir::new("test_find_manifest").unwrap();
-        let path = dir.path();
-        let manifest_path = path.join(MANIFEST_FILENAME);
-
-        assert_eq!(find_manifest(path), None);
-
-        std::fs::write(&manifest_path, "").unwrap();
-        assert_eq!(find_manifest(path).as_ref(), Some(&manifest_path));
-
-        let subdir_path = path.join("some/random/subdir");
-        std::fs::create_dir_all(&subdir_path).unwrap();
-        assert_eq!(find_manifest(&subdir_path).as_ref(), Some(&manifest_path));
-    }
-}
diff --git a/crates/mun/src/ops.rs b/crates/mun/src/ops.rs
new file mode 100644
index 000000000..cf77cc2cf
--- /dev/null
+++ b/crates/mun/src/ops.rs
@@ -0,0 +1,11 @@
+mod build;
+pub mod init;
+mod language_server;
+mod new;
+mod start;
+
+pub use build::build;
+pub use init::init;
+pub use language_server::language_server;
+pub use new::new;
+pub use start::start;
diff --git a/crates/mun/src/ops/build.rs b/crates/mun/src/ops/build.rs
new file mode 100644
index 000000000..7cb1bffd7
--- /dev/null
+++ b/crates/mun/src/ops/build.rs
@@ -0,0 +1,111 @@
+use std::env;
+use std::path::{Path, PathBuf};
+
+use anyhow::anyhow;
+use clap::ArgMatches;
+use mun_compiler::{Config, DisplayColor, Target};
+use mun_project::MANIFEST_FILENAME;
+
+use crate::ExitStatus;
+
+/// This method is invoked when the executable is run with the `build` argument indicating that a
+/// user requested us to build a project in the current directory or one of its parent directories.
+pub fn build(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
+    log::trace!("starting build");
+
+    let options = compiler_options(matches)?;
+
+    // Locate the manifest
+    let manifest_path = match matches.value_of("manifest-path") {
+        None => {
+            let current_dir =
+                std::env::current_dir().expect("could not determine currrent working directory");
+            find_manifest(&current_dir).ok_or_else(|| {
+                anyhow::anyhow!(
+                    "could not find {} in '{}' or a parent directory",
+                    MANIFEST_FILENAME,
+                    current_dir.display()
+                )
+            })?
+        }
+        Some(path) => std::fs::canonicalize(Path::new(path))
+            .map_err(|_| anyhow::anyhow!("'{}' does not refer to a valid manifest path", path))?,
+    };
+
+    log::info!("located build manifest at: {}", manifest_path.display());
+
+    if matches.is_present("watch") {
+        mun_compiler_daemon::compile_and_watch_manifest(&manifest_path, options)
+    } else {
+        mun_compiler::compile_manifest(&manifest_path, options)
+    }
+    .map(Into::into)
+}
+
+/// Find a Mun manifest file in the specified directory or one of its parents.
+fn find_manifest(directory: &Path) -> Option<PathBuf> {
+    let mut current_dir = Some(directory);
+    while let Some(dir) = current_dir {
+        let manifest_path = dir.join(MANIFEST_FILENAME);
+        if manifest_path.exists() {
+            return Some(manifest_path);
+        }
+        current_dir = dir.parent();
+    }
+    None
+}
+
+fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::Config, anyhow::Error> {
+    let optimization_lvl = match matches.value_of("opt-level") {
+        Some("0") => mun_compiler::OptimizationLevel::None,
+        Some("1") => mun_compiler::OptimizationLevel::Less,
+        None | Some("2") => mun_compiler::OptimizationLevel::Default,
+        Some("3") => mun_compiler::OptimizationLevel::Aggressive,
+        _ => return Err(anyhow!("Only optimization levels 0-3 are supported")),
+    };
+
+    let display_color = matches
+        .value_of("color")
+        .map(ToOwned::to_owned)
+        .or_else(|| env::var("MUN_TERMINAL_COLOR").ok())
+        .map(|value| match value.as_str() {
+            "disable" => DisplayColor::Disable,
+            "enable" => DisplayColor::Enable,
+            _ => DisplayColor::Auto,
+        })
+        .unwrap_or(DisplayColor::Auto);
+
+    Ok(Config {
+        target: matches
+            .value_of("target")
+            .map_or_else(Target::host_target, Target::search)?,
+        optimization_lvl,
+        out_dir: None,
+        display_color,
+    })
+}
+
+#[cfg(test)]
+mod test {
+    use super::find_manifest;
+    use mun_project::MANIFEST_FILENAME;
+
+    #[test]
+    fn test_find_manifest() {
+        let dir = tempfile::Builder::new()
+            .prefix("test_find_manifest")
+            .tempdir()
+            .unwrap();
+        let path = dir.path();
+        let manifest_path = path.join(MANIFEST_FILENAME);
+
+        assert_eq!(find_manifest(path), None);
+
+        std::fs::write(&manifest_path, "").unwrap();
+        assert_eq!(find_manifest(path).as_ref(), Some(&manifest_path));
+
+        let subdir_path = path.join("some/random/subdir");
+        std::fs::create_dir_all(&subdir_path).unwrap();
+        assert_eq!(find_manifest(&subdir_path).as_ref(), Some(&manifest_path));
+    }
+}
diff --git a/crates/mun/src/ops/init.rs b/crates/mun/src/ops/init.rs
new file mode 100644
index 000000000..8067912db
--- /dev/null
+++ b/crates/mun/src/ops/init.rs
@@ -0,0 +1,68 @@
+use std::fs;
+use std::path::Path;
+
+use anyhow::anyhow;
+
+use crate::ExitStatus;
+
+/// This method is invoked when the executable is run with the `init` argument indicating that a
+/// user requested us to create a new project in the current directory.
+pub fn init() -> Result<ExitStatus, anyhow::Error> {
+    let create_in = std::env::current_dir().expect("could not determine current working directory");
+    let project_name = create_in
+        .file_name()
+        .expect("Failed to fetch name of current folder.")
+        .to_str()
+        .expect("Project name must be valid UTF-8");
+
+    create_project(&create_in, project_name)
+}
+
+/// This is used by `init` and `new` arguments to create projects in different paths.
+pub fn create_project(create_in: &Path, project_name: &str) -> Result<ExitStatus, anyhow::Error> {
+    log::trace!("Creating new project");
+    {
+        let manifest_path = create_in.join("mun.toml");
+
+        write(
+            &manifest_path,
+            format!(
+                // @TODO. Nothing is done yet to find out who the author is.
+                r#"[package]
+name="{}"
+authors=[]
+version="0.1.0"
+"#,
+                project_name,
+            ),
+        )?;
+    }
+    {
+        let src_path = create_in.join("src");
+        create_dir(&src_path)?;
+
+        let main_file_path = src_path.join("main.mun");
+
+        write(
+            &main_file_path,
+            r#"pub fn main() -> f64 {
+    3.14159
+}
+"#,
+        )?;
+    }
+    println!("Created `{}` package", project_name);
+    Ok(ExitStatus::Success)
+}
+
+/// Shortcut function for creating new directories.
+pub fn create_dir(path: impl AsRef<Path>) -> anyhow::Result<()> {
+    fs::create_dir(&path)
+        .map_err(|_| anyhow!("failed to create directory `{}`", path.as_ref().display()))
+}
+
+/// Shortcut function for creating new files.
+pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> anyhow::Result<()> {
+    let path = path.as_ref();
+    fs::write(path, contents.as_ref()).map_err(|_| anyhow!("failed to write `{}`", path.display()))
+}
diff --git a/crates/mun/src/ops/language_server.rs b/crates/mun/src/ops/language_server.rs
new file mode 100644
index 000000000..6e91468ad
--- /dev/null
+++ b/crates/mun/src/ops/language_server.rs
@@ -0,0 +1,10 @@
+use clap::ArgMatches;
+
+use crate::ExitStatus;
+
+/// This function is invoked when the executable is invoked with the `language-server` argument. A
+/// Mun language server is started ready to serve language information about one or more projects.
+pub fn language_server(_matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
+    mun_language_server::run_server().map_err(|e| anyhow::anyhow!("{}", e))?;
+    Ok(ExitStatus::Success)
+}
diff --git a/crates/mun/src/ops/new.rs b/crates/mun/src/ops/new.rs
new file mode 100644
index 000000000..655f5986f
--- /dev/null
+++ b/crates/mun/src/ops/new.rs
@@ -0,0 +1,27 @@
+use clap::ArgMatches;
+
+use crate::ops::init::{create_dir, create_project};
+use crate::ExitStatus;
+
+/// This method is invoked when the executable is run with the `new` argument indicating that a
+/// user requested us to create a new project in a new directory.
+pub fn new(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
+    let project_name = matches.value_of("path").expect(
+        "Path argument not found: This should be unreachable as clap requires this argument.",
+    );
+
+    let create_in = std::env::current_dir()
+        .expect("could not determine current working directory")
+        .join(project_name);
+
+    if create_in.exists() {
+        eprint!(
+            "destination `{}` already exists\n\n\
+             Use `mun init` to initialize the directory",
+            create_in.display()
+        );
+        return Ok(ExitStatus::Error);
+    }
+    create_dir(&create_in)?;
+    create_project(&create_in, project_name)
+}
diff --git a/crates/mun/src/ops/start.rs b/crates/mun/src/ops/start.rs
new file mode 100644
index 000000000..aab21b4b3
--- /dev/null
+++ b/crates/mun/src/ops/start.rs
@@ -0,0 +1,68 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::time::Duration;
+
+use anyhow::anyhow;
+use clap::ArgMatches;
+use mun_runtime::{invoke_fn, ReturnTypeReflection, Runtime, RuntimeBuilder};
+
+use crate::ExitStatus;
+
+/// Starts the runtime with the specified library and invokes function `entry`.
+pub fn start(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
+    let runtime = runtime(matches)?;
+
+    let borrowed = runtime.borrow();
+    let entry_point = matches.value_of("entry").unwrap_or("main");
+    let fn_definition = borrowed
+        .get_function_definition(entry_point)
+        .ok_or_else(|| {
+            std::io::Error::new(
+                std::io::ErrorKind::InvalidInput,
+                format!("Failed to obtain entry point '{}'", entry_point),
+            )
+        })?;
+
+    if let Some(ret_type) = fn_definition.prototype.signature.return_type() {
+        let type_guid = &ret_type.guid;
+        if *type_guid == bool::type_guid() {
+            let result: bool = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
+
+            println!("{}", result)
+        } else if *type_guid == f64::type_guid() {
+            let result: f64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
+
+            println!("{}", result)
+        } else if *type_guid == i64::type_guid() {
+            let result: i64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
+
+            println!("{}", result)
+        } else {
+            return Err(anyhow!(
+                "Only native Mun return types are supported for entry points. Found: {}",
+                ret_type.name()
+            ));
+        };
+        Ok(ExitStatus::Success)
+    } else {
+        #[allow(clippy::unit_arg)]
+        invoke_fn!(runtime, entry_point)
+            .map(|_: ()| ExitStatus::Success)
+            .map_err(|e| anyhow!("{}", e))
+    }
+}
+
+fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, anyhow::Error> {
+    let builder = RuntimeBuilder::new(
+        matches.value_of("LIBRARY").unwrap(), // Safe because its a required arg
+    );
+
+    let builder = if let Some(delay) = matches.value_of("delay") {
+        let delay: u64 = delay.parse()?;
+        builder.set_delay(Duration::from_millis(delay))
+    } else {
+        builder
+    };
+
+    builder.spawn()
+}


Test patch:
diff --git a/crates/mun/tests/integration.rs b/crates/mun/tests/integration.rs
index dea9418eb..cd44e783a 100644
--- a/crates/mun/tests/integration.rs
+++ b/crates/mun/tests/integration.rs
@@ -1,63 +1,57 @@
 use mun::run_with_args;
 use mun_runtime::{invoke_fn, RuntimeBuilder};
+use serial_test::serial;
+use std::env::set_current_dir;
 use std::ffi::OsString;
-use tempdir::TempDir;
-
-const TEST_VAL: i32 = 567;
-
-/// Creates a simple test project in a temporary directory and returns the directory.
-fn create_project() -> tempdir::TempDir {
-    let project_dir = TempDir::new("mun_project_example").unwrap();
-    let project_path = project_dir.path();
-
-    std::fs::write(
-        project_path.join("mun.toml"),
-        r#"
-[package]
-name="test"
-authors=["Mun Team"]
-version="0.1.0"
-    "#,
-    )
-    .unwrap();
-
-    std::fs::create_dir_all(project_path.join("src")).unwrap();
-
-    std::fs::write(
-        project_path.join("src/main.mun"),
-        format!(
-            r#"
-pub fn main() -> i32 {{
-    {}
-}}"#,
-            TEST_VAL
-        ),
-    )
-    .unwrap();
-
-    project_dir
+use std::path::Path;
+
+/// Creates a new project using `mun init` and then tests that it works.
+#[test]
+#[serial] // This test must be run in serial as files may conflict.
+fn mun_init() {
+    let project = tempfile::Builder::new()
+        .prefix("mun_project_example")
+        .tempdir()
+        .unwrap();
+
+    set_current_dir(&project).unwrap();
+
+    let args: Vec<OsString> = vec!["mun".into(), "init".into()];
+    assert_eq!(run_with_args(args).unwrap(), mun::ExitStatus::Success);
+    build_and_run(project);
 }
 
+/// Creates a new project using `mun new` and then tests that it works.
 #[test]
-fn build_and_run() {
-    pretty_env_logger::env_logger::Builder::from_default_env()
-        .is_test(true)
-        .init();
+#[serial] // This test must be run in serial as files may conflict.
+fn mun_new() {
+    let project = tempfile::Builder::new()
+        .prefix("mun_projects")
+        .tempdir()
+        .unwrap();
+
+    set_current_dir(&project).unwrap();
 
-    let project = create_project();
+    let args: Vec<OsString> = vec!["mun".into(), "new".into(), "mun_project_example".into()];
+    assert_eq!(run_with_args(args).unwrap(), mun::ExitStatus::Success);
+    dbg!(project.as_ref().join("mun_project_example").ancestors());
+    build_and_run(project.as_ref().join("mun_project_example"));
+}
 
+/// Builds and runs an newly generated mun project
+fn build_and_run(project: impl AsRef<Path>) {
     let args: Vec<OsString> = vec![
         "mun".into(),
         "build".into(),
         "--manifest-path".into(),
-        project.path().join("mun.toml").into(),
+        project.as_ref().join("mun.toml").into(),
     ];
     assert_eq!(run_with_args(args).unwrap(), mun::ExitStatus::Success);
 
-    let library_path = project.path().join("target/main.munlib");
+    let library_path = project.as_ref().join("target/main.munlib");
     assert!(library_path.is_file());
 
     let runtime = RuntimeBuilder::new(&library_path).spawn().unwrap();
-    let result: i32 = invoke_fn!(runtime, "main").unwrap();
-    assert_eq!(result, TEST_VAL);
+    let result: f64 = invoke_fn!(runtime, "main").unwrap();
+    assert_eq!(result, 3.14159);
 }