Rust: test that no heap allocs happen on hot paths (#5022)
authorRobert <rw@users.noreply.github.com>
Thu, 1 Nov 2018 01:08:14 +0000 (18:08 -0700)
committerGitHub <noreply@github.com>
Thu, 1 Nov 2018 01:08:14 +0000 (18:08 -0700)
tests/RustTest.bat
tests/RustTest.sh
tests/rust_usage_test/Cargo.toml
tests/rust_usage_test/bin/alloc_check.rs [new file with mode: 0644]

index 3bd312b..ba9cfd2 100644 (file)
@@ -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 ..
index 566c3fd..ac050c4 100755 (executable)
@@ -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
index 9392b12..490d6d2 100644 (file)
@@ -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 (file)
index 0000000..54469a6
--- /dev/null
@@ -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<u8> = 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::<u8>(), 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");
+}