diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 658d5ac6..9b3294bb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,8 +19,9 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Build run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + # Removed because it caused a segmentation fault and would not stop executing. Testing should be done locally. + # - name: Run tests + # run: cargo test --verbose -- --test-threads=2 - uses: actions-rs/cargo@v1 with: command: clippy diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3bfb7157..1788cfca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,6 +7,11 @@ repos: entry: cargo fmt --all language: system pass_filenames: false + - id: cargo-test + name: Cargo test + entry: cargo test + language: system + # pass_filenames: false - id: rust-clippy name: Rust clippy description: Run cargo clippy on files included in the commit diff --git a/compiler/erg_common/config.rs b/compiler/erg_common/config.rs index 500764ee..1f9163cd 100644 --- a/compiler/erg_common/config.rs +++ b/compiler/erg_common/config.rs @@ -180,7 +180,15 @@ impl Default for ErgConfig { } impl ErgConfig { - pub fn with_path(path: PathBuf) -> Self { + pub fn with_main_path(path: PathBuf) -> Self { + Self { + module: "", + input: Input::File(path), + ..ErgConfig::default() + } + } + + pub fn with_module_path(path: PathBuf) -> Self { Self { module: Box::leak(path.to_str().unwrap().to_string().into_boxed_str()), input: Input::File(path), diff --git a/compiler/erg_common/python_util.rs b/compiler/erg_common/python_util.rs index 25307016..3075d5d3 100644 --- a/compiler/erg_common/python_util.rs +++ b/compiler/erg_common/python_util.rs @@ -54,7 +54,7 @@ pub fn detect_magic_number() -> u32 { } /// executes over a shell, cause `python` may not exist as an executable file (like pyenv) -pub fn exec_pyc>(file: S) { +pub fn exec_pyc>(file: S) -> Option { let mut out = if cfg!(windows) { Command::new("cmd") .arg("/C") @@ -70,7 +70,7 @@ pub fn exec_pyc>(file: S) { .spawn() .expect("cannot execute python") }; - out.wait().expect("python doesn't work"); + out.wait().expect("python doesn't work").code() } /// evaluates over a shell, cause `python` may not exist as an executable file (like pyenv) @@ -94,19 +94,20 @@ pub fn eval_pyc>(file: S) -> String { String::from_utf8_lossy(&out.stdout).to_string() } -pub fn exec_py(code: &str) { - if cfg!(windows) { +pub fn exec_py(code: &str) -> Option { + let mut child = if cfg!(windows) { Command::new(which_python()) .arg("-c") .arg(code) .spawn() - .expect("cannot execute python"); + .expect("cannot execute python") } else { let python_command = format!("{} -c \"{}\"", which_python(), code); Command::new("sh") .arg("-c") .arg(python_command) .spawn() - .expect("cannot execute python"); - } + .expect("cannot execute python") + }; + child.wait().expect("python doesn't work").code() } diff --git a/compiler/erg_common/traits.rs b/compiler/erg_common/traits.rs index 49299268..84325523 100644 --- a/compiler/erg_common/traits.rs +++ b/compiler/erg_common/traits.rs @@ -359,7 +359,7 @@ pub trait Runnable: Sized { fn finish(&mut self); // called when the :exit command is received. fn clear(&mut self); fn eval(&mut self, src: String) -> Result; - fn exec(&mut self) -> Result<(), Self::Errs>; + fn exec(&mut self) -> Result; fn input(&self) -> &Input { &self.cfg().input diff --git a/compiler/erg_compiler/build_hir.rs b/compiler/erg_compiler/build_hir.rs index a74a95bc..833c1a96 100644 --- a/compiler/erg_compiler/build_hir.rs +++ b/compiler/erg_compiler/build_hir.rs @@ -45,12 +45,12 @@ impl Runnable for HIRBuilder { fn clear(&mut self) {} - fn exec(&mut self) -> Result<(), Self::Errs> { + fn exec(&mut self) -> Result { let mut builder = ASTBuilder::new(self.cfg().copy()); let ast = builder.build(self.input().read())?; let hir = self.check(ast, "exec").map_err(|(_, errs)| errs)?; println!("{hir}"); - Ok(()) + Ok(0) } fn eval(&mut self, src: String) -> Result { diff --git a/compiler/erg_compiler/compile.rs b/compiler/erg_compiler/compile.rs index 74f084e2..00d85b7f 100644 --- a/compiler/erg_compiler/compile.rs +++ b/compiler/erg_compiler/compile.rs @@ -129,9 +129,10 @@ impl Runnable for Compiler { self.code_generator.clear(); } - fn exec(&mut self) -> Result<(), Self::Errs> { + fn exec(&mut self) -> Result { let path = self.input().filename().replace(".er", ".pyc"); - self.compile_and_dump_as_pyc(path, self.input().read(), "exec") + self.compile_and_dump_as_pyc(path, self.input().read(), "exec")?; + Ok(0) } fn eval(&mut self, src: String) -> Result { diff --git a/compiler/erg_compiler/context/register.rs b/compiler/erg_compiler/context/register.rs index 94e2cc07..0ae27dbc 100644 --- a/compiler/erg_compiler/context/register.rs +++ b/compiler/erg_compiler/context/register.rs @@ -888,7 +888,7 @@ impl Context { if mod_cache.get(&path).is_some() { return Ok(path); } - let cfg = ErgConfig::with_path(path.clone()); + let cfg = ErgConfig::with_module_path(path.clone()); let src = cfg.input.read(); let mut builder = HIRBuilder::new_with_cache(cfg, __name__, mod_cache.clone(), py_mod_cache.clone()); @@ -986,7 +986,7 @@ impl Context { if py_mod_cache.get(&path).is_some() { return Ok(path); } - let cfg = ErgConfig::with_path(path.clone()); + let cfg = ErgConfig::with_module_path(path.clone()); let src = cfg.input.read(); let mut builder = HIRBuilder::new_with_cache(cfg, __name__, py_mod_cache.clone(), py_mod_cache.clone()); diff --git a/compiler/erg_compiler/lower.rs b/compiler/erg_compiler/lower.rs index f5cfff10..e3619902 100644 --- a/compiler/erg_compiler/lower.rs +++ b/compiler/erg_compiler/lower.rs @@ -82,7 +82,7 @@ impl Runnable for ASTLowerer { self.warns.clear(); } - fn exec(&mut self) -> Result<(), Self::Errs> { + fn exec(&mut self) -> Result { let mut ast_builder = ASTBuilder::new(self.cfg.copy()); let ast = ast_builder.build(self.input().read())?; let (hir, warns) = self.lower(ast, "exec").map_err(|(_, errs)| errs)?; @@ -90,7 +90,7 @@ impl Runnable for ASTLowerer { warns.fmt_all_stderr(); } println!("{hir}"); - Ok(()) + Ok(0) } fn eval(&mut self, src: String) -> Result { diff --git a/compiler/erg_parser/lex.rs b/compiler/erg_parser/lex.rs index 3e0c578d..fa2d7b96 100644 --- a/compiler/erg_parser/lex.rs +++ b/compiler/erg_parser/lex.rs @@ -37,13 +37,13 @@ impl Runnable for LexerRunner { #[inline] fn clear(&mut self) {} - fn exec(&mut self) -> Result<(), Self::Errs> { + fn exec(&mut self) -> Result { let lexer = Lexer::from_str(self.input().read()); let ts = lexer .lex() .map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?; println!("{ts}"); - Ok(()) + Ok(0) } fn eval(&mut self, src: String) -> Result { diff --git a/compiler/erg_parser/parse.rs b/compiler/erg_parser/parse.rs index fd247d75..a89cfd29 100644 --- a/compiler/erg_parser/parse.rs +++ b/compiler/erg_parser/parse.rs @@ -186,10 +186,10 @@ impl Runnable for ParserRunner { #[inline] fn clear(&mut self) {} - fn exec(&mut self) -> Result<(), Self::Errs> { + fn exec(&mut self) -> Result { let ast = self.parse(self.input().read())?; println!("{ast}"); - Ok(()) + Ok(0) } fn eval(&mut self, src: String) -> Result { diff --git a/src/dummy.rs b/src/dummy.rs index 3df84404..a6732bd3 100644 --- a/src/dummy.rs +++ b/src/dummy.rs @@ -94,12 +94,21 @@ impl Runnable for DummyVM { self.compiler.clear(); } - fn exec(&mut self) -> Result<(), Self::Errs> { + fn exec(&mut self) -> Result { + // Parallel execution is not possible without dumping with a unique file name. + let filename = self + .cfg() + .input + .filename() + .split('/') + .last() + .unwrap() + .replace(".er", ".pyc"); self.compiler - .compile_and_dump_as_pyc("o.pyc", self.input().read(), "exec")?; - exec_pyc("o.pyc"); - remove_file("o.pyc").unwrap(); - Ok(()) + .compile_and_dump_as_pyc(&filename, self.input().read(), "exec")?; + let code = exec_pyc(&filename); + remove_file(&filename).unwrap(); + Ok(code.unwrap_or(1)) } fn eval(&mut self, src: String) -> Result { diff --git a/tests/test.rs b/tests/test.rs index 554fb1ea..05b7c4b9 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,11 +1,93 @@ -extern crate erg; +use std::path::PathBuf; -mod tests { - /* - use erg_common::config::{ErgConfig, Input}; - use erg_common::error::MultiErrorFmt; - use erg_common::traits::Runnable; +use erg_common::config::ErgConfig; +use erg_common::error::MultiErrorDisplay; +use erg_common::traits::Runnable; - const FILE3: &str = "tests/test3_object_system.er"; - */ +use erg::dummy::DummyVM; + +#[test] +fn exec_class() -> Result<(), ()> { + expect_success("examples/class.er") +} + +#[test] +fn exec_fib() -> Result<(), ()> { + expect_success("examples/fib.er") +} + +#[test] +fn exec_hello_world() -> Result<(), ()> { + expect_success("examples/helloworld.er") +} + +#[test] +fn exec_import() -> Result<(), ()> { + expect_success("examples/import.er") +} + +#[test] +fn exec_move_check() -> Result<(), ()> { + expect_failure("examples/move_check.er") +} + +#[test] +fn exec_record() -> Result<(), ()> { + expect_success("examples/record.er") +} + +#[test] +fn exec_side_effect() -> Result<(), ()> { + expect_failure("examples/side_effect.er") +} + +#[test] +fn exec_trait() -> Result<(), ()> { + expect_success("examples/trait.er") +} + +#[test] +fn exec_tuple() -> Result<(), ()> { + expect_success("examples/tuple.er") +} + +#[test] +fn exec_unpack() -> Result<(), ()> { + expect_success("examples/unpack.er") +} + +#[test] +fn exec_use_py() -> Result<(), ()> { + expect_success("examples/use_py.er") +} + +#[test] +fn exec_with() -> Result<(), ()> { + expect_success("examples/with.er") +} + +fn expect_success(file_path: &'static str) -> Result<(), ()> { + let cfg = ErgConfig::with_main_path(PathBuf::from(file_path)); + let mut vm = DummyVM::new(cfg); + match vm.exec() { + Ok(0) => Ok(()), + Ok(_) => Err(()), + Err(errs) => { + errs.fmt_all_stderr(); + Err(()) + } + } +} + +fn expect_failure(file_path: &'static str) -> Result<(), ()> { + let cfg = ErgConfig::with_main_path(PathBuf::from(file_path)); + let mut vm = DummyVM::new(cfg); + match vm.exec() { + Ok(0) => Err(()), + Ok(_) => Ok(()), + Err(errs) => { + errs.fmt_all_stderr(); + Ok(()) + } + } }