From 4b0a4e0a75f51d9780bee2eac0bd98d6bc87960d Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Tue, 2 Jun 2026 04:50:53 -0700 Subject: [PATCH] [vm,dyn_modules] Fix vm/cc/Profiler tests for dynamic modules. The only actual change required to the profiler service is appropriately retrieving the source position for interpreted frames during processing. Otherwise, most of the tests just need small changes to account for the slightly different stack trace generated by interpreted code when walking the stack as code instead of functions: * Interpreted code calls the runtime entry DRT_AllocateObject directly for slow-path allocation instead of via the AllocateObjectSlow stub. * The current name for bytecode frames when walked as code starts with "[Bytecode]" instead of "[Unoptimized]". The test vm/cc/Profiler_GetSourceReport is skipped since: * It tests compiled code-specific features like classifying source positions. * Inserting fake samples for interpreted functions is messier since many PC offsets may map to the same source position. The other skipped tests are specific to inlining and/or optimized compiled code, so there's no point in running them on interpreted code. TEST=vm/cc/Profiler Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try Change-Id: I3e73f42b2562eb6e3e73fe8d5c3f14389039ee04 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/507082 Reviewed-by: Alexander Markov Commit-Queue: Tess Strickland --- runtime/tests/vm/vm.status | 9 +- runtime/vm/profiler_service.cc | 15 ++- runtime/vm/profiler_test.cc | 187 ++++++++++++++++++++------------- 3 files changed, 133 insertions(+), 78 deletions(-) diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status index 8545949323a..c9057f7f8e6 100644 --- a/runtime/tests/vm/vm.status +++ b/runtime/tests/vm/vm.status @@ -103,6 +103,12 @@ cc/NonStrictCondition*: SkipByDesign # Building flow graph is not supported from cc/NotEqualCondition*: SkipByDesign # Building flow graph is not supported from bytecode. cc/OptimizeCompileFunctionOnHelperThread: SkipByDesign # JIT compilation is not supported from bytecode. cc/PeriodicAndDerived: SkipByDesign # Building flow graph is not supported from bytecode. +cc/Profiler_BasicSourcePositionOptimized: SkipByDesign # Tests profiling of optimized code. +cc/Profiler_BinaryOperatorSourcePositionOptimized: SkipByDesign # Tests profiling of optimized code. +cc/Profiler_FunctionInline: SkipByDesign # Tests profiling of inlined optimized code. +cc/Profiler_GetSourceReport: SkipByDesign # Tests classifying source positions not found in bytecode. +cc/Profiler_InliningIntervalBoundary: SkipByDesign # Tests profiling of inlined optimized code. +cc/Profiler_SourcePositionOptimized: SkipByDesign # Tests profiling of optimized code. cc/RangeAnalysis_*: SkipByDesign # Building flow graph is not supported from bytecode. cc/ReachabilityFence_*: SkipByDesign # Building flow graph is not supported from bytecode. cc/SecondExit*: SkipByDesign # Building flow graph is not supported from bytecode. @@ -468,9 +474,6 @@ dart/finalizer/finalizer_isolate_groups_run_gc_test: SkipByDesign # uses spawnUr dart/isolates/send_object_to_spawn_uri_isolate_test: SkipByDesign # uses spawnUri dart/issue32950_test: SkipByDesign # uses spawnUri. -[ $runtime != dart_precompiled || $sanitizer != msan && $sanitizer != tsan ] -dart/sanitizer_compatibility_test: SkipByDesign - [ $system != macos || $simulator ] dart/thread_priority_macos_test: SkipByDesign diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc index 1efa73e172f..05d7b50dedd 100644 --- a/runtime/vm/profiler_service.cc +++ b/runtime/vm/profiler_service.cc @@ -1080,10 +1080,11 @@ class ProfileBuilder : public ValueObject { const intptr_t code_index = profile_code->code_table_index(); ASSERT(profile_code != nullptr); + Zone* const zone = Thread::Current()->zone(); GrowableArray* inlined_functions = nullptr; GrowableArray* inlined_token_positions = nullptr; TokenPosition token_position = TokenPosition::kNoSource; - Code& code = Code::ZoneHandle(); + Code& code = Code::ZoneHandle(zone); if (profile_code->code().IsCode()) { code ^= profile_code->code().ptr(); inlined_functions_cache_->Get(pc, code, sample, frame_index, @@ -1091,8 +1092,8 @@ class ProfileBuilder : public ValueObject { &inlined_token_positions, &token_position); if (FLAG_trace_profiler_verbose && (inlined_functions != nullptr)) { for (intptr_t i = 0; i < inlined_functions->length(); i++) { - const String& name = - String::Handle((*inlined_functions)[i]->QualifiedScrubbedName()); + const String& name = String::Handle( + zone, (*inlined_functions)[i]->QualifiedScrubbedName()); THR_Print("InlinedFunction[%" Pd "] = {%s, %s}\n", i, name.ToCString(), (*inlined_token_positions)[i].ToCString()); @@ -1100,6 +1101,14 @@ class ProfileBuilder : public ValueObject { } } +#if defined(DART_DYNAMIC_MODULES) + if (profile_code->code().IsBytecode()) { + const auto& bytecode = + Bytecode::CheckedHandle(zone, profile_code->code().ptr()); + token_position = bytecode.GetTokenIndexOfPC(pc); + } +#endif + if (code.IsNull() || (inlined_functions == nullptr) || (inlined_functions->length() <= 1)) { ProcessFunction(sample_index, sample, frame_index, function, diff --git a/runtime/vm/profiler_test.cc b/runtime/vm/profiler_test.cc index 0b8d18272d6..4afbff8e441 100644 --- a/runtime/vm/profiler_test.cc +++ b/runtime/vm/profiler_test.cc @@ -24,6 +24,25 @@ DECLARE_FLAG(bool, profile_vm_allocation); DECLARE_FLAG(int, max_profile_depth); DECLARE_FLAG(int, optimization_counter_threshold); +static void ExpectUnoptimizedOrBytecode(dart::Expect expect, + const char* expected, + const char* got) { +#if defined(TESTING) + TextBuffer buffer(1024); + if (FLAG_interpreter) { + buffer.AddString("[Bytecode] "); + } else { + buffer.AddString("[Unoptimized] "); + } + buffer.Printf("%s", expected); + expect.StringEquals(buffer.buffer(), got); +#endif +} + +#define EXPECT_UNOPTIMIZED_OR_BYTECODE(expected, got) \ + ExpectUnoptimizedOrBytecode(dart::Expect(__FILE__, __LINE__), (expected), \ + (got)) + // SampleVisitor ignores samples with timestamp == 0. const int64_t kValidTimeStamp = 1; @@ -509,15 +528,17 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TrivialRecordAllocation) { // Move down from the root. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -600,15 +621,17 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) { ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -686,17 +709,19 @@ ISOLATE_UNIT_TEST_CASE(Profiler_CodeTicks) { // Move down from the root. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif - EXPECT_EQ(3, walker.CurrentExclusiveTicks()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); + EXPECT_EQ(3, walker.CurrentExclusiveTicks()); + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(!walker.Down()); @@ -758,14 +783,15 @@ ISOLATE_UNIT_TEST_CASE(Profiler_FunctionTicks) { ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); - + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif - EXPECT_EQ(3, walker.CurrentExclusiveTicks()); - EXPECT(walker.Down()); + EXPECT_EQ(3, walker.CurrentExclusiveTicks()); + EXPECT(walker.Down()); + } EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT(walker.Down()); @@ -818,12 +844,17 @@ ISOLATE_UNIT_TEST_CASE(Profiler_IntrinsicAllocation) { EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); - EXPECT_STREQ("Double_add", walker.VMTagName()); - EXPECT_STREQ("[Unoptimized] double._add", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] double.+", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); + if (FLAG_interpreter) { + // No intrinsic in the bytecode version. + EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + } else { + EXPECT_STREQ("Double_add", walker.VMTagName()); + EXPECT_STREQ("[Unoptimized] double._add", walker.CurrentName()); + EXPECT(walker.Down()); + EXPECT_STREQ("[Unoptimized] double.+", walker.CurrentName()); + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -882,7 +913,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) { EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] new _List", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -962,9 +993,11 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ContextAllocation) { ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateContext", walker.VMTagName()); - EXPECT_STREQ("[Stub] AllocateContext", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); + if (!FLAG_interpreter) { + EXPECT_STREQ("[Stub] AllocateContext", walker.CurrentName()); + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -1021,9 +1054,11 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ClosureAllocation) { ProfileStackWalker walker(&profile); EXPECT_SUBSTRING("DRT_AllocateClosure", walker.VMTagName()); - EXPECT_STREQ("[Stub] AllocateClosure1", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_SUBSTRING("foo", walker.CurrentName()); + if (!FLAG_interpreter) { + EXPECT_STREQ("[Stub] AllocateClosure1", walker.CurrentName()); + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -1089,7 +1124,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) { EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] new Float32List", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -1163,7 +1198,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) { EXPECT_STREQ("String_concat", walker.VMTagName()); EXPECT_STREQ("[Unoptimized] _StringBase.+", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -1243,7 +1278,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) { EXPECT_STREQ("[Unoptimized] _StringBase._interpolate", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); EXPECT(!walker.Down()); } @@ -1563,51 +1598,53 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ChainedSamples) { ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif + EXPECT(walker.Down()); + } + EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("orange", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] orange", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("napkin", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] napkin", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("mayo", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] mayo", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("lemon", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] lemon", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("kindle", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] kindle", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("jeep", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] jeep", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("ice", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] ice", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("haystack", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] haystack", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("granola", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] granola", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("fred", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] fred", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("elephant", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] elephant", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("dog", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] dog", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("cantaloupe", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] cantaloupe", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("banana", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] banana", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("apple", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] apple", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("secondInit", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] secondInit", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("init", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] init", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("go", walker.CurrentName()); EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] go", walker.CurrentName()); - EXPECT(walker.Down()); - EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); + EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName()); EXPECT(!walker.Down()); } } @@ -1657,13 +1694,15 @@ ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePosition) { ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif - EXPECT_EQ(1, walker.CurrentExclusiveTicks()); - EXPECT(walker.Down()); + EXPECT_EQ(1, walker.CurrentExclusiveTicks()); + EXPECT(walker.Down()); + } EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); @@ -1815,13 +1854,15 @@ ISOLATE_UNIT_TEST_CASE(Profiler_SourcePosition) { ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif - EXPECT_EQ(1, walker.CurrentExclusiveTicks()); - EXPECT(walker.Down()); + EXPECT_EQ(1, walker.CurrentExclusiveTicks()); + EXPECT(walker.Down()); + } EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); @@ -2017,13 +2058,15 @@ ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePosition) { ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); + if (!FLAG_interpreter) { #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. - EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); + EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else - EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); + EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif - EXPECT_EQ(1, walker.CurrentExclusiveTicks()); - EXPECT(walker.Down()); + EXPECT_EQ(1, walker.CurrentExclusiveTicks()); + EXPECT(walker.Down()); + } EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken());