Просмотр исходного кода

Create one log file for each test.

This is done by storing the logger in each thread. Each newly created
thread should inherit the logger from the parent thread.

A feature integration-test is created for conditionally enabling. Note that
`#[cfg(test)]` does not work in integration tests, because they are
considered external binaries.

Create a feature to make sure test is run.
Jing Yang 4 лет назад
Родитель
Сommit
10e45a5224
5 измененных файлов с 107 добавлено и 17 удалено
  1. 6 0
      Cargo.toml
  2. 9 0
      src/daemon_env.rs
  3. 2 1
      test_utils/src/lib.rs
  4. 5 16
      test_utils/src/logging.rs
  5. 85 0
      test_utils/src/thread_local_logger.rs

+ 6 - 0
Cargo.toml

@@ -25,10 +25,16 @@ rand = "0.8"
 serde = "1.0.116"
 serde_derive = "1.0.116"
 tokio = { version = "1.7", features = ["rt-multi-thread", "time", "parking_lot"] }
+test_utils = { path = "test_utils", optional = true }
+
+[features]
+default = []
+integration-test = ["test_utils"]
 
 [dev-dependencies]
 anyhow = "1.0"
 futures = { version = "0.3.15", features = ["thread-pool"] }
+ruaft = { path = ".", features = ["integration-test"] }
 scopeguard = "1.1.0"
 test_utils = { path = "test_utils" }
 kvraft = { path = "kvraft" }

+ 9 - 0
src/daemon_env.rs

@@ -5,6 +5,8 @@ use parking_lot::Mutex;
 
 use crate::index_term::IndexTerm;
 use crate::{Peer, RaftState, State, Term};
+#[cfg(feature = "integration-test")]
+use test_utils::thread_local_logger::{self, LocalLogger};
 
 /// A convenient macro to record errors.
 #[macro_export]
@@ -205,6 +207,8 @@ impl DaemonEnv {
         // pointer instead of downgrading frequently.
         let thread_env = ThreadEnv {
             data: Arc::downgrade(&data),
+            #[cfg(feature = "integration-test")]
+            local_logger: thread_local_logger::get(),
         };
         Self { data, thread_env }
     }
@@ -231,6 +235,8 @@ impl DaemonEnv {
 #[derive(Clone, Debug, Default)]
 pub(crate) struct ThreadEnv {
     data: Weak<Mutex<DaemonEnvData>>,
+    #[cfg(feature = "integration-test")]
+    local_logger: LocalLogger,
 }
 
 impl ThreadEnv {
@@ -263,6 +269,9 @@ impl ThreadEnv {
 
     /// Attach this instance to the current thread.
     pub fn attach(self) {
+        #[cfg(feature = "integration-test")]
+        thread_local_logger::set(self.local_logger.clone());
+
         Self::ENV.with(|env| env.replace(self));
     }
 

+ 2 - 1
test_utils/src/lib.rs

@@ -3,4 +3,5 @@ extern crate log;
 extern crate rand;
 
 mod logging;
-pub use logging::{init_log, init_log_once, LOG_DIR};
+pub mod thread_local_logger;
+pub use logging::{init_log, LOG_DIR};

+ 5 - 16
test_utils/src/logging.rs

@@ -2,27 +2,14 @@ use std::path::PathBuf;
 use std::time::SystemTime;
 
 use rand::Rng;
-use std::sync::Once;
 
 #[macro_export]
 macro_rules! init_test_log {
     () => {
-        $crate::init_log_once(module_path!())
+        $crate::init_log(module_path!()).unwrap()
     };
 }
 
-static INIT_LOG_ONCE: Once = Once::new();
-static mut LOG_FILE: Option<PathBuf> = None;
-
-pub fn init_log_once(module_path: &str) -> PathBuf {
-    INIT_LOG_ONCE.call_once(|| unsafe {
-        LOG_FILE.replace(
-            init_log(module_path).expect("init_log() should never fail"),
-        );
-    });
-    unsafe { LOG_FILE.clone().expect("The log file should have been set") }
-}
-
 pub const LOG_DIR: &str = "/tmp/ruaft-test-logs/";
 
 pub fn init_log(module_path: &str) -> std::io::Result<PathBuf> {
@@ -48,12 +35,14 @@ pub fn init_log(module_path: &str) -> std::io::Result<PathBuf> {
     let log_file = std::fs::File::create(path.as_path())?;
 
     let env = env_logger::Env::default().default_filter_or("info");
-    env_logger::Builder::from_env(env)
+    let logger = env_logger::Builder::from_env(env)
         .target(env_logger::Target::Pipe(Box::new(log_file)))
         .filter(Some(module.as_str()), log::LevelFilter::Trace)
         .format_timestamp_millis()
         .is_test(true)
-        .init();
+        .build();
+
+    crate::thread_local_logger::thread_init(logger);
 
     Ok(path)
 }

+ 85 - 0
test_utils/src/thread_local_logger.rs

@@ -0,0 +1,85 @@
+use std::cell::RefCell;
+use std::sync::{Arc, Once};
+
+use log::{Log, Metadata, Record};
+use std::fmt::Formatter;
+
+pub struct GlobalLogger;
+#[derive(Clone)]
+pub struct LocalLogger(Arc<dyn Log>);
+
+thread_local!(static LOCAL_LOGGER: RefCell<LocalLogger> = Default::default());
+
+pub fn global_init_once() {
+    static GLOBAL_LOGGER: GlobalLogger = GlobalLogger;
+    static INIT_LOG_ONCE: Once = Once::new();
+
+    INIT_LOG_ONCE.call_once(|| {
+        log::set_logger(&GLOBAL_LOGGER).unwrap();
+        // Enable all logging globally. Each local logger should have its own
+        // filtering, which could be less efficient.
+        log::set_max_level(log::LevelFilter::max());
+    });
+}
+
+pub fn thread_init<T: 'static + Log>(logger: T) {
+    global_init_once();
+    self::set(LocalLogger(Arc::new(logger)));
+}
+
+pub fn get() -> LocalLogger {
+    LOCAL_LOGGER.with(|inner| inner.borrow().clone())
+}
+
+pub fn set(logger: LocalLogger) {
+    LOCAL_LOGGER.with(|inner| inner.replace(logger));
+}
+
+pub fn reset() {
+    self::set(LocalLogger::default())
+}
+
+impl Default for LocalLogger {
+    fn default() -> Self {
+        Self(Arc::new(NopLogger))
+    }
+}
+
+impl std::ops::Deref for LocalLogger {
+    type Target = dyn Log;
+
+    fn deref(&self) -> &Self::Target {
+        self.0.deref()
+    }
+}
+
+impl std::fmt::Debug for LocalLogger {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.write_str("thread local logger")
+    }
+}
+
+impl Log for GlobalLogger {
+    fn enabled(&self, metadata: &Metadata) -> bool {
+        LOCAL_LOGGER.with(|inner| inner.borrow().enabled(metadata))
+    }
+
+    fn log(&self, record: &Record) {
+        LOCAL_LOGGER.with(|inner| inner.borrow().log(record))
+    }
+
+    fn flush(&self) {
+        LOCAL_LOGGER.with(|inner| inner.borrow().flush())
+    }
+}
+
+struct NopLogger;
+
+impl Log for NopLogger {
+    fn enabled(&self, _: &Metadata) -> bool {
+        false
+    }
+
+    fn log(&self, _: &Record) {}
+    fn flush(&self) {}
+}