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();
+}