diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/uniffi-example-rondpoint/tests | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi-example-rondpoint/tests')
5 files changed, 779 insertions, 0 deletions
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts new file mode 100644 index 0000000000..db2a3df975 --- /dev/null +++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts @@ -0,0 +1,250 @@ +import uniffi.rondpoint.* + +val dico = Dictionnaire(Enumeration.DEUX, true, 0u, 123456789u) +val copyDico = copieDictionnaire(dico) +assert(dico == copyDico) + +assert(copieEnumeration(Enumeration.DEUX) == Enumeration.DEUX) +assert(copieEnumerations(listOf(Enumeration.UN, Enumeration.DEUX)) == listOf(Enumeration.UN, Enumeration.DEUX)) +assert(copieCarte(mapOf( + "0" to EnumerationAvecDonnees.Zero, + "1" to EnumerationAvecDonnees.Un(1u), + "2" to EnumerationAvecDonnees.Deux(2u, "deux") +)) == mapOf( + "0" to EnumerationAvecDonnees.Zero, + "1" to EnumerationAvecDonnees.Un(1u), + "2" to EnumerationAvecDonnees.Deux(2u, "deux") +)) + +val var1: EnumerationAvecDonnees = EnumerationAvecDonnees.Zero +val var2: EnumerationAvecDonnees = EnumerationAvecDonnees.Un(1u) +val var3: EnumerationAvecDonnees = EnumerationAvecDonnees.Un(2u) +assert(var1 != var2) +assert(var2 != var3) +assert(var1 == EnumerationAvecDonnees.Zero) +assert(var1 != EnumerationAvecDonnees.Un(1u)) +assert(var2 == EnumerationAvecDonnees.Un(1u)) + +assert(switcheroo(false)) + +// Test the roundtrip across the FFI. +// This shows that the values we send come back in exactly the same state as we sent them. +// i.e. it shows that lowering from kotlin and lifting into rust is symmetrical with +// lowering from rust and lifting into kotlin. +val rt = Retourneur() + +fun <T> List<T>.affirmAllerRetour(fn: (T) -> T) { + this.forEach { v -> + assert(fn.invoke(v) == v) { "$fn($v)" } + } +} + +// Booleans +listOf(true, false).affirmAllerRetour(rt::identiqueBoolean) + +// Bytes. +listOf(Byte.MIN_VALUE, Byte.MAX_VALUE).affirmAllerRetour(rt::identiqueI8) +listOf(0x00, 0xFF).map { it.toUByte() }.affirmAllerRetour(rt::identiqueU8) + +// Shorts +listOf(Short.MIN_VALUE, Short.MAX_VALUE).affirmAllerRetour(rt::identiqueI16) +listOf(0x0000, 0xFFFF).map { it.toUShort() }.affirmAllerRetour(rt::identiqueU16) + +// Ints +listOf(0, 1, -1, Int.MIN_VALUE, Int.MAX_VALUE).affirmAllerRetour(rt::identiqueI32) +listOf(0x00000000, 0xFFFFFFFF).map { it.toUInt() }.affirmAllerRetour(rt::identiqueU32) + +// Longs +listOf(0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE).affirmAllerRetour(rt::identiqueI64) +listOf(0u, 1u, ULong.MIN_VALUE, ULong.MAX_VALUE).affirmAllerRetour(rt::identiqueU64) + +// Floats +listOf(0.0F, 0.5F, 0.25F, Float.MIN_VALUE, Float.MAX_VALUE).affirmAllerRetour(rt::identiqueFloat) + +// Doubles +listOf(0.0, 1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmAllerRetour(rt::identiqueDouble) + +// Strings +listOf("", "abc", "null\u0000byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨👧👦multi-emoji, 🇨🇭a flag, a canal, panama") + .affirmAllerRetour(rt::identiqueString) + +listOf(-1, 0, 1).map { DictionnaireNombresSignes(it.toByte(), it.toShort(), it.toInt(), it.toLong()) } + .affirmAllerRetour(rt::identiqueNombresSignes) + +listOf(0, 1).map { DictionnaireNombres(it.toUByte(), it.toUShort(), it.toUInt(), it.toULong()) } + .affirmAllerRetour(rt::identiqueNombres) + + +rt.destroy() + +// Test one way across the FFI. +// +// We send one representation of a value to lib.rs, and it transforms it into another, a string. +// lib.rs sends the string back, and then we compare here in kotlin. +// +// This shows that the values are transformed into strings the same way in both kotlin and rust. +// i.e. if we assume that the string return works (we test this assumption elsewhere) +// we show that lowering from kotlin and lifting into rust has values that both kotlin and rust +// both stringify in the same way. i.e. the same values. +// +// If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust t here, +// and this convinces us that lowering/lifting from here to rust is correct, then +// together, we've shown the correctness of the return leg. +val st = Stringifier() + +typealias StringyEquals<T> = (observed: String, expected: T) -> Boolean +fun <T> List<T>.affirmEnchaine( + fn: (T) -> String, + equals: StringyEquals<T> = { obs, exp -> obs == exp.toString() } +) { + this.forEach { exp -> + val obs = fn.invoke(exp) + assert(equals(obs, exp)) { "$fn($exp): observed=$obs, expected=$exp" } + } +} + +// Test the efficacy of the string transport from rust. If this fails, but everything else +// works, then things are very weird. +val wellKnown = st.wellKnownString("kotlin") +assert("uniffi 💚 kotlin!" == wellKnown) { "wellKnownString 'uniffi 💚 kotlin!' == '$wellKnown'" } + +// Booleans +listOf(true, false).affirmEnchaine(st::toStringBoolean) + +// Bytes. +listOf(Byte.MIN_VALUE, Byte.MAX_VALUE).affirmEnchaine(st::toStringI8) +listOf(UByte.MIN_VALUE, UByte.MAX_VALUE).affirmEnchaine(st::toStringU8) + +// Shorts +listOf(Short.MIN_VALUE, Short.MAX_VALUE).affirmEnchaine(st::toStringI16) +listOf(UShort.MIN_VALUE, UShort.MAX_VALUE).affirmEnchaine(st::toStringU16) + +// Ints +listOf(0, 1, -1, Int.MIN_VALUE, Int.MAX_VALUE).affirmEnchaine(st::toStringI32) +listOf(0u, 1u, UInt.MIN_VALUE, UInt.MAX_VALUE).affirmEnchaine(st::toStringU32) + +// Longs +listOf(0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE).affirmEnchaine(st::toStringI64) +listOf(0u, 1u, ULong.MIN_VALUE, ULong.MAX_VALUE).affirmEnchaine(st::toStringU64) + +// Floats +// MIN_VAUE is 1.4E-45. Accuracy and formatting get weird at small sizes. +listOf(0.0F, 1.0F, -1.0F, Float.MIN_VALUE, Float.MAX_VALUE).affirmEnchaine(st::toStringFloat) { s, n -> s.toFloat() == n } + +// Doubles +// MIN_VALUE is 4.9E-324. Accuracy and formatting get weird at small sizes. +listOf(0.0, 1.0, -1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmEnchaine(st::toStringDouble) { s, n -> s.toDouble() == n } + +st.destroy() + +// Prove to ourselves that default arguments are being used. +// Step 1: call the methods without arguments, and check against the UDL. +val op = Optionneur() + +assert(op.sinonString() == "default") + +assert(op.sinonBoolean() == false) + +assert(op.sinonSequence() == listOf<String>()) + +// optionals +assert(op.sinonNull() == null) +assert(op.sinonZero() == 0) + +// decimal integers +assert(op.sinonI8Dec() == (-42).toByte()) +assert(op.sinonU8Dec() == 42.toUByte()) +assert(op.sinonI16Dec() == 42.toShort()) +assert(op.sinonU16Dec() == 42.toUShort()) +assert(op.sinonI32Dec() == 42) +assert(op.sinonU32Dec() == 42.toUInt()) +assert(op.sinonI64Dec() == 42L) +assert(op.sinonU64Dec() == 42uL) + +// hexadecimal integers +assert(op.sinonI8Hex() == (-0x7f).toByte()) +assert(op.sinonU8Hex() == 0xff.toUByte()) +assert(op.sinonI16Hex() == 0x7f.toShort()) +assert(op.sinonU16Hex() == 0xffff.toUShort()) +assert(op.sinonI32Hex() == 0x7fffffff) +assert(op.sinonU32Hex() == 0xffffffff.toUInt()) +assert(op.sinonI64Hex() == 0x7fffffffffffffffL) +assert(op.sinonU64Hex() == 0xffffffffffffffffuL) + +// octal integers +assert(op.sinonU32Oct() == 493u) // 0o755 + +// floats +assert(op.sinonF32() == 42.0f) +assert(op.sinonF64() == 42.1) + +// enums +assert(op.sinonEnum() == Enumeration.TROIS) + +// Step 2. Convince ourselves that if we pass something else, then that changes the output. +// We have shown something coming out of the sinon methods, but without eyeballing the Rust +// we can't be sure that the arguments will change the return value. +listOf("foo", "bar").affirmAllerRetour(op::sinonString) +listOf(true, false).affirmAllerRetour(op::sinonBoolean) +listOf(listOf("a", "b"), listOf()).affirmAllerRetour(op::sinonSequence) + +// optionals +listOf("0", "1").affirmAllerRetour(op::sinonNull) +listOf(0, 1).affirmAllerRetour(op::sinonZero) + +// integers +listOf(0, 1).map { it.toUByte() }.affirmAllerRetour(op::sinonU8Dec) +listOf(0, 1).map { it.toByte() }.affirmAllerRetour(op::sinonI8Dec) +listOf(0, 1).map { it.toUShort() }.affirmAllerRetour(op::sinonU16Dec) +listOf(0, 1).map { it.toShort() }.affirmAllerRetour(op::sinonI16Dec) +listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Dec) +listOf(0, 1).map { it.toInt() }.affirmAllerRetour(op::sinonI32Dec) +listOf(0, 1).map { it.toULong() }.affirmAllerRetour(op::sinonU64Dec) +listOf(0, 1).map { it.toLong() }.affirmAllerRetour(op::sinonI64Dec) + +listOf(0, 1).map { it.toUByte() }.affirmAllerRetour(op::sinonU8Hex) +listOf(0, 1).map { it.toByte() }.affirmAllerRetour(op::sinonI8Hex) +listOf(0, 1).map { it.toUShort() }.affirmAllerRetour(op::sinonU16Hex) +listOf(0, 1).map { it.toShort() }.affirmAllerRetour(op::sinonI16Hex) +listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Hex) +listOf(0, 1).map { it.toInt() }.affirmAllerRetour(op::sinonI32Hex) +listOf(0, 1).map { it.toULong() }.affirmAllerRetour(op::sinonU64Hex) +listOf(0, 1).map { it.toLong() }.affirmAllerRetour(op::sinonI64Hex) + +listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Oct) + +// floats +listOf(0.0f, 1.0f).affirmAllerRetour(op::sinonF32) +listOf(0.0, 1.0).affirmAllerRetour(op::sinonF64) + +// enums +Enumeration.values().toList().affirmAllerRetour(op::sinonEnum) + +op.destroy() + +// Testing defaulting properties in record types. +val defaultes = OptionneurDictionnaire() +val explicite = OptionneurDictionnaire( + i8Var = -8, + u8Var = 8u, + i16Var = -16, + u16Var = 0x10u, + i32Var = -32, + u32Var = 32u, + i64Var = -64L, + u64Var = 64uL, + floatVar = 4.0f, + doubleVar = 8.0, + booleanVar = true, + stringVar = "default", + listVar = listOf(), + enumerationVar = Enumeration.DEUX, + dictionnaireVar = null +) +assert(defaultes == explicite) + +// …and makes sure they travel across and back the FFI. +val rt2 = Retourneur() +listOf(defaultes).affirmAllerRetour(rt2::identiqueOptionneurDictionnaire) + +rt2.destroy() diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py new file mode 100644 index 0000000000..ecfcc1e527 --- /dev/null +++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py @@ -0,0 +1,146 @@ +import sys +import ctypes +from rondpoint import * + +dico = Dictionnaire(Enumeration.DEUX, True, 0, 123456789) +copyDico = copie_dictionnaire(dico) +assert dico == copyDico + +assert copie_enumeration(Enumeration.DEUX) == Enumeration.DEUX +assert copie_enumerations([Enumeration.UN, Enumeration.DEUX]) == [Enumeration.UN, Enumeration.DEUX] +assert copie_carte({ + "0": EnumerationAvecDonnees.ZERO(), + "1": EnumerationAvecDonnees.UN(1), + "2": EnumerationAvecDonnees.DEUX(2, "deux"), +}) == { + "0": EnumerationAvecDonnees.ZERO(), + "1": EnumerationAvecDonnees.UN(1), + "2": EnumerationAvecDonnees.DEUX(2, "deux"), +} + +assert switcheroo(False) is True + +assert EnumerationAvecDonnees.ZERO() != EnumerationAvecDonnees.UN(1) +assert EnumerationAvecDonnees.UN(1) == EnumerationAvecDonnees.UN(1) +assert EnumerationAvecDonnees.UN(1) != EnumerationAvecDonnees.UN(2) + +# Test the roundtrip across the FFI. +# This shows that the values we send come back in exactly the same state as we sent them. +# i.e. it shows that lowering from python and lifting into rust is symmetrical with +# lowering from rust and lifting into python. +rt = Retourneur() + +def affirmAllerRetour(vals, identique): + for v in vals: + id_v = identique(v) + assert id_v == v, f"Round-trip failure: {v} => {id_v}" + +MIN_I8 = -1 * 2**7 +MAX_I8 = 2**7 - 1 +MIN_I16 = -1 * 2**15 +MAX_I16 = 2**15 - 1 +MIN_I32 = -1 * 2**31 +MAX_I32 = 2**31 - 1 +MIN_I64 = -1 * 2**31 +MAX_I64 = 2**31 - 1 + +# Python floats are always doubles, so won't round-trip through f32 correctly. +# This truncates them appropriately. +F32_ONE_THIRD = ctypes.c_float(1.0 / 3).value + +# Booleans +affirmAllerRetour([True, False], rt.identique_boolean) + +# Bytes. +affirmAllerRetour([MIN_I8, -1, 0, 1, MAX_I8], rt.identique_i8) +affirmAllerRetour([0x00, 0x12, 0xFF], rt.identique_u8) + +# Shorts +affirmAllerRetour([MIN_I16, -1, 0, 1, MAX_I16], rt.identique_i16) +affirmAllerRetour([0x0000, 0x1234, 0xFFFF], rt.identique_u16) + +# Ints +affirmAllerRetour([MIN_I32, -1, 0, 1, MAX_I32], rt.identique_i32) +affirmAllerRetour([0x00000000, 0x12345678, 0xFFFFFFFF], rt.identique_u32) + +# Longs +affirmAllerRetour([MIN_I64, -1, 0, 1, MAX_I64], rt.identique_i64) +affirmAllerRetour([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], rt.identique_u64) + +# Floats +affirmAllerRetour([0.0, 0.5, 0.25, 1.0, F32_ONE_THIRD], rt.identique_float) + +# Doubles +affirmAllerRetour( + [0.0, 0.5, 0.25, 1.0, 1.0 / 3, sys.float_info.max, sys.float_info.min], + rt.identique_double +) + +# Strings +affirmAllerRetour( + ["", "abc", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨👧👦multi-emoji, 🇨🇭a flag, a canal, panama"], + rt.identique_string +) + +# Test one way across the FFI. +# +# We send one representation of a value to lib.rs, and it transforms it into another, a string. +# lib.rs sends the string back, and then we compare here in python. +# +# This shows that the values are transformed into strings the same way in both python and rust. +# i.e. if we assume that the string return works (we test this assumption elsewhere) +# we show that lowering from python and lifting into rust has values that both python and rust +# both stringify in the same way. i.e. the same values. +# +# If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust to here, +# and this convinces us that lowering/lifting from here to rust is correct, then +# together, we've shown the correctness of the return leg. +st = Stringifier() + +def affirmEnchaine(vals, toString, rustyStringify=lambda v: str(v).lower()): + for v in vals: + str_v = toString(v) + assert rustyStringify(v) == str_v, f"String compare error {v} => {str_v}" + +# Test the efficacy of the string transport from rust. If this fails, but everything else +# works, then things are very weird. +wellKnown = st.well_known_string("python") +assert "uniffi 💚 python!" == wellKnown + +# Booleans +affirmEnchaine([True, False], st.to_string_boolean) + +# Bytes. +affirmEnchaine([MIN_I8, -1, 0, 1, MAX_I8], st.to_string_i8) +affirmEnchaine([0x00, 0x12, 0xFF], st.to_string_u8) + +# Shorts +affirmEnchaine([MIN_I16, -1, 0, 1, MAX_I16], st.to_string_i16) +affirmEnchaine([0x0000, 0x1234, 0xFFFF], st.to_string_u16) + +# Ints +affirmEnchaine([MIN_I32, -1, 0, 1, MAX_I32], st.to_string_i32) +affirmEnchaine([0x00000000, 0x12345678, 0xFFFFFFFF], st.to_string_u32) + +# Longs +affirmEnchaine([MIN_I64, -1, 0, 1, MAX_I64], st.to_string_i64) +affirmEnchaine([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], st.to_string_u64) + +# Floats +def rustyFloatToStr(v): + """Stringify a float in the same way that rust seems to.""" + # Rust doesn't include the decimal part of whole enumber floats when stringifying. + if int(v) == v: + return str(int(v)) + return str(v) + +affirmEnchaine([0.0, 0.5, 0.25, 1.0], st.to_string_float, rustyFloatToStr) +assert st.to_string_float(F32_ONE_THIRD) == "0.33333334" # annoyingly different string repr + +# Doubles +# TODO: float_info.max/float_info.min don't stringify-roundtrip properly yet, TBD. +affirmEnchaine( + [0.0, 0.5, 0.25, 1.0, 1.0 / 3], + st.to_string_double, + rustyFloatToStr, +) diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb new file mode 100644 index 0000000000..0121f6e0f9 --- /dev/null +++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require 'test/unit' +require 'rondpoint' + +include Test::Unit::Assertions +include Rondpoint + +dico = Dictionnaire.new Enumeration::DEUX, true, 0, 123_456_789 + +assert_equal dico, Rondpoint.copie_dictionnaire(dico) + +assert_equal Rondpoint.copie_enumeration(Enumeration::DEUX), Enumeration::DEUX + +assert_equal Rondpoint.copie_enumerations([ + Enumeration::UN, + Enumeration::DEUX + ]), [Enumeration::UN, Enumeration::DEUX] + +assert_equal Rondpoint.copie_carte({ + '0' => EnumerationAvecDonnees::ZERO.new, + '1' => EnumerationAvecDonnees::UN.new(1), + '2' => EnumerationAvecDonnees::DEUX.new(2, 'deux') + }), { + '0' => EnumerationAvecDonnees::ZERO.new, + '1' => EnumerationAvecDonnees::UN.new(1), + '2' => EnumerationAvecDonnees::DEUX.new(2, 'deux') + } + +assert Rondpoint.switcheroo(false) + +assert_not_equal EnumerationAvecDonnees::ZERO.new, EnumerationAvecDonnees::UN.new(1) +assert_equal EnumerationAvecDonnees::UN.new(1), EnumerationAvecDonnees::UN.new(1) +assert_not_equal EnumerationAvecDonnees::UN.new(1), EnumerationAvecDonnees::UN.new(2) + +# Test the roundtrip across the FFI. +# This shows that the values we send come back in exactly the same state as we sent them. +# i.e. it shows that lowering from ruby and lifting into rust is symmetrical with +# lowering from rust and lifting into ruby. +RT = Retourneur.new + +def affirm_aller_retour(vals, fn_name) + vals.each do |v| + id_v = RT.public_send fn_name, v + + assert_equal id_v, v, "Round-trip failure: #{v} => #{id_v}" + end +end + +MIN_I8 = -1 * 2**7 +MAX_I8 = 2**7 - 1 +MIN_I16 = -1 * 2**15 +MAX_I16 = 2**15 - 1 +MIN_I32 = -1 * 2**31 +MAX_I32 = 2**31 - 1 +MIN_I64 = -1 * 2**31 +MAX_I64 = 2**31 - 1 + +# Ruby floats are always doubles, so won't round-trip through f32 correctly. +# This truncates them appropriately. +F32_ONE_THIRD = [1.0 / 3].pack('f').unpack('f')[0] + +# Booleans +affirm_aller_retour([true, false], :identique_boolean) + +# Bytes. +affirm_aller_retour([MIN_I8, -1, 0, 1, MAX_I8], :identique_i8) +affirm_aller_retour([0x00, 0x12, 0xFF], :identique_u8) + +# Shorts +affirm_aller_retour([MIN_I16, -1, 0, 1, MAX_I16], :identique_i16) +affirm_aller_retour([0x0000, 0x1234, 0xFFFF], :identique_u16) + +# Ints +affirm_aller_retour([MIN_I32, -1, 0, 1, MAX_I32], :identique_i32) +affirm_aller_retour([0x00000000, 0x12345678, 0xFFFFFFFF], :identique_u32) + +# Longs +affirm_aller_retour([MIN_I64, -1, 0, 1, MAX_I64], :identique_i64) +affirm_aller_retour([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], :identique_u64) + +# Floats +affirm_aller_retour([0.0, 0.5, 0.25, 1.0, F32_ONE_THIRD], :identique_float) + +# Doubles +affirm_aller_retour( + [0.0, 0.5, 0.25, 1.0, 1.0 / 3, Float::MAX, Float::MIN], + :identique_double +) + +# Strings +affirm_aller_retour( + ['', 'abc', 'été', 'ښي لاس ته لوستلو لوستل', + '😻emoji 👨👧👦multi-emoji, 🇨🇭a flag, a canal, panama'], + :identique_string +) + +# Test one way across the FFI. +# +# We send one representation of a value to lib.rs, and it transforms it into another, a string. +# lib.rs sends the string back, and then we compare here in ruby. +# +# This shows that the values are transformed into strings the same way in both ruby and rust. +# i.e. if we assume that the string return works (we test this assumption elsewhere) +# we show that lowering from ruby and lifting into rust has values that both ruby and rust +# both stringify in the same way. i.e. the same values. +# +# If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust to here, +# and this convinces us that lowering/lifting from here to rust is correct, then +# together, we've shown the correctness of the return leg. +ST = Stringifier.new + +def affirm_enchaine(vals, fn_name) + vals.each do |v| + str_v = ST.public_send fn_name, v + + assert_equal v.to_s, str_v, "String compare error #{v} => #{str_v}" + end +end + +# Test the efficacy of the string transport from rust. If this fails, but everything else +# works, then things are very weird. +assert_equal ST.well_known_string('ruby'), 'uniffi 💚 ruby!' + +# Booleans +affirm_enchaine([true, false], :to_string_boolean) + +# Bytes. +affirm_enchaine([MIN_I8, -1, 0, 1, MAX_I8], :to_string_i8) +affirm_enchaine([0x00, 0x12, 0xFF], :to_string_u8) + +# Shorts +affirm_enchaine([MIN_I16, -1, 0, 1, MAX_I16], :to_string_i16) +affirm_enchaine([0x0000, 0x1234, 0xFFFF], :to_string_u16) + +# Ints +affirm_enchaine([MIN_I32, -1, 0, 1, MAX_I32], :to_string_i32) +affirm_enchaine([0x00000000, 0x12345678, 0xFFFFFFFF], :to_string_u32) + +# Longs +affirm_enchaine([MIN_I64, -1, 0, 1, MAX_I64], :to_string_i64) +affirm_enchaine([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], :to_string_u64) diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift new file mode 100644 index 0000000000..756f1b8a44 --- /dev/null +++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift @@ -0,0 +1,232 @@ +import rondpoint + +let dico = Dictionnaire(un: .deux, deux: false, petitNombre: 0, grosNombre: 123456789) +let copyDico = copieDictionnaire(d: dico) +assert(dico == copyDico) + +assert(copieEnumeration(e: .deux) == .deux) +assert(copieEnumerations(e: [.un, .deux]) == [.un, .deux]) +assert(copieCarte(c: + ["0": .zero, + "1": .un(premier: 1), + "2": .deux(premier: 2, second: "deux") +]) == [ + "0": .zero, + "1": .un(premier: 1), + "2": .deux(premier: 2, second: "deux") +]) + +assert(EnumerationAvecDonnees.zero != EnumerationAvecDonnees.un(premier: 1)) +assert(EnumerationAvecDonnees.un(premier: 1) == EnumerationAvecDonnees.un(premier: 1)) +assert(EnumerationAvecDonnees.un(premier: 1) != EnumerationAvecDonnees.un(premier: 2)) + + +assert(switcheroo(b: false)) + +// Test the roundtrip across the FFI. +// This shows that the values we send come back in exactly the same state as we sent them. +// i.e. it shows that lowering from swift and lifting into rust is symmetrical with +// lowering from rust and lifting into swift. +let rt = Retourneur() + +// Booleans +[true, false].affirmAllerRetour(rt.identiqueBoolean) + +// Bytes. +[.min, .max].affirmAllerRetour(rt.identiqueI8) +[0x00, 0xFF].map { $0 as UInt8 }.affirmAllerRetour(rt.identiqueU8) + +// Shorts +[.min, .max].affirmAllerRetour(rt.identiqueI16) +[0x0000, 0xFFFF].map { $0 as UInt16 }.affirmAllerRetour(rt.identiqueU16) + +// Ints +[0, 1, -1, .min, .max].affirmAllerRetour(rt.identiqueI32) +[0x00000000, 0xFFFFFFFF].map { $0 as UInt32 }.affirmAllerRetour(rt.identiqueU32) + +// Longs +[.zero, 1, -1, .min, .max].affirmAllerRetour(rt.identiqueI64) +[.zero, 1, .min, .max].affirmAllerRetour(rt.identiqueU64) + +// Floats +[.zero, 1, 0.25, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmAllerRetour(rt.identiqueFloat) + +// Doubles +[0.0, 1.0, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmAllerRetour(rt.identiqueDouble) + +// Strings +["", "abc", "null\0byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨👧👦multi-emoji, 🇨🇭a flag, a canal, panama"] + .affirmAllerRetour(rt.identiqueString) + +// Test one way across the FFI. +// +// We send one representation of a value to lib.rs, and it transforms it into another, a string. +// lib.rs sends the string back, and then we compare here in swift. +// +// This shows that the values are transformed into strings the same way in both swift and rust. +// i.e. if we assume that the string return works (we test this assumption elsewhere) +// we show that lowering from swift and lifting into rust has values that both swift and rust +// both stringify in the same way. i.e. the same values. +// +// If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust t here, +// and this convinces us that lowering/lifting from here to rust is correct, then +// together, we've shown the correctness of the return leg. +let st = Stringifier() + +// Test the effigacy of the string transport from rust. If this fails, but everything else +// works, then things are very weird. +let wellKnown = st.wellKnownString(value: "swift") +assert("uniffi 💚 swift!" == wellKnown, "wellKnownString 'uniffi 💚 swift!' == '\(wellKnown)'") + +// Booleans +[true, false].affirmEnchaine(st.toStringBoolean) + +// Bytes. +[.min, .max].affirmEnchaine(st.toStringI8) +[.min, .max].affirmEnchaine(st.toStringU8) + +// Shorts +[.min, .max].affirmEnchaine(st.toStringI16) +[.min, .max].affirmEnchaine(st.toStringU16) + +// Ints +[0, 1, -1, .min, .max].affirmEnchaine(st.toStringI32) +[0, 1, .min, .max].affirmEnchaine(st.toStringU32) + +// Longs +[.zero, 1, -1, .min, .max].affirmEnchaine(st.toStringI64) +[.zero, 1, .min, .max].affirmEnchaine(st.toStringU64) + +// Floats +[.zero, 1, -1, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmEnchaine(st.toStringFloat) { Float.init($0) == $1 } + +// Doubles +[.zero, 1, -1, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmEnchaine(st.toStringDouble) { Double.init($0) == $1 } + +// Some extension functions for testing the results of roundtripping and stringifying +extension Array where Element: Equatable { + static func defaultEquals(_ observed: String, expected: Element) -> Bool { + let exp = "\(expected)" + return observed == exp + } + + func affirmEnchaine(_ fn: (Element) -> String, equals: (String, Element) -> Bool = defaultEquals) { + self.forEach { v in + let obs = fn(v) + assert(equals(obs, v), "toString_\(type(of:v))(\(v)): observed=\(obs), expected=\(v)") + } + } + + func affirmAllerRetour(_ fn: (Element) -> Element) { + self.forEach { v in + assert(fn(v) == v, "identique_\(type(of:v))(\(v))") + } + } +} + +// Prove to ourselves that default arguments are being used. +// Step 1: call the methods without arguments, and check against the UDL. +let op = Optionneur() + +assert(op.sinonString() == "default") + +assert(op.sinonBoolean() == false) + +assert(op.sinonSequence() == []) + +// optionals +assert(op.sinonNull() == nil) +assert(op.sinonZero() == 0) + +// decimal integers +assert(op.sinonU8Dec() == UInt8(42)) +assert(op.sinonI8Dec() == Int8(-42)) +assert(op.sinonU16Dec() == UInt16(42)) +assert(op.sinonI16Dec() == Int16(42)) +assert(op.sinonU32Dec() == UInt32(42)) +assert(op.sinonI32Dec() == Int32(42)) +assert(op.sinonU64Dec() == UInt64(42)) +assert(op.sinonI64Dec() == Int64(42)) + +// hexadecimal integers +assert(op.sinonU8Hex() == UInt8(0xff)) +assert(op.sinonI8Hex() == Int8(-0x7f)) +assert(op.sinonU16Hex() == UInt16(0xffff)) +assert(op.sinonI16Hex() == Int16(0x7f)) +assert(op.sinonU32Hex() == UInt32(0xffffffff)) +assert(op.sinonI32Hex() == Int32(0x7fffffff)) +assert(op.sinonU64Hex() == UInt64(0xffffffffffffffff)) +assert(op.sinonI64Hex() == Int64(0x7fffffffffffffff)) + +// octal integers +assert(op.sinonU32Oct() == UInt32(0o755)) + +// floats +assert(op.sinonF32() == 42.0) +assert(op.sinonF64() == Double(42.1)) + +// enums +assert(op.sinonEnum() == .trois) + +// Step 2. Convince ourselves that if we pass something else, then that changes the output. +// We have shown something coming out of the sinon methods, but without eyeballing the Rust +// we can't be sure that the arguments will change the return value. +["foo", "bar"].affirmAllerRetour(op.sinonString) +[true, false].affirmAllerRetour(op.sinonBoolean) +[["a", "b"], []].affirmAllerRetour(op.sinonSequence) + +// optionals +["0", "1"].affirmAllerRetour(op.sinonNull) +[0, 1].affirmAllerRetour(op.sinonZero) + +// integers +[0, 1].affirmAllerRetour(op.sinonU8Dec) +[0, 1].affirmAllerRetour(op.sinonI8Dec) +[0, 1].affirmAllerRetour(op.sinonU16Dec) +[0, 1].affirmAllerRetour(op.sinonI16Dec) +[0, 1].affirmAllerRetour(op.sinonU32Dec) +[0, 1].affirmAllerRetour(op.sinonI32Dec) +[0, 1].affirmAllerRetour(op.sinonU64Dec) +[0, 1].affirmAllerRetour(op.sinonI64Dec) + +[0, 1].affirmAllerRetour(op.sinonU8Hex) +[0, 1].affirmAllerRetour(op.sinonI8Hex) +[0, 1].affirmAllerRetour(op.sinonU16Hex) +[0, 1].affirmAllerRetour(op.sinonI16Hex) +[0, 1].affirmAllerRetour(op.sinonU32Hex) +[0, 1].affirmAllerRetour(op.sinonI32Hex) +[0, 1].affirmAllerRetour(op.sinonU64Hex) +[0, 1].affirmAllerRetour(op.sinonI64Hex) + +[0, 1].affirmAllerRetour(op.sinonU32Oct) + +// floats +[0.0, 1.0].affirmAllerRetour(op.sinonF32) +[0.0, 1.0].affirmAllerRetour(op.sinonF64) + +// enums +[.un, .deux, .trois].affirmAllerRetour(op.sinonEnum) + +// Testing defaulting properties in record types. +let defaultes = OptionneurDictionnaire() +let explicite = OptionneurDictionnaire( + i8Var: Int8(-8), + u8Var: UInt8(8), + i16Var: Int16(-16), + u16Var: UInt16(0x10), + i32Var: -32, + u32Var: UInt32(32), + i64Var: Int64(-64), + u64Var: UInt64(64), + floatVar: Float(4.0), + doubleVar: Double(8.0), + booleanVar: true, + stringVar: "default", + listVar: [], + enumerationVar: .deux, + dictionnaireVar: nil +) + +// …and makes sure they travel across and back the FFI. +assert(defaultes == explicite) +[defaultes].affirmAllerRetour(rt.identiqueOptionneurDictionnaire) diff --git a/third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs new file mode 100644 index 0000000000..ddf705708c --- /dev/null +++ b/third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs @@ -0,0 +1,9 @@ +uniffi_macros::build_foreign_language_testcases!( + ["src/rondpoint.udl",], + [ + "tests/bindings/test_rondpoint.kts", + "tests/bindings/test_rondpoint.swift", + "tests/bindings/test_rondpoint.py", + "tests/bindings/test_rondpoint.rb", + ] +); |