diff --git a/Cargo.lock b/Cargo.lock index 3ff65e934e5d0dd4c1031e8fe2fc8addc5323a50..e83aee2f3d8a5a102aa6b7979e611a2bd0091013 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,15 +445,6 @@ dependencies = [ "tarantool-test", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "flate2" version = "1.0.26" @@ -669,15 +660,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -734,16 +716,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd4ad156b9934dc21cad96fd17278a7cb6f30a5657a9d976cd7b71d6d49c02c" dependencies = [ - "linkme-impl 0.2.10", -] - -[[package]] -name = "linkme" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97f3302efc6ebb7b5f0810a1096dbbb44a536711a4576bc89264a8f9a1d634d8" -dependencies = [ - "linkme-impl 0.3.10", + "linkme-impl", ] [[package]] @@ -757,17 +730,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "linkme-impl" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279a77bf40c85a08513aca203635b96610ebf0e37a92cb0cee76e04da100a426" -dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.28", - "syn 2.0.22", -] - [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -1451,7 +1413,7 @@ dependencies = [ "dlopen", "futures", "libc", - "linkme 0.2.10", + "linkme", "log", "nix", "num-derive", @@ -1490,29 +1452,14 @@ dependencies = [ name = "tarantool-test" version = "0.1.0" dependencies = [ + "anyhow", "clap", - "linkme 0.3.10", "serde", "serde_json", "tarantool", - "tempfile", "tester", ] -[[package]] -name = "tempfile" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "fastrand", - "redox_syscall 0.3.5", - "rustix 0.37.20", - "windows-sys 0.48.0", -] - [[package]] name = "term" version = "0.6.1" @@ -1534,6 +1481,16 @@ dependencies = [ "term", ] +[[package]] +name = "tests" +version = "0.1.0" +dependencies = [ + "anyhow", + "example", + "serde", + "serde_json", +] + [[package]] name = "thiserror" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index 699ba2a2f5425d0951d772c58c01384d9dc8d3aa..3aae7a5ec3f8bea65ae0c076594ba9e9a468d8e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ members = [ "tarantool-test", - "example" + "example", + "tests" ] diff --git a/Makefile b/Makefile index 32f674b4570bcd448188f0e448ec85930fe006d2..d85886648a3e1876bc3e80e77b05b696ad867095 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,14 @@ +ifeq ($(OS), Linux) + LIBS_EXT = so +else + ifeq ($(OS), Darwin) + LIBS_EXT = dylib + endif +endif + + build-example: cargo build -p example run-example: build-example - cargo run --features="bin" -p tarantool-test -- -p libexample + tarantool-runner -p ./target/debug/libexample.$(LIBS_EXT) -e cli_test_suite_entrypoint -- --filter \ No newline at end of file diff --git a/example/Cargo.toml b/example/Cargo.toml index 4192ec65c11f2374a7cf4c5cec5693eb20d2128a..1a4b09a628bc967825edbd6ababd5aca6e6154d4 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] tarantool = { version = "1.1.0", features = ["test"] } -tarantool-test = { path = "../tarantool-test" } +tarantool-test = { path = "../tarantool-test", features = ["cli"] } anyhow = "1" shors = "0.4" diff --git a/example/src/lib.rs b/example/src/lib.rs index e1cc7c87f8b93ea80dc248e3f6fe8479c37c148a..192ce97dfa1e9a7f221a0cdf0c76b202a2a54098 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -3,7 +3,9 @@ use std::ffi::c_int; use anyhow::Result; use shors::shors_info; use tarantool::tlua::ffi::lua_State; -use tarantool_test::{default_tests_execute, SuiteConfig, TestsSetupConfig}; +use tarantool_test::{ + cli::parse_tests_config_from_cli, execute_tests_with_suite, SuiteConfig, TestsConfig, +}; #[tarantool::test] fn test_example() { @@ -15,29 +17,31 @@ fn test_panic_example() { assert_eq!(1, 0) } +fn before_all() { + shors_info!("before_all executed") +} +fn after_all() { + shors_info!("after_all executed") +} +fn before_each() { + shors_info!("before_each executed") +} +fn after_each() { + shors_info!("after_each executed") +} + #[tarantool::proc] -pub fn tarantool_test_entrypoint(config: TestsSetupConfig) -> Result<()> { - fn before_all() { - shors_info!("before_all executed") - } - fn after_all() { - shors_info!("after_all executed") - } - fn before_each() { - shors_info!("before_each executed") - } - fn after_each() { - shors_info!("after_each executed") - } - - default_tests_execute(SuiteConfig { - setup: config, +pub fn cli_test_suite_entrypoint(input: String) -> Result<()> { + let tests_config = parse_tests_config_from_cli(input)?; + + execute_tests_with_suite(SuiteConfig { + tests_cfg: tests_config, before_all, after_all, before_each, after_each, }) - .expect("default test suite failed"); + .expect("cli-based test suite failed"); Ok(()) } diff --git a/tarantool-test/Cargo.toml b/tarantool-test/Cargo.toml index e06a4de4702cd5840c0ce06b06ce585ca7d09977..cdae9127f046c1a44f72d62a65a00faee85863d8 100644 --- a/tarantool-test/Cargo.toml +++ b/tarantool-test/Cargo.toml @@ -4,19 +4,15 @@ version = "0.1.0" edition = "2021" [features] -bin = ["dep:clap", "dep:serde_json", "dep:tempfile"] +# Introduces CLI interface for suite. +cli = ["dep:clap", "dep:serde_json"] [dependencies] -linkme = "0.3" tarantool = {version = "1.1.0", features = ["test"] } serde = "1.0" tester = "0.7" +anyhow = "1" # Binary deps. clap = {version = "4", features = ["derive"], optional = true} serde_json = {version = "1", optional = true} -tempfile = {version = "3", optional = true } - -[[bin]] -name = "tarantool-test" -required-features = ["bin"] \ No newline at end of file diff --git a/tarantool-test/src/cli.rs b/tarantool-test/src/cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d904185f1688b39a1fab562b7fa2a9f4235704f --- /dev/null +++ b/tarantool-test/src/cli.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use std::{ + fs::{create_dir, File}, + io::Write, + process::Command, +}; + +use clap::Parser; + +use crate::{SuiteConfig, TestsConfig}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + #[arg( + short, + long, + help = "Filter out needed tests by passing their fullname or even part of the name." + )] + filter: Option<String>, +} + +impl From<Cli> for TestsConfig { + fn from(cli: Cli) -> Self { + Self { filter: cli.filter } + } +} + +pub fn parse_tests_config_from_cli(cli_input: String) -> Result<TestsConfig> { + let cli = Cli::try_parse_from(std::iter::once(cli_input))?; + Ok(cli.into()) +} diff --git a/tarantool-test/src/default_init.lua b/tarantool-test/src/default_init.lua deleted file mode 100644 index 1c18a50a5ace750980f92f61e026a080460156a7..0000000000000000000000000000000000000000 --- a/tarantool-test/src/default_init.lua +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env tarantool - -local fio = require('fio') - -local test_dir = os.environ()["TARANTOOL_TEST_DIR"] -local tmpdir = test_dir .. "/tmp" -local test_package = os.environ()["TARANTOOL_TEST_PACKAGE"] -local package_entrypoint = test_package .. ".tarantool_test_entrypoint" -local cfg = require("json").decode(os.environ()["TARANTOOL_TEST_CONFIG"]) - -box.cfg { - listen = 3301, - wal_mode = 'none', - memtx_dir = tmpdir, - election_mode = 'manual', -} - -box.ctl.promote() - -fio.rmtree(tmpdir) - --- Add executable location to lua search path -package.cpath = 'target/debug/?.so;target/debug/?.dylib;' .. package.cpath -require(test_package) - --- Run tests -box.schema.func.create(package_entrypoint, { language = 'C', if_not_exists = true }) - -local function exec_tests() - box.func[package_entrypoint]:call({cfg}) -end - -if not pcall(exec_tests) then - require("log").error('failed to execute tests, exiting with error code 1') - os.exit(1) -end -os.exit(0) diff --git a/tarantool-test/src/lib.rs b/tarantool-test/src/lib.rs index bbda53947f6216598f585007758b4eb554d6a6dd..723283856942260ada5d881cfd237f38c7607c00 100644 --- a/tarantool-test/src/lib.rs +++ b/tarantool-test/src/lib.rs @@ -4,17 +4,18 @@ use serde::{Deserialize, Serialize}; use tarantool::test::test_cases; use tester::{TestDescAndFn, TestFn, TestOpts}; -pub use linkme; +#[cfg(feature = "cli")] +pub mod cli; #[derive(Clone, Deserialize, Serialize, Debug, Default)] -pub struct TestsSetupConfig { +pub struct TestsConfig { #[serde(default)] pub filter: Option<String>, } #[derive(Clone, Debug)] pub struct SuiteConfig { - pub setup: TestsSetupConfig, + pub tests_cfg: TestsConfig, pub before_all: fn(), pub after_all: fn(), pub before_each: fn(), @@ -25,7 +26,7 @@ impl Default for SuiteConfig { fn default() -> Self { fn noop() {} Self { - setup: Default::default(), + tests_cfg: Default::default(), before_all: noop, after_all: noop, before_each: noop, @@ -45,7 +46,7 @@ pub fn collect_tests(suite: &SuiteConfig) -> Vec<TestDescAndFn> { .iter() .filter(|case| { suite - .setup + .tests_cfg .filter .as_ref() .map(|must_contains| case.name().contains(must_contains)) @@ -67,9 +68,9 @@ pub fn collect_tests(suite: &SuiteConfig) -> Vec<TestDescAndFn> { /// Executes tests with given suite in an ordinary manner. /// /// Most of the time, if you don't want to modify the test system behavior, you are happy with this method. -pub fn default_tests_execute(mut suite: SuiteConfig) -> Result<(), Box<dyn Error>> { - if suite.setup.filter == Some("".to_string()) { - suite.setup.filter = None +pub fn execute_tests_with_suite(mut suite: SuiteConfig) -> Result<(), Box<dyn Error>> { + if suite.tests_cfg.filter == Some("".to_string()) { + suite.tests_cfg.filter = None } let opts = &TestOpts { diff --git a/tarantool-test/src/main.rs b/tarantool-test/src/main.rs deleted file mode 100644 index ee7e8d7d0c5e4235f5ce4e2813a269375e5ed1be..0000000000000000000000000000000000000000 --- a/tarantool-test/src/main.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::{ - fs::{create_dir, File}, - io::Write, - process::Command, -}; - -use clap::Parser; -use tarantool_test::TestsSetupConfig; -use tempfile::tempdir; - -const DEFAULT_LUA_INIT: &str = include_str!("default_init.lua"); - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - #[arg( - short, - long, - help = r#"Package where tests would be executed. Package file must follow rules stated in the docs."# - )] - package: String, - - #[arg( - short, - long, - help = "Filter out needed tests by passing their fullname or even part of the name." - )] - filter: Option<String>, -} - -fn main() { - let cli = Cli::parse(); - - let setup_config = TestsSetupConfig { filter: cli.filter }; - let serialized_config = - serde_json::to_string(&setup_config).expect("failed to serialize test config"); - - let tmp_dir = tempdir().expect("failed to create tempdir"); - let tarantool_init_file = tmp_dir.path().join("init.lua"); - let tarantool_tmpdir = tmp_dir.path().join("tmp"); - - create_dir(tarantool_tmpdir.as_path()).unwrap(); - let mut file = File::create(&tarantool_init_file).unwrap(); - file.write_all(DEFAULT_LUA_INIT.as_bytes()).unwrap(); - - let status = Command::new("tarantool") - .arg(tarantool_init_file) - .env("TARANTOOL_TEST_PACKAGE", cli.package) - .env("TARANTOOL_TEST_DIR", tmp_dir.path()) - .env("TARANTOOL_TEST_CONFIG", serialized_config) - .status() - .expect("failed to get status code from executed tarantool part"); - assert!( - status.success(), - "exit code of the tarantool part is not success: {status:?}" - ); -} diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ff823e7cf4cbc5af6ff9dd62375191b5f5f2111e --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "1.0" +anyhow = "1" + +serde_json = "1" +example = {path = "../example"} \ No newline at end of file diff --git a/tests/src/lib.rs b/tests/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d0c8a002330d4dfd5cfd094e9cbc38847c844a5e --- /dev/null +++ b/tests/src/lib.rs @@ -0,0 +1,86 @@ +#![cfg(test)] +use std::process::{Command, ExitStatus}; + +use anyhow::Result; + +/// Determines package extension of the prepared package. +fn package_extension() -> &'static str { + if cfg!(target_os = "macos") { + "dylib" + } else { + "so" + } +} + +fn example_crate_run(entrypoint: &str, input: &str) -> Command { + let path_to_package = format!("../target/debug/libexample.{}", package_extension()); + + let mut command = Command::new("../target/debug/tarantool-runner"); + + command + .arg("run") + .arg("-p") + .arg(path_to_package) + .arg("-e") + .arg(entrypoint) + .arg("--") + .arg(input); + + command +} + +#[allow(unused)] +#[derive(Debug, Clone)] +struct CommandResult { + status: ExitStatus, + stdout: String, + stderr: String, +} + +impl CommandResult { + fn from_command(command: &mut Command) -> Result<CommandResult> { + let output = command.output()?; + Ok(CommandResult { + status: command.status()?, + stdout: String::from_utf8(output.stdout)?, + stderr: String::from_utf8(output.stderr)?, + }) + } + + fn assert_status_success(&self, success: bool) { + assert_eq!( + self.status.success(), + success, + "command status invalid: {:?}, command result is: {:?}", + self.status, + self + ) + } +} + +#[test] +fn it_tests_entrypoint_with_input() -> Result<()> { + let input = EntrypointInput { + field_a: "field_a_content".to_string(), + field_b: 10, + }; + + let serialized = serde_json::to_string(&input).unwrap(); + let mut command = example_crate_run("entrypoint_with_input", &serialized); + let result = CommandResult::from_command(&mut command)?; + result.assert_status_success(true); + + assert!(result.stdout.contains(&format!("{:?}", input))); + + Ok(()) +} + +#[test] +fn it_tests_panicking_entrypoint() -> Result<()> { + let mut command = example_crate_run("panicking_entrypoint", ""); + let result = CommandResult::from_command(&mut command)?; + result.assert_status_success(false); + + assert!(result.stdout.contains("must be seen")); + Ok(()) +}