Skip to content

Commit

Permalink
Merge pull request #996 from badboy/callbacks-move
Browse files Browse the repository at this point in the history
Move callbacks testing code into its own fixture crate for testing
  • Loading branch information
badboy authored Aug 4, 2021
2 parents e602e11 + 75d9652 commit de2fd5e
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 165 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"examples/sprites",
"examples/todolist",
"fixtures/coverall",
"fixtures/callbacks",
"fixtures/regressions/enum-without-i32-helpers",
"fixtures/regressions/kotlin-experimental-unsigned-types",
"fixtures/regressions/cdylib-crate-type-dependency/ffi-crate",
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Newcomers are recommended to explore them in the following order:
object-oriented style.
* [`./todolist`](./todolist/) is a simplistic todo-list that can only add items and show the last item,
meant to show how interacting with strings works.
* [`.rondpoint`](./rondpoint/) exercises complex data types by round-tripping them from the foreign-language
* [`./rondpoint`](./rondpoint/) exercises complex data types by round-tripping them from the foreign-language
code, through rust and back agian.
* [`./fxa-client`](./fxa-client/) doesn't work yet, but it contains aspirational example of what the UDL
might look like for an actual real-world component.
Expand Down
2 changes: 1 addition & 1 deletion examples/callbacks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ publish = false

[lib]
crate-type = ["cdylib", "lib"]
name = "uniffi_callbacks"
name = "callbacks"

[dependencies]
uniffi_macros = {path = "../../uniffi_macros"}
Expand Down
37 changes: 0 additions & 37 deletions examples/callbacks/src/callbacks.udl
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,3 @@ callback interface OnCallAnswered {
void busy();
void text_received(string text);
};

/// These objects are implemented by the foreign language and passed
/// to Rust. Rust then calls methods on it when it needs to.
callback interface ForeignGetters {
boolean get_bool(boolean v, boolean arg2);
string get_string(string v, boolean arg2);
string? get_option(string? v, boolean arg2);
sequence<i32> get_list(sequence<i32> v, boolean arg2);
};

/// These objects are implemented in Rust, and call out to `ForeignGetters`
/// to get the value.
interface RustGetters {
boolean get_bool(ForeignGetters callback, boolean v, boolean arg2);
string get_string(ForeignGetters callback, string v, boolean arg2);
string? get_option(ForeignGetters callback, string? v, boolean arg2);
sequence<i32> get_list(ForeignGetters callback, sequence<i32> v, boolean arg2);
};

/// These objects are implemented by the foreign language and passed
/// to Rust. Rust then calls methods on it when it needs to.
/// Rust developers need to declare these traits extending `Send` so
/// they can be stored in Rust— i.e. not passed in as an argument to
/// be used immediately.
callback interface StoredForeignStringifier {
string from_simple_type(i32 value);
// Test if types are collected from callback interfaces.
// kotlinc compile time error if not.
string from_complex_type(sequence<f64?>? values);
};

/// Rust object that uses the StoredForeignStringifier to produce string representations
/// of passed arguments.
interface RustStringifier {
constructor(StoredForeignStringifier callback);
string from_simple_type(i32 value);
};
61 changes: 0 additions & 61 deletions examples/callbacks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,65 +24,4 @@ impl Telephone {
}
}

trait ForeignGetters {
fn get_bool(&self, v: bool, arg2: bool) -> bool;
fn get_string(&self, v: String, arg2: bool) -> String;
fn get_option(&self, v: Option<String>, arg2: bool) -> Option<String>;
fn get_list(&self, v: Vec<i32>, arg2: bool) -> Vec<i32>;
}

#[derive(Debug, Clone)]
pub struct RustGetters;

impl RustGetters {
pub fn new() -> Self {
RustGetters
}
fn get_bool(&self, callback: Box<dyn ForeignGetters>, v: bool, arg2: bool) -> bool {
callback.get_bool(v, arg2)
}
fn get_string(&self, callback: Box<dyn ForeignGetters>, v: String, arg2: bool) -> String {
callback.get_string(v, arg2)
}
fn get_option(
&self,
callback: Box<dyn ForeignGetters>,
v: Option<String>,
arg2: bool,
) -> Option<String> {
callback.get_option(v, arg2)
}
fn get_list(&self, callback: Box<dyn ForeignGetters>, v: Vec<i32>, arg2: bool) -> Vec<i32> {
callback.get_list(v, arg2)
}
}

impl Default for RustGetters {
fn default() -> Self {
Self::new()
}
}

// Use `Send+Send` because we want to store the callback in an exposed
// `Send+Sync` object.
trait StoredForeignStringifier: Send + Sync + std::fmt::Debug {
fn from_simple_type(&self, value: i32) -> String;
fn from_complex_type(&self, values: Option<Vec<Option<f64>>>) -> String;
}

#[derive(Debug)]
pub struct RustStringifier {
callback: Box<dyn StoredForeignStringifier>,
}

impl RustStringifier {
fn new(callback: Box<dyn StoredForeignStringifier>) -> Self {
RustStringifier { callback }
}

fn from_simple_type(&self, value: i32) -> String {
self.callback.from_simple_type(value)
}
}

include!(concat!(env!("OUT_DIR"), "/callbacks.uniffi.rs"));
66 changes: 1 addition & 65 deletions examples/callbacks/tests/bindings/test_callbacks.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import uniffi.callbacks.*

// 0. Simple example just to see it work.
// Simple example just to see it work.
// Pass in a string, get a string back.
// Pass in nothing, get unit back.
class OnCallAnsweredImpl : OnCallAnswered {
Expand Down Expand Up @@ -47,67 +47,3 @@ assert(cbObjet2.busyCount == 0) { "yesCount=${cbObjet2.busyCount} (should be 0)"
assert(cbObjet2.yesCount == 1) { "yesCount=${cbObjet2.yesCount} (should be 1)" }

telephone.destroy()

// A bit more systematic in testing, but this time in English.
//
// 1. Pass in the callback as arguments.
// Make the callback methods use multiple aruments, with a variety of types, and
// with a variety of return types.
val rustGetters = RustGetters()
class KotlinGetters(): ForeignGetters {
override fun getBool(v: Boolean, arg2: Boolean): Boolean = v xor arg2
override fun getString(v: String, arg2: Boolean): String = if (arg2) "1234567890123" else v
override fun getOption(v: String?, arg2: Boolean): String? = if (arg2) v?.uppercase() else v
override fun getList(v: List<Int>, arg2: Boolean): List<Int> = if (arg2) v else listOf()
}

val callback = KotlinGetters()
listOf(true, false).forEach { v ->
val flag = true
val expected = callback.getBool(v, flag)
val observed = rustGetters.getBool(callback, v, flag)
assert(expected == observed) { "roundtripping through callback: $expected != $observed" }
}

listOf(listOf(1,2), listOf(0,1)).forEach { v ->
val flag = true
val expected = callback.getList(v, flag)
val observed = rustGetters.getList(callback, v, flag)
assert(expected == observed) { "roundtripping through callback: $expected != $observed" }
}

listOf("Hello", "world").forEach { v ->
val flag = true
val expected = callback.getString(v, flag)
val observed = rustGetters.getString(callback, v, flag)
assert(expected == observed) { "roundtripping through callback: $expected != $observed" }
}

listOf("Some", null).forEach { v ->
val flag = false
val expected = callback.getOption(v, flag)
val observed = rustGetters.getOption(callback, v, flag)
assert(expected == observed) { "roundtripping through callback: $expected != $observed" }
}
rustGetters.destroy()

// 2. Pass the callback in as a constructor argument, to be stored on the Object struct.
// This is crucial if we want to configure a system at startup,
// then use it without passing callbacks all the time.

class StoredKotlinStringifier: StoredForeignStringifier {
override fun fromSimpleType(value: Int): String = "kotlin: $value"
// We don't test this, but we're checking that the arg type is included in the minimal list of types used
// in the UDL.
// If this doesn't compile, then look at TypeResolver.
override fun fromComplexType(values: List<Double?>?): String = "kotlin: $values"
}

val kotlinStringifier = StoredKotlinStringifier()
val rustStringifier = RustStringifier(kotlinStringifier)
listOf(1, 2).forEach { v ->
val expected = kotlinStringifier.fromSimpleType(v)
val observed = rustStringifier.fromSimpleType(v)
assert(expected == observed) { "callback is sent on construction: $expected != $observed" }
}
rustStringifier.destroy()
3 changes: 3 additions & 0 deletions examples/callbacks/uniffi.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[bindings.kotlin]
package_name = "uniffi.callbacks"
cdylib_name = "callbacks"
19 changes: 19 additions & 0 deletions fixtures/callbacks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "callbacks"
version = "0.12.0"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
edition = "2018"
publish = false

[lib]
crate-type = ["staticlib", "cdylib"]
name = "uniffi_callbacks"

[dependencies]
uniffi_macros = {path = "../../uniffi_macros"}
uniffi = {path = "../../uniffi", features=["builtin-bindgen"]}
thiserror = "1.0"
lazy_static = "1.4"

[build-dependencies]
uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]}
11 changes: 11 additions & 0 deletions fixtures/callbacks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# A "Callbacks" test for uniffi components

This is similar to the `callbacks` example, but it's intended to be contrived and to
ensure we get good test coverage of all possible options.

It's here so it doesn't even need to make a cursory effort to be a "good"
example; it intentionally panics, asserts params are certain values, has
no-op methods etc. If you're trying to get your head around uniffi then the
"examples" directory will be a much better bet.

This is its own crate, because the callback mechanism is not implemented for all backends yet.
7 changes: 7 additions & 0 deletions fixtures/callbacks/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* 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/. */

fn main() {
uniffi_build::generate_scaffolding("./src/callbacks.udl").unwrap();
}
38 changes: 38 additions & 0 deletions fixtures/callbacks/src/callbacks.udl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace callbacks {};

/// These objects are implemented by the foreign language and passed
/// to Rust. Rust then calls methods on it when it needs to.
callback interface ForeignGetters {
boolean get_bool(boolean v, boolean arg2);
string get_string(string v, boolean arg2);
string? get_option(string? v, boolean arg2);
sequence<i32> get_list(sequence<i32> v, boolean arg2);
};

/// These objects are implemented in Rust, and call out to `ForeignGetters`
/// to get the value.
interface RustGetters {
boolean get_bool(ForeignGetters callback, boolean v, boolean arg2);
string get_string(ForeignGetters callback, string v, boolean arg2);
string? get_option(ForeignGetters callback, string? v, boolean arg2);
sequence<i32> get_list(ForeignGetters callback, sequence<i32> v, boolean arg2);
};

/// These objects are implemented by the foreign language and passed
/// to Rust. Rust then calls methods on it when it needs to.
/// Rust developers need to declare these traits extending `Send` so
/// they can be stored in Rust— i.e. not passed in as an argument to
/// be used immediately.
callback interface StoredForeignStringifier {
string from_simple_type(i32 value);
// Test if types are collected from callback interfaces.
// kotlinc compile time error if not.
string from_complex_type(sequence<f64?>? values);
};

/// Rust object that uses the StoredForeignStringifier to produce string representations
/// of passed arguments.
interface RustStringifier {
constructor(StoredForeignStringifier callback);
string from_simple_type(i32 value);
};
66 changes: 66 additions & 0 deletions fixtures/callbacks/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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/. */

trait ForeignGetters {
fn get_bool(&self, v: bool, arg2: bool) -> bool;
fn get_string(&self, v: String, arg2: bool) -> String;
fn get_option(&self, v: Option<String>, arg2: bool) -> Option<String>;
fn get_list(&self, v: Vec<i32>, arg2: bool) -> Vec<i32>;
}

#[derive(Debug, Clone)]
pub struct RustGetters;

impl RustGetters {
pub fn new() -> Self {
RustGetters
}
fn get_bool(&self, callback: Box<dyn ForeignGetters>, v: bool, arg2: bool) -> bool {
callback.get_bool(v, arg2)
}
fn get_string(&self, callback: Box<dyn ForeignGetters>, v: String, arg2: bool) -> String {
callback.get_string(v, arg2)
}
fn get_option(
&self,
callback: Box<dyn ForeignGetters>,
v: Option<String>,
arg2: bool,
) -> Option<String> {
callback.get_option(v, arg2)
}
fn get_list(&self, callback: Box<dyn ForeignGetters>, v: Vec<i32>, arg2: bool) -> Vec<i32> {
callback.get_list(v, arg2)
}
}

impl Default for RustGetters {
fn default() -> Self {
Self::new()
}
}

// Use `Send+Send` because we want to store the callback in an exposed
// `Send+Sync` object.
trait StoredForeignStringifier: Send + Sync + std::fmt::Debug {
fn from_simple_type(&self, value: i32) -> String;
fn from_complex_type(&self, values: Option<Vec<Option<f64>>>) -> String;
}

#[derive(Debug)]
pub struct RustStringifier {
callback: Box<dyn StoredForeignStringifier>,
}

impl RustStringifier {
fn new(callback: Box<dyn StoredForeignStringifier>) -> Self {
RustStringifier { callback }
}

fn from_simple_type(&self, value: i32) -> String {
self.callback.from_simple_type(value)
}
}

include!(concat!(env!("OUT_DIR"), "/callbacks.uniffi.rs"));
Loading

0 comments on commit de2fd5e

Please sign in to comment.