blob: 62416f3ff8dabde2d4e4b663efd04cc1b55b979b [file] [log] [blame]
// Copyright 2021 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/testing/dart_isolate_runner.h"
#include "flutter/testing/fixture_test.h"
#include "tonic/dart_args.h"
#include "tonic/dart_wrappable.h"
namespace flutter {
namespace testing {
class MyNativeClass : public RefCountedDartWrappable<MyNativeClass> {
DEFINE_WRAPPERTYPEINFO();
FML_FRIEND_MAKE_REF_COUNTED(MyNativeClass);
MyNativeClass(intptr_t value) : _value(value){};
public:
intptr_t _value = 0;
static void Create(Dart_Handle path_handle, intptr_t value) {
auto path = fml::MakeRefCounted<MyNativeClass>(value);
path->AssociateWithDartWrapper(path_handle);
}
// Dummy test functions:
static int32_t MyTestFunction(MyNativeClass* ptr,
int32_t x,
Dart_Handle handle) {
return ptr->_value + x;
}
Dart_Handle MyTestMethod(int64_t x) { return Dart_NewInteger(_value + x); }
};
IMPLEMENT_WRAPPERTYPEINFO("Lib", MyNativeClass)
class FfiNativeTest : public FixtureTest {
public:
FfiNativeTest()
: settings_(CreateSettingsForFixture()),
vm_(DartVMRef::Create(settings_)),
thread_(CreateNewThread()),
task_runners_(GetCurrentTestName(),
thread_,
thread_,
thread_,
thread_) {}
~FfiNativeTest() = default;
[[nodiscard]] bool RunWithEntrypoint(const std::string& entrypoint) {
if (running_isolate_) {
return false;
}
auto isolate =
RunDartCodeInIsolate(vm_, settings_, task_runners_, entrypoint, {},
GetDefaultKernelFilePath());
if (!isolate || isolate->get()->GetPhase() != DartIsolate::Phase::Running) {
return false;
}
running_isolate_ = std::move(isolate);
return true;
}
template <typename C, typename Signature, Signature function>
void DoCallThroughTest(const char* testName, const char* testEntry) {
auto weak_persistent_value = tonic::DartWeakPersistentValue();
fml::AutoResetWaitableEvent event;
AddFfiNativeCallback(
testName, reinterpret_cast<void*>(
tonic::FfiDispatcher<C, Signature, function>::Call));
AddFfiNativeCallback(
"CreateNative",
reinterpret_cast<void*>(
tonic::FfiDispatcher<void, decltype(&MyNativeClass::Create),
&MyNativeClass::Create>::Call));
AddNativeCallback("SignalDone",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
// Clear on initiative of Dart.
weak_persistent_value.Clear();
event.Signal();
}));
ASSERT_TRUE(RunWithEntrypoint(testEntry));
event.Wait();
running_isolate_->Shutdown();
}
template <typename C, typename Signature, Signature function>
void DoSerialiseTest(bool leaf,
const char* returnFfi,
const char* returnDart,
const char* argsFfi,
const char* argsDart) {
auto dispatcher = tonic::FfiDispatcher<C, Signature, function>();
EXPECT_EQ(dispatcher.AllowedAsLeafCall(), leaf);
EXPECT_STREQ(dispatcher.GetReturnFfiRepresentation(), returnFfi);
EXPECT_STREQ(dispatcher.GetReturnDartRepresentation(), returnDart);
{
std::ostringstream stream_;
dispatcher.WriteFfiArguments(&stream_);
EXPECT_STREQ(stream_.str().c_str(), argsFfi);
}
{
std::ostringstream stream_;
dispatcher.WriteDartArguments(&stream_);
EXPECT_STREQ(stream_.str().c_str(), argsDart);
}
}
protected:
Settings settings_;
DartVMRef vm_;
std::unique_ptr<AutoIsolateShutdown> running_isolate_;
fml::RefPtr<fml::TaskRunner> thread_;
TaskRunners task_runners_;
FML_DISALLOW_COPY_AND_ASSIGN(FfiNativeTest);
};
//
// Call bindings.
//
// Call and serialise a simple function through the Tonic FFI bindings.
void Nop() {}
TEST_F(FfiNativeTest, FfiBindingCallNop) {
DoCallThroughTest<void, decltype(&Nop), &Nop>("Nop", "callNop");
}
TEST_F(FfiNativeTest, SerialiseNop) {
DoSerialiseTest<void, decltype(&Nop), &Nop>(
/*leaf=*/true, "Void", "void", "", "");
}
// Call and serialise function with bool.
bool EchoBool(bool arg) {
EXPECT_TRUE(arg);
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoBool) {
DoCallThroughTest<void, decltype(&EchoBool), &EchoBool>("EchoBool",
"callEchoBool");
}
TEST_F(FfiNativeTest, SerialiseEchoBool) {
DoSerialiseTest<void, decltype(&EchoBool), &EchoBool>(
/*leaf=*/true, "Bool", "bool", "Bool", "bool");
}
// Call and serialise function with intptr_t.
intptr_t EchoIntPtr(intptr_t arg) {
EXPECT_EQ(arg, 23);
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoIntPtr) {
DoCallThroughTest<void, decltype(&EchoIntPtr), &EchoIntPtr>("EchoIntPtr",
"callEchoIntPtr");
}
TEST_F(FfiNativeTest, SerialiseEchoIntPtr) {
if (sizeof(intptr_t) == 8) {
DoSerialiseTest<void, decltype(&EchoIntPtr), &EchoIntPtr>(
/*leaf=*/true, "Int64", "int", "Int64", "int");
} else {
EXPECT_EQ(sizeof(intptr_t), 4ul);
DoSerialiseTest<void, decltype(&EchoIntPtr), &EchoIntPtr>(
/*leaf=*/true, "Int32", "int", "Int32", "int");
}
}
// Call and serialise function with double.
double EchoDouble(double arg) {
EXPECT_EQ(arg, 23.0);
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoDouble) {
DoCallThroughTest<void, decltype(&EchoDouble), &EchoDouble>("EchoDouble",
"callEchoDouble");
}
TEST_F(FfiNativeTest, SerialiseEchoDouble) {
DoSerialiseTest<void, decltype(&EchoDouble), &EchoDouble>(
/*leaf=*/true, "Double", "double", "Double", "double");
}
// Call and serialise function with Dart_Handle.
Dart_Handle EchoHandle(Dart_Handle str) {
const char* c_str = nullptr;
Dart_StringToCString(str, &c_str);
EXPECT_STREQ(c_str, "Hello EchoHandle");
return str;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoHandle) {
DoCallThroughTest<void, decltype(&EchoHandle), &EchoHandle>("EchoHandle",
"callEchoHandle");
}
TEST_F(FfiNativeTest, SerialiseEchoHandle) {
DoSerialiseTest<void, decltype(&EchoHandle), &EchoHandle>(
/*leaf=*/false, "Handle", "Object", "Handle", "Object");
}
// Call and serialise function with std::string.
std::string EchoString(std::string arg) {
EXPECT_STREQ(arg.c_str(), "Hello EchoString");
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoString) {
DoCallThroughTest<void, decltype(&EchoString), &EchoString>("EchoString",
"callEchoString");
}
TEST_F(FfiNativeTest, SerialiseEchoString) {
DoSerialiseTest<void, decltype(&EchoString), &EchoString>(
/*leaf=*/false, "Handle", "String", "Handle", "String");
}
// Call and serialise function with std::u16string.
std::u16string EchoU16String(std::u16string arg) {
EXPECT_EQ(arg, u"Hello EchoU16String");
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoU16String) {
DoCallThroughTest<void, decltype(&EchoU16String), &EchoU16String>(
"EchoU16String", "callEchoU16String");
}
TEST_F(FfiNativeTest, SerialiseEchoU16String) {
DoSerialiseTest<void, decltype(&EchoU16String), &EchoU16String>(
/*leaf=*/false, "Handle", "String", "Handle", "String");
}
// Call and serialise function with std::vector.
std::vector<std::string> EchoVector(const std::vector<std::string>& arg) {
EXPECT_STREQ(arg[0].c_str(), "Hello EchoVector");
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoVector) {
DoCallThroughTest<void, decltype(&EchoVector), &EchoVector>("EchoVector",
"callEchoVector");
}
TEST_F(FfiNativeTest, SerialiseEchoVector) {
DoSerialiseTest<void, decltype(&EchoVector), &EchoVector>(
/*leaf=*/false, "Handle", "List", "Handle", "List");
}
// Call and serialise function with DartWrappable.
intptr_t EchoWrappable(MyNativeClass* arg) {
EXPECT_EQ(arg->_value, 0x1234);
return arg->_value;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoWrappable) {
DoCallThroughTest<void, decltype(&EchoWrappable), &EchoWrappable>(
"EchoWrappable", "callEchoWrappable");
}
TEST_F(FfiNativeTest, SerialiseEchoWrappable) {
if (sizeof(intptr_t) == 8) {
DoSerialiseTest<void, decltype(&EchoWrappable), &EchoWrappable>(
/*leaf=*/true, "Int64", "int", "Pointer", "Pointer");
} else {
EXPECT_EQ(sizeof(intptr_t), 4ul);
DoSerialiseTest<void, decltype(&EchoWrappable), &EchoWrappable>(
/*leaf=*/true, "Int32", "int", "Pointer", "Pointer");
}
}
// Call and serialise function with TypedList<..>.
tonic::Float32List EchoTypedList(tonic::Float32List arg) {
EXPECT_NEAR(arg[1], 3.14, 0.01);
return arg;
}
TEST_F(FfiNativeTest, FfiBindingCallEchoTypedList) {
DoCallThroughTest<void, decltype(&EchoTypedList), &EchoTypedList>(
"EchoTypedList", "callEchoTypedList");
}
TEST_F(FfiNativeTest, SerialiseEchoTypedList) {
DoSerialiseTest<void, decltype(&EchoTypedList), &EchoTypedList>(
/*leaf=*/false, "Handle", "Object", "Handle", "Object");
}
// Call and serialise a static class member function.
TEST_F(FfiNativeTest, FfiBindingCallClassMemberFunction) {
DoCallThroughTest<void, decltype(&MyNativeClass::MyTestFunction),
&MyNativeClass::MyTestFunction>(
"MyNativeClass::MyTestFunction", "callMyTestFunction");
}
TEST_F(FfiNativeTest, SerialiseClassMemberFunction) {
DoSerialiseTest<void, decltype(&MyNativeClass::MyTestFunction),
&MyNativeClass::MyTestFunction>(
/*leaf=*/false, "Int32", "int", "Pointer, Int32, Handle",
"Pointer, int, Object");
}
// Call and serialise an instance method.
TEST_F(FfiNativeTest, FfiBindingCallClassMemberMethod) {
DoCallThroughTest<MyNativeClass, decltype(&MyNativeClass::MyTestMethod),
&MyNativeClass::MyTestMethod>("MyNativeClass::MyTestMethod",
"callMyTestMethod");
}
TEST_F(FfiNativeTest, SerialiseClassMemberMethod) {
DoSerialiseTest<MyNativeClass, decltype(&MyNativeClass::MyTestMethod),
&MyNativeClass::MyTestMethod>(
/*leaf=*/false, "Handle", "Object", "Pointer, Int64", "Pointer, int");
}
} // namespace testing
} // namespace flutter