From 4c3b6c247d8bb76b524c013e22ecfeb3f83ad680 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 31 Oct 2018 18:08:14 -0700 Subject: [PATCH] Rust: test that no heap allocs happen on hot paths (#5022) --- tests/RustTest.bat | 1 + tests/RustTest.sh | 11 ++- tests/rust_usage_test/Cargo.toml | 4 + tests/rust_usage_test/bin/alloc_check.rs | 140 +++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 tests/rust_usage_test/bin/alloc_check.rs diff --git a/tests/RustTest.bat b/tests/RustTest.bat index 3bd312b..ba9cfd2 100644 --- a/tests/RustTest.bat +++ b/tests/RustTest.bat @@ -19,4 +19,5 @@ rem TODO(rw): how do we make this script abort the calling script in appveyor? cd rust_usage_test cargo test -- --quiet || exit /b 1 +cargo run --bin=alloc_check || exit /b 1 cd .. diff --git a/tests/RustTest.sh b/tests/RustTest.sh index 566c3fd..ac050c4 100755 --- a/tests/RustTest.sh +++ b/tests/RustTest.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -ex +set -e # # Copyright 2018 Google Inc. All rights reserved. # @@ -25,4 +25,13 @@ else exit 1 fi +cargo run --bin=alloc_check +TEST_RESULT=$? +if [[ $TEST_RESULT == 0 ]]; then + echo "OK: Rust heap alloc test passed." +else + echo "KO: Rust heap alloc test failed." + exit 1 +fi + cargo bench diff --git a/tests/rust_usage_test/Cargo.toml b/tests/rust_usage_test/Cargo.toml index 9392b12..490d6d2 100644 --- a/tests/rust_usage_test/Cargo.toml +++ b/tests/rust_usage_test/Cargo.toml @@ -10,6 +10,10 @@ flatbuffers = { path = "../../rust/flatbuffers" } name = "monster_example" path = "bin/monster_example.rs" +[[bin]] +name = "alloc_check" +path = "bin/alloc_check.rs" + [dev-dependencies] quickcheck = "0.6" diff --git a/tests/rust_usage_test/bin/alloc_check.rs b/tests/rust_usage_test/bin/alloc_check.rs new file mode 100644 index 0000000..54469a6 --- /dev/null +++ b/tests/rust_usage_test/bin/alloc_check.rs @@ -0,0 +1,140 @@ +// define a passthrough allocator that tracks alloc calls. +// (note that we can't drop this in to the usual test suite, because it's a big +// global variable). +use std::alloc::{GlobalAlloc, Layout, System}; + +static mut N_ALLOCS: usize = 0; + +struct TrackingAllocator; + +impl TrackingAllocator { + fn n_allocs(&self) -> usize { + unsafe { N_ALLOCS } + } +} +unsafe impl GlobalAlloc for TrackingAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + N_ALLOCS += 1; + System.alloc(layout) + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + System.dealloc(ptr, layout) + } +} + +// use the tracking allocator: +#[global_allocator] +static A: TrackingAllocator = TrackingAllocator; + +// import the flatbuffers generated code: +extern crate flatbuffers; +#[path = "../../monster_test_generated.rs"] +mod monster_test_generated; +pub use monster_test_generated::my_game; + +// verbatim from the test suite: +fn create_serialized_example_with_generated_code(builder: &mut flatbuffers::FlatBufferBuilder) { + let mon = { + let s0 = builder.create_string("test1"); + let s1 = builder.create_string("test2"); + let fred_name = builder.create_string("Fred"); + + // can't inline creation of this Vec3 because we refer to it by reference, so it must live + // long enough to be used by MonsterArgs. + let pos = my_game::example::Vec3::new(1.0, 2.0, 3.0, 3.0, my_game::example::Color::Green, &my_game::example::Test::new(5i16, 6i8)); + + let args = my_game::example::MonsterArgs{ + hp: 80, + mana: 150, + name: Some(builder.create_string("MyMonster")), + pos: Some(&pos), + test_type: my_game::example::Any::Monster, + test: Some(my_game::example::Monster::create(builder, &my_game::example::MonsterArgs{ + name: Some(fred_name), + ..Default::default() + }).as_union_value()), + inventory: Some(builder.create_vector_direct(&[0u8, 1, 2, 3, 4][..])), + test4: Some(builder.create_vector_direct(&[my_game::example::Test::new(10, 20), + my_game::example::Test::new(30, 40)])), + testarrayofstring: Some(builder.create_vector(&[s0, s1])), + ..Default::default() + }; + my_game::example::Monster::create(builder, &args) + }; + my_game::example::finish_monster_buffer(builder, mon); +} + +fn main() { + // test the allocation tracking: + { + let before = A.n_allocs(); + let _x: Vec = vec![0u8; 1]; + let after = A.n_allocs(); + assert_eq!(before + 1, after); + } + + let builder = &mut flatbuffers::FlatBufferBuilder::new(); + { + // warm up the builder (it can make small allocs internally, such as for storing vtables): + create_serialized_example_with_generated_code(builder); + } + + // reset the builder, clearing its heap-allocted memory: + builder.reset(); + + { + let before = A.n_allocs(); + create_serialized_example_with_generated_code(builder); + let after = A.n_allocs(); + assert_eq!(before, after, "KO: Heap allocs occurred in Rust write path"); + } + + let buf = builder.finished_data(); + + // use the allocation tracking on the read path: + { + let before = A.n_allocs(); + + // do many reads, forcing them to execute by using assert_eq: + { + let m = my_game::example::get_root_as_monster(buf); + assert_eq!(80, m.hp()); + assert_eq!(150, m.mana()); + assert_eq!("MyMonster", m.name()); + + let pos = m.pos().unwrap(); + assert_eq!(pos.x(), 1.0f32); + assert_eq!(pos.y(), 2.0f32); + assert_eq!(pos.z(), 3.0f32); + assert_eq!(pos.test1(), 3.0f64); + assert_eq!(pos.test2(), my_game::example::Color::Green); + let pos_test3 = pos.test3(); + assert_eq!(pos_test3.a(), 5i16); + assert_eq!(pos_test3.b(), 6i8); + assert_eq!(m.test_type(), my_game::example::Any::Monster); + let table2 = m.test().unwrap(); + let m2 = my_game::example::Monster::init_from_table(table2); + + assert_eq!(m2.name(), "Fred"); + + let inv = m.inventory().unwrap(); + assert_eq!(inv.len(), 5); + assert_eq!(inv.iter().sum::(), 10u8); + + let test4 = m.test4().unwrap(); + assert_eq!(test4.len(), 2); + assert_eq!(test4[0].a() as i32 + test4[0].b() as i32 + + test4[1].a() as i32 + test4[1].b() as i32, 100); + + let testarrayofstring = m.testarrayofstring().unwrap(); + assert_eq!(testarrayofstring.len(), 2); + assert_eq!(testarrayofstring.get(0), "test1"); + assert_eq!(testarrayofstring.get(1), "test2"); + } + + // assert that no allocs occurred: + let after = A.n_allocs(); + assert_eq!(before, after, "KO: Heap allocs occurred in Rust read path"); + } + println!("Rust: Heap alloc checks completed successfully"); +} -- 2.7.4