From b0155a72a7a34eb0b554213676e3e2c64d11530c Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Wed, 4 Dec 2019 13:02:48 +0000 Subject: [PATCH] [vm/ffi] Add script to extract existing positive ffi tests into bundle to be used for flutter/flutter integration test The `tests/ffi/prepare_flutter_bundle.dart` script will try to discover all synchronous, positive test from the "ffi" test suite, rewrite them slightly and output a directory which can be used in a flutter/flutter FFI integration test. We split the ffi test functions into two parts, because a subset of the C functions will issue calls to the VM via `dart_api.h`, which is not available for the flutter/flutter integration test. => We make `runtime/bin/ffi_test/ffi_test_functions.cc` a pure C library, usable without `dart_api.h` and move the remaining VM specific code to .../ffi_test_functions_special.cc contains. All tests from `tests/ffi/*_test.dart` will be included in the generated bundle, which * don't use async/isolates * don't rely on VM api * don't rely on DynamicLibrary.{process,executable} * don't take too long to execute The script can be used as follows: sdk % dart tests/ffi/prepare_flutter_bundle.dart foo Using SDK root: .../sdk The following tests will be included: aliasing_test.dart data_not_asan_test.dart data_test.dart extension_methods_test.dart external_typed_data_test.dart function_structs_test.dart negative_function_test.dart regress_37254_test.dart regress_39044_test.dart regress_39063_test.dart regress_39068_test.dart stacktrace_regress_37910_test.dart structs_test.dart variance_function_test.dart The following tests were filtered due to using dart_api.h/async/DynamicLibrary.{process,executable}/... function_callbacks_test.dart function_gc_test.dart function_test.dart object_gc_test.dart regress_37100_test.dart regress_37511_callbacks_test.dart regress_37511_test.dart regress_37780_test.dart Please copy generated files into FFI flutter test application * foo/lib/src/generated * foo/ios/Classes Change-Id: Ia13f97df3bbc90829bb8fde8265a7e1d2c0f8260 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/127006 Reviewed-by: Daco Harkes Commit-Queue: Martin Kustermann --- runtime/bin/BUILD.gn | 1 + .../bin/ffi_test/ffi_test_dynamic_library.cc | 7 +- runtime/bin/ffi_test/ffi_test_functions.cc | 253 +---------------- .../ffi_test/ffi_test_functions_special.cc | 267 ++++++++++++++++++ tests/ffi/aliasing_test.dart | 2 - tests/ffi/data_not_asan_test.dart | 2 - tests/ffi/data_test.dart | 2 - tests/ffi/dynamic_library_test.dart | 2 - tests/ffi/function_callbacks_test.dart | 2 - tests/ffi/function_structs_test.dart | 2 - tests/ffi/object_gc_test.dart | 2 - tests/ffi/prepare_flutter_bundle.dart | 221 +++++++++++++++ tests/ffi/regress_37254_test.dart | 1 - tests/ffi/regress_37511_callbacks_test.dart | 2 - tests/ffi/regress_37511_test.dart | 2 - tests/ffi/regress_39068_test.dart | 4 +- tests/ffi/static_checks_test.dart | 2 - tests/ffi/structs_test.dart | 2 - 18 files changed, 507 insertions(+), 269 deletions(-) create mode 100644 runtime/bin/ffi_test/ffi_test_functions_special.cc create mode 100644 tests/ffi/prepare_flutter_bundle.dart diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index 50a049d4765b..74c200117a9e 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn @@ -1122,6 +1122,7 @@ shared_library("ffi_test_functions") { ] sources = [ "ffi_test/ffi_test_functions.cc", + "ffi_test/ffi_test_functions_special.cc", ] if (is_win && current_cpu == "x64") { sources += [ "ffi_test/clobber_x64_win.S" ] diff --git a/runtime/bin/ffi_test/ffi_test_dynamic_library.cc b/runtime/bin/ffi_test/ffi_test_dynamic_library.cc index c925300eaf1f..c812c6d97ef5 100644 --- a/runtime/bin/ffi_test/ffi_test_dynamic_library.cc +++ b/runtime/bin/ffi_test/ffi_test_dynamic_library.cc @@ -2,7 +2,12 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -#include "include/dart_api.h" +#if defined(_WIN32) +#define DART_EXPORT extern "C" __declspec(dllexport) +#else +#define DART_EXPORT \ + extern "C" __attribute__((visibility("default"))) __attribute((used)) +#endif DART_EXPORT int return42() { return 42; diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc index f673f13138ed..c7fa6a17088d 100644 --- a/runtime/bin/ffi_test/ffi_test_functions.cc +++ b/runtime/bin/ffi_test/ffi_test_functions.cc @@ -7,28 +7,17 @@ #include #include #include -#include -#include "platform/globals.h" -#if defined(HOST_OS_WINDOWS) -#include -#else -#include - -// Only OK to use here because this is test code. -#include // NOLINT(build/c++11) -#include // NOLINT(build/c++11) -#include // NOLINT(build/c++11) -#include // NOLINT(build/c++11) -#endif - -#include -#include +#include #include #include -#include "include/dart_api.h" -#include "include/dart_native_api.h" +#if defined(_WIN32) +#define DART_EXPORT extern "C" __declspec(dllexport) +#else +#define DART_EXPORT \ + extern "C" __attribute__((visibility("default"))) __attribute((used)) +#endif namespace dart { @@ -108,7 +97,7 @@ DART_EXPORT intptr_t TakeMinInt16(int16_t x) { } DART_EXPORT intptr_t TakeMinInt32(int32_t x) { - const int64_t expected = kMinInt32; + const int64_t expected = INT32_MIN; const int64_t received = x; return expected == received ? 1 : 0; } @@ -509,125 +498,6 @@ DART_EXPORT float InventFloatValue() { return retval; } -//////////////////////////////////////////////////////////////////////////////// -// Functions for stress-testing. - -DART_EXPORT int64_t MinInt64() { - Dart_ExecuteInternalCommand("gc-on-nth-allocation", - reinterpret_cast(1)); - return 0x8000000000000000; -} - -DART_EXPORT int64_t MinInt32() { - Dart_ExecuteInternalCommand("gc-on-nth-allocation", - reinterpret_cast(1)); - return 0x80000000; -} - -DART_EXPORT double SmallDouble() { - Dart_ExecuteInternalCommand("gc-on-nth-allocation", - reinterpret_cast(1)); - return 0x80000000 * -1.0; -} - -// Requires boxing on 32-bit and 64-bit systems, even if the top 32-bits are -// truncated. -DART_EXPORT void* LargePointer() { - Dart_ExecuteInternalCommand("gc-on-nth-allocation", - reinterpret_cast(1)); - uint64_t origin = 0x8100000082000000; - return reinterpret_cast(origin); -} - -DART_EXPORT void TriggerGC(uint64_t count) { - Dart_ExecuteInternalCommand("gc-now", nullptr); -} - -DART_EXPORT void CollectOnNthAllocation(intptr_t num_allocations) { - Dart_ExecuteInternalCommand("gc-on-nth-allocation", - reinterpret_cast(num_allocations)); -} - -// Triggers GC. Has 11 dummy arguments as unboxed odd integers which should be -// ignored by GC. -DART_EXPORT void Regress37069(uint64_t a, - uint64_t b, - uint64_t c, - uint64_t d, - uint64_t e, - uint64_t f, - uint64_t g, - uint64_t h, - uint64_t i, - uint64_t j, - uint64_t k) { - Dart_ExecuteInternalCommand("gc-now", nullptr); -} - -#if !defined(HOST_OS_WINDOWS) -DART_EXPORT void* UnprotectCodeOtherThread(void* isolate, - std::condition_variable* var, - std::mutex* mut) { - std::function callback = [&]() { - mut->lock(); - var->notify_all(); - mut->unlock(); - - // Wait for mutator thread to continue (and block) before leaving the - // safepoint. - while (Dart_ExecuteInternalCommand("is-mutator-in-native", isolate) != - nullptr) { - usleep(10 * 1000 /*10 ms*/); - } - }; - - struct { - void* isolate; - std::function* callback; - } args = {.isolate = isolate, .callback = &callback}; - - Dart_ExecuteInternalCommand("run-in-safepoint-and-rw-code", &args); - return nullptr; -} - -struct HelperThreadState { - std::mutex mutex; - std::condition_variable cvar; - std::unique_ptr helper; -}; - -DART_EXPORT void* TestUnprotectCode(void (*fn)(void*)) { - HelperThreadState* state = new HelperThreadState; - - { - std::unique_lock lock(state->mutex); // locks the mutex - state->helper.reset(new std::thread(UnprotectCodeOtherThread, - Dart_CurrentIsolate(), &state->cvar, - &state->mutex)); - - state->cvar.wait(lock); - } - - if (fn != nullptr) { - fn(state); - return nullptr; - } else { - return state; - } -} - -DART_EXPORT void WaitForHelper(HelperThreadState* helper) { - helper->helper->join(); - delete helper; -} -#else -// Our version of VSC++ doesn't support std::thread yet. -DART_EXPORT void WaitForHelper(void* helper) {} -DART_EXPORT void* TestUnprotectCode(void (*fn)(void)) { - return nullptr; -} -#endif - //////////////////////////////////////////////////////////////////////////////// // Tests for callbacks. @@ -663,7 +533,7 @@ DART_EXPORT int TestSimpleMultiply(double (*fn)(double)) { } DART_EXPORT int TestSimpleMultiplyFloat(float (*fn)(float)) { - CHECK(std::abs(fn(2.0) - 2.0 * 1.337) < 0.001); + CHECK(::std::abs(fn(2.0) - 2.0 * 1.337) < 0.001); return 0; } @@ -741,24 +611,6 @@ DART_EXPORT int TestNullPointers(int64_t* (*fn)(int64_t* ptr)) { return 0; } -// Defined in ffi_test_functions.S. -// -// Clobbers some registers with special meaning in Dart before re-entry, for -// stress-testing. Not used on 32-bit Windows due to complications with Windows -// "safeseh". -#if defined(TARGET_OS_WINDOWS) && defined(HOST_ARCH_IA32) -void ClobberAndCall(void (*fn)()) { - fn(); -} -#else -extern "C" void ClobberAndCall(void (*fn)()); -#endif - -DART_EXPORT int TestGC(void (*do_gc)()) { - ClobberAndCall(do_gc); - return 0; -} - DART_EXPORT int TestReturnVoid(int (*return_void)()) { CHECK_EQ(return_void(), 0); return 0; @@ -779,93 +631,6 @@ DART_EXPORT int TestThrowException(int (*fn)()) { return 0; } -struct CallbackTestData { - int success; - void (*callback)(); -}; - -#if defined(TARGET_OS_LINUX) - -thread_local sigjmp_buf buf; -void CallbackTestSignalHandler(int) { - siglongjmp(buf, 1); -} - -int ExpectAbort(void (*fn)()) { - fprintf(stderr, "**** EXPECT STACKTRACE TO FOLLOW. THIS IS OK. ****\n"); - - struct sigaction old_action = {}; - int result = __sigsetjmp(buf, /*savesigs=*/1); - if (result == 0) { - // Install signal handler. - struct sigaction handler = {}; - handler.sa_handler = CallbackTestSignalHandler; - sigemptyset(&handler.sa_mask); - handler.sa_flags = 0; - - sigaction(SIGABRT, &handler, &old_action); - - fn(); - } else { - // Caught the setjmp. - sigaction(SIGABRT, &old_action, NULL); - exit(0); - } - fprintf(stderr, "Expected abort!!!\n"); - exit(1); -} - -void* TestCallbackOnThreadOutsideIsolate(void* parameter) { - CallbackTestData* data = reinterpret_cast(parameter); - data->success = ExpectAbort(data->callback); - return NULL; -} - -int TestCallbackOtherThreadHelper(void* (*tester)(void*), void (*fn)()) { - CallbackTestData data = {1, fn}; - pthread_attr_t attr; - int result = pthread_attr_init(&attr); - CHECK_EQ(result, 0); - - pthread_t tid; - result = pthread_create(&tid, &attr, tester, &data); - CHECK_EQ(result, 0); - - result = pthread_attr_destroy(&attr); - CHECK_EQ(result, 0); - - void* retval; - result = pthread_join(tid, &retval); - - // Doesn't actually return because the other thread will exit when the test is - // finished. - return 1; -} - -// Run a callback on another thread and verify that it triggers SIGABRT. -DART_EXPORT int TestCallbackWrongThread(void (*fn)()) { - return TestCallbackOtherThreadHelper(&TestCallbackOnThreadOutsideIsolate, fn); -} - -// Verify that we get SIGABRT when invoking a native callback outside an -// isolate. -DART_EXPORT int TestCallbackOutsideIsolate(void (*fn)()) { - Dart_Isolate current = Dart_CurrentIsolate(); - - Dart_ExitIsolate(); - CallbackTestData data = {1, fn}; - TestCallbackOnThreadOutsideIsolate(&data); - Dart_EnterIsolate(current); - - return data.success; -} - -DART_EXPORT int TestCallbackWrongIsolate(void (*fn)()) { - return ExpectAbort(fn); -} - -#endif // defined(TARGET_OS_LINUX) - // Receives some pointer (Pointer in Dart) and writes some bits. DART_EXPORT void NativeTypePointerParam(void* p) { uint8_t* p2 = reinterpret_cast(p); diff --git a/runtime/bin/ffi_test/ffi_test_functions_special.cc b/runtime/bin/ffi_test/ffi_test_functions_special.cc new file mode 100644 index 000000000000..8e13bf135424 --- /dev/null +++ b/runtime/bin/ffi_test/ffi_test_functions_special.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// This file contains test functions for the dart:ffi test cases. + +#include +#include +#include +#include + +#include "platform/globals.h" +#if defined(HOST_OS_WINDOWS) +#include +#else +#include + +// Only OK to use here because this is test code. +#include // NOLINT(build/c++11) +#include // NOLINT(build/c++11) +#include // NOLINT(build/c++11) +#include // NOLINT(build/c++11) +#endif + +#include +#include +#include +#include + +#include "include/dart_api.h" +#include "include/dart_native_api.h" + +namespace dart { + +#define CHECK(X) \ + if (!(X)) { \ + fprintf(stderr, "%s\n", "Check failed: " #X); \ + return 1; \ + } + +#define CHECK_EQ(X, Y) CHECK((X) == (Y)) + +//////////////////////////////////////////////////////////////////////////////// +// Functions for stress-testing. + +DART_EXPORT int64_t MinInt64() { + Dart_ExecuteInternalCommand("gc-on-nth-allocation", + reinterpret_cast(1)); + return 0x8000000000000000; +} + +DART_EXPORT int64_t MinInt32() { + Dart_ExecuteInternalCommand("gc-on-nth-allocation", + reinterpret_cast(1)); + return 0x80000000; +} + +DART_EXPORT double SmallDouble() { + Dart_ExecuteInternalCommand("gc-on-nth-allocation", + reinterpret_cast(1)); + return 0x80000000 * -1.0; +} + +// Requires boxing on 32-bit and 64-bit systems, even if the top 32-bits are +// truncated. +DART_EXPORT void* LargePointer() { + Dart_ExecuteInternalCommand("gc-on-nth-allocation", + reinterpret_cast(1)); + uint64_t origin = 0x8100000082000000; + return reinterpret_cast(origin); +} + +DART_EXPORT void TriggerGC(uint64_t count) { + Dart_ExecuteInternalCommand("gc-now", nullptr); +} + +DART_EXPORT void CollectOnNthAllocation(intptr_t num_allocations) { + Dart_ExecuteInternalCommand("gc-on-nth-allocation", + reinterpret_cast(num_allocations)); +} + +// Triggers GC. Has 11 dummy arguments as unboxed odd integers which should be +// ignored by GC. +DART_EXPORT void Regress37069(uint64_t a, + uint64_t b, + uint64_t c, + uint64_t d, + uint64_t e, + uint64_t f, + uint64_t g, + uint64_t h, + uint64_t i, + uint64_t j, + uint64_t k) { + Dart_ExecuteInternalCommand("gc-now", nullptr); +} + +#if !defined(HOST_OS_WINDOWS) +DART_EXPORT void* UnprotectCodeOtherThread(void* isolate, + std::condition_variable* var, + std::mutex* mut) { + std::function callback = [&]() { + mut->lock(); + var->notify_all(); + mut->unlock(); + + // Wait for mutator thread to continue (and block) before leaving the + // safepoint. + while (Dart_ExecuteInternalCommand("is-mutator-in-native", isolate) != + nullptr) { + usleep(10 * 1000 /*10 ms*/); + } + }; + + struct { + void* isolate; + std::function* callback; + } args = {.isolate = isolate, .callback = &callback}; + + Dart_ExecuteInternalCommand("run-in-safepoint-and-rw-code", &args); + return nullptr; +} + +struct HelperThreadState { + std::mutex mutex; + std::condition_variable cvar; + std::unique_ptr helper; +}; + +DART_EXPORT void* TestUnprotectCode(void (*fn)(void*)) { + HelperThreadState* state = new HelperThreadState; + + { + std::unique_lock lock(state->mutex); // locks the mutex + state->helper.reset(new std::thread(UnprotectCodeOtherThread, + Dart_CurrentIsolate(), &state->cvar, + &state->mutex)); + + state->cvar.wait(lock); + } + + if (fn != nullptr) { + fn(state); + return nullptr; + } else { + return state; + } +} + +DART_EXPORT void WaitForHelper(HelperThreadState* helper) { + helper->helper->join(); + delete helper; +} +#else +// Our version of VSC++ doesn't support std::thread yet. +DART_EXPORT void WaitForHelper(void* helper) {} +DART_EXPORT void* TestUnprotectCode(void (*fn)(void)) { + return nullptr; +} +#endif + +// Defined in ffi_test_functions.S. +// +// Clobbers some registers with special meaning in Dart before re-entry, for +// stress-testing. Not used on 32-bit Windows due to complications with Windows +// "safeseh". +#if defined(TARGET_OS_WINDOWS) && defined(HOST_ARCH_IA32) +void ClobberAndCall(void (*fn)()) { + fn(); +} +#else +extern "C" void ClobberAndCall(void (*fn)()); +#endif + +DART_EXPORT int TestGC(void (*do_gc)()) { + ClobberAndCall(do_gc); + return 0; +} + +struct CallbackTestData { + int success; + void (*callback)(); +}; + +#if defined(TARGET_OS_LINUX) + +thread_local sigjmp_buf buf; +void CallbackTestSignalHandler(int) { + siglongjmp(buf, 1); +} + +int ExpectAbort(void (*fn)()) { + fprintf(stderr, "**** EXPECT STACKTRACE TO FOLLOW. THIS IS OK. ****\n"); + + struct sigaction old_action = {}; + int result = __sigsetjmp(buf, /*savesigs=*/1); + if (result == 0) { + // Install signal handler. + struct sigaction handler = {}; + handler.sa_handler = CallbackTestSignalHandler; + sigemptyset(&handler.sa_mask); + handler.sa_flags = 0; + + sigaction(SIGABRT, &handler, &old_action); + + fn(); + } else { + // Caught the setjmp. + sigaction(SIGABRT, &old_action, NULL); + exit(0); + } + fprintf(stderr, "Expected abort!!!\n"); + exit(1); +} + +void* TestCallbackOnThreadOutsideIsolate(void* parameter) { + CallbackTestData* data = reinterpret_cast(parameter); + data->success = ExpectAbort(data->callback); + return NULL; +} + +int TestCallbackOtherThreadHelper(void* (*tester)(void*), void (*fn)()) { + CallbackTestData data = {1, fn}; + pthread_attr_t attr; + int result = pthread_attr_init(&attr); + CHECK_EQ(result, 0); + + pthread_t tid; + result = pthread_create(&tid, &attr, tester, &data); + CHECK_EQ(result, 0); + + result = pthread_attr_destroy(&attr); + CHECK_EQ(result, 0); + + void* retval; + result = pthread_join(tid, &retval); + + // Doesn't actually return because the other thread will exit when the test is + // finished. + return 1; +} + +// Run a callback on another thread and verify that it triggers SIGABRT. +DART_EXPORT int TestCallbackWrongThread(void (*fn)()) { + return TestCallbackOtherThreadHelper(&TestCallbackOnThreadOutsideIsolate, fn); +} + +// Verify that we get SIGABRT when invoking a native callback outside an +// isolate. +DART_EXPORT int TestCallbackOutsideIsolate(void (*fn)()) { + Dart_Isolate current = Dart_CurrentIsolate(); + + Dart_ExitIsolate(); + CallbackTestData data = {1, fn}; + TestCallbackOnThreadOutsideIsolate(&data); + Dart_EnterIsolate(current); + + return data.success; +} + +DART_EXPORT int TestCallbackWrongIsolate(void (*fn)()) { + return ExpectAbort(fn); +} + +#endif // defined(TARGET_OS_LINUX) + +} // namespace dart diff --git a/tests/ffi/aliasing_test.dart b/tests/ffi/aliasing_test.dart index 9ae36e877bfc..6c2f698c0e24 100644 --- a/tests/ffi/aliasing_test.dart +++ b/tests/ffi/aliasing_test.dart @@ -8,8 +8,6 @@ // SharedObjects=ffi_test_functions // VMOptions=--deterministic --optimization-counter-threshold=50 -library FfiTest; - import 'dart:ffi'; import "package:ffi/ffi.dart"; diff --git a/tests/ffi/data_not_asan_test.dart b/tests/ffi/data_not_asan_test.dart index aa9cf240d50b..e95658f2fd1e 100644 --- a/tests/ffi/data_not_asan_test.dart +++ b/tests/ffi/data_not_asan_test.dart @@ -7,8 +7,6 @@ // These mallocs trigger an asan alarm, so these tests are in a separate file // which is excluded in asan mode. -library FfiTest; - import 'dart:ffi'; import "package:ffi/ffi.dart"; diff --git a/tests/ffi/data_test.dart b/tests/ffi/data_test.dart index 3df645198559..0dc4d191ebd8 100644 --- a/tests/ffi/data_test.dart +++ b/tests/ffi/data_test.dart @@ -6,8 +6,6 @@ // // SharedObjects=ffi_test_functions -library FfiTest; - import 'dart:ffi'; import "package:expect/expect.dart"; diff --git a/tests/ffi/dynamic_library_test.dart b/tests/ffi/dynamic_library_test.dart index f3d53c81b745..5c65c5cfd115 100644 --- a/tests/ffi/dynamic_library_test.dart +++ b/tests/ffi/dynamic_library_test.dart @@ -6,8 +6,6 @@ // // SharedObjects=ffi_test_dynamic_library ffi_test_functions -library FfiTest; - import 'dart:io'; import 'dart:ffi'; diff --git a/tests/ffi/function_callbacks_test.dart b/tests/ffi/function_callbacks_test.dart index 8b037d45ef4f..1729757690e0 100644 --- a/tests/ffi/function_callbacks_test.dart +++ b/tests/ffi/function_callbacks_test.dart @@ -17,8 +17,6 @@ // VMOptions=--use-slow-path --enable-testing-pragmas --write-protect-code --no-dual-map-code --stacktrace-every=100 // SharedObjects=ffi_test_functions -library FfiTest; - import 'dart:io'; import 'dart:ffi'; import 'dart:isolate'; diff --git a/tests/ffi/function_structs_test.dart b/tests/ffi/function_structs_test.dart index ee71a4bc8420..37388768017a 100644 --- a/tests/ffi/function_structs_test.dart +++ b/tests/ffi/function_structs_test.dart @@ -7,8 +7,6 @@ // // SharedObjects=ffi_test_functions -library FfiTest; - import 'dart:ffi'; import 'dylib_utils.dart'; diff --git a/tests/ffi/object_gc_test.dart b/tests/ffi/object_gc_test.dart index de7d3f714663..50aeca4f7a92 100644 --- a/tests/ffi/object_gc_test.dart +++ b/tests/ffi/object_gc_test.dart @@ -6,8 +6,6 @@ // // SharedObjects=ffi_test_functions -library FfiTest; - import 'dart:ffi'; import "package:expect/expect.dart"; diff --git a/tests/ffi/prepare_flutter_bundle.dart b/tests/ffi/prepare_flutter_bundle.dart new file mode 100644 index 000000000000..6cf708e9ca01 --- /dev/null +++ b/tests/ffi/prepare_flutter_bundle.dart @@ -0,0 +1,221 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:args/args.dart'; + +main(List args) async { + if (args.length != 1) { + print('Usage ${Platform.executable} ${Platform.script} '); + exit(1); + } + + final sdkRoot = + path.canonicalize(path.join(Platform.script.path, '../../..')); + final flutterTestsDir = args.single; + + print('Using SDK root: $sdkRoot'); + final testFiles = []; + final failedOrTimedOut = []; + final filteredTests = []; + await for (final testFile in listTestFiles(sdkRoot, filteredTests)) { + final duration = await run(sdkRoot, testFile); + if (duration != null && duration.inSeconds < 5) { + testFiles.add(testFile); + } else { + failedOrTimedOut.add(testFile); + } + } + testFiles.sort(); + failedOrTimedOut.sort(); + filteredTests.sort(); + + dumpTestList(testFiles, 'The following tests will be included:'); + dumpTestList(failedOrTimedOut, + 'The following tests will be excluded due to timeout or test failure:'); + dumpTestList( + filteredTests, + 'The following tests were filtered due to using ' + 'dart_api.h/async/DynamicLibrary.{process,executable}/...'); + + final allFiles = {}; + allFiles.add(path.join(sdkRoot, 'pkg/expect/lib/expect.dart')); + for (final testFile in testFiles) { + allFiles.add(testFile); + await addImportedFilesTo(allFiles, testFile); + } + + await generateCleanDir(flutterTestsDir); + + final dartTestsDir = path.join(flutterTestsDir, 'lib/src/generated'); + await generateDartTests(dartTestsDir, allFiles, testFiles); + + final ccDir = path.join(flutterTestsDir, 'ios/Classes'); + await generateCLibs(sdkRoot, ccDir, allFiles, testFiles); + + print(''); + print('Please copy generated files into FFI flutter test application'); + print(' * $dartTestsDir'); + print(' * $ccDir'); +} + +void dumpTestList(List testFiles, String message) { + if (testFiles.isEmpty) return; + + print(message); + for (final testFile in testFiles) { + print(' ${path.basename(testFile)}'); + } +} + +final importRegExp = RegExp(r'''^import.*['"](.+)['"].*;'''); + +Future addImportedFilesTo(Set allFiles, String testFile) async { + final content = await File(testFile).readAsString(); + for (final line in content.split('\n')) { + final match = importRegExp.matchAsPrefix(line); + if (match != null) { + final filename = match.group(1); + if (!filename.contains('dart:') && + !filename.contains('package:expect') && + !filename.contains('package:ffi')) { + final importedFile = Uri.file(testFile).resolve(filename).toFilePath(); + if (allFiles.add(importedFile)) { + addImportedFilesTo(allFiles, importedFile); + } + } + } + } +} + +Future generateCLibs(String sdkRoot, String destDir, Set allFiles, + List testFiles) async { + final dir = await generateCleanDir(destDir); + + String destinationFile; + + final lib1 = + path.join(sdkRoot, 'runtime/bin/ffi_test/ffi_test_dynamic_library.cc'); + destinationFile = + path.join(dir.path, path.basename(lib1)).replaceAll('.cc', '.cpp'); + File(destinationFile) + .writeAsStringSync(cleanCC(File(lib1).readAsStringSync())); + + final lib2 = path.join(sdkRoot, 'runtime/bin/ffi_test/ffi_test_functions.cc'); + destinationFile = + path.join(dir.path, path.basename(lib2)).replaceAll('.cc', '.cpp'); + File(destinationFile) + .writeAsStringSync(cleanCC(File(lib2).readAsStringSync())); +} + +String cleanCC(String content) { + return content.replaceAll('DART_EXPORT', 'extern "C" '); +} + +String cleanDart(String content) { + return content.replaceAll('package:expect/expect.dart', 'expect.dart'); +} + +Future generateDartTests( + String destDir, Set allFiles, List testFiles) async { + final dir = await generateCleanDir(destDir); + + final sink = File(path.join(dir.path, 'all.dart')).openWrite(); + for (int i = 0; i < testFiles.length; ++i) { + sink.writeln('import "${path.basename(testFiles[i])}" as main$i;'); + } + sink.writeln(''); + sink.writeln('invoke(fn) {'); + sink.writeln(' if (fn is void Function()) {'); + sink.writeln(' fn();'); + sink.writeln(' } else {'); + sink.writeln(' fn([]);'); + sink.writeln(' }'); + sink.writeln('}'); + sink.writeln(''); + sink.writeln('main() {'); + for (int i = 0; i < testFiles.length; ++i) { + sink.writeln(' invoke(main$i.main);'); + } + sink.writeln('}'); + await sink.close(); + + for (final file in allFiles) { + File(path.join(dir.path, path.basename(file))) + .writeAsStringSync(cleanDart(File(file).readAsStringSync())); + } + + File(path.join(dir.path, 'dylib_utils.dart')).writeAsStringSync(''' +import 'dart:ffi' as ffi; +import 'dart:io' show Platform; + +ffi.DynamicLibrary dlopenPlatformSpecific(String name, {String path}) { + return Platform.isAndroid + ? ffi.DynamicLibrary.open('libffi_tests.so') + : ffi.DynamicLibrary.process(); +} +'''); +} + +Stream listTestFiles( + String sdkRoot, List filteredTests) async* { + await for (final file in Directory(path.join(sdkRoot, 'tests/ffi')).list()) { + if (file is File && file.path.endsWith('_test.dart')) { + // These tests are VM specific and cannot necessarily be run on Flutter. + final blacklistedTests = const [ + 'function_callbacks_test.dart', + 'function_callbacks_test.dart', + 'function_gc_test.dart', + 'function_test.dart', + 'object_gc_test.dart', + 'regress_37100_test.dart', + 'regress_37511_callbacks_test.dart', + 'regress_37511_test.dart', + 'regress_37780_test.dart', + ]; + if (blacklistedTests.contains(path.basename(file.path))) { + filteredTests.add(file.path); + continue; + } + final contents = file.readAsStringSync(); + if (!contents.contains('//#') && + !contents.contains('dart:async') && + !contents.contains('dart:isolate') && + !contents.contains('async') && + !contents.contains('Future') && + !contents.contains('DynamicLibrary.process') && + !contents.contains('DynamicLibrary.executable') && + !contents.contains('Future')) { + yield file.path; + } + } + } +} + +Future run(String sdkRoot, String testFile) async { + final env = Map.from(Platform.environment); + env['LD_LIBRARY_PATH'] = path.join(sdkRoot, 'out/ReleaseX64'); + final sw = Stopwatch()..start(); + final Process process = await Process.start( + Platform.executable, [testFile], + environment: env); + final timer = Timer(const Duration(seconds: 3), () => process.kill()); + process.stdout.listen((_) {}); + process.stderr.listen((_) {}); + if (await process.exitCode != 0) return null; + timer.cancel(); + return sw.elapsed; +} + +Future generateCleanDir(String dirname) async { + final directory = Directory(dirname); + if (await directory.exists()) { + await directory.delete(recursive: true); + } + await directory.create(recursive: true); + return directory; +} diff --git a/tests/ffi/regress_37254_test.dart b/tests/ffi/regress_37254_test.dart index 27ab95590a09..dcd42b9cf7f3 100644 --- a/tests/ffi/regress_37254_test.dart +++ b/tests/ffi/regress_37254_test.dart @@ -240,7 +240,6 @@ void load6() { void main() { // Trigger both the runtime entry and the IL in bytecode. for (int i = 0; i < 100; i++) { - print(i); store1(); store2(); store3(); diff --git a/tests/ffi/regress_37511_callbacks_test.dart b/tests/ffi/regress_37511_callbacks_test.dart index 37407eed4e6c..0153560ddd54 100644 --- a/tests/ffi/regress_37511_callbacks_test.dart +++ b/tests/ffi/regress_37511_callbacks_test.dart @@ -11,8 +11,6 @@ // TODO(37295): Merge this file with regress_37511_test.dart when callback // support lands. -library FfiTest; - import 'dart:ffi'; import 'ffi_test_helpers.dart'; diff --git a/tests/ffi/regress_37511_test.dart b/tests/ffi/regress_37511_test.dart index e2a2e70d23a3..6d857e52bed0 100644 --- a/tests/ffi/regress_37511_test.dart +++ b/tests/ffi/regress_37511_test.dart @@ -8,8 +8,6 @@ // // SharedObjects=ffi_test_functions -library FfiTest; - import 'dart:ffi'; import 'dylib_utils.dart'; diff --git a/tests/ffi/regress_39068_test.dart b/tests/ffi/regress_39068_test.dart index fbfca38bac9e..6709a53ed3a2 100644 --- a/tests/ffi/regress_39068_test.dart +++ b/tests/ffi/regress_39068_test.dart @@ -60,10 +60,12 @@ main() { final sumManyNumbers = ffiTestFunctions .lookupFunction("SumManyNumbers"); + bool gotException = false; try { sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13, 14.0, 15, 16.0, 17, 18.0, null, 20.0); } on Error { - print('Expected exception on passing null for int'); + gotException = true; } + Expect.isTrue(gotException); } diff --git a/tests/ffi/static_checks_test.dart b/tests/ffi/static_checks_test.dart index 3c607039d7fe..3a54970b9448 100644 --- a/tests/ffi/static_checks_test.dart +++ b/tests/ffi/static_checks_test.dart @@ -6,8 +6,6 @@ // // SharedObjects=ffi_test_dynamic_library -library FfiTest; - import 'dart:ffi'; import "package:ffi/ffi.dart"; diff --git a/tests/ffi/structs_test.dart b/tests/ffi/structs_test.dart index 669e837ef57a..263d562f8708 100644 --- a/tests/ffi/structs_test.dart +++ b/tests/ffi/structs_test.dart @@ -6,8 +6,6 @@ // // VMOptions=--deterministic --optimization-counter-threshold=50 --enable-inlining-annotations -library FfiTest; - import 'dart:ffi'; import "package:expect/expect.dart";