summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi/src/testing.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/uniffi/src/testing.rs149
1 files changed, 149 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/src/testing.rs b/third_party/rust/uniffi/src/testing.rs
new file mode 100644
index 0000000000..ada10645ea
--- /dev/null
+++ b/third_party/rust/uniffi/src/testing.rs
@@ -0,0 +1,149 @@
+/* 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/. */
+
+//! Runtime support functionality for testing generated bindings.
+//!
+//! This module helps you run a foreign language script as a testcase to exercise the
+//! bindings generated from your rust code. You probably don't want to use it directly,
+//! and should instead use the `build_foreign_language_testcases!` macro provided by
+//! the `uniffi_macros` crate.
+
+use anyhow::{bail, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use cargo_metadata::Message;
+use once_cell::sync::Lazy;
+use std::{
+ collections::HashMap,
+ process::{Command, Stdio},
+ sync::Mutex,
+};
+
+// These statics are used for a bit of simple caching and concurrency control.
+// They map uniffi component crate directories to data about build steps that have already
+// been executed by this process.
+static COMPILED_COMPONENTS: Lazy<Mutex<HashMap<Utf8PathBuf, Utf8PathBuf>>> =
+ Lazy::new(|| Mutex::new(HashMap::new()));
+// Since uniffi-bindgen does the actual generating/compiling of bindings and script files,
+// we ensure that only one call happens at once (making tests pretty much serialized sorry :/).
+static UNIFFI_BINDGEN: Lazy<Mutex<i32>> = Lazy::new(|| Mutex::new(0));
+
+/// Execute the given foreign-language script as part of a rust test suite.
+///
+/// This function takes the top-level directory of a uniffi component crate, and the path to
+/// a foreign-language test file that exercises that component's bindings. It ensures that the
+/// component is compiled and available for use and then executes the foreign language script,
+/// returning successfully iff the script exits successfully.
+pub fn run_foreign_language_testcase(
+ pkg_dir: &str,
+ udl_files: &[&str],
+ test_file: &str,
+) -> Result<()> {
+ let cdylib_file = ensure_compiled_cdylib(pkg_dir)?;
+ let _lock = UNIFFI_BINDGEN.lock();
+ run_uniffi_bindgen_test(cdylib_file.as_str(), udl_files, test_file)?;
+ Ok(())
+}
+
+/// Ensure that a uniffi component crate is compiled and ready for use.
+///
+/// This function takes the top-level directory of a uniffi component crate, ensures that the
+/// component's cdylib is compiled and available for use in generating bindings and running
+/// foreign language code.
+///
+/// Internally, this function does a bit of caching and concurrency management to avoid rebuilding
+/// the component for multiple testcases.
+pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result<Utf8PathBuf> {
+ let pkg_dir = Utf8Path::new(pkg_dir);
+
+ // Have we already compiled this component?
+ let mut compiled_components = COMPILED_COMPONENTS.lock().unwrap();
+ if let Some(cdylib_file) = compiled_components.get(pkg_dir) {
+ return Ok(cdylib_file.to_owned());
+ }
+ // Nope, looks like we'll have to compile it afresh.
+ let mut cmd = Command::new("cargo");
+ cmd.arg("build").arg("--message-format=json").arg("--lib");
+ cmd.current_dir(pkg_dir);
+ cmd.stdout(Stdio::piped());
+ let mut child = cmd.spawn()?;
+ let output = std::io::BufReader::new(child.stdout.take().unwrap());
+ // Build the crate, looking for any cdylibs that it might produce.
+ let cdylibs = Message::parse_stream(output)
+ .filter_map(|message| match message {
+ Err(e) => Some(Err(e.into())),
+ Ok(Message::CompilerArtifact(artifact)) => {
+ if artifact.target.kind.iter().any(|item| item == "cdylib") {
+ Some(Ok(artifact))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ })
+ .collect::<Result<Vec<_>>>()?;
+ if !child.wait()?.success() {
+ bail!("Failed to execute `cargo build`");
+ }
+ // If we didn't just build exactly one cdylib, we're going to use the one most likely to be produced by `pkg_dir`,
+ // or we will have a bad time.
+ let cdylib = match cdylibs.len() {
+ 0 => bail!("Crate did not produce any cdylibs, it must not be a uniffi component"),
+ 1 => &cdylibs[0],
+ _ => {
+ match cdylibs
+ .iter()
+ .find(|cdylib| cdylib.target.src_path.starts_with(pkg_dir))
+ {
+ Some(cdylib) => {
+ log::warn!(
+ "Crate produced multiple cdylibs, using the one produced by {pkg_dir}",
+ );
+ cdylib
+ }
+ None => {
+ bail!(
+ "Crate produced multiple cdylibs, none of which is produced by {pkg_dir}",
+ );
+ }
+ }
+ }
+ };
+ let cdylib_files: Vec<_> = cdylib
+ .filenames
+ .iter()
+ .filter(|nm| matches!(nm.extension(), Some(std::env::consts::DLL_EXTENSION)))
+ .collect();
+ if cdylib_files.len() != 1 {
+ bail!("Failed to build exactly one cdylib file, it must not be a uniffi component");
+ }
+ let cdylib_file = cdylib_files[0];
+ // Cache the result for subsequent tests.
+ compiled_components.insert(pkg_dir.to_owned(), cdylib_file.to_owned());
+ Ok(cdylib_file.to_owned())
+}
+
+/// Execute the `uniffi-bindgen test` command.
+///
+/// The default behaviour, suitable for most consumers, is to shell out to the `uniffi-bindgen`
+/// command found on the system.
+///
+/// If the "builtin-bindgen" feature is enabled then this will instead take a direct dependency
+/// on the `uniffi_bindgen` crate and execute its methods in-process. This is useful for folks
+/// who are working on uniffi itself and want to test out their changes to the bindings generator.
+#[cfg(not(feature = "builtin-bindgen"))]
+fn run_uniffi_bindgen_test(cdylib_file: &str, udl_files: &[&str], test_file: &str) -> Result<()> {
+ let udl_files = udl_files.join("\n");
+ let status = Command::new("uniffi-bindgen")
+ .args(&["test", cdylib_file, &udl_files, test_file])
+ .status()?;
+ if !status.success() {
+ bail!("Error while running tests: {status}");
+ }
+ Ok(())
+}
+
+#[cfg(feature = "builtin-bindgen")]
+fn run_uniffi_bindgen_test(cdylib_file: &str, udl_files: &[&str], test_file: &str) -> Result<()> {
+ uniffi_bindgen::run_tests(cdylib_file, udl_files, &[test_file], None)
+}