This is an automated email from the ASF dual-hosted git repository. mgrigorov pushed a commit to branch avro-3839-replace-lazy-static-with-oncelock in repository https://gitbox.apache.org/repos/asf/avro.git
commit 674ff2b0a453b874fa407b61e01803bd336ad622 Author: Martin Tzvetanov Grigorov <[email protected]> AuthorDate: Mon Aug 21 15:02:35 2023 +0300 AVRO-3839: [Rust] Replace lazy_static crate with std::sync::OnceLock Signed-off-by: Martin Tzvetanov Grigorov <[email protected]> --- lang/rust/Cargo.lock | 2 - lang/rust/avro/Cargo.toml | 1 - lang/rust/avro/src/rabin.rs | 13 +- lang/rust/avro/src/schema.rs | 44 +++-- lang/rust/avro/tests/io.rs | 271 +++++++++++++++++++++---------- lang/rust/avro/tests/schema.rs | 68 ++++---- lang/rust/avro_test_helper/Cargo.toml | 1 - lang/rust/avro_test_helper/src/logger.rs | 11 +- 8 files changed, 266 insertions(+), 145 deletions(-) diff --git a/lang/rust/Cargo.lock b/lang/rust/Cargo.lock index bf7fc242a..4dcb29e2d 100644 --- a/lang/rust/Cargo.lock +++ b/lang/rust/Cargo.lock @@ -73,7 +73,6 @@ dependencies = [ "criterion", "digest", "hex-literal", - "lazy_static", "libflate", "log", "md-5", @@ -117,7 +116,6 @@ dependencies = [ "color-backtrace", "ctor", "env_logger", - "lazy_static", "log", "ref_thread_local", ] diff --git a/lang/rust/avro/Cargo.toml b/lang/rust/avro/Cargo.toml index 29ef270d8..ed5721165 100644 --- a/lang/rust/avro/Cargo.toml +++ b/lang/rust/avro/Cargo.toml @@ -58,7 +58,6 @@ apache-avro-derive = { default-features = false, version = "0.16.0", path = "../ bzip2 = { default-features = false, version = "0.4.4", optional = true } crc32fast = { default-features = false, version = "1.3.2", optional = true } digest = { default-features = false, version = "0.10.7", features = ["core-api"] } -lazy_static = { default-features = false, version = "1.4.0" } libflate = { default-features = false, version = "2.0.0", features = ["std"] } log = { default-features = false, version = "0.4.20" } num-bigint = { default-features = false, version = "0.4.3" } diff --git a/lang/rust/avro/src/rabin.rs b/lang/rust/avro/src/rabin.rs index fc63f8999..f860769eb 100644 --- a/lang/rust/avro/src/rabin.rs +++ b/lang/rust/avro/src/rabin.rs @@ -20,22 +20,23 @@ use digest::{ consts::U8, core_api::OutputSizeUser, generic_array::GenericArray, FixedOutput, FixedOutputReset, HashMarker, Output, Reset, Update, }; -use lazy_static::lazy_static; +use std::sync::OnceLock; const EMPTY: i64 = -4513414715797952619; -lazy_static! { - static ref FPTABLE: [i64; 256] = { +fn fp_table() -> &'static [i64; 256] { + static FPTABLE_ONCE: OnceLock<[i64; 256]> = OnceLock::new(); + FPTABLE_ONCE.get_or_init(|| { let mut fp_table: [i64; 256] = [0; 256]; for i in 0..256 { let mut fp = i; for _ in 0..8 { fp = (fp as u64 >> 1) as i64 ^ (EMPTY & -(fp & 1)); } - fp_table[i as usize] = fp + fp_table[i as usize] = fp; } fp_table - }; + }) } /// Implementation of the Rabin fingerprint algorithm using the Digest trait as described in [schema_fingerprints](https://avro.apache.org/docs/current/spec.html#schema_fingerprints). @@ -94,7 +95,7 @@ impl Update for Rabin { fn update(&mut self, data: &[u8]) { for b in data { self.result = (self.result as u64 >> 8) as i64 - ^ FPTABLE[((self.result ^ *b as i64) & 0xff) as usize]; + ^ fp_table()[((self.result ^ *b as i64) & 0xff) as usize]; } } } diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs index cd2e56bbf..ef82f9b96 100644 --- a/lang/rust/avro/src/schema.rs +++ b/lang/rust/avro/src/schema.rs @@ -18,7 +18,6 @@ //! Logic for parsing and interacting with schemas in Avro format. use crate::{error::Error, types, util::MapHelper, AvroResult}; use digest::Digest; -use lazy_static::lazy_static; use regex::Regex; use serde::{ ser::{SerializeMap, SerializeSeq}, @@ -34,19 +33,36 @@ use std::{ hash::Hash, io::Read, str::FromStr, + sync::OnceLock, }; use strum_macros::{EnumDiscriminants, EnumString}; -lazy_static! { - static ref ENUM_SYMBOL_NAME_R: Regex = Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap(); +fn enum_symbol_name_r() -> &'static Regex { + static ENUM_SYMBOL_NAME_ONCE: OnceLock<Regex> = OnceLock::new(); + ENUM_SYMBOL_NAME_ONCE.get_or_init(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap()) +} - // An optional namespace (with optional dots) followed by a name without any dots in it. - static ref SCHEMA_NAME_R: Regex = - Regex::new(r"^((?P<namespace>([A-Za-z_][A-Za-z0-9_\.]*)*)\.)?(?P<name>[A-Za-z_][A-Za-z0-9_]*)$").unwrap(); +// An optional namespace (with optional dots) followed by a name without any dots in it. +fn schema_name_r() -> &'static Regex { + static SCHEMA_NAME_ONCE: OnceLock<Regex> = OnceLock::new(); + SCHEMA_NAME_ONCE.get_or_init(|| { + Regex::new( + r"^((?P<namespace>([A-Za-z_][A-Za-z0-9_\.]*)*)\.)?(?P<name>[A-Za-z_][A-Za-z0-9_]*)$", + ) + .unwrap() + }) +} - static ref FIELD_NAME_R: Regex = Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap(); +fn field_name_r() -> &'static Regex { + static FIELD_NAME_ONCE: OnceLock<Regex> = OnceLock::new(); + FIELD_NAME_ONCE.get_or_init(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap()) +} - static ref NAMESPACE_R: Regex = Regex::new(r"^([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?$").unwrap(); +fn namespace_r() -> &'static Regex { + static NAMESPACE_ONCE: OnceLock<Regex> = OnceLock::new(); + NAMESPACE_ONCE.get_or_init(|| { + Regex::new(r"^([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?$").unwrap() + }) } /// Represents an Avro schema fingerprint @@ -236,9 +252,9 @@ impl Name { } fn get_name_and_namespace(name: &str) -> AvroResult<(String, Namespace)> { - let caps = SCHEMA_NAME_R + let caps = schema_name_r() .captures(name) - .ok_or_else(|| Error::InvalidSchemaName(name.to_string(), SCHEMA_NAME_R.as_str()))?; + .ok_or_else(|| Error::InvalidSchemaName(name.to_string(), schema_name_r().as_str()))?; Ok(( caps["name"].to_string(), caps.name("namespace").map(|s| s.as_str().to_string()), @@ -269,10 +285,10 @@ impl Name { .filter(|ns| !ns.is_empty()); if let Some(ref ns) = namespace { - if !NAMESPACE_R.is_match(ns) { + if !namespace_r().is_match(ns) { return Err(Error::InvalidNamespace( ns.to_string(), - NAMESPACE_R.as_str(), + namespace_r().as_str(), )); } } @@ -641,7 +657,7 @@ impl RecordField { ) -> AvroResult<Self> { let name = field.name().ok_or(Error::GetNameFieldFromRecord)?; - if !FIELD_NAME_R.is_match(&name) { + if !field_name_r().is_match(&name) { return Err(Error::FieldName(name)); } @@ -1532,7 +1548,7 @@ impl Parser { let mut existing_symbols: HashSet<&String> = HashSet::with_capacity(symbols.len()); for symbol in symbols.iter() { // Ensure enum symbol names match [A-Za-z_][A-Za-z0-9_]* - if !ENUM_SYMBOL_NAME_R.is_match(symbol) { + if !enum_symbol_name_r().is_match(symbol) { return Err(Error::EnumSymbolName(symbol.to_string())); } diff --git a/lang/rust/avro/tests/io.rs b/lang/rust/avro/tests/io.rs index b835fe7af..2b3aa2376 100644 --- a/lang/rust/avro/tests/io.rs +++ b/lang/rust/avro/tests/io.rs @@ -18,90 +18,189 @@ //! 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 apache_avro_test_helper::TestResult; -use lazy_static::lazy_static; use pretty_assertions::assert_eq; -use std::io::Cursor; +use std::{io::Cursor, sync::OnceLock}; -lazy_static! { - static ref SCHEMAS_TO_VALIDATE: Vec<(&'static str, Value)> = vec![ - (r#""null""#, Value::Null), - (r#""boolean""#, Value::Boolean(true)), - (r#""string""#, Value::String("adsfasdf09809dsf-=adsf".to_string())), - (r#""bytes""#, Value::Bytes("12345abcd".to_string().into_bytes())), - (r#""int""#, Value::Int(1234)), - (r#""long""#, Value::Long(1234)), - (r#""float""#, Value::Float(1234.0)), - (r#""double""#, Value::Double(1234.0)), - (r#"{"type": "fixed", "name": "Test", "size": 1}"#, Value::Fixed(1, vec![b'B'])), - (r#"{"type": "enum", "name": "Test", "symbols": ["A", "B"]}"#, Value::Enum(1, "B".to_string())), - (r#"{"type": "array", "items": "long"}"#, Value::Array(vec![Value::Long(1), Value::Long(3), Value::Long(2)])), - (r#"{"type": "map", "values": "long"}"#, Value::Map([("a".to_string(), Value::Long(1i64)), ("b".to_string(), Value::Long(3i64)), ("c".to_string(), Value::Long(2i64))].iter().cloned().collect())), - (r#"["string", "null", "long"]"#, Value::Union(1, Box::new(Value::Null))), - (r#"{"type": "record", "name": "Test", "fields": [{"name": "f", "type": "long"}]}"#, Value::Record(vec![("f".to_string(), Value::Long(1))])) - ]; - - static ref BINARY_ENCODINGS: Vec<(i64, Vec<u8>)> = vec![ - (0, vec![0x00]), - (-1, vec![0x01]), - (1, vec![0x02]), - (-2, vec![0x03]), - (2, vec![0x04]), - (-64, vec![0x7f]), - (64, vec![0x80, 0x01]), - (8192, vec![0x80, 0x80, 0x01]), - (-8193, vec![0x81, 0x80, 0x01]), - ]; +fn schemas_to_validate() -> &'static Vec<(&'static str, Value)> { + static SCHEMAS_TO_VALIDATE_ONCE: OnceLock<Vec<(&'static str, Value)>> = OnceLock::new(); + SCHEMAS_TO_VALIDATE_ONCE.get_or_init(|| { + vec![ + (r#""null""#, Value::Null), + (r#""boolean""#, Value::Boolean(true)), + ( + r#""string""#, + Value::String("adsfasdf09809dsf-=adsf".to_string()), + ), + ( + r#""bytes""#, + Value::Bytes("12345abcd".to_string().into_bytes()), + ), + (r#""int""#, Value::Int(1234)), + (r#""long""#, Value::Long(1234)), + (r#""float""#, Value::Float(1234.0)), + (r#""double""#, Value::Double(1234.0)), + ( + r#"{"type": "fixed", "name": "Test", "size": 1}"#, + Value::Fixed(1, vec![b'B']), + ), + ( + r#"{"type": "enum", "name": "Test", "symbols": ["A", "B"]}"#, + Value::Enum(1, "B".to_string()), + ), + ( + r#"{"type": "array", "items": "long"}"#, + Value::Array(vec![Value::Long(1), Value::Long(3), Value::Long(2)]), + ), + ( + r#"{"type": "map", "values": "long"}"#, + Value::Map( + [ + ("a".to_string(), Value::Long(1i64)), + ("b".to_string(), Value::Long(3i64)), + ("c".to_string(), Value::Long(2i64)), + ] + .iter() + .cloned() + .collect(), + ), + ), + ( + r#"["string", "null", "long"]"#, + Value::Union(1, Box::new(Value::Null)), + ), + ( + r#"{"type": "record", "name": "Test", "fields": [{"name": "f", "type": "long"}]}"#, + Value::Record(vec![("f".to_string(), Value::Long(1))]), + ), + ] + }) +} - static ref DEFAULT_VALUE_EXAMPLES: Vec<(&'static str, &'static str, Value)> = vec![ - (r#""null""#, "null", Value::Null), - (r#""boolean""#, "true", Value::Boolean(true)), - (r#""string""#, r#""foo""#, Value::String("foo".to_string())), - (r#""bytes""#, r#""a""#, Value::Bytes(vec![97])), // ASCII 'a' => one byte - (r#""bytes""#, r#""\u00FF""#, Value::Bytes(vec![195, 191])), // The value is between U+0080 and U+07FF => two bytes - (r#""int""#, "5", Value::Int(5)), - (r#""long""#, "5", Value::Long(5)), - (r#""float""#, "1.1", Value::Float(1.1)), - (r#""double""#, "1.1", Value::Double(1.1)), - (r#"{"type": "fixed", "name": "F", "size": 2}"#, r#""a""#, Value::Fixed(1, vec![97])), // ASCII 'a' => one byte - (r#"{"type": "fixed", "name": "F", "size": 2}"#, r#""\u00FF""#, Value::Fixed(2, vec![195, 191])), // The value is between U+0080 and U+07FF => two bytes - (r#"{"type": "enum", "name": "F", "symbols": ["FOO", "BAR"]}"#, r#""FOO""#, Value::Enum(0, "FOO".to_string())), - (r#"{"type": "array", "items": "int"}"#, "[1, 2, 3]", Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])), - (r#"{"type": "map", "values": "int"}"#, r#"{"a": 1, "b": 2}"#, Value::Map([("a".to_string(), Value::Int(1)), ("b".to_string(), Value::Int(2))].iter().cloned().collect())), - (r#"["int", "null"]"#, "5", Value::Union(0, Box::new(Value::Int(5)))), - (r#"{"type": "record", "name": "F", "fields": [{"name": "A", "type": "int"}]}"#, r#"{"A": 5}"#,Value::Record(vec![("A".to_string(), Value::Int(5))])), - (r#"["null", "int"]"#, "null", Value::Union(0, Box::new(Value::Null))), - ]; +fn binary_encodings() -> &'static Vec<(i64, Vec<u8>)> { + static BINARY_ENCODINGS_ONCE: OnceLock<Vec<(i64, Vec<u8>)>> = OnceLock::new(); + BINARY_ENCODINGS_ONCE.get_or_init(|| { + vec![ + (0, vec![0x00]), + (-1, vec![0x01]), + (1, vec![0x02]), + (-2, vec![0x03]), + (2, vec![0x04]), + (-64, vec![0x7f]), + (64, vec![0x80, 0x01]), + (8192, vec![0x80, 0x80, 0x01]), + (-8193, vec![0x81, 0x80, 0x01]), + ] + }) +} - static ref LONG_RECORD_SCHEMA: Schema = Schema::parse_str(r#" - { - "type": "record", - "name": "Test", - "fields": [ - {"name": "A", "type": "int"}, - {"name": "B", "type": "int"}, - {"name": "C", "type": "int"}, - {"name": "D", "type": "int"}, - {"name": "E", "type": "int"}, - {"name": "F", "type": "int"}, - {"name": "G", "type": "int"} +fn default_value_examples() -> &'static Vec<(&'static str, &'static str, Value)> { + static DEFAULT_VALUE_EXAMPLES_ONCE: OnceLock<Vec<(&'static str, &'static str, Value)>> = + OnceLock::new(); + DEFAULT_VALUE_EXAMPLES_ONCE.get_or_init(|| { + vec![ + (r#""null""#, "null", Value::Null), + (r#""boolean""#, "true", Value::Boolean(true)), + (r#""string""#, r#""foo""#, Value::String("foo".to_string())), + (r#""bytes""#, r#""a""#, Value::Bytes(vec![97])), // ASCII 'a' => one byte + (r#""bytes""#, r#""\u00FF""#, Value::Bytes(vec![195, 191])), // The value is between U+0080 and U+07FF => two bytes + (r#""int""#, "5", Value::Int(5)), + (r#""long""#, "5", Value::Long(5)), + (r#""float""#, "1.1", Value::Float(1.1)), + (r#""double""#, "1.1", Value::Double(1.1)), + ( + r#"{"type": "fixed", "name": "F", "size": 2}"#, + r#""a""#, + Value::Fixed(1, vec![97]), + ), // ASCII 'a' => one byte + ( + r#"{"type": "fixed", "name": "F", "size": 2}"#, + r#""\u00FF""#, + Value::Fixed(2, vec![195, 191]), + ), // The value is between U+0080 and U+07FF => two bytes + ( + r#"{"type": "enum", "name": "F", "symbols": ["FOO", "BAR"]}"#, + r#""FOO""#, + Value::Enum(0, "FOO".to_string()), + ), + ( + r#"{"type": "array", "items": "int"}"#, + "[1, 2, 3]", + Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]), + ), + ( + r#"{"type": "map", "values": "int"}"#, + r#"{"a": 1, "b": 2}"#, + Value::Map( + [ + ("a".to_string(), Value::Int(1)), + ("b".to_string(), Value::Int(2)), + ] + .iter() + .cloned() + .collect(), + ), + ), + ( + r#"["int", "null"]"#, + "5", + Value::Union(0, Box::new(Value::Int(5))), + ), + ( + r#"{"type": "record", "name": "F", "fields": [{"name": "A", "type": "int"}]}"#, + r#"{"A": 5}"#, + Value::Record(vec![("A".to_string(), Value::Int(5))]), + ), + ( + r#"["null", "int"]"#, + "null", + Value::Union(0, Box::new(Value::Null)), + ), ] - } - "#).unwrap(); + }) +} - static ref LONG_RECORD_DATUM: Value = Value::Record(vec![ - ("A".to_string(), Value::Int(1)), - ("B".to_string(), Value::Int(2)), - ("C".to_string(), Value::Int(3)), - ("D".to_string(), Value::Int(4)), - ("E".to_string(), Value::Int(5)), - ("F".to_string(), Value::Int(6)), - ("G".to_string(), Value::Int(7)), - ]); +fn long_record_schema() -> &'static Schema { + static LONG_RECORD_SCHEMA_ONCE: OnceLock<Schema> = OnceLock::new(); + LONG_RECORD_SCHEMA_ONCE.get_or_init(|| { + Schema::parse_str( + r#" +{ + "type": "record", + "name": "Test", + "fields": [ + {"name": "A", "type": "int"}, + {"name": "B", "type": "int"}, + {"name": "C", "type": "int"}, + {"name": "D", "type": "int"}, + {"name": "E", "type": "int"}, + {"name": "F", "type": "int"}, + {"name": "G", "type": "int"} + ] +} +"#, + ) + .unwrap() + }) +} + +fn long_record_datum() -> &'static Value { + static LONG_RECORD_DATUM_ONCE: OnceLock<Value> = OnceLock::new(); + LONG_RECORD_DATUM_ONCE.get_or_init(|| { + Value::Record(vec![ + ("A".to_string(), Value::Int(1)), + ("B".to_string(), Value::Int(2)), + ("C".to_string(), Value::Int(3)), + ("D".to_string(), Value::Int(4)), + ("E".to_string(), Value::Int(5)), + ("F".to_string(), Value::Int(6)), + ("G".to_string(), Value::Int(7)), + ]) + }) } #[test] fn test_validate() -> TestResult { - for (raw_schema, value) in SCHEMAS_TO_VALIDATE.iter() { + for (raw_schema, value) in schemas_to_validate().iter() { let schema = Schema::parse_str(raw_schema)?; assert!( value.validate(&schema), @@ -114,7 +213,7 @@ fn test_validate() -> TestResult { #[test] fn test_round_trip() -> TestResult { - for (raw_schema, value) in SCHEMAS_TO_VALIDATE.iter() { + for (raw_schema, value) in schemas_to_validate().iter() { let schema = Schema::parse_str(raw_schema)?; let encoded = to_avro_datum(&schema, value.clone()).unwrap(); let decoded = from_avro_datum(&schema, &mut Cursor::new(encoded), None).unwrap(); @@ -126,7 +225,7 @@ fn test_round_trip() -> TestResult { #[test] fn test_binary_int_encoding() -> TestResult { - for (number, hex_encoding) in BINARY_ENCODINGS.iter() { + for (number, hex_encoding) in binary_encodings().iter() { let encoded = to_avro_datum(&Schema::Int, Value::Int(*number as i32))?; assert_eq!(&encoded, hex_encoding); } @@ -136,7 +235,7 @@ fn test_binary_int_encoding() -> TestResult { #[test] fn test_binary_long_encoding() -> TestResult { - for (number, hex_encoding) in BINARY_ENCODINGS.iter() { + for (number, hex_encoding) in binary_encodings().iter() { let encoded = to_avro_datum(&Schema::Long, Value::Long(*number))?; assert_eq!(&encoded, hex_encoding); } @@ -196,7 +295,7 @@ fn test_unknown_symbol() -> TestResult { #[test] fn test_default_value() -> TestResult { - for (field_type, default_json, default_datum) in DEFAULT_VALUE_EXAMPLES.iter() { + for (field_type, default_json, default_datum) in default_value_examples().iter() { let reader_schema = Schema::parse_str(&format!( r#"{{ "type": "record", @@ -207,9 +306,9 @@ fn test_default_value() -> TestResult { }}"# ))?; let datum_to_read = Value::Record(vec![("H".to_string(), default_datum.clone())]); - let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, LONG_RECORD_DATUM.clone())?; + let encoded = to_avro_datum(long_record_schema(), long_record_datum().clone())?; let datum_read = from_avro_datum( - &LONG_RECORD_SCHEMA, + long_record_schema(), &mut Cursor::new(encoded), Some(&reader_schema), )?; @@ -234,9 +333,9 @@ fn test_no_default_value() -> TestResult { ] }"#, )?; - let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, LONG_RECORD_DATUM.clone())?; + let encoded = to_avro_datum(long_record_schema(), long_record_datum().clone())?; let result = from_avro_datum( - &LONG_RECORD_SCHEMA, + long_record_schema(), &mut Cursor::new(encoded), Some(&reader_schema), ); @@ -263,9 +362,9 @@ fn test_projection() -> TestResult { ("E".to_string(), Value::Int(5)), ("F".to_string(), Value::Int(6)), ]); - let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, LONG_RECORD_DATUM.clone())?; + let encoded = to_avro_datum(long_record_schema(), long_record_datum().clone())?; let datum_read = from_avro_datum( - &LONG_RECORD_SCHEMA, + long_record_schema(), &mut Cursor::new(encoded), Some(&reader_schema), )?; @@ -292,9 +391,9 @@ fn test_field_order() -> TestResult { ("F".to_string(), Value::Int(6)), ("E".to_string(), Value::Int(5)), ]); - let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, LONG_RECORD_DATUM.clone())?; + let encoded = to_avro_datum(long_record_schema(), long_record_datum().clone())?; let datum_read = from_avro_datum( - &LONG_RECORD_SCHEMA, + long_record_schema(), &mut Cursor::new(encoded), Some(&reader_schema), )?; diff --git a/lang/rust/avro/tests/schema.rs b/lang/rust/avro/tests/schema.rs index 223305876..3d2db1a33 100644 --- a/lang/rust/avro/tests/schema.rs +++ b/lang/rust/avro/tests/schema.rs @@ -15,7 +15,10 @@ // specific language governing permissions and limitations // under the License. -use std::io::{Cursor, Read}; +use std::{ + io::{Cursor, Read}, + sync::OnceLock, +}; use apache_avro::{ from_avro_datum, from_value, @@ -25,7 +28,6 @@ use apache_avro::{ Codec, Error, Reader, Schema, Writer, }; use apache_avro_test_helper::{init, TestResult}; -use lazy_static::lazy_static; const PRIMITIVE_EXAMPLES: &[(&str, bool)] = &[ (r#""null""#, true), @@ -589,29 +591,35 @@ const TIMESTAMPMICROS_LOGICAL_TYPE: &[(&str, bool)] = &[ ), ]; -lazy_static! { - static ref EXAMPLES: Vec<(&'static str, bool)> = Vec::new() - .iter() - .copied() - .chain(PRIMITIVE_EXAMPLES.iter().copied()) - .chain(FIXED_EXAMPLES.iter().copied()) - .chain(ENUM_EXAMPLES.iter().copied()) - .chain(ARRAY_EXAMPLES.iter().copied()) - .chain(MAP_EXAMPLES.iter().copied()) - .chain(UNION_EXAMPLES.iter().copied()) - .chain(RECORD_EXAMPLES.iter().copied()) - .chain(DOC_EXAMPLES.iter().copied()) - .chain(OTHER_ATTRIBUTES_EXAMPLES.iter().copied()) - .chain(DECIMAL_LOGICAL_TYPE.iter().copied()) - .chain(DECIMAL_LOGICAL_TYPE_ATTRIBUTES.iter().copied()) - .chain(DATE_LOGICAL_TYPE.iter().copied()) - .chain(TIMEMILLIS_LOGICAL_TYPE.iter().copied()) - .chain(TIMEMICROS_LOGICAL_TYPE.iter().copied()) - .chain(TIMESTAMPMILLIS_LOGICAL_TYPE.iter().copied()) - .chain(TIMESTAMPMICROS_LOGICAL_TYPE.iter().copied()) - .collect(); - static ref VALID_EXAMPLES: Vec<(&'static str, bool)> = - EXAMPLES.iter().copied().filter(|s| s.1).collect(); +fn examples() -> &'static Vec<(&'static str, bool)> { + static EXAMPLES_ONCE: OnceLock<Vec<(&'static str, bool)>> = OnceLock::new(); + EXAMPLES_ONCE.get_or_init(|| { + Vec::new() + .iter() + .copied() + .chain(PRIMITIVE_EXAMPLES.iter().copied()) + .chain(FIXED_EXAMPLES.iter().copied()) + .chain(ENUM_EXAMPLES.iter().copied()) + .chain(ARRAY_EXAMPLES.iter().copied()) + .chain(MAP_EXAMPLES.iter().copied()) + .chain(UNION_EXAMPLES.iter().copied()) + .chain(RECORD_EXAMPLES.iter().copied()) + .chain(DOC_EXAMPLES.iter().copied()) + .chain(OTHER_ATTRIBUTES_EXAMPLES.iter().copied()) + .chain(DECIMAL_LOGICAL_TYPE.iter().copied()) + .chain(DECIMAL_LOGICAL_TYPE_ATTRIBUTES.iter().copied()) + .chain(DATE_LOGICAL_TYPE.iter().copied()) + .chain(TIMEMILLIS_LOGICAL_TYPE.iter().copied()) + .chain(TIMEMICROS_LOGICAL_TYPE.iter().copied()) + .chain(TIMESTAMPMILLIS_LOGICAL_TYPE.iter().copied()) + .chain(TIMESTAMPMICROS_LOGICAL_TYPE.iter().copied()) + .collect() + }) +} + +fn valid_examples() -> &'static Vec<(&'static str, bool)> { + static VALID_EXAMPLES_ONCE: OnceLock<Vec<(&'static str, bool)>> = OnceLock::new(); + VALID_EXAMPLES_ONCE.get_or_init(|| examples().iter().copied().filter(|s| s.1).collect()) } #[test] @@ -668,7 +676,7 @@ fn test_correct_recursive_extraction() -> TestResult { #[test] fn test_parse() -> TestResult { init(); - for (raw_schema, valid) in EXAMPLES.iter() { + for (raw_schema, valid) in examples().iter() { let schema = Schema::parse_str(raw_schema); if *valid { assert!( @@ -688,7 +696,7 @@ fn test_parse() -> TestResult { #[test] fn test_3799_parse_reader() -> TestResult { init(); - for (raw_schema, valid) in EXAMPLES.iter() { + for (raw_schema, valid) in examples().iter() { let schema = Schema::parse_reader(&mut Cursor::new(raw_schema)); if *valid { assert!( @@ -704,7 +712,7 @@ fn test_3799_parse_reader() -> TestResult { } // Ensure it works for trait objects too. - for (raw_schema, valid) in EXAMPLES.iter() { + for (raw_schema, valid) in examples().iter() { let reader: &mut dyn Read = &mut Cursor::new(raw_schema); let schema = Schema::parse_reader(reader); if *valid { @@ -744,7 +752,7 @@ fn test_3799_raise_io_error_from_parse_read() -> Result<(), String> { /// Test that the string generated by an Avro Schema object is, in fact, a valid Avro schema. fn test_valid_cast_to_string_after_parse() -> TestResult { init(); - for (raw_schema, _) in VALID_EXAMPLES.iter() { + for (raw_schema, _) in valid_examples().iter() { let schema = Schema::parse_str(raw_schema)?; Schema::parse_str(schema.canonical_form().as_str())?; } @@ -757,7 +765,7 @@ fn test_valid_cast_to_string_after_parse() -> TestResult { /// 3. Ensure "original" and "round trip" schemas are equivalent. fn test_equivalence_after_round_trip() -> TestResult { init(); - for (raw_schema, _) in VALID_EXAMPLES.iter() { + for (raw_schema, _) in valid_examples().iter() { let original_schema = Schema::parse_str(raw_schema)?; let round_trip_schema = Schema::parse_str(original_schema.canonical_form().as_str())?; assert_eq!(original_schema, round_trip_schema); diff --git a/lang/rust/avro_test_helper/Cargo.toml b/lang/rust/avro_test_helper/Cargo.toml index 3330dce24..f5a77125f 100644 --- a/lang/rust/avro_test_helper/Cargo.toml +++ b/lang/rust/avro_test_helper/Cargo.toml @@ -35,6 +35,5 @@ anyhow = { default-features = false, version = "1.0.75", features = ["std"] } color-backtrace = { default-features = false, version = "0.5.1" } ctor = { default-features = false, version = "0.2.4" } env_logger = { default-features = false, version = "0.10.0" } -lazy_static = { default-features = false, version = "1.4.0" } log = { default-features = false, version = "0.4.20" } ref_thread_local = { default-features = false, version = "0.1.1" } diff --git a/lang/rust/avro_test_helper/src/logger.rs b/lang/rust/avro_test_helper/src/logger.rs index 09fc1bede..505e42541 100644 --- a/lang/rust/avro_test_helper/src/logger.rs +++ b/lang/rust/avro_test_helper/src/logger.rs @@ -16,9 +16,9 @@ // under the License. use crate::LOG_MESSAGES; -use lazy_static::lazy_static; use log::{LevelFilter, Log, Metadata}; use ref_thread_local::RefThreadLocal; +use std::sync::OnceLock; struct TestLogger { delegate: env_logger::Logger, @@ -41,14 +41,15 @@ impl Log for TestLogger { fn flush(&self) {} } -lazy_static! { +fn test_logger() -> &'static TestLogger { // Lazy static because the Logger has to be 'static - static ref TEST_LOGGER: TestLogger = TestLogger { + static TEST_LOGGER_ONCE: OnceLock<TestLogger> = OnceLock::new(); + TEST_LOGGER_ONCE.get_or_init(|| TestLogger { delegate: env_logger::Builder::from_default_env() .filter_level(LevelFilter::Off) .parse_default_env() .build(), - }; + }) } pub fn clear_log_messages() { @@ -70,7 +71,7 @@ pub fn assert_logged(expected_message: &str) { #[cfg(not(target_arch = "wasm32"))] pub(crate) fn install() { - log::set_logger(&*TEST_LOGGER) + log::set_logger(test_logger()) .map(|_| log::set_max_level(LevelFilter::Trace)) .map_err(|err| { eprintln!("Failed to set the custom logger: {err:?}");
