diff options
Diffstat (limited to 'xpcom/rust/gtest')
-rw-r--r-- | xpcom/rust/gtest/bench-collections/Bench.cpp | 297 | ||||
-rw-r--r-- | xpcom/rust/gtest/bench-collections/Cargo.toml | 12 | ||||
-rw-r--r-- | xpcom/rust/gtest/bench-collections/bench.rs | 101 | ||||
-rw-r--r-- | xpcom/rust/gtest/moz.build | 14 | ||||
-rw-r--r-- | xpcom/rust/gtest/moz_task/Cargo.toml | 13 | ||||
-rw-r--r-- | xpcom/rust/gtest/moz_task/TestMozTask.cpp | 14 | ||||
-rw-r--r-- | xpcom/rust/gtest/moz_task/test.rs | 78 | ||||
-rw-r--r-- | xpcom/rust/gtest/nsstring/Cargo.toml | 12 | ||||
-rw-r--r-- | xpcom/rust/gtest/nsstring/TestnsString.cpp | 177 | ||||
-rw-r--r-- | xpcom/rust/gtest/nsstring/test.rs | 131 | ||||
-rw-r--r-- | xpcom/rust/gtest/xpcom/Cargo.toml | 14 | ||||
-rw-r--r-- | xpcom/rust/gtest/xpcom/TestXpcom.cpp | 66 | ||||
-rw-r--r-- | xpcom/rust/gtest/xpcom/test.rs | 131 |
13 files changed, 1060 insertions, 0 deletions
diff --git a/xpcom/rust/gtest/bench-collections/Bench.cpp b/xpcom/rust/gtest/bench-collections/Bench.cpp new file mode 100644 index 0000000000..41152470c7 --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/Bench.cpp @@ -0,0 +1,297 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Overview +// -------- +// This file measures the speed of various implementations of C++ and Rust +// collections (hash tables, etc.) used within the codebase. There are a small +// number of benchmarks for each collection type; each benchmark tests certain +// operations (insertion, lookup, iteration, etc.) More benchmarks could easily +// be envisioned, but this small number is enough to characterize the major +// differences between implementations, while keeping the file size and +// complexity low. +// +// Details +// ------- +// The file uses `MOZ_GTEST_BENCH_F` so that results are integrated into +// PerfHerder. It is also designed so that individual test benchmarks can be +// run under a profiler. +// +// The C++ code uses `MOZ_RELEASE_ASSERT` extensively to check values and +// ensure operations aren't optimized away by the compiler. The Rust code uses +// `assert!()`. These should be roughly equivalent, but aren't guaranteed to be +// the same. As a result, the intra-C++ comparisons should be reliable, and the +// intra-Rust comparisons should be reliable, but the C++ vs. Rust comparisons +// may be less reliable. +// +// Note that the Rust implementations run very slowly without --enable-release. +// +// Profiling +// --------- +// If you want to measure a particular implementation under a profiler such as +// Callgrind, do something like this: +// +// MOZ_RUN_GTEST=1 GTEST_FILTER='*BenchCollections*$IMPL*' +// valgrind --tool=callgrind --callgrind-out-file=clgout +// $OBJDIR/dist/bin/firefox -unittest +// callgrind_annotate --auto=yes clgout > clgann +// +// where $IMPL is part of an implementation name in a test (e.g. "PLDHash", +// "MozHash") and $OBJDIR is an objdir containing a --enable-release build. +// +// Note that multiple processes are spawned, so `clgout` gets overwritten +// multiple times, but the last process to write its profiling data to file is +// the one of interest. (Alternatively, use --callgrind-out-file=clgout.%p to +// get separate output files for each process, with a PID suffix.) + +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH +#include "mozilla/AllocPolicy.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "PLDHashTable.h" +#include <unordered_set> + +using namespace mozilla; + +// This function gives a pseudo-random sequence with the following properties: +// - Deterministic and platform-independent. +// - No duplicates in the first VALS_LEN results, which is useful for ensuring +// the tables get to a particular size, and also for guaranteeing lookups +// that fail. +static uintptr_t MyRand() { + static uintptr_t s = 0; + s = s * 1103515245 + 12345; + return s; +} + +// Keep this in sync with Params in bench.rs. +struct Params { + const char* mConfigName; + size_t mNumInserts; // Insert this many unique keys + size_t mNumSuccessfulLookups; // Does mNumInserts lookups each time + size_t mNumFailingLookups; // Does mNumInserts lookups each time + size_t mNumIterations; // Iterates the full table each time + bool mRemoveInserts; // Remove all entries at end? +}; + +// We don't use std::unordered_{set,map}, but it's an interesting thing to +// benchmark against. +// +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_unordered_set(const Params* aParams, void** aVals, + size_t aLen) { + std::unordered_set<void*> hs; + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.insert(aVals[j]); + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.find(aVals[j]) != hs.end()); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(hs.find(aVals[j]) == hs.end()); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (const auto& elem : hs) { + (void)elem; + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.size() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.erase(aVals[j]) == 1); + } + MOZ_RELEASE_ASSERT(hs.size() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.size() == aParams->mNumInserts); + } +} + +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_PLDHashTable(const Params* aParams, void** aVals, + size_t aLen) { + PLDHashTable hs(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + auto entry = static_cast<PLDHashEntryStub*>(hs.Add(aVals[j])); + MOZ_RELEASE_ASSERT(!entry->key); + entry->key = aVals[j]; + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.Search(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(!hs.Search(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (auto iter = hs.Iter(); !iter.Done(); iter.Next()) { + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.EntryCount() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.Remove(aVals[j]); + } + MOZ_RELEASE_ASSERT(hs.EntryCount() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.EntryCount() == aParams->mNumInserts); + } +} + +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_MozHashSet(const Params* aParams, void** aVals, + size_t aLen) { + mozilla::HashSet<void*, mozilla::DefaultHasher<void*>, MallocAllocPolicy> hs; + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.put(aVals[j])); + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.has(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(!hs.has(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (auto iter = hs.iter(); !iter.done(); iter.next()) { + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.count() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.remove(aVals[j]); + } + MOZ_RELEASE_ASSERT(hs.count() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.count() == aParams->mNumInserts); + } +} + +extern "C" { +void Bench_Rust_HashSet(const Params* params, void** aVals, size_t aLen); +void Bench_Rust_FnvHashSet(const Params* params, void** aVals, size_t aLen); +void Bench_Rust_FxHashSet(const Params* params, void** aVals, size_t aLen); +} + +static const size_t VALS_LEN = 131072; + +// Each benchmark measures a different aspect of performance. +// Note that no "Inserts" value can exceed VALS_LEN. +// Also, if any failing lookups are done, Inserts must be <= VALS_LEN/2. +const Params gParamsList[] = { + // clang-format off + // Successful Failing Remove + // Inserts lookups lookups Iterations inserts + { "succ_lookups", 1024, 5000, 0, 0, false }, + { "fail_lookups", 1024, 0, 5000, 0, false }, + { "insert_remove", VALS_LEN, 0, 0, 0, true }, + { "iterate", 1024, 0, 0, 5000, false }, + // clang-format on +}; + +class BenchCollections : public ::testing::Test { + protected: + void SetUp() override { + StaticMutexAutoLock lock(sValsMutex); + + if (!sVals) { + sVals = (void**)malloc(VALS_LEN * sizeof(void*)); + for (size_t i = 0; i < VALS_LEN; i++) { + // This leaves the high 32 bits zero on 64-bit platforms, but that + // should still be enough randomness to get typical behaviour. + sVals[i] = reinterpret_cast<void*>(uintptr_t(MyRand())); + } + } + + printf("\n"); + for (size_t i = 0; i < ArrayLength(gParamsList); i++) { + const Params* params = &gParamsList[i]; + printf("%14s", params->mConfigName); + } + printf("%14s\n", "total"); + } + + public: + void BenchImpl(void (*aBench)(const Params*, void**, size_t)) { + StaticMutexAutoLock lock(sValsMutex); + + double total = 0; + for (size_t i = 0; i < ArrayLength(gParamsList); i++) { + const Params* params = &gParamsList[i]; + TimeStamp t1 = TimeStamp::Now(); + aBench(params, sVals, VALS_LEN); + TimeStamp t2 = TimeStamp::Now(); + double t = (t2 - t1).ToMilliseconds(); + printf("%11.1f ms", t); + total += t; + } + printf("%11.1f ms\n", total); + } + + private: + // Random values used in the benchmarks. + static void** sVals; + + // A mutex that protects all benchmark operations, ensuring that two + // benchmarks never run concurrently. + static StaticMutex sValsMutex MOZ_UNANNOTATED; +}; + +void** BenchCollections::sVals; +StaticMutex BenchCollections::sValsMutex; + +MOZ_GTEST_BENCH_F(BenchCollections, unordered_set, + [this] { BenchImpl(Bench_Cpp_unordered_set); }); + +MOZ_GTEST_BENCH_F(BenchCollections, PLDHash, + [this] { BenchImpl(Bench_Cpp_PLDHashTable); }); + +MOZ_GTEST_BENCH_F(BenchCollections, MozHash, + [this] { BenchImpl(Bench_Cpp_MozHashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustHash, + [this] { BenchImpl(Bench_Rust_HashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustFnvHash, + [this] { BenchImpl(Bench_Rust_FnvHashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustFxHash, + [this] { BenchImpl(Bench_Rust_FxHashSet); }); diff --git a/xpcom/rust/gtest/bench-collections/Cargo.toml b/xpcom/rust/gtest/bench-collections/Cargo.toml new file mode 100644 index 0000000000..857d87cffd --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bench-collections-gtest" +version = "0.1.0" +license = "MPL-2.0" +description = "Benchmarks for various collections" + +[dependencies] +fnv = "1.0" +fxhash = "0.2.1" + +[lib] +path = "bench.rs" diff --git a/xpcom/rust/gtest/bench-collections/bench.rs b/xpcom/rust/gtest/bench-collections/bench.rs new file mode 100644 index 0000000000..1aba69f01f --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/bench.rs @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case)] + +extern crate fnv; +extern crate fxhash; + +use fnv::FnvHashSet; +use fxhash::FxHashSet; +use std::collections::HashSet; +use std::os::raw::{c_char, c_void}; +use std::slice; + +/// Keep this in sync with Params in Bench.cpp. +#[derive(Debug)] +#[repr(C)] +pub struct Params { + config_name: *const c_char, + num_inserts: usize, + num_successful_lookups: usize, + num_failing_lookups: usize, + num_iterations: usize, + remove_inserts: bool, +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_HashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs: HashSet<_> = std::collections::HashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_FnvHashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs = FnvHashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_FxHashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs = FxHashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +// Keep this in sync with all the other Bench_*() functions. +fn Bench_Rust<H: std::hash::BuildHasher>( + mut hs: HashSet<*const c_void, H>, + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let params = unsafe { &*params }; + let vals = unsafe { slice::from_raw_parts(vals, len) }; + + for j in 0..params.num_inserts { + hs.insert(vals[j]); + } + + for _i in 0..params.num_successful_lookups { + for j in 0..params.num_inserts { + assert!(hs.contains(&vals[j])); + } + } + + for _i in 0..params.num_failing_lookups { + for j in params.num_inserts..params.num_inserts * 2 { + assert!(!hs.contains(&vals[j])); + } + } + + for _i in 0..params.num_iterations { + let mut n = 0; + for _ in hs.iter() { + n += 1; + } + assert!(params.num_inserts == n); + assert!(hs.len() == n); + } + + if params.remove_inserts { + for j in 0..params.num_inserts { + assert!(hs.remove(&vals[j])); + } + assert!(hs.is_empty()); + } else { + assert!(hs.len() == params.num_inserts); + } +} diff --git a/xpcom/rust/gtest/moz.build b/xpcom/rust/gtest/moz.build new file mode 100644 index 0000000000..d3457930c8 --- /dev/null +++ b/xpcom/rust/gtest/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "bench-collections/Bench.cpp", + "moz_task/TestMozTask.cpp", + "nsstring/TestnsString.cpp", + "xpcom/TestXpcom.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/xpcom/rust/gtest/moz_task/Cargo.toml b/xpcom/rust/gtest/moz_task/Cargo.toml new file mode 100644 index 0000000000..09d240c309 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "moz_task-gtest" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom event target types" +edition = "2018" + +[dependencies] +moz_task = { path = "../../moz_task" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/moz_task/TestMozTask.cpp b/xpcom/rust/gtest/moz_task/TestMozTask.cpp new file mode 100644 index 0000000000..6c40f1bc97 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/TestMozTask.cpp @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +extern "C" void Rust_Future(bool* aItWorked); + +TEST(RustMozTask, Future) +{ + bool itWorked = false; + Rust_Future(&itWorked); + EXPECT_TRUE(itWorked); +} diff --git a/xpcom/rust/gtest/moz_task/test.rs b/xpcom/rust/gtest/moz_task/test.rs new file mode 100644 index 0000000000..fa4c0af852 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/test.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use moz_task; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +/// Demo `Future` to demonstrate executing futures to completion via `nsIEventTarget`. +struct MyFuture { + poll_count: u32, + waker: Option<Waker>, + expect_main_thread: bool, +} + +impl MyFuture { + fn new(expect_main_thread: bool) -> Self { + Self { + poll_count: 0, + waker: None, + expect_main_thread, + } + } +} + +impl Future for MyFuture { + type Output = u32; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> { + assert_eq!(moz_task::is_main_thread(), self.expect_main_thread); + + self.poll_count += 1; + + if let Some(waker) = &mut self.waker { + if !waker.will_wake(cx.waker()) { + *waker = cx.waker().clone(); + } + } else { + let waker = cx.waker().clone(); + self.waker = Some(waker); + } + + println!("Poll count = {}", self.poll_count); + if self.poll_count > 5 { + Poll::Ready(self.poll_count) + } else { + // Just notify the task that we need to re-polled. + if let Some(waker) = &self.waker { + waker.wake_by_ref(); + } + Poll::Pending + } + } +} + +#[no_mangle] +pub extern "C" fn Rust_Future(it_worked: *mut bool) { + let future = async move { + assert_eq!(MyFuture::new(true).await, 6); + assert_eq!( + moz_task::spawn_local("Rust_Future inner spawn_local", MyFuture::new(true)).await, + 6 + ); + assert_eq!( + moz_task::spawn("Rust_Future inner spawn", MyFuture::new(false)).await, + 6 + ); + unsafe { + *it_worked = true; + } + }; + unsafe { + moz_task::gtest_only::spin_event_loop_until("Rust_Future", future).unwrap(); + }; +} diff --git a/xpcom/rust/gtest/nsstring/Cargo.toml b/xpcom/rust/gtest/nsstring/Cargo.toml new file mode 100644 index 0000000000..fd997926a7 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nsstring-gtest" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom string types" + +[dependencies] +nsstring = { path = "../../nsstring" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/nsstring/TestnsString.cpp b/xpcom/rust/gtest/nsstring/TestnsString.cpp new file mode 100644 index 0000000000..4e14c7bca5 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/TestnsString.cpp @@ -0,0 +1,177 @@ +#include "gtest/gtest.h" +#include <stdint.h> +#include <utility> +#include "nsString.h" + +extern "C" { +// This function is called by the rust code in test.rs if a non-fatal test +// failure occurs. +void GTest_ExpectFailure(const char* aMessage) { EXPECT_STREQ(aMessage, ""); } +} + +#define SIZE_ALIGN_CHECK(Clazz) \ + extern "C" void Rust_Test_ReprSizeAlign_##Clazz(size_t* size, \ + size_t* align); \ + TEST(RustNsString, ReprSizeAlign_##Clazz) \ + { \ + size_t size, align; \ + Rust_Test_ReprSizeAlign_##Clazz(&size, &align); \ + EXPECT_EQ(size, sizeof(Clazz)); \ + EXPECT_EQ(align, alignof(Clazz)); \ + } + +SIZE_ALIGN_CHECK(nsString) +SIZE_ALIGN_CHECK(nsCString) + +#define MEMBER_CHECK(Clazz, Member) \ + extern "C" void Rust_Test_Member_##Clazz##_##Member( \ + size_t* size, size_t* align, size_t* offset); \ + TEST(RustNsString, ReprMember_##Clazz##_##Member) \ + { \ + class Hack : public Clazz { \ + public: \ + static void RunTest() { \ + size_t size, align, offset; \ + Rust_Test_Member_##Clazz##_##Member(&size, &align, &offset); \ + EXPECT_EQ(size, sizeof(std::declval<Hack>().Member)); \ + EXPECT_EQ(align, alignof(decltype(std::declval<Hack>().Member))); \ + EXPECT_EQ(offset, offsetof(Hack, Member)); \ + } \ + }; \ + static_assert(sizeof(Clazz) == sizeof(Hack), "Hack matches class"); \ + Hack::RunTest(); \ + } + +MEMBER_CHECK(nsString, mData) +MEMBER_CHECK(nsString, mLength) +MEMBER_CHECK(nsString, mDataFlags) +MEMBER_CHECK(nsString, mClassFlags) +MEMBER_CHECK(nsCString, mData) +MEMBER_CHECK(nsCString, mLength) +MEMBER_CHECK(nsCString, mDataFlags) +MEMBER_CHECK(nsCString, mClassFlags) + +extern "C" void Rust_Test_NsStringFlags( + uint16_t* f_terminated, uint16_t* f_voided, uint16_t* f_refcounted, + uint16_t* f_owned, uint16_t* f_inline, uint16_t* f_literal, + uint16_t* f_class_inline, uint16_t* f_class_null_terminated); +TEST(RustNsString, NsStringFlags) +{ + uint16_t f_terminated, f_voided, f_refcounted, f_owned, f_inline, f_literal, + f_class_inline, f_class_null_terminated; + Rust_Test_NsStringFlags(&f_terminated, &f_voided, &f_refcounted, &f_owned, + &f_inline, &f_literal, &f_class_inline, + &f_class_null_terminated); + EXPECT_EQ(f_terminated, uint16_t(nsAString::DataFlags::TERMINATED)); + EXPECT_EQ(f_terminated, uint16_t(nsACString::DataFlags::TERMINATED)); + EXPECT_EQ(f_voided, uint16_t(nsAString::DataFlags::VOIDED)); + EXPECT_EQ(f_voided, uint16_t(nsACString::DataFlags::VOIDED)); + EXPECT_EQ(f_refcounted, uint16_t(nsAString::DataFlags::REFCOUNTED)); + EXPECT_EQ(f_refcounted, uint16_t(nsACString::DataFlags::REFCOUNTED)); + EXPECT_EQ(f_owned, uint16_t(nsAString::DataFlags::OWNED)); + EXPECT_EQ(f_owned, uint16_t(nsACString::DataFlags::OWNED)); + EXPECT_EQ(f_inline, uint16_t(nsAString::DataFlags::INLINE)); + EXPECT_EQ(f_inline, uint16_t(nsACString::DataFlags::INLINE)); + EXPECT_EQ(f_literal, uint16_t(nsAString::DataFlags::LITERAL)); + EXPECT_EQ(f_literal, uint16_t(nsACString::DataFlags::LITERAL)); + EXPECT_EQ(f_class_inline, uint16_t(nsAString::ClassFlags::INLINE)); + EXPECT_EQ(f_class_inline, uint16_t(nsACString::ClassFlags::INLINE)); + EXPECT_EQ(f_class_null_terminated, + uint16_t(nsAString::ClassFlags::NULL_TERMINATED)); + EXPECT_EQ(f_class_null_terminated, + uint16_t(nsACString::ClassFlags::NULL_TERMINATED)); +} + +extern "C" void Rust_StringFromCpp(const nsACString* aCStr, + const nsAString* aStr); +TEST(RustNsString, StringFromCpp) +{ + nsAutoCString foo; + foo.AssignASCII("Hello, World!"); + + nsAutoString bar; + bar.AssignASCII("Hello, World!"); + + Rust_StringFromCpp(&foo, &bar); +} + +extern "C" void Rust_AssignFromRust(nsACString* aCStr, nsAString* aStr); +TEST(RustNsString, AssignFromRust) +{ + nsAutoCString cs; + nsAutoString s; + Rust_AssignFromRust(&cs, &s); + EXPECT_TRUE(cs.EqualsASCII("Hello, World!")); + EXPECT_TRUE(s.EqualsASCII("Hello, World!")); +} + +extern "C" { +void Cpp_AssignFromCpp(nsACString* aCStr, nsAString* aStr) { + aCStr->AssignASCII("Hello, World!"); + aStr->AssignASCII("Hello, World!"); +} +} +extern "C" void Rust_AssignFromCpp(); +TEST(RustNsString, AssignFromCpp) +{ Rust_AssignFromCpp(); } + +extern "C" void Rust_StringWrite(); +TEST(RustNsString, StringWrite) +{ Rust_StringWrite(); } + +extern "C" void Rust_FromEmptyRustString(); +TEST(RustNsString, FromEmptyRustString) +{ Rust_FromEmptyRustString(); } + +extern "C" void Rust_WriteToBufferFromRust(nsACString* aCStr, nsAString* aStr, + nsACString* aFallibleCStr, + nsAString* aFallibleStr); +TEST(RustNsString, WriteToBufferFromRust) +{ + nsAutoCString cStr; + nsAutoString str; + nsAutoCString fallibleCStr; + nsAutoString fallibleStr; + + cStr.AssignLiteral("abc"); + str.AssignLiteral("abc"); + fallibleCStr.AssignLiteral("abc"); + fallibleStr.AssignLiteral("abc"); + + Rust_WriteToBufferFromRust(&cStr, &str, &fallibleCStr, &fallibleStr); + + EXPECT_TRUE(cStr.EqualsASCII("ABC")); + EXPECT_TRUE(str.EqualsASCII("ABC")); + EXPECT_TRUE(fallibleCStr.EqualsASCII("ABC")); + EXPECT_TRUE(fallibleStr.EqualsASCII("ABC")); +} + +extern "C" void Rust_InlineCapacityFromRust(const nsACString* aCStr, + const nsAString* aStr, + size_t* aCStrCapacity, + size_t* aStrCapacity); +TEST(RustNsString, InlineCapacityFromRust) +{ + size_t cStrCapacity; + size_t strCapacity; + nsAutoCStringN<93> cs; + nsAutoStringN<93> s; + Rust_InlineCapacityFromRust(&cs, &s, &cStrCapacity, &strCapacity); + EXPECT_EQ(cStrCapacity, 92U); + EXPECT_EQ(strCapacity, 92U); +} + +extern "C" void Rust_VoidStringFromRust(nsACString* aCStr, nsAString* aStr); +TEST(RustNsString, VoidStringFromRust) +{ + nsAutoCString cs; + nsAutoString s; + + EXPECT_FALSE(cs.IsVoid()); + EXPECT_FALSE(s.IsVoid()); + + Rust_VoidStringFromRust(&cs, &s); + + EXPECT_TRUE(cs.IsVoid()); + EXPECT_TRUE(s.IsVoid()); +} diff --git a/xpcom/rust/gtest/nsstring/test.rs b/xpcom/rust/gtest/nsstring/test.rs new file mode 100644 index 0000000000..a5d142f2b2 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/test.rs @@ -0,0 +1,131 @@ +#![allow(non_snake_case)] + +extern crate nsstring; + +use nsstring::*; +use std::ffi::CString; +use std::fmt::Write; +use std::os::raw::c_char; + +fn nonfatal_fail(msg: String) { + extern "C" { + fn GTest_ExpectFailure(message: *const c_char); + } + unsafe { + let msg = CString::new(msg).unwrap(); + GTest_ExpectFailure(msg.as_ptr()); + } +} + +/// This macro checks if the two arguments are equal, and causes a non-fatal +/// GTest test failure if they are not. +macro_rules! expect_eq { + ($x:expr, $y:expr) => { + match (&$x, &$y) { + (x, y) => { + if *x != *y { + nonfatal_fail(format!( + "check failed: (`{:?}` == `{:?}`) at {}:{}", + x, + y, + file!(), + line!() + )) + } + } + } + }; +} + +#[no_mangle] +pub extern "C" fn Rust_StringFromCpp(cs: *const nsACString, s: *const nsAString) { + unsafe { + expect_eq!(&*cs, "Hello, World!"); + expect_eq!(&*s, "Hello, World!"); + } +} + +#[no_mangle] +pub extern "C" fn Rust_AssignFromRust(cs: *mut nsACString, s: *mut nsAString) { + unsafe { + (*cs).assign(&nsCString::from("Hello, World!")); + expect_eq!(&*cs, "Hello, World!"); + (*s).assign(&nsString::from("Hello, World!")); + expect_eq!(&*s, "Hello, World!"); + } +} + +extern "C" { + fn Cpp_AssignFromCpp(cs: *mut nsACString, s: *mut nsAString); +} + +#[no_mangle] +pub extern "C" fn Rust_AssignFromCpp() { + let mut cs = nsCString::new(); + let mut s = nsString::new(); + unsafe { + Cpp_AssignFromCpp(&mut *cs, &mut *s); + } + expect_eq!(cs, "Hello, World!"); + expect_eq!(s, "Hello, World!"); +} + +#[no_mangle] +pub extern "C" fn Rust_StringWrite() { + let mut cs = nsCString::new(); + let mut s = nsString::new(); + + write!(s, "a").unwrap(); + write!(cs, "a").unwrap(); + expect_eq!(s, "a"); + expect_eq!(cs, "a"); + write!(s, "bc").unwrap(); + write!(cs, "bc").unwrap(); + expect_eq!(s, "abc"); + expect_eq!(cs, "abc"); + write!(s, "{}", 123).unwrap(); + write!(cs, "{}", 123).unwrap(); + expect_eq!(s, "abc123"); + expect_eq!(cs, "abc123"); +} + +#[no_mangle] +pub extern "C" fn Rust_FromEmptyRustString() { + let mut test = nsString::from("Blah"); + test.assign_utf8(&nsCString::from(String::new())); + assert!(test.is_empty()); +} + +#[no_mangle] +pub extern "C" fn Rust_WriteToBufferFromRust( + cs: *mut nsACString, + s: *mut nsAString, + fallible_cs: *mut nsACString, + fallible_s: *mut nsAString, +) { + unsafe { + let cs_buf = (*cs).to_mut(); + let s_buf = (*s).to_mut(); + let fallible_cs_buf = (*fallible_cs).fallible_to_mut().unwrap(); + let fallible_s_buf = (*fallible_s).fallible_to_mut().unwrap(); + + cs_buf[0] = b'A'; + cs_buf[1] = b'B'; + cs_buf[2] = b'C'; + s_buf[0] = b'A' as u16; + s_buf[1] = b'B' as u16; + s_buf[2] = b'C' as u16; + fallible_cs_buf[0] = b'A'; + fallible_cs_buf[1] = b'B'; + fallible_cs_buf[2] = b'C'; + fallible_s_buf[0] = b'A' as u16; + fallible_s_buf[1] = b'B' as u16; + fallible_s_buf[2] = b'C' as u16; + } +} + +#[no_mangle] +pub extern "C" fn Rust_VoidStringFromRust(cs: &mut nsACString, s: &mut nsAString) { + cs.set_is_void(true); + s.set_is_void(true); +} diff --git a/xpcom/rust/gtest/xpcom/Cargo.toml b/xpcom/rust/gtest/xpcom/Cargo.toml new file mode 100644 index 0000000000..777080b33b --- /dev/null +++ b/xpcom/rust/gtest/xpcom/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xpcom-gtest" +version = "0.1.0" +authors = ["michael@thelayzells.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom interfaces" + +[dependencies] +xpcom = { path = "../../xpcom" } +nserror = { path = "../../nserror" } +nsstring = { path = "../../nsstring" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/xpcom/TestXpcom.cpp b/xpcom/rust/gtest/xpcom/TestXpcom.cpp new file mode 100644 index 0000000000..69425274f6 --- /dev/null +++ b/xpcom/rust/gtest/xpcom/TestXpcom.cpp @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" + +extern "C" nsIObserverService* Rust_ObserveFromRust(); + +TEST(RustXpcom, ObserverFromRust) +{ + nsCOMPtr<nsIObserverService> rust = Rust_ObserveFromRust(); + nsCOMPtr<nsIObserverService> cpp = mozilla::services::GetObserverService(); + EXPECT_EQ(rust, cpp); +} + +extern "C" void Rust_ImplementRunnableInRust(bool* aItWorked, + nsIRunnable** aRunnable); + +TEST(RustXpcom, ImplementRunnableInRust) +{ + bool itWorked = false; + nsCOMPtr<nsIRunnable> runnable; + Rust_ImplementRunnableInRust(&itWorked, getter_AddRefs(runnable)); + + EXPECT_TRUE(runnable); + EXPECT_FALSE(itWorked); + runnable->Run(); + EXPECT_TRUE(itWorked); +} + +extern "C" void Rust_GetMultipleInterfaces(nsIRunnable** aRunnable, + nsIObserver** aObserver); + +TEST(RustXpcom, DynamicCastVoid) +{ + nsCOMPtr<nsIRunnable> runnable; + nsCOMPtr<nsIObserver> observer; + Rust_GetMultipleInterfaces(getter_AddRefs(runnable), + getter_AddRefs(observer)); + + // They should have different addresses when `static_cast` to void* + EXPECT_NE(static_cast<void*>(runnable.get()), + static_cast<void*>(observer.get())); + + // These should be the same object + nsCOMPtr<nsISupports> runnableSupports = do_QueryInterface(runnable); + nsCOMPtr<nsISupports> observerSupports = do_QueryInterface(observer); + EXPECT_EQ(runnableSupports.get(), observerSupports.get()); + +#ifndef XP_WIN + // They should have the same address when dynamic_cast to void* + // dynamic_cast<void*> is not supported without rtti on windows. + EXPECT_EQ(dynamic_cast<void*>(runnable.get()), + dynamic_cast<void*>(observer.get())); + + // The nsISupports pointer from `do_QueryInterface` should match + // `dynamic_cast<void*>` + EXPECT_EQ(dynamic_cast<void*>(observer.get()), + static_cast<void*>(observerSupports.get())); +#endif +} diff --git a/xpcom/rust/gtest/xpcom/test.rs b/xpcom/rust/gtest/xpcom/test.rs new file mode 100644 index 0000000000..f26a0140f3 --- /dev/null +++ b/xpcom/rust/gtest/xpcom/test.rs @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case)] + +#[macro_use] +extern crate xpcom; + +extern crate nserror; + +use nserror::{nsresult, NS_OK}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::ptr; +use xpcom::{interfaces, RefPtr}; + +#[no_mangle] +pub unsafe extern "C" fn Rust_ObserveFromRust() -> *const interfaces::nsIObserverService { + let obssvc: RefPtr<interfaces::nsIObserverService> = + xpcom::components::Observer::service().unwrap(); + + // Define an observer + #[xpcom(implement(nsIObserver), nonatomic)] + struct Observer { + run: *mut bool, + } + impl Observer { + unsafe fn Observe( + &self, + _subject: *const interfaces::nsISupports, + topic: *const c_char, + _data: *const u16, + ) -> nsresult { + *self.run = true; + assert!(CStr::from_ptr(topic).to_str() == Ok("test-rust-observe")); + NS_OK + } + } + + let topic = CString::new("test-rust-observe").unwrap(); + + let mut run = false; + let observer = Observer::allocate(InitObserver { run: &mut run }); + let rv = obssvc.AddObserver( + observer.coerce::<interfaces::nsIObserver>(), + topic.as_ptr(), + false, + ); + assert!(rv.succeeded()); + + let rv = obssvc.NotifyObservers(ptr::null(), topic.as_ptr(), ptr::null()); + assert!(rv.succeeded()); + assert!(run, "The observer should have been run!"); + + let rv = obssvc.RemoveObserver(observer.coerce::<interfaces::nsIObserver>(), topic.as_ptr()); + assert!(rv.succeeded()); + + assert!( + observer.coerce::<interfaces::nsISupports>() as *const _ + == &*observer + .query_interface::<interfaces::nsISupports>() + .unwrap() as *const _ + ); + + &*obssvc +} + +#[no_mangle] +pub unsafe extern "C" fn Rust_ImplementRunnableInRust( + it_worked: *mut bool, + runnable: *mut *const interfaces::nsIRunnable, +) { + // Define a type which implements nsIRunnable in rust. + #[xpcom(implement(nsIRunnable), atomic)] + struct RunnableFn<F: Fn() + 'static> { + run: F, + } + + impl<F: Fn() + 'static> RunnableFn<F> { + unsafe fn Run(&self) -> nsresult { + (self.run)(); + NS_OK + } + } + + let my_runnable = RunnableFn::allocate(InitRunnableFn { + run: move || { + *it_worked = true; + }, + }); + my_runnable + .query_interface::<interfaces::nsIRunnable>() + .unwrap() + .forget(&mut *runnable); +} + +#[no_mangle] +pub unsafe extern "C" fn Rust_GetMultipleInterfaces( + runnable: *mut *const interfaces::nsIRunnable, + observer: *mut *const interfaces::nsIObserver, +) { + // Define a type which implements nsIRunnable and nsIObserver in rust, and + // hand both references back to c++ + #[xpcom(implement(nsIRunnable, nsIObserver), atomic)] + struct MultipleInterfaces {} + + impl MultipleInterfaces { + unsafe fn Run(&self) -> nsresult { + NS_OK + } + unsafe fn Observe( + &self, + _subject: *const interfaces::nsISupports, + _topic: *const c_char, + _data: *const u16, + ) -> nsresult { + NS_OK + } + } + + let instance = MultipleInterfaces::allocate(InitMultipleInterfaces {}); + instance + .query_interface::<interfaces::nsIRunnable>() + .unwrap() + .forget(&mut *runnable); + instance + .query_interface::<interfaces::nsIObserver>() + .unwrap() + .forget(&mut *observer); +} |