1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! High level node tests, similar to spawning `tanssi-node --dev` and inspecting output logs.
18

            
19
use {
20
    crate::cli::Cli,
21
    sc_cli::{Runner, SubstrateCli},
22
    sc_service::TaskManager,
23
    std::time::Duration,
24
};
25

            
26
mod panics;
27

            
28
// Create a runner for tests
29
6
fn create_runner() -> Runner<Cli> {
30
6
    // tanssi-node args should go here, `--dev` is probably enough
31
6
    let cli = Cli::from_iter(["--dev"]);
32
6

            
33
6
    cli.create_runner(&cli.run.normalize()).unwrap()
34
6
}
35

            
36
// Nice hack from polkadot-sdk to run a unit test in a separate process.
37
// We need to use this because create_runner sets up logging and a new panic hook, and that is
38
// global and fails if it was already setup by a previous test.
39
// Improved from upstream by using the exact test name, and by never capturing the test output.
40
2
fn run_test_in_another_process(
41
2
    test_name: &str,
42
2
    test_body: impl FnOnce(),
43
2
) -> Option<std::process::Output> {
44
2
    run_test_in_another_process_expect_error(test_name, 0, test_body)
45
2
}
46

            
47
14
fn run_test_in_another_process_expect_error(
48
14
    test_name: &str,
49
14
    exit_code: i32,
50
14
    test_body: impl FnOnce(),
51
14
) -> Option<std::process::Output> {
52
14
    if std::env::var("RUN_FORKED_TEST").is_ok() {
53
7
        test_body();
54
7
        None
55
    } else {
56
7
        let output = std::process::Command::new(std::env::current_exe().unwrap())
57
7
            .arg(test_name)
58
7
            .arg("--exact")
59
7
            .arg("--nocapture")
60
7
            .arg("--include-ignored")
61
7
            .env("RUN_FORKED_TEST", "1")
62
7
            .output()
63
7
            .unwrap();
64
7

            
65
7
        assert_eq!(output.status.code(), Some(exit_code));
66
7
        Some(output)
67
    }
68
14
}
69

            
70
/// Macro to get the name of the current function at runtime. Used to make calling
71
/// `run_test_in_another_process` less error-prone. Copied from `stdext`, but modified to remove
72
/// the binary name from the output.
73
// https://github.com/popzxc/stdext-rs/blob/dc03b4afa28b3a1d2451ca54ad252244f029099b/src/macros.rs#L63
74
#[macro_export]
75
macro_rules! function_name {
76
    () => {{
77
        // Okay, this is ugly, I get it. However, this is the best we can get on a stable rust.
78
        fn f() {}
79
15
        fn type_name_of<T>(_: T) -> &'static str {
80
15
            std::any::type_name::<T>()
81
15
        }
82
        let name = type_name_of(f);
83
        // `3` is the length of the `::f`.
84
        let name = &name[..name.len() - 3];
85
        // Strip initial tanssi_node::
86
180
        let end_of_first_item = name.bytes().position(|x| x == b':').unwrap();
87
        // `2` is the length of the `::` after `tanssi_node`
88
        &name[end_of_first_item + 2..]
89
    }};
90
}
91

            
92
#[test]
93
1
fn function_name_works() {
94
1
    assert_eq!(function_name!(), "tests::function_name_works");
95
1
}
96

            
97
#[test]
98
2
fn run_test_in_another_process_works() {
99
2
    let parent_pid = std::process::id();
100
2
    let output = run_test_in_another_process(function_name!(), || {
101
1
        let child_pid = std::process::id();
102
1
        eprintln!("Child process running, PID: {}.", child_pid);
103
2
    });
104
2

            
105
2
    if output.is_none() {
106
        // Assert that the output is only None if we are the child process
107
1
        assert!(std::env::var("RUN_FORKED_TEST").is_ok());
108
1
    }
109

            
110
2
    let Some(output) = output else { return };
111

            
112
1
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
113
1

            
114
1
    assert!(stderr.contains("Child process running, PID: "));
115
    // Assert child process id is different from parent process id
116
1
    assert!(!stderr.contains(&format!("PID: {}.", parent_pid)));
117
2
}