| // Copyright (c) 2015, 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. | |
| #include <cstring> | |
| #include <memory> | |
| #include "platform/assert.h" | |
| #include "vm/dart_api_impl.h" | |
| #include "vm/dart_api_state.h" | |
| #include "vm/globals.h" | |
| #include "vm/timeline.h" | |
| #include "vm/unit_test.h" | |
| namespace dart { | |
| #ifndef PRODUCT | |
| template <class T> | |
| class TimelineRecorderOverride : public ValueObject { | |
| public: | |
| TimelineRecorderOverride() : recorder_(Timeline::recorder()) { | |
| Timeline::recorder_ = new T(); | |
| } | |
| explicit TimelineRecorderOverride(T* recorder) | |
| : recorder_(Timeline::recorder()) { | |
| Timeline::recorder_ = recorder; | |
| } | |
| ~TimelineRecorderOverride() { | |
| Timeline::Clear(); | |
| delete Timeline::recorder_; | |
| Timeline::recorder_ = recorder_; | |
| } | |
| T* recorder() { return static_cast<T*>(Timeline::recorder()); } | |
| private: | |
| TimelineEventRecorder* recorder_; | |
| }; | |
| class TimelineTestHelper : public AllStatic { | |
| public: | |
| static void SetStream(TimelineEvent* event, TimelineStream* stream) { | |
| event->StreamInit(stream); | |
| } | |
| static Mutex& GetRecorderLock(TimelineEventRecorder& recorder) { | |
| return recorder.lock_; | |
| } | |
| static void FakeThreadEvent(TimelineEventBlock* block, | |
| intptr_t ftid, | |
| const char* label = "fake", | |
| TimelineStream* stream = nullptr) { | |
| OSThread& current_thread = *OSThread::Current(); | |
| MutexLocker ml(current_thread.timeline_block_lock()); | |
| TimelineEvent* event = block->StartEventLocked(); | |
| ASSERT(event != nullptr); | |
| event->DurationBegin(label); | |
| event->thread_ = OSThread::ThreadIdFromIntPtr(ftid); | |
| if (stream != nullptr) { | |
| event->StreamInit(stream); | |
| } | |
| } | |
| static void FakeDuration(TimelineEventRecorder* recorder, | |
| const char* label, | |
| int64_t start, | |
| int64_t end) { | |
| ASSERT(recorder != nullptr); | |
| ASSERT(start < end); | |
| ASSERT(label != nullptr); | |
| TimelineEvent* event = recorder->StartEvent(); | |
| ASSERT(event != nullptr); | |
| event->Duration(label, start, end); | |
| recorder->CompleteEvent(event); | |
| } | |
| static void FakeBegin(TimelineEventRecorder* recorder, | |
| const char* label, | |
| int64_t start) { | |
| ASSERT(recorder != nullptr); | |
| ASSERT(label != nullptr); | |
| ASSERT(start >= 0); | |
| TimelineEvent* event = recorder->StartEvent(); | |
| ASSERT(event != nullptr); | |
| event->Begin(label, /*id=*/-1, start); | |
| recorder->CompleteEvent(event); | |
| } | |
| static void FakeEnd(TimelineEventRecorder* recorder, | |
| const char* label, | |
| int64_t end) { | |
| ASSERT(recorder != nullptr); | |
| ASSERT(label != nullptr); | |
| ASSERT(end >= 0); | |
| TimelineEvent* event = recorder->StartEvent(); | |
| ASSERT(event != nullptr); | |
| event->End(label, end); | |
| recorder->CompleteEvent(event); | |
| } | |
| static void FinishBlock(TimelineEventBlock* block) { block->Finish(); } | |
| }; | |
| TEST_CASE(TimelineEventIsValid) { | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| TimelineEvent event; | |
| TimelineTestHelper::SetStream(&event, &stream); | |
| // Starts invalid. | |
| EXPECT(!event.IsValid()); | |
| // Becomes valid. | |
| event.Instant("hello"); | |
| EXPECT(event.IsValid()); | |
| // Becomes invalid. | |
| event.Reset(); | |
| EXPECT(!event.IsValid()); | |
| } | |
| TEST_CASE(TimelineEventDuration) { | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| // Create a test event. | |
| TimelineEvent event; | |
| TimelineTestHelper::SetStream(&event, &stream); | |
| event.DurationBegin("apple"); | |
| // Measure the duration. | |
| int64_t current_duration = event.TimeDuration(); | |
| event.SetTimeEnd(); | |
| // Verify that duration is larger. | |
| EXPECT_GE(event.TimeDuration(), current_duration); | |
| } | |
| TEST_CASE(TimelineEventDurationPrintJSON) { | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| // Create a test event. | |
| TimelineEvent event; | |
| TimelineTestHelper::SetStream(&event, &stream); | |
| event.DurationBegin("apple"); | |
| { | |
| // Test printing to JSON. | |
| JSONStream js; | |
| event.PrintJSON(&js); | |
| // Check category | |
| EXPECT_SUBSTRING("\"cat\":\"testStream\"", js.ToCString()); | |
| // Check name. | |
| EXPECT_SUBSTRING("\"name\":\"apple\"", js.ToCString()); | |
| // Check phase. | |
| EXPECT_SUBSTRING("\"ph\":\"X\"", js.ToCString()); | |
| // Check that ts key is present. | |
| EXPECT_SUBSTRING("\"ts\":", js.ToCString()); | |
| // Check that dur key is present. | |
| EXPECT_SUBSTRING("\"dur\":", js.ToCString()); | |
| } | |
| event.SetTimeEnd(); | |
| } | |
| #if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX) | |
| TEST_CASE(TimelineEventPrintSystrace) { | |
| const intptr_t kBufferLength = 1024; | |
| char buffer[kBufferLength]; | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| // Create a test event. | |
| TimelineEvent event; | |
| TimelineTestHelper::SetStream(&event, &stream); | |
| // Test a Begin event. | |
| event.Begin("apple", 1, 2); | |
| TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0], | |
| kBufferLength); | |
| EXPECT_SUBSTRING("|apple", buffer); | |
| EXPECT_SUBSTRING("B|", buffer); | |
| // Test an End event. | |
| event.End("apple", 2, 3); | |
| TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0], | |
| kBufferLength); | |
| EXPECT_STREQ("E", buffer); | |
| // Test a Counter event. We only report the first counter value (in this case | |
| // "4"). | |
| event.Counter("CTR", 1); | |
| // We have two counters. | |
| event.SetNumArguments(2); | |
| // Set the first counter value. | |
| event.CopyArgument(0, "cats", "4"); | |
| // Set the second counter value. | |
| event.CopyArgument(1, "dogs", "1"); | |
| TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0], | |
| kBufferLength); | |
| EXPECT_SUBSTRING("C|", buffer); | |
| EXPECT_SUBSTRING("|CTR|4", buffer); | |
| // Test a duration event. This event kind is not supported so we should | |
| // serialize it to an empty string. | |
| event.Duration("DUR", 0, 1); | |
| TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0], | |
| kBufferLength); | |
| EXPECT_STREQ("", buffer); | |
| } | |
| #endif // defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX) | |
| TEST_CASE(TimelineEventArguments) { | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| // Create a test event. | |
| TimelineEvent event; | |
| TimelineTestHelper::SetStream(&event, &stream); | |
| // Allocate room for four arguments. | |
| event.SetNumArguments(4); | |
| // Reset. | |
| event.Reset(); | |
| event.DurationBegin("apple"); | |
| event.SetNumArguments(2); | |
| event.CopyArgument(0, "arg1", "value1"); | |
| event.CopyArgument(1, "arg2", "value2"); | |
| event.SetTimeEnd(); | |
| } | |
| TEST_CASE(TimelineEventArgumentsPrintJSON) { | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| // Create a test event. | |
| TimelineEvent event; | |
| TimelineTestHelper::SetStream(&event, &stream); | |
| event.DurationBegin("apple"); | |
| event.SetNumArguments(2); | |
| event.CopyArgument(0, "arg1", "value1"); | |
| event.CopyArgument(1, "arg2", "value2"); | |
| event.SetTimeEnd(); | |
| { | |
| // Test printing to JSON. | |
| JSONStream js; | |
| event.PrintJSON(&js); | |
| // Check both arguments. | |
| EXPECT_SUBSTRING("\"arg1\":\"value1\"", js.ToCString()); | |
| EXPECT_SUBSTRING("\"arg2\":\"value2\"", js.ToCString()); | |
| } | |
| } | |
| TEST_CASE(TimelineEventBufferPrintJSON) { | |
| JSONStream js; | |
| TimelineEventFilter filter; | |
| Timeline::recorder()->PrintJSON(&js, &filter); | |
| // Check the type. | |
| EXPECT_SUBSTRING("\"type\":\"Timeline\"", js.ToCString()); | |
| // Check that there is a traceEvents array. | |
| EXPECT_SUBSTRING("\"traceEvents\":[", js.ToCString()); | |
| } | |
| // Count the number of each event type seen. | |
| class EventCounterRecorder : public TimelineEventCallbackRecorder { | |
| public: | |
| EventCounterRecorder() { | |
| for (intptr_t i = 0; i < TimelineEvent::kNumEventTypes; i++) { | |
| counts_[i] = 0; | |
| } | |
| } | |
| void OnEvent(TimelineEvent* event) { counts_[event->event_type()]++; } | |
| intptr_t CountFor(TimelineEvent::EventType type) { return counts_[type]; } | |
| private: | |
| intptr_t counts_[TimelineEvent::kNumEventTypes]; | |
| }; | |
| TEST_CASE(TimelineEventCallbackRecorderBasic) { | |
| TimelineRecorderOverride<EventCounterRecorder> override; | |
| // Initial counts are all zero. | |
| for (intptr_t i = TimelineEvent::kNone + 1; i < TimelineEvent::kNumEventTypes; | |
| i++) { | |
| EXPECT_EQ(0, override.recorder()->CountFor( | |
| static_cast<TimelineEvent::EventType>(i))); | |
| } | |
| // Create a test stream. | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| TimelineEvent* event = nullptr; | |
| event = stream.StartEvent(); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kDuration)); | |
| event->DurationBegin("cabbage"); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kDuration)); | |
| event->SetTimeEnd(); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kDuration)); | |
| event->Complete(); | |
| EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kDuration)); | |
| event = stream.StartEvent(); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kInstant)); | |
| event->Instant("instantCabbage"); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kInstant)); | |
| event->Complete(); | |
| EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kInstant)); | |
| event = stream.StartEvent(); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncBegin)); | |
| int64_t async_id = thread->GetNextTaskId(); | |
| EXPECT(async_id != 0); | |
| event->AsyncBegin("asyncBeginCabbage", async_id); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncBegin)); | |
| event->Complete(); | |
| EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kAsyncBegin)); | |
| event = stream.StartEvent(); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncInstant)); | |
| event->AsyncInstant("asyncInstantCabbage", async_id); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncInstant)); | |
| event->Complete(); | |
| EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kAsyncInstant)); | |
| event = stream.StartEvent(); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncEnd)); | |
| event->AsyncEnd("asyncEndCabbage", async_id); | |
| EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncEnd)); | |
| event->Complete(); | |
| EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kAsyncEnd)); | |
| } | |
| TEST_CASE(TimelineRingRecorderJSONOrder) { | |
| TimelineStream stream("testStream", "testStream", false, true); | |
| TimelineEventRingRecorder* recorder = | |
| new TimelineEventRingRecorder(TimelineEventBlock::kBlockSize * 2); | |
| TimelineRecorderOverride<TimelineEventRingRecorder> override(recorder); | |
| { | |
| Mutex& recorder_lock = TimelineTestHelper::GetRecorderLock(*recorder); | |
| MutexLocker ml(&recorder_lock); | |
| TimelineEventBlock* block_0 = Timeline::recorder()->GetNewBlockLocked(); | |
| EXPECT(block_0 != nullptr); | |
| TimelineEventBlock* block_1 = Timeline::recorder()->GetNewBlockLocked(); | |
| EXPECT(block_1 != nullptr); | |
| // Test that we wrapped. | |
| EXPECT(block_0 == Timeline::recorder()->GetNewBlockLocked()); | |
| // Emit the earlier event into block_1. | |
| TimelineTestHelper::FakeThreadEvent(block_1, 2, "Alpha", &stream); | |
| OS::Sleep(32); | |
| // Emit the later event into block_0. | |
| TimelineTestHelper::FakeThreadEvent(block_0, 2, "Beta", &stream); | |
| TimelineTestHelper::FinishBlock(block_0); | |
| TimelineTestHelper::FinishBlock(block_1); | |
| } | |
| JSONStream js; | |
| TimelineEventFilter filter; | |
| Timeline::recorder()->PrintJSON(&js, &filter); | |
| // trace-event has a requirement that events for a thread must have | |
| // monotonically increasing timestamps. | |
| // Verify that "Alpha" comes before "Beta" even though "Beta" is in the first | |
| // block. | |
| const char* alpha = strstr(js.ToCString(), "Alpha"); | |
| const char* beta = strstr(js.ToCString(), "Beta"); | |
| EXPECT(alpha < beta); | |
| } | |
| TEST_CASE(TimelineRingRecorderRace) { | |
| struct ReportEventsArguments { | |
| Monitor& synchronization_monitor; | |
| TimelineEventRecorder& recorder; | |
| ThreadJoinId join_id = OSThread::kInvalidThreadJoinId; | |
| }; | |
| // Note that |recorder| will be freed by |TimelineRecorderOverride|'s | |
| // destructor. | |
| TimelineEventRingRecorder& recorder = | |
| *(new TimelineEventRingRecorder(2 * TimelineEventBlock::kBlockSize)); | |
| TimelineRecorderOverride<TimelineEventRingRecorder> override(&recorder); | |
| Monitor synchronization_monitor; | |
| JSONStream js; | |
| TimelineEventFilter filter; | |
| ReportEventsArguments report_events_1_arguments{synchronization_monitor, | |
| recorder}; | |
| ReportEventsArguments report_events_2_arguments{synchronization_monitor, | |
| recorder}; | |
| // Try concurrently writing events, serializing them, and clearing the | |
| // timeline. It is not possible to assert anything about the outcome, because | |
| // of scheduling uncertainty. This test is just used to ensure that TSAN | |
| // checks the ring recorder code. | |
| OSThread::Start( | |
| "ReportEvents1", | |
| [](uword arguments_ptr) { | |
| ReportEventsArguments& arguments = | |
| *reinterpret_cast<ReportEventsArguments*>(arguments_ptr); | |
| for (intptr_t i = 0; i < 2 * TimelineEventBlock::kBlockSize; ++i) { | |
| TimelineTestHelper::FakeDuration(&arguments.recorder, "testEvent", | |
| /*start=*/0, /*end=*/1); | |
| } | |
| MonitorLocker ml(&arguments.synchronization_monitor); | |
| arguments.join_id = | |
| OSThread::GetCurrentThreadJoinId(OSThread::Current()); | |
| ml.Notify(); | |
| }, | |
| reinterpret_cast<uword>(&report_events_1_arguments)); | |
| OSThread::Start( | |
| "ReportEvents2", | |
| [](uword arguments_ptr) { | |
| ReportEventsArguments& arguments = | |
| *reinterpret_cast<ReportEventsArguments*>(arguments_ptr); | |
| for (intptr_t i = 0; i < 2 * TimelineEventBlock::kBlockSize; ++i) { | |
| TimelineTestHelper::FakeDuration(&arguments.recorder, "testEvent", | |
| /*start=*/0, /*end=*/1); | |
| } | |
| MonitorLocker ml(&arguments.synchronization_monitor); | |
| arguments.join_id = | |
| OSThread::GetCurrentThreadJoinId(OSThread::Current()); | |
| ml.Notify(); | |
| }, | |
| reinterpret_cast<uword>(&report_events_2_arguments)); | |
| Timeline::Clear(); | |
| recorder.PrintJSON(&js, &filter); | |
| MonitorLocker ml(&synchronization_monitor); | |
| while (report_events_1_arguments.join_id == OSThread::kInvalidThreadJoinId || | |
| report_events_2_arguments.join_id == OSThread::kInvalidThreadJoinId) { | |
| ml.Wait(); | |
| } | |
| OSThread::Join(report_events_1_arguments.join_id); | |
| OSThread::Join(report_events_2_arguments.join_id); | |
| } | |
| // |OSThread::Start()| takes in a function pointer, and only lambdas that don't | |
| // capture can be converted to function pointers. So, we use these macros to | |
| // avoid needing to capture. | |
| #define FAKE_PROCESS_ID 1 | |
| #define FAKE_TRACE_ID 1 | |
| TEST_CASE(TimelineTrackMetadataRace) { | |
| struct ReportMetadataArguments { | |
| Monitor& synchronization_monitor; | |
| TimelineEventRecorder& recorder; | |
| ThreadJoinId join_id = OSThread::kInvalidThreadJoinId; | |
| }; | |
| Monitor synchronization_monitor; | |
| TimelineEventRecorder& recorder = *Timeline::recorder(); | |
| // Try concurrently reading from / writing to the metadata map. It is not | |
| // possible to assert anything about the outcome, because of scheduling | |
| // uncertainty. This test is just used to ensure that TSAN checks the metadata | |
| // map code. | |
| JSONStream js; | |
| TimelineEventFilter filter; | |
| ReportMetadataArguments report_metadata_1_arguments{synchronization_monitor, | |
| recorder}; | |
| ReportMetadataArguments report_metadata_2_arguments{synchronization_monitor, | |
| recorder}; | |
| OSThread::Start( | |
| "ReportMetadata1", | |
| [](uword arguments_ptr) { | |
| ReportMetadataArguments& arguments = | |
| *reinterpret_cast<ReportMetadataArguments*>(arguments_ptr); | |
| arguments.recorder.AddTrackMetadataBasedOnThread( | |
| FAKE_PROCESS_ID, FAKE_TRACE_ID, "Thread 1"); | |
| MonitorLocker ml(&arguments.synchronization_monitor); | |
| arguments.join_id = | |
| OSThread::GetCurrentThreadJoinId(OSThread::Current()); | |
| ml.Notify(); | |
| }, | |
| reinterpret_cast<uword>(&report_metadata_1_arguments)); | |
| OSThread::Start( | |
| "ReportMetadata2", | |
| [](uword arguments_ptr) { | |
| ReportMetadataArguments& arguments = | |
| *reinterpret_cast<ReportMetadataArguments*>(arguments_ptr); | |
| arguments.recorder.AddTrackMetadataBasedOnThread( | |
| FAKE_PROCESS_ID, FAKE_TRACE_ID, "Incorrect Name"); | |
| MonitorLocker ml(&arguments.synchronization_monitor); | |
| arguments.join_id = | |
| OSThread::GetCurrentThreadJoinId(OSThread::Current()); | |
| ml.Notify(); | |
| }, | |
| reinterpret_cast<uword>(&report_metadata_2_arguments)); | |
| recorder.PrintJSON(&js, &filter); | |
| MonitorLocker ml(&synchronization_monitor); | |
| while ( | |
| report_metadata_1_arguments.join_id == OSThread::kInvalidThreadJoinId || | |
| report_metadata_2_arguments.join_id == OSThread::kInvalidThreadJoinId) { | |
| ml.Wait(); | |
| } | |
| OSThread::Join(report_metadata_1_arguments.join_id); | |
| OSThread::Join(report_metadata_2_arguments.join_id); | |
| } | |
| #undef FAKE_PROCESS_ID | |
| #undef FAKE_TRACE_ID | |
| #endif // !PRODUCT | |
| #if defined(SUPPORT_TIMELINE) | |
| static Dart_Port expected_isolate; | |
| static Dart_IsolateGroupId expected_isolate_group; | |
| static bool saw_begin; | |
| static bool saw_end; | |
| static void* expected_isolate_data; | |
| static void* expected_isolate_group_data; | |
| static void TestTimelineRecorderCallback(Dart_TimelineRecorderEvent* event) { | |
| EXPECT_EQ(DART_TIMELINE_RECORDER_CURRENT_VERSION, event->version); | |
| if ((event->type == Dart_Timeline_Event_Begin) && | |
| (strcmp(event->label, "TestEvent") == 0)) { | |
| saw_begin = true; | |
| EXPECT_NE(0, event->timestamp0); | |
| EXPECT_EQ(expected_isolate, event->isolate); | |
| EXPECT_EQ(expected_isolate_group, event->isolate_group); | |
| EXPECT_EQ(expected_isolate_data, event->isolate_data); | |
| EXPECT_EQ(expected_isolate_group_data, event->isolate_group_data); | |
| EXPECT_STREQ("Dart", event->stream); | |
| EXPECT_EQ(1, event->argument_count); | |
| EXPECT_STREQ("Dart Arguments", event->arguments[0].name); | |
| EXPECT_STREQ("{\"key\":\"value\"}", event->arguments[0].value); | |
| } | |
| if ((event->type == Dart_Timeline_Event_End) && | |
| (strcmp(event->label, "TestEvent") == 0)) { | |
| saw_end = true; | |
| EXPECT_NE(0, event->timestamp0); | |
| EXPECT_EQ(expected_isolate, event->isolate); | |
| EXPECT_EQ(expected_isolate_group, event->isolate_group); | |
| EXPECT_EQ(expected_isolate_data, event->isolate_data); | |
| EXPECT_EQ(expected_isolate_group_data, event->isolate_group_data); | |
| EXPECT_STREQ("Dart", event->stream); | |
| EXPECT_EQ(1, event->argument_count); | |
| EXPECT_STREQ("Dart Arguments", event->arguments[0].name); | |
| EXPECT_STREQ("{\"key\":\"value\"}", event->arguments[0].value); | |
| } | |
| } | |
| UNIT_TEST_CASE(DartAPI_SetTimelineRecorderCallback) { | |
| int argc = TesterState::argc + 2; | |
| const char** argv = new const char*[argc]; | |
| for (intptr_t i = 0; i < argc - 2; i++) { | |
| argv[i] = TesterState::argv[i]; | |
| } | |
| argv[argc - 2] = "--timeline_recorder=callback"; | |
| argv[argc - 1] = "--timeline_streams=Dart"; | |
| Dart_SetTimelineRecorderCallback(TestTimelineRecorderCallback); | |
| EXPECT(Dart_SetVMFlags(argc, argv) == nullptr); | |
| Dart_InitializeParams params; | |
| memset(¶ms, 0, sizeof(Dart_InitializeParams)); | |
| params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION; | |
| params.create_group = TesterState::create_callback; | |
| params.shutdown_isolate = TesterState::shutdown_callback; | |
| params.cleanup_group = TesterState::group_cleanup_callback; | |
| params.start_kernel_isolate = true; | |
| int64_t isolate_data = 0; | |
| EXPECT(Dart_Initialize(¶ms) == nullptr); | |
| { | |
| // Note: run_vm_tests will create and attach an instance of | |
| // bin::IsolateGroupData to the newly created isolate group. | |
| TestIsolateScope scope(/*isolate_group_data=*/nullptr, &isolate_data); | |
| const char* kScriptChars = | |
| "import 'dart:developer';\n" | |
| "main() {\n" | |
| " Timeline.startSync('TestEvent', arguments: {'key':'value'});\n" | |
| " Timeline.finishSync();\n" | |
| "}\n"; | |
| Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, nullptr); | |
| EXPECT_VALID(lib); | |
| expected_isolate = Dart_GetMainPortId(); | |
| EXPECT_NE(ILLEGAL_PORT, expected_isolate); | |
| expected_isolate_group = Dart_CurrentIsolateGroupId(); | |
| EXPECT_NE(ILLEGAL_PORT, expected_isolate_group); | |
| expected_isolate_data = &isolate_data; | |
| EXPECT_EQ(expected_isolate_data, Dart_CurrentIsolateData()); | |
| expected_isolate_group_data = Dart_CurrentIsolateGroupData(); | |
| saw_begin = false; | |
| saw_end = false; | |
| Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr); | |
| EXPECT_VALID(result); | |
| EXPECT(saw_begin); | |
| EXPECT(saw_end); | |
| } | |
| EXPECT(Dart_Cleanup() == nullptr); | |
| Dart_SetTimelineRecorderCallback(nullptr); | |
| delete[] argv; | |
| } | |
| #endif // defined(SUPPORT_TIMELINE) | |
| } // namespace dart |