summaryrefslogtreecommitdiffstats
path: root/xpcom/rust/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpcom/rust/gtest
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/rust/gtest')
-rw-r--r--xpcom/rust/gtest/bench-collections/Bench.cpp297
-rw-r--r--xpcom/rust/gtest/bench-collections/Cargo.toml12
-rw-r--r--xpcom/rust/gtest/bench-collections/bench.rs101
-rw-r--r--xpcom/rust/gtest/moz.build14
-rw-r--r--xpcom/rust/gtest/moz_task/Cargo.toml13
-rw-r--r--xpcom/rust/gtest/moz_task/TestMozTask.cpp14
-rw-r--r--xpcom/rust/gtest/moz_task/test.rs78
-rw-r--r--xpcom/rust/gtest/nsstring/Cargo.toml12
-rw-r--r--xpcom/rust/gtest/nsstring/TestnsString.cpp177
-rw-r--r--xpcom/rust/gtest/nsstring/test.rs131
-rw-r--r--xpcom/rust/gtest/xpcom/Cargo.toml14
-rw-r--r--xpcom/rust/gtest/xpcom/TestXpcom.cpp66
-rw-r--r--xpcom/rust/gtest/xpcom/test.rs131
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);
+}