This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch branch-1.11
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/branch-1.11 by this push:
     new cf7f9492a AVRO-3522: Better logging in tests (#1690)
cf7f9492a is described below

commit cf7f9492ad8682459b4b9db18f203fd6e2c2f7f8
Author: Martin Grigorov <[email protected]>
AuthorDate: Sun May 22 21:32:05 2022 +0300

    AVRO-3522: Better logging in tests (#1690)
    
    * AVRO-3522: Use ctor crate to setup/teardown tests
    
    Use TestLogger for all tests. Now it delegates to env_logger too
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    * AVRO-3522: Improve the README
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    * AVRO-3522: Give better names to some methods
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    * AVRO-3522: Make LOG_MESSAGES module-private
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    * AVRO-3522: Add ASL2 to the new files
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    * AVRO-3522: Use pretty_assertions for better diffs when assertions fail
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    (cherry picked from commit 2c13cd7e6d6d7485e1cdf93c9777f7d2ccae7558)
---
 lang/rust/Cargo.toml                        |  1 +
 lang/rust/avro/Cargo.toml                   | 10 ++---
 lang/rust/avro/src/codec.rs                 |  1 +
 lang/rust/avro/src/de.rs                    |  1 +
 lang/rust/avro/src/decimal.rs               |  1 +
 lang/rust/avro/src/decode.rs                |  1 +
 lang/rust/avro/src/encode.rs                |  2 +
 lang/rust/avro/src/lib.rs                   |  1 +
 lang/rust/avro/src/rabin.rs                 |  1 +
 lang/rust/avro/src/reader.rs                |  1 +
 lang/rust/avro/src/schema.rs                |  1 +
 lang/rust/avro/src/ser.rs                   |  1 +
 lang/rust/avro/src/types.rs                 | 68 +++++-----------------------
 lang/rust/avro/src/util.rs                  |  1 +
 lang/rust/avro/src/writer.rs                |  1 +
 lang/rust/avro/tests/io.rs                  |  1 +
 lang/rust/avro/tests/schema.rs              | 16 ++-----
 lang/rust/{ => avro_test_helper}/Cargo.toml | 18 +++++---
 lang/rust/avro_test_helper/README.md        | 51 +++++++++++++++++++++
 lang/rust/avro_test_helper/src/lib.rs       | 48 ++++++++++++++++++++
 lang/rust/avro_test_helper/src/logger.rs    | 69 +++++++++++++++++++++++++++++
 21 files changed, 217 insertions(+), 78 deletions(-)

diff --git a/lang/rust/Cargo.toml b/lang/rust/Cargo.toml
index 9f188c065..527bfb3fe 100644
--- a/lang/rust/Cargo.toml
+++ b/lang/rust/Cargo.toml
@@ -17,6 +17,7 @@
 
 [workspace]
 members = [
+    "avro_test_helper",
     "avro",
     "avro_derive"
 ]
diff --git a/lang/rust/avro/Cargo.toml b/lang/rust/avro/Cargo.toml
index 268cdfb2d..042184bdd 100644
--- a/lang/rust/avro/Cargo.toml
+++ b/lang/rust/avro/Cargo.toml
@@ -77,10 +77,10 @@ zstd = { default-features = false, version = 
"0.11.1+zstd.1.5.2", optional = tru
 apache-avro-derive = { default-features = false, version= "0.14.0", path = 
"../avro_derive", optional = true }
 
 [dev-dependencies]
-md-5 = "0.10.1"
-sha2 = "0.10.2"
-criterion = "0.3.5"
 anyhow = "1.0.56"
+apache-avro-test-helper = { version= "0.1.0", path = "../avro_test_helper" }
+criterion = "0.3.5"
 hex-literal = "0.3.4"
-env_logger = "0.9.0"
-ref_thread_local = "0.1.1"
+md-5 = "0.10.1"
+pretty_assertions = "1.2.1"
+sha2 = "0.10.2"
diff --git a/lang/rust/avro/src/codec.rs b/lang/rust/avro/src/codec.rs
index c9a584ec2..4d63e4cd5 100644
--- a/lang/rust/avro/src/codec.rs
+++ b/lang/rust/avro/src/codec.rs
@@ -185,6 +185,7 @@ impl Codec {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pretty_assertions::{assert_eq, assert_ne};
 
     const INPUT: &[u8] = 
b"theanswertolifetheuniverseandeverythingis42theanswertolifetheuniverseandeverythingis4theanswertolifetheuniverseandeverythingis2";
 
diff --git a/lang/rust/avro/src/de.rs b/lang/rust/avro/src/de.rs
index 24e9958b2..6562e622e 100644
--- a/lang/rust/avro/src/de.rs
+++ b/lang/rust/avro/src/de.rs
@@ -593,6 +593,7 @@ pub fn from_value<'de, D: Deserialize<'de>>(value: &'de 
Value) -> Result<D, Erro
 
 #[cfg(test)]
 mod tests {
+    use pretty_assertions::assert_eq;
     use serde::Serialize;
     use uuid::Uuid;
 
diff --git a/lang/rust/avro/src/decimal.rs b/lang/rust/avro/src/decimal.rs
index cfd876b23..e67430384 100644
--- a/lang/rust/avro/src/decimal.rs
+++ b/lang/rust/avro/src/decimal.rs
@@ -102,6 +102,7 @@ impl<T: AsRef<[u8]>> From<T> for Decimal {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pretty_assertions::assert_eq;
     use std::convert::TryFrom;
 
     #[test]
diff --git a/lang/rust/avro/src/decode.rs b/lang/rust/avro/src/decode.rs
index d88157307..6017d9165 100644
--- a/lang/rust/avro/src/decode.rs
+++ b/lang/rust/avro/src/decode.rs
@@ -300,6 +300,7 @@ mod tests {
         },
         Decimal,
     };
+    use pretty_assertions::assert_eq;
     use std::collections::HashMap;
 
     #[test]
diff --git a/lang/rust/avro/src/encode.rs b/lang/rust/avro/src/encode.rs
index cf4e0f5da..6524eb933 100644
--- a/lang/rust/avro/src/encode.rs
+++ b/lang/rust/avro/src/encode.rs
@@ -244,7 +244,9 @@ pub fn encode_to_vec(value: &Value, schema: &Schema) -> 
AvroResult<Vec<u8>> {
 #[allow(clippy::expect_fun_call)]
 pub(crate) mod tests {
     use super::*;
+    use pretty_assertions::assert_eq;
     use std::collections::HashMap;
+
     pub(crate) fn success(value: &Value, schema: &Schema) -> String {
         format!(
             "Value: {:?}\n should encode with schema:\n{:?}",
diff --git a/lang/rust/avro/src/lib.rs b/lang/rust/avro/src/lib.rs
index 8b53f1635..967980211 100644
--- a/lang/rust/avro/src/lib.rs
+++ b/lang/rust/avro/src/lib.rs
@@ -764,6 +764,7 @@ mod tests {
         types::{Record, Value},
         Codec, Reader, Schema, Writer,
     };
+    use pretty_assertions::assert_eq;
 
     //TODO: move where it fits better
     #[test]
diff --git a/lang/rust/avro/src/rabin.rs b/lang/rust/avro/src/rabin.rs
index 011a60019..ce5f0761f 100644
--- a/lang/rust/avro/src/rabin.rs
+++ b/lang/rust/avro/src/rabin.rs
@@ -133,6 +133,7 @@ mod tests {
     use super::Rabin;
     use byteorder::{ByteOrder, LittleEndian};
     use digest::Digest;
+    use pretty_assertions::assert_eq;
 
     // See: 
https://github.com/apache/avro/blob/master/share/test/data/schema-tests.txt
     #[test]
diff --git a/lang/rust/avro/src/reader.rs b/lang/rust/avro/src/reader.rs
index f2e0f38b6..72513bf1e 100644
--- a/lang/rust/avro/src/reader.rs
+++ b/lang/rust/avro/src/reader.rs
@@ -444,6 +444,7 @@ where
 mod tests {
     use super::*;
     use crate::{encode::encode, from_value, types::Record, Reader};
+    use pretty_assertions::assert_eq;
     use serde::Deserialize;
     use std::io::Cursor;
 
diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs
index 1c589690f..fa698b10c 100644
--- a/lang/rust/avro/src/schema.rs
+++ b/lang/rust/avro/src/schema.rs
@@ -1895,6 +1895,7 @@ pub mod derive {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pretty_assertions::assert_eq;
 
     #[test]
     fn test_invalid_schema() {
diff --git a/lang/rust/avro/src/ser.rs b/lang/rust/avro/src/ser.rs
index 5cff13e56..ccd0a0a90 100644
--- a/lang/rust/avro/src/ser.rs
+++ b/lang/rust/avro/src/ser.rs
@@ -485,6 +485,7 @@ pub fn to_value<S: Serialize>(value: S) -> Result<Value, 
Error> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pretty_assertions::assert_eq;
     use serde::{Deserialize, Serialize};
 
     #[derive(Debug, Deserialize, Serialize, Clone)]
diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs
index 78b000552..f9e472227 100644
--- a/lang/rust/avro/src/types.rs
+++ b/lang/rust/avro/src/types.rs
@@ -938,48 +938,10 @@ mod tests {
         schema::{Name, RecordField, RecordFieldOrder, Schema, UnionSchema},
         types::Value,
     };
-    use log::{Level, LevelFilter, Metadata};
+    use apache_avro_test_helper::logger::assert_logged;
+    use pretty_assertions::assert_eq;
     use uuid::Uuid;
 
-    use ref_thread_local::{ref_thread_local, RefThreadLocal};
-
-    ref_thread_local! {
-        // The unit tests run in parallel
-        // We need to keep the log messages in a thread-local variable
-        // and clear them after assertion
-        static managed LOG_MESSAGES: Vec<String> = Vec::new();
-    }
-
-    struct TestLogger;
-
-    impl log::Log for TestLogger {
-        fn enabled(&self, metadata: &Metadata) -> bool {
-            metadata.level() <= Level::Error
-        }
-
-        fn log(&self, record: &log::Record) {
-            if self.enabled(record.metadata()) {
-                let mut msgs = LOG_MESSAGES.borrow_mut();
-                msgs.push(format!("{}", record.args()));
-            }
-        }
-
-        fn flush(&self) {}
-    }
-
-    static TEST_LOGGER: TestLogger = TestLogger;
-
-    fn init() {
-        let _ = log::set_logger(&TEST_LOGGER);
-        log::set_max_level(LevelFilter::Info);
-    }
-
-    fn assert_log_message(expected_message: &str) {
-        let mut msgs = LOG_MESSAGES.borrow_mut();
-        assert_eq!(msgs.pop().unwrap(), expected_message);
-        msgs.clear();
-    }
-
     #[test]
     fn validate() {
         let value_schema_valid = vec![
@@ -1120,8 +1082,6 @@ mod tests {
 
     #[test]
     fn validate_fixed() {
-        init();
-
         let schema = Schema::Fixed {
             size: 4,
             name: Name::new("some_fixed").unwrap(),
@@ -1132,7 +1092,7 @@ mod tests {
         assert!(Value::Fixed(4, vec![0, 0, 0, 0]).validate(&schema));
         let value = Value::Fixed(5, vec![0, 0, 0, 0, 0]);
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             format!(
                 "Invalid value: {:?} for schema: {:?}. Reason: {}",
                 value, schema, "The value's size (5) is different than the 
schema's size (4)"
@@ -1143,7 +1103,7 @@ mod tests {
         assert!(Value::Bytes(vec![0, 0, 0, 0]).validate(&schema));
         let value = Value::Bytes(vec![0, 0, 0, 0, 0]);
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             format!(
                 "Invalid value: {:?} for schema: {:?}. Reason: {}",
                 value, schema, "The bytes' length (5) is different than the 
schema's size (4)"
@@ -1154,8 +1114,6 @@ mod tests {
 
     #[test]
     fn validate_enum() {
-        init();
-
         let schema = Schema::Enum {
             name: Name::new("some_enum").unwrap(),
             aliases: None,
@@ -1173,7 +1131,7 @@ mod tests {
 
         let value = Value::Enum(1, "spades".to_string());
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             format!(
                 "Invalid value: {:?} for schema: {:?}. Reason: {}",
                 value, schema, "Symbol 'spades' is not at position '1'"
@@ -1183,7 +1141,7 @@ mod tests {
 
         let value = Value::Enum(1000, "spades".to_string());
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             format!(
                 "Invalid value: {:?} for schema: {:?}. Reason: {}",
                 value, schema, "No symbol at position '1000'"
@@ -1193,7 +1151,7 @@ mod tests {
 
         let value = Value::String("lorem".to_string());
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             format!(
                 "Invalid value: {:?} for schema: {:?}. Reason: {}",
                 value, schema, "'lorem' is not a member of the possible 
symbols"
@@ -1215,7 +1173,7 @@ mod tests {
 
         let value = Value::Enum(0, "spades".to_string());
         assert!(!value.validate(&other_schema));
-        assert_log_message(
+        assert_logged(
             format!(
                 "Invalid value: {:?} for schema: {:?}. Reason: {}",
                 value, other_schema, "Symbol 'spades' is not at position '0'"
@@ -1226,8 +1184,6 @@ mod tests {
 
     #[test]
     fn validate_record() {
-        init();
-
         // {
         //    "type": "record",
         //    "fields": [
@@ -1280,14 +1236,14 @@ mod tests {
             ("b".to_string(), Value::String("foo".to_string())),
         ]);
         assert!(!value.validate(&schema));
-        assert_log_message("Invalid value: Record([(\"a\", Boolean(false)), 
(\"b\", String(\"foo\"))]) for schema: Record { name: Name { name: 
\"some_record\", namespace: None }, aliases: None, doc: None, fields: 
[RecordField { name: \"a\", doc: None, default: None, schema: Long, order: 
Ascending, position: 0 }, RecordField { name: \"b\", doc: None, default: None, 
schema: String, order: Ascending, position: 1 }], lookup: {\"a\": 0, \"b\": 1} 
}. Reason: Unsupported value-schema combination");
+        assert_logged("Invalid value: Record([(\"a\", Boolean(false)), (\"b\", 
String(\"foo\"))]) for schema: Record { name: Name { name: \"some_record\", 
namespace: None }, aliases: None, doc: None, fields: [RecordField { name: 
\"a\", doc: None, default: None, schema: Long, order: Ascending, position: 0 }, 
RecordField { name: \"b\", doc: None, default: None, schema: String, order: 
Ascending, position: 1 }], lookup: {\"a\": 0, \"b\": 1} }. Reason: Unsupported 
value-schema combination");
 
         let value = Value::Record(vec![
             ("a".to_string(), Value::Long(42i64)),
             ("c".to_string(), Value::String("foo".to_string())),
         ]);
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             "Invalid value: Record([(\"a\", Long(42)), (\"c\", 
String(\"foo\"))]) for schema: Record { name: Name { name: \"some_record\", 
namespace: None }, aliases: None, doc: None, fields: [RecordField { name: 
\"a\", doc: None, default: None, schema: Long, order: Ascending, position: 0 }, 
RecordField { name: \"b\", doc: None, default: None, schema: String, order: 
Ascending, position: 1 }], lookup: {\"a\": 0, \"b\": 1} }. Reason: There is no 
schema field for field 'c'"
         );
 
@@ -1297,7 +1253,7 @@ mod tests {
             ("c".to_string(), Value::Null),
         ]);
         assert!(!value.validate(&schema));
-        assert_log_message(
+        assert_logged(
             r#"Invalid value: Record([("a", Long(42)), ("b", String("foo")), 
("c", Null)]) for schema: Record { name: Name { name: "some_record", namespace: 
None }, aliases: None, doc: None, fields: [RecordField { name: "a", doc: None, 
default: None, schema: Long, order: Ascending, position: 0 }, RecordField { 
name: "b", doc: None, default: None, schema: String, order: Ascending, 
position: 1 }], lookup: {"a": 0, "b": 1} }. Reason: The value's records length 
(3) is different than the sche [...]
         );
 
@@ -1317,7 +1273,7 @@ mod tests {
                 .collect()
         )
         .validate(&schema));
-        assert_log_message(
+        assert_logged(
             r#"Invalid value: Map({"c": Long(123)}) for schema: Record { name: 
Name { name: "some_record", namespace: None }, aliases: None, doc: None, 
fields: [RecordField { name: "a", doc: None, default: None, schema: Long, 
order: Ascending, position: 0 }, RecordField { name: "b", doc: None, default: 
None, schema: String, order: Ascending, position: 1 }], lookup: {"a": 0, "b": 
1} }. Reason: Field with name '"a"' is not a member of the map items
 Field with name '"b"' is not a member of the map items"#,
         );
diff --git a/lang/rust/avro/src/util.rs b/lang/rust/avro/src/util.rs
index fe77f07c1..5929c06dd 100644
--- a/lang/rust/avro/src/util.rs
+++ b/lang/rust/avro/src/util.rs
@@ -155,6 +155,7 @@ pub fn safe_len(len: usize) -> AvroResult<usize> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pretty_assertions::assert_eq;
 
     #[test]
     fn test_zigzag() {
diff --git a/lang/rust/avro/src/writer.rs b/lang/rust/avro/src/writer.rs
index 1e6f80909..18eb833c0 100644
--- a/lang/rust/avro/src/writer.rs
+++ b/lang/rust/avro/src/writer.rs
@@ -524,6 +524,7 @@ mod tests {
         types::Record,
         util::zig_i64,
     };
+    use pretty_assertions::assert_eq;
     use serde::{Deserialize, Serialize};
 
     const AVRO_OBJECT_HEADER_LEN: usize = AVRO_OBJECT_HEADER.len();
diff --git a/lang/rust/avro/tests/io.rs b/lang/rust/avro/tests/io.rs
index 18b3c7006..193c18140 100644
--- a/lang/rust/avro/tests/io.rs
+++ b/lang/rust/avro/tests/io.rs
@@ -18,6 +18,7 @@
 //! Port of 
https://github.com/apache/avro/blob/release-1.9.1/lang/py/test/test_io.py
 use apache_avro::{from_avro_datum, to_avro_datum, types::Value, Error, Schema};
 use lazy_static::lazy_static;
+use pretty_assertions::assert_eq;
 use std::io::Cursor;
 
 lazy_static! {
diff --git a/lang/rust/avro/tests/schema.rs b/lang/rust/avro/tests/schema.rs
index c311eb335..b4f59e553 100644
--- a/lang/rust/avro/tests/schema.rs
+++ b/lang/rust/avro/tests/schema.rs
@@ -21,15 +21,8 @@ use apache_avro::{
     types::{Record, Value},
     Codec, Error, Reader, Schema, Writer,
 };
+use apache_avro_test_helper::init;
 use lazy_static::lazy_static;
-use log::debug;
-
-fn init() {
-    let _ = env_logger::builder()
-        .filter_level(log::LevelFilter::Trace)
-        .is_test(true)
-        .try_init();
-}
 
 const PRIMITIVE_EXAMPLES: &[(&str, bool)] = &[
     (r#""null""#, true),
@@ -641,7 +634,6 @@ fn test_correct_recursive_extraction() {
 #[test]
 fn test_parse() {
     init();
-
     for (raw_schema, valid) in EXAMPLES.iter() {
         let schema = Schema::parse_str(raw_schema);
         if *valid {
@@ -1176,7 +1168,6 @@ fn 
test_fullname_name_namespace_and_default_namespace_specified() {
 #[test]
 fn test_doc_attributes() {
     init();
-
     fn assert_doc(schema: &Schema) {
         match schema {
             Schema::Enum { doc, .. } => assert!(doc.is_some()),
@@ -1235,7 +1226,6 @@ fn test_other_attributes() {
 #[test]
 fn test_root_error_is_not_swallowed_on_parse_error() -> Result<(), String> {
     init();
-
     let raw_schema = r#"/not/a/real/file"#;
     let error = Schema::parse_str(raw_schema).unwrap_err();
 
@@ -1257,6 +1247,7 @@ fn test_root_error_is_not_swallowed_on_parse_error() -> 
Result<(), String> {
 // AVRO-3302
 #[test]
 fn test_record_schema_with_cyclic_references() {
+    init();
     let schema = Schema::parse_str(
         r#"
             {
@@ -1314,7 +1305,7 @@ fn test_record_schema_with_cyclic_references() {
 
     match Reader::new(&mut bytes.as_slice()) {
         Ok(mut reader) => match reader.next() {
-            Some(value) => debug!("{:?}", value.unwrap()),
+            Some(value) => log::debug!("{:?}", value.unwrap()),
             None => panic!("No value was read!"),
         },
         Err(err) => panic!("An error occurred while reading datum: {:?}", err),
@@ -1325,6 +1316,7 @@ fn test_record_schema_with_cyclic_references() {
 // TODO: (#93) add support for logical type and attributes and uncomment (may 
need some tweaks to compile)
 #[test]
 fn test_decimal_valid_type_attributes() {
+    init();
     let fixed_decimal = 
Schema::parse_str(DECIMAL_LOGICAL_TYPE_ATTRIBUTES[0]).unwrap();
     assert_eq!(4, fixed_decimal.get_attribute("precision"));
     assert_eq!(2, fixed_decimal.get_attribute("scale"));
diff --git a/lang/rust/Cargo.toml b/lang/rust/avro_test_helper/Cargo.toml
similarity index 67%
copy from lang/rust/Cargo.toml
copy to lang/rust/avro_test_helper/Cargo.toml
index 9f188c065..c7b3aa19e 100644
--- a/lang/rust/Cargo.toml
+++ b/lang/rust/avro_test_helper/Cargo.toml
@@ -15,8 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[workspace]
-members = [
-    "avro",
-    "avro_derive"
-]
+[package]
+name = "apache-avro-test-helper"
+version = "0.1.0"
+edition = "2018"
+description = "Avro test helper. This crate is not supposed to be published!"
+
+[dependencies]
+ctor = "0.1.22"
+color-backtrace = { version = "0.5" }
+env_logger = "0.9.0"
+lazy_static = { default-features = false, version="1.4.0" }
+log = { default-features = false, version="0.4.16" }
+ref_thread_local = "0.1.1"
diff --git a/lang/rust/avro_test_helper/README.md 
b/lang/rust/avro_test_helper/README.md
new file mode 100644
index 000000000..924516922
--- /dev/null
+++ b/lang/rust/avro_test_helper/README.md
@@ -0,0 +1,51 @@
+<!---
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+
+# Avro Test Helper
+
+A module that provides several test related goodies to the other Avro crates:
+
+### Custom Logger
+
+The logger:
+
+* collects the logged messages, so that a test could assert what has been 
logged
+* delegates to env_logger so that they printed on the stderr
+
+### Colorized Backtraces
+
+Uses `color-backtrace` to make the backtraces easier to read.
+
+# Setup
+
+### Unit tests
+
+The module is automatically setup for all unit tests when this crate is listed 
as a `[dev-dependency]` in Cargo.toml.
+
+### Integration tests
+
+Since integration tests are actually crates without Cargo.toml, the test 
author needs to call `test_logger::init()` in the beginning of a test.
+
+# Usage
+
+To assert that a given message was logged, use the `assert_logged` function.
+```rust
+apache_avro_test_helper::logger::assert_logged("An expected message");
+```
diff --git a/lang/rust/avro_test_helper/src/lib.rs 
b/lang/rust/avro_test_helper/src/lib.rs
new file mode 100644
index 000000000..ed4cb359b
--- /dev/null
+++ b/lang/rust/avro_test_helper/src/lib.rs
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use ctor::{ctor, dtor};
+
+use ref_thread_local::ref_thread_local;
+
+ref_thread_local! {
+    // The unit tests run in parallel
+    // We need to keep the log messages in a thread-local variable
+    // and clear them after assertion
+    pub(crate) static managed LOG_MESSAGES: Vec<String> = Vec::new();
+}
+
+pub mod logger;
+
+#[ctor]
+fn before_all() {
+    // better stacktraces in tests
+    color_backtrace::install();
+
+    // enable logging in tests
+    logger::install();
+}
+
+#[dtor]
+fn after_all() {
+    logger::clear_log_messages();
+}
+
+/// Does nothing. Just loads the crate.
+/// Should be used in the integration tests, because they do not use 
[dev-dependencies]
+/// and do not auto-load this crate.
+pub fn init() {}
diff --git a/lang/rust/avro_test_helper/src/logger.rs 
b/lang/rust/avro_test_helper/src/logger.rs
new file mode 100644
index 000000000..26cdf9b3e
--- /dev/null
+++ b/lang/rust/avro_test_helper/src/logger.rs
@@ -0,0 +1,69 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::LOG_MESSAGES;
+use lazy_static::lazy_static;
+use log::{LevelFilter, Log, Metadata};
+use ref_thread_local::RefThreadLocal;
+
+struct TestLogger {
+    delegate: env_logger::Logger,
+}
+
+impl Log for TestLogger {
+    #[inline]
+    fn enabled(&self, _metadata: &Metadata) -> bool {
+        true
+    }
+
+    fn log(&self, record: &log::Record) {
+        if self.enabled(record.metadata()) {
+            LOG_MESSAGES.borrow_mut().push(format!("{}", record.args()));
+
+            self.delegate.log(record);
+        }
+    }
+
+    fn flush(&self) {}
+}
+
+lazy_static! {
+    // Lazy static because the Logger has to be 'static
+    static ref TEST_LOGGER: TestLogger = TestLogger {
+        delegate: env_logger::Builder::from_default_env()
+            .filter_level(LevelFilter::Off)
+            .parse_default_env()
+            .build(),
+    };
+}
+
+pub fn clear_log_messages() {
+    LOG_MESSAGES.borrow_mut().clear();
+}
+
+pub fn assert_logged(expected_message: &str) {
+    assert_eq!(LOG_MESSAGES.borrow_mut().pop().unwrap(), expected_message);
+}
+
+pub(crate) fn install() {
+    log::set_logger(&*TEST_LOGGER)
+        .map(|_| log::set_max_level(LevelFilter::Trace))
+        .map_err(|err| {
+            eprintln!("Failed to set the custom logger: {:?}", err);
+        })
+        .unwrap();
+}

Reply via email to