瀏覽代碼

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 年之前
父節點
當前提交
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) {}
+}