Support Types in instance-ref/instance-view.

Refactor JSON printing for all instances to share more code.  This allows Types to act like Instances in Observatory.

Update the look and feel for the function-view page.

Add script info for functions.

Update tests.

R=johnmccutchan@google.com

Review URL: https://codereview.chromium.org//211283004

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@34435 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
turnidge@google.com 2014-03-26 17:13:03 +00:00
parent 6e1d5c293a
commit 99fca31c02
22 changed files with 9533 additions and 8553 deletions

View file

@ -289,7 +289,7 @@
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<div>
<span>
<template if="{{ isUnexpected(ref.serviceType) }}">
unexpected reference type &lt;{{ ref.serviceType }}&gt;
</template>
@ -309,6 +309,10 @@
<a href="{{ url }}">{{ ref['preview'] }}</a>
</template>
<template if="{{ (isType(ref.serviceType)) }}">
<a href="{{ url }}">{{ ref['user_name'] }}</a>
</template>
<template if="{{ isClosure(ref.serviceType) }}">
<a href="{{ url }}">
<!-- TODO(turnidge): Switch this to fully-qualified function -->
@ -349,8 +353,7 @@
</div>
</curly-block>
</template>
</div>
</span>
</template>
</polymer-element>
@ -443,11 +446,12 @@
var
</template>
<template if="{{ (ref['declared_type']['name'] != 'dynamic') }}">
<class-ref ref="{{ ref['declared_type'] }}"></class-ref>
<instance-ref ref="{{ ref['declared_type'] }}"></instance-ref>
</template>
<a title="{{ hoverText }}" href="{{ url }}">{{ name }}</a>
</div>
</template> </polymer-element><polymer-element name="function-ref" extends="service-ref">
</template> </polymer-element>
<polymer-element name="function-ref" extends="service-ref">
<template><!-- These comments are here to allow newlines.
--><template if="{{ qualified &amp;&amp; !hasParent &amp;&amp; hasClass }}"><!--
--><class-ref ref="{{ ref['class'] }}"></class-ref>.</template><!--
@ -801,6 +805,28 @@
</polymer-element>
<polymer-element name="function-view" extends="observatory-element">
<template>
<style>
.content {
padding-left: 10%;
font: 400 14px 'Montserrat', sans-serif;
}
h1 {
font: 400 18px 'Montserrat', sans-serif;
}
.memberList {
display: table;
}
.memberItem {
display: table-row;
}
.memberName, .memberValue {
display: table-cell;
vertical-align: top;
padding: 3px 0 3px 1em;
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<nav-bar>
<top-nav-menu></top-nav-menu>
<isolate-nav-menu isolate="{{ function.isolate }}"></isolate-nav-menu>
@ -815,50 +841,86 @@
<nav-refresh callback="{{ refresh }}"></nav-refresh>
</nav-bar>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-warning">
<div class="panel-heading">
{{ function['user_name'] }} ({{ function['name'] }})
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
<div class="panel-body">
<div>
<code-ref ref="{{ function['code'] }}"></code-ref>
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
<div class="content">
<h1>function {{ qualifiedName }}</h1>
<div class="memberList">
<div class="memberItem">
<div class="memberName">kind</div>
<div class="memberValue">
<template if="{{ function['is_static'] }}">static</template>
<template if="{{ function['is_const'] }}">const</template>
{{ kind }}
</div>
<table class="table table-hover">
<tbody>
<tr>
<td>static</td><td>{{ function['is_static'] }}</td>
</tr>
<tr>
<td>Const</td><td>{{ function['is_const'] }}</td>
</tr>
<tr>
<td>Optimizable</td><td>{{ function['is_optimizable'] }}</td>
</tr>
<tr>
<td>Inlinable</td><td>{{ function['is_inlinable'] }}</td>
</tr>
<tr>
<td>Kind</td><td>{{ function['kind'] }}</td>
</tr>
<tr>
<td>Usage Count</td><td>{{ function['usage_counter'] }}</td>
</tr>
<tr>
<td>Optimized Call Site Count</td><td>{{ function['optimized_call_site_count'] }}</td>
</tr>
<tr>
<td>Deoptimizations</td><td>{{ function['deoptimizations'] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<template if="{{ function['parent'] != null }}">
<div class="memberItem">
<div class="memberName">parent function</div>
<div class="memberValue">
<function-ref ref="{{ function['parent'] }}"></function-ref>
</div>
</div>
</template>
<template if="{{ function['parent'] == null &amp;&amp;
function['class'] != null }}">
<div class="memberItem">
<div class="memberName">parent class</div>
<div class="memberValue">
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">script</div>
<div class="memberValue">
<script-ref ref="{{ function['script'] }}">
</script-ref>
</div>
</div>
<div class="memberItem">&nbsp;</div>
<template if="{{ function['code'] != null }}">
<div class="memberItem">
<div class="memberName">optimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['code'] }}"></code-ref>
</div>
</div>
</template>
<template if="{{ function['unoptimized_code'] != null }}">
<div class="memberItem">
<div class="memberName">unoptimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
</div>
<div class="memberValue">
<span title="This count is used to determine when a function will be optimized. It is a combination of call counts and other factors.">
(usage count: {{ function['usage_counter'] }})
</span>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">deoptimizations</div>
<div class="memberValue">{{ function['deoptimizations'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">optimizable</div>
<div class="memberValue">{{ function['is_optimizable'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">inlinable</div>
<div class="memberValue">{{ function['is_inlinable'] }}</div>
</div>
<template if="{{ function.name != function.vmName }}">
<div class="memberItem">
<div class="memberName">vm name</div>
<div class="memberValue">{{ function.vmName }}</div>
</div>
</template>
</div>
</div>
</div>
</template>
</polymer-element>
@ -1215,7 +1277,12 @@
<template if="{{ instance['error'] == null }}">
<div class="content">
<!-- TODO(turnidge): Handle null instances. -->
<h1>instance of {{ instance['class']['user_name'] }}</h1>
<template if="{{ isType(instance.serviceType) }}">
<h1>type {{ instance['user_name'] }}</h1>
</template>
<template if="{{ !isType(instance.serviceType) }}">
<h1>instance of {{ instance['class']['user_name'] }}</h1>
</template>
<div class="memberList">
<div class="memberItem">
<div class="memberName">class</div>
@ -1234,6 +1301,15 @@
<div class="memberName">size</div>
<div class="memberValue">{{ instance['size'] | formatSize }}</div>
</div>
<template if="{{ instance['type_class'] != null }}">
<div class="memberItem">
<div class="memberName">type class</div>
<div class="memberValue">
<class-ref ref="{{ instance['type_class'] }}">
</class-ref>
</div>
</div>
</template>
</div>
</div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -288,7 +288,7 @@
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<div>
<span>
<template if="{{ isUnexpected(ref.serviceType) }}">
unexpected reference type &lt;{{ ref.serviceType }}&gt;
</template>
@ -308,6 +308,10 @@
<a href="{{ url }}">{{ ref['preview'] }}</a>
</template>
<template if="{{ (isType(ref.serviceType)) }}">
<a href="{{ url }}">{{ ref['user_name'] }}</a>
</template>
<template if="{{ isClosure(ref.serviceType) }}">
<a href="{{ url }}">
<!-- TODO(turnidge): Switch this to fully-qualified function -->
@ -348,8 +352,7 @@
</div>
</curly-block>
</template>
</div>
</span>
</template>
</polymer-element>
@ -442,11 +445,12 @@
var
</template>
<template if="{{ (ref['declared_type']['name'] != 'dynamic') }}">
<class-ref ref="{{ ref['declared_type'] }}"></class-ref>
<instance-ref ref="{{ ref['declared_type'] }}"></instance-ref>
</template>
<a title="{{ hoverText }}" href="{{ url }}">{{ name }}</a>
</div>
</template> </polymer-element><polymer-element name="function-ref" extends="service-ref">
</template> </polymer-element>
<polymer-element name="function-ref" extends="service-ref">
<template><!-- These comments are here to allow newlines.
--><template if="{{ qualified &amp;&amp; !hasParent &amp;&amp; hasClass }}"><!--
--><class-ref ref="{{ ref['class'] }}"></class-ref>.</template><!--
@ -800,6 +804,28 @@
</polymer-element>
<polymer-element name="function-view" extends="observatory-element">
<template>
<style>
.content {
padding-left: 10%;
font: 400 14px 'Montserrat', sans-serif;
}
h1 {
font: 400 18px 'Montserrat', sans-serif;
}
.memberList {
display: table;
}
.memberItem {
display: table-row;
}
.memberName, .memberValue {
display: table-cell;
vertical-align: top;
padding: 3px 0 3px 1em;
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<nav-bar>
<top-nav-menu></top-nav-menu>
<isolate-nav-menu isolate="{{ function.isolate }}"></isolate-nav-menu>
@ -814,50 +840,86 @@
<nav-refresh callback="{{ refresh }}"></nav-refresh>
</nav-bar>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-warning">
<div class="panel-heading">
{{ function['user_name'] }} ({{ function['name'] }})
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
<div class="panel-body">
<div>
<code-ref ref="{{ function['code'] }}"></code-ref>
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
<div class="content">
<h1>function {{ qualifiedName }}</h1>
<div class="memberList">
<div class="memberItem">
<div class="memberName">kind</div>
<div class="memberValue">
<template if="{{ function['is_static'] }}">static</template>
<template if="{{ function['is_const'] }}">const</template>
{{ kind }}
</div>
<table class="table table-hover">
<tbody>
<tr>
<td>static</td><td>{{ function['is_static'] }}</td>
</tr>
<tr>
<td>Const</td><td>{{ function['is_const'] }}</td>
</tr>
<tr>
<td>Optimizable</td><td>{{ function['is_optimizable'] }}</td>
</tr>
<tr>
<td>Inlinable</td><td>{{ function['is_inlinable'] }}</td>
</tr>
<tr>
<td>Kind</td><td>{{ function['kind'] }}</td>
</tr>
<tr>
<td>Usage Count</td><td>{{ function['usage_counter'] }}</td>
</tr>
<tr>
<td>Optimized Call Site Count</td><td>{{ function['optimized_call_site_count'] }}</td>
</tr>
<tr>
<td>Deoptimizations</td><td>{{ function['deoptimizations'] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<template if="{{ function['parent'] != null }}">
<div class="memberItem">
<div class="memberName">parent function</div>
<div class="memberValue">
<function-ref ref="{{ function['parent'] }}"></function-ref>
</div>
</div>
</template>
<template if="{{ function['parent'] == null &amp;&amp;
function['class'] != null }}">
<div class="memberItem">
<div class="memberName">parent class</div>
<div class="memberValue">
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">script</div>
<div class="memberValue">
<script-ref ref="{{ function['script'] }}">
</script-ref>
</div>
</div>
<div class="memberItem">&nbsp;</div>
<template if="{{ function['code'] != null }}">
<div class="memberItem">
<div class="memberName">optimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['code'] }}"></code-ref>
</div>
</div>
</template>
<template if="{{ function['unoptimized_code'] != null }}">
<div class="memberItem">
<div class="memberName">unoptimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
</div>
<div class="memberValue">
<span title="This count is used to determine when a function will be optimized. It is a combination of call counts and other factors.">
(usage count: {{ function['usage_counter'] }})
</span>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">deoptimizations</div>
<div class="memberValue">{{ function['deoptimizations'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">optimizable</div>
<div class="memberValue">{{ function['is_optimizable'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">inlinable</div>
<div class="memberValue">{{ function['is_inlinable'] }}</div>
</div>
<template if="{{ function.name != function.vmName }}">
<div class="memberItem">
<div class="memberName">vm name</div>
<div class="memberValue">{{ function.vmName }}</div>
</div>
</template>
</div>
</div>
</div>
</template>
</polymer-element>
@ -1214,7 +1276,12 @@
<template if="{{ instance['error'] == null }}">
<div class="content">
<!-- TODO(turnidge): Handle null instances. -->
<h1>instance of {{ instance['class']['user_name'] }}</h1>
<template if="{{ isType(instance.serviceType) }}">
<h1>type {{ instance['user_name'] }}</h1>
</template>
<template if="{{ !isType(instance.serviceType) }}">
<h1>instance of {{ instance['class']['user_name'] }}</h1>
</template>
<div class="memberList">
<div class="memberItem">
<div class="memberName">class</div>
@ -1233,6 +1300,15 @@
<div class="memberName">size</div>
<div class="memberValue">{{ instance['size'] | formatSize }}</div>
</div>
<template if="{{ instance['type_class'] != null }}">
<div class="memberItem">
<div class="memberName">type class</div>
<div class="memberValue">
<class-ref ref="{{ instance['type_class'] }}">
</class-ref>
</div>
</div>
</template>
</div>
</div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
<head>
<link rel="import" href="class_ref.html">
<link rel="import" href="instance_ref.html">
<link rel="import" href="observatory_element.html">
<link rel="import" href="service_ref.html">
</head>
@ -12,8 +12,8 @@
var
</template>
<template if="{{ (ref['declared_type']['name'] != 'dynamic') }}">
<class-ref ref="{{ ref['declared_type'] }}"></class-ref>
<instance-ref ref="{{ ref['declared_type'] }}"></instance-ref>
</template>
<a title="{{ hoverText }}" href="{{ url }}">{{ name }}</a>
</div>
</template> <script type="application/dart" src="field_ref.dart"></script> </polymer-element>
</template> <script type="application/dart" src="field_ref.dart"></script> </polymer-element>

View file

@ -1,11 +1,35 @@
<head>
<link rel="import" href="class_ref.html">
<link rel="import" href="code_ref.html">
<link rel="import" href="function_ref.html">
<link rel="import" href="observatory_element.html">
<link rel="import" href="nav_bar.html">
<link rel="import" href="script_ref.html">
</head>
<polymer-element name="function-view" extends="observatory-element">
<template>
<style>
.content {
padding-left: 10%;
font: 400 14px 'Montserrat', sans-serif;
}
h1 {
font: 400 18px 'Montserrat', sans-serif;
}
.memberList {
display: table;
}
.memberItem {
display: table-row;
}
.memberName, .memberValue {
display: table-cell;
vertical-align: top;
padding: 3px 0 3px 1em;
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<nav-bar>
<top-nav-menu></top-nav-menu>
<isolate-nav-menu isolate="{{ function.isolate }}"></isolate-nav-menu>
@ -20,50 +44,86 @@
<nav-refresh callback="{{ refresh }}"></nav-refresh>
</nav-bar>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-warning">
<div class="panel-heading">
{{ function['user_name'] }} ({{ function['name'] }})
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
<div class="panel-body">
<div>
<code-ref ref="{{ function['code'] }}"></code-ref>
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
<div class="content">
<h1>function {{ qualifiedName }}</h1>
<div class="memberList">
<div class="memberItem">
<div class="memberName">kind</div>
<div class="memberValue">
<template if="{{ function['is_static'] }}">static</template>
<template if="{{ function['is_const'] }}">const</template>
{{ kind }}
</div>
<table class="table table-hover">
<tbody>
<tr>
<td>static</td><td>{{ function['is_static'] }}</td>
</tr>
<tr>
<td>Const</td><td>{{ function['is_const'] }}</td>
</tr>
<tr>
<td>Optimizable</td><td>{{ function['is_optimizable'] }}</td>
</tr>
<tr>
<td>Inlinable</td><td>{{ function['is_inlinable'] }}</td>
</tr>
<tr>
<td>Kind</td><td>{{ function['kind'] }}</td>
</tr>
<tr>
<td>Usage Count</td><td>{{ function['usage_counter'] }}</td>
</tr>
<tr>
<td>Optimized Call Site Count</td><td>{{ function['optimized_call_site_count'] }}</td>
</tr>
<tr>
<td>Deoptimizations</td><td>{{ function['deoptimizations'] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<template if="{{ function['parent'] != null }}">
<div class="memberItem">
<div class="memberName">parent function</div>
<div class="memberValue">
<function-ref ref="{{ function['parent'] }}"></function-ref>
</div>
</div>
</template>
<template if="{{ function['parent'] == null &&
function['class'] != null }}">
<div class="memberItem">
<div class="memberName">parent class</div>
<div class="memberValue">
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">script</div>
<div class="memberValue">
<script-ref ref="{{ function['script'] }}">
</script-ref>
</div>
</div>
<div class="memberItem">&nbsp;</div>
<template if="{{ function['code'] != null }}">
<div class="memberItem">
<div class="memberName">optimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['code'] }}"></code-ref>
</div>
</div>
</template>
<template if="{{ function['unoptimized_code'] != null }}">
<div class="memberItem">
<div class="memberName">unoptimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
</div>
<div class="memberValue">
<span title="This count is used to determine when a function will be optimized. It is a combination of call counts and other factors.">
(usage count: {{ function['usage_counter'] }})
</span>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">deoptimizations</div>
<div class="memberValue">{{ function['deoptimizations'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">optimizable</div>
<div class="memberValue">{{ function['is_optimizable'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">inlinable</div>
<div class="memberValue">{{ function['is_inlinable'] }}</div>
</div>
<template if="{{ function.name != function.vmName }}">
<div class="memberItem">
<div class="memberName">vm name</div>
<div class="memberValue">{{ function.vmName }}</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script type="application/dart" src="function_view.dart"></script>
</polymer-element>

View file

@ -19,7 +19,7 @@
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<div>
<span>
<template if="{{ isUnexpected(ref.serviceType) }}">
unexpected reference type &lt;{{ ref.serviceType }}&gt;
</template>
@ -39,6 +39,10 @@
<a href="{{ url }}">{{ ref['preview'] }}</a>
</template>
<template if="{{ (isType(ref.serviceType)) }}">
<a href="{{ url }}">{{ ref['user_name'] }}</a>
</template>
<template if="{{ isClosure(ref.serviceType) }}">
<a href="{{ url }}">
<!-- TODO(turnidge): Switch this to fully-qualified function -->
@ -79,8 +83,7 @@
</div>
</curly-block>
</template>
</div>
</span>
</template>
<script type="application/dart" src="instance_ref.dart"></script>
</polymer-element>

View file

@ -47,7 +47,12 @@
<template if="{{ instance['error'] == null }}">
<div class="content">
<!-- TODO(turnidge): Handle null instances. -->
<h1>instance of {{ instance['class']['user_name'] }}</h1>
<template if="{{ isType(instance.serviceType) }}">
<h1>type {{ instance['user_name'] }}</h1>
</template>
<template if="{{ !isType(instance.serviceType) }}">
<h1>instance of {{ instance['class']['user_name'] }}</h1>
</template>
<div class="memberList">
<div class="memberItem">
<div class="memberName">class</div>
@ -66,6 +71,15 @@
<div class="memberName">size</div>
<div class="memberValue">{{ instance['size'] | formatSize }}</div>
</div>
<template if="{{ instance['type_class'] != null }}">
<div class="memberItem">
<div class="memberName">type class</div>
<div class="memberValue">
<class-ref ref="{{ instance['type_class'] }}">
</class-ref>
</div>
</div>
</template>
</div>
</div>

View file

@ -1,5 +1,5 @@
<head>
<link rel="import" href="class_ref.html">
<link rel="import" href="instance_ref.html">
<link rel="import" href="observatory_element.html">
<link rel="import" href="service_ref.html">
</head>
@ -12,8 +12,8 @@
var
</template>
<template if="{{ (ref['declared_type']['name'] != 'dynamic') }}">
<class-ref ref="{{ ref['declared_type'] }}"></class-ref>
<instance-ref ref="{{ ref['declared_type'] }}"></instance-ref>
</template>
<a title="{{ hoverText }}" href="{{ url }}">{{ name }}</a>
</div>
</template> <script type="application/dart" src="field_ref.dart"></script> </polymer-element>
</template> <script type="application/dart" src="field_ref.dart"></script> </polymer-element>

View file

@ -14,7 +14,76 @@ class FunctionViewElement extends ObservatoryElement {
@published ServiceMap function;
FunctionViewElement.created() : super.created();
// TODO(turnidge): Once we create a Function object, these fields
// should move there.
@published String qualifiedName;
@published String kind;
String _getQualifiedName(ServiceMap function) {
var parent = (function != null && function['parent'] != null
? function['parent'] : null);
if (parent != null) {
return "${_getQualifiedName(parent)}.${function['user_name']}";
}
var cls = (function != null &&
function['class'] != null &&
function['class']['user_name'] != null &&
function['class']['user_name'] != '::'
? function['class'] : null);
if (cls != null) {
return "${cls['user_name']}.${function['user_name']}";
}
return "${function['username']}";
}
void functionChanged(oldValue) {
notifyPropertyChange(#qualifiedName, 0, 1);
notifyPropertyChange(#kind, 0, 1);
qualifiedName = _getQualifiedName(function);
switch(function['kind']) {
case 'kRegularFunction':
kind = 'function';
break;
case 'kClosureFunction':
kind = 'closure function';
break;
case 'kSignatureFunction':
kind = 'signature function';
break;
case 'kGetterFunction':
kind = 'getter function';
break;
case 'kSetterFunction':
kind = 'setter function';
break;
case 'kConstructor':
kind = 'constructor';
break;
case 'kImplicitGetterFunction':
kind = 'implicit getter function';
break;
case 'kImplicitSetterFunction':
kind = 'implicit setter function';
break;
case 'kStaticInitializer':
kind = 'static initializer';
break;
case 'kMethodExtractor':
kind = 'method extractor';
break;
case 'kNoSuchMethodDispatcher':
kind = 'noSuchMethod dispatcher';
break;
case 'kInvokeFieldDispatcher':
kind = 'invoke field dispatcher';
break;
default:
kind = 'UNKNOWN';
break;
}
}
void refresh(var done) {
function.reload().whenComplete(done);
}
}
}

View file

@ -1,11 +1,35 @@
<head>
<link rel="import" href="class_ref.html">
<link rel="import" href="code_ref.html">
<link rel="import" href="function_ref.html">
<link rel="import" href="observatory_element.html">
<link rel="import" href="nav_bar.html">
<link rel="import" href="script_ref.html">
</head>
<polymer-element name="function-view" extends="observatory-element">
<template>
<style>
.content {
padding-left: 10%;
font: 400 14px 'Montserrat', sans-serif;
}
h1 {
font: 400 18px 'Montserrat', sans-serif;
}
.memberList {
display: table;
}
.memberItem {
display: table-row;
}
.memberName, .memberValue {
display: table-cell;
vertical-align: top;
padding: 3px 0 3px 1em;
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<nav-bar>
<top-nav-menu></top-nav-menu>
<isolate-nav-menu isolate="{{ function.isolate }}"></isolate-nav-menu>
@ -20,50 +44,86 @@
<nav-refresh callback="{{ refresh }}"></nav-refresh>
</nav-bar>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-warning">
<div class="panel-heading">
{{ function['user_name'] }} ({{ function['name'] }})
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
<div class="panel-body">
<div>
<code-ref ref="{{ function['code'] }}"></code-ref>
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
<div class="content">
<h1>function {{ qualifiedName }}</h1>
<div class="memberList">
<div class="memberItem">
<div class="memberName">kind</div>
<div class="memberValue">
<template if="{{ function['is_static'] }}">static</template>
<template if="{{ function['is_const'] }}">const</template>
{{ kind }}
</div>
<table class="table table-hover">
<tbody>
<tr>
<td>static</td><td>{{ function['is_static'] }}</td>
</tr>
<tr>
<td>Const</td><td>{{ function['is_const'] }}</td>
</tr>
<tr>
<td>Optimizable</td><td>{{ function['is_optimizable'] }}</td>
</tr>
<tr>
<td>Inlinable</td><td>{{ function['is_inlinable'] }}</td>
</tr>
<tr>
<td>Kind</td><td>{{ function['kind'] }}</td>
</tr>
<tr>
<td>Usage Count</td><td>{{ function['usage_counter'] }}</td>
</tr>
<tr>
<td>Optimized Call Site Count</td><td>{{ function['optimized_call_site_count'] }}</td>
</tr>
<tr>
<td>Deoptimizations</td><td>{{ function['deoptimizations'] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<template if="{{ function['parent'] != null }}">
<div class="memberItem">
<div class="memberName">parent function</div>
<div class="memberValue">
<function-ref ref="{{ function['parent'] }}"></function-ref>
</div>
</div>
</template>
<template if="{{ function['parent'] == null &&
function['class'] != null }}">
<div class="memberItem">
<div class="memberName">parent class</div>
<div class="memberValue">
<class-ref ref="{{ function['class'] }}"></class-ref>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">script</div>
<div class="memberValue">
<script-ref ref="{{ function['script'] }}">
</script-ref>
</div>
</div>
<div class="memberItem">&nbsp;</div>
<template if="{{ function['code'] != null }}">
<div class="memberItem">
<div class="memberName">optimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['code'] }}"></code-ref>
</div>
</div>
</template>
<template if="{{ function['unoptimized_code'] != null }}">
<div class="memberItem">
<div class="memberName">unoptimized code</div>
<div class="memberValue">
<code-ref ref="{{ function['unoptimized_code'] }}"></code-ref>
</div>
<div class="memberValue">
<span title="This count is used to determine when a function will be optimized. It is a combination of call counts and other factors.">
(usage count: {{ function['usage_counter'] }})
</span>
</div>
</div>
</template>
<div class="memberItem">
<div class="memberName">deoptimizations</div>
<div class="memberValue">{{ function['deoptimizations'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">optimizable</div>
<div class="memberValue">{{ function['is_optimizable'] }}</div>
</div>
<div class="memberItem">
<div class="memberName">inlinable</div>
<div class="memberValue">{{ function['is_inlinable'] }}</div>
</div>
<template if="{{ function.name != function.vmName }}">
<div class="memberItem">
<div class="memberName">vm name</div>
<div class="memberValue">{{ function.vmName }}</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script type="application/dart" src="function_view.dart"></script>
</polymer-element>

View file

@ -19,7 +19,7 @@
font: 400 14px 'Montserrat', sans-serif;
}
</style>
<div>
<span>
<template if="{{ isUnexpected(ref.serviceType) }}">
unexpected reference type &lt;{{ ref.serviceType }}&gt;
</template>
@ -39,6 +39,10 @@
<a href="{{ url }}">{{ ref['preview'] }}</a>
</template>
<template if="{{ (isType(ref.serviceType)) }}">
<a href="{{ url }}">{{ ref['user_name'] }}</a>
</template>
<template if="{{ isClosure(ref.serviceType) }}">
<a href="{{ url }}">
<!-- TODO(turnidge): Switch this to fully-qualified function -->
@ -79,8 +83,7 @@
</div>
</curly-block>
</template>
</div>
</span>
</template>
<script type="application/dart" src="instance_ref.dart"></script>
</polymer-element>

View file

@ -47,7 +47,12 @@
<template if="{{ instance['error'] == null }}">
<div class="content">
<!-- TODO(turnidge): Handle null instances. -->
<h1>instance of {{ instance['class']['user_name'] }}</h1>
<template if="{{ isType(instance.serviceType) }}">
<h1>type {{ instance['user_name'] }}</h1>
</template>
<template if="{{ !isType(instance.serviceType) }}">
<h1>instance of {{ instance['class']['user_name'] }}</h1>
</template>
<div class="memberList">
<div class="memberItem">
<div class="memberName">class</div>
@ -66,6 +71,15 @@
<div class="memberName">size</div>
<div class="memberValue">{{ instance['size'] | formatSize }}</div>
</div>
<template if="{{ instance['type_class'] != null }}">
<div class="memberItem">
<div class="memberName">type class</div>
<div class="memberValue">
<class-ref ref="{{ instance['type_class'] }}">
</class-ref>
</div>
</div>
</template>
</div>
</div>

View file

@ -161,6 +161,10 @@ class ObservatoryElement extends PolymerElement {
type == 'Array');
}
bool isType(String type) {
return (type == 'Type');
}
bool isUnexpected(String type) {
return (!['Null',
'Smi',
@ -173,6 +177,7 @@ class ObservatoryElement extends PolymerElement {
'Instance',
'GrowableObjectArray',
'Array',
'Type',
'Error'].contains(type));
}
}

View file

@ -60,6 +60,7 @@ class ServiceObjectViewElement extends ObservatoryElement {
case 'Instance':
case 'Smi':
case 'String':
case 'Type':
InstanceViewElement element = new Element.tag('instance-view');
element.instance = object;
return element;

View file

@ -899,6 +899,8 @@ void Isolate::PrintToJSONStream(JSONStream* stream, bool ref) {
jsheap.AddProperty("capacityOld", heap()->CapacityInWords(Heap::kOld));
}
// TODO(turnidge): Don't compute a full stack trace every time we
// request an isolate's info.
DebuggerStackTrace* stack = debugger()->StackTrace();
if (stack->Length() > 0) {
JSONObject jsframe(&jsobj, "topFrame");

View file

@ -6365,6 +6365,13 @@ void Function::PrintToJSONStream(JSONStream* stream, bool ref) const {
jsobj.AddProperty("code", Object::Handle(CurrentCode()));
jsobj.AddProperty("deoptimizations",
static_cast<intptr_t>(deoptimization_counter()));
const Script& script = Script::Handle(this->script());
if (!script.IsNull()) {
jsobj.AddProperty("script", script);
jsobj.AddProperty("token_pos", token_pos());
jsobj.AddProperty("end_token_pos", end_token_pos());
}
}
@ -6663,14 +6670,8 @@ void Field::PrintToJSONStream(JSONStream* stream, bool ref) const {
jsobj.AddProperty("owner", cls);
// TODO(turnidge): Once the vmservice supports returning types,
// return the type here instead of the class.
AbstractType& declared_type = AbstractType::Handle(type());
if (declared_type.HasResolvedTypeClass()) {
cls = declared_type.type_class();
jsobj.AddProperty("declared_type", cls);
}
jsobj.AddProperty("declared_type", declared_type);
jsobj.AddProperty("static", is_static());
jsobj.AddProperty("final", is_final());
jsobj.AddProperty("const", is_const());
@ -12472,11 +12473,54 @@ const char* Instance::ToUserCString(intptr_t max_len, intptr_t nesting) const {
}
void Instance::PrintSharedInstanceJSON(JSONObject* jsobj, bool ref) const {
jsobj->AddProperty("type", JSONType(ref));
Class& cls = Class::Handle(this->clazz());
jsobj->AddProperty("class", cls);
if (ref) {
return;
}
jsobj->AddProperty("size", raw()->Size());
// Walk the superclass chain, adding all instance fields.
{
Instance& fieldValue = Instance::Handle();
JSONArray jsarr(jsobj, "fields");
while (!cls.IsNull()) {
const Array& field_array = Array::Handle(cls.fields());
Field& field = Field::Handle();
if (!field_array.IsNull()) {
for (intptr_t i = 0; i < field_array.Length(); i++) {
field ^= field_array.At(i);
if (!field.is_static()) {
fieldValue ^= GetField(field);
JSONObject jsfield(&jsarr);
jsfield.AddProperty("decl", field);
jsfield.AddProperty("value", fieldValue);
}
}
}
cls = cls.SuperClass();
}
}
if (NumNativeFields() > 0) {
JSONArray jsarr(jsobj, "nativeFields");
for (intptr_t i = 0; i < NumNativeFields(); i++) {
intptr_t value = GetNativeField(i);
JSONObject jsfield(&jsarr);
jsfield.AddProperty("index", i);
jsfield.AddProperty("value", value);
}
}
}
void Instance::PrintToJSONStream(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
Class& cls = Class::Handle(this->clazz());
// TODO(turnidge): Handle <optimized out> like other null-like values.
// Handle certain special instance values.
if (IsNull()) {
jsobj.AddProperty("type", ref ? "@Null" : "Null");
jsobj.AddProperty("id", "objects/null");
@ -12500,57 +12544,20 @@ void Instance::PrintToJSONStream(JSONStream* stream, bool ref) const {
jsobj.AddProperty("id", "objects/optimized-out");
jsobj.AddProperty("preview", "<optimized out>");
return;
} else {
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
if (IsClosure()) {
const Function& closureFunc = Function::Handle(Closure::function(*this));
jsobj.AddProperty("closureFunc", closureFunc);
jsobj.AddProperty("type", ref ? "@Closure" : "Closure");
} else {
jsobj.AddProperty("type", JSONType(ref));
}
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
jsobj.AddProperty("class", cls);
jsobj.AddProperty("preview", ToUserCString(40));
}
PrintSharedInstanceJSON(&jsobj, ref);
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
if (IsClosure()) {
const Function& closureFunc = Function::Handle(Closure::function(*this));
jsobj.AddProperty("closureFunc", closureFunc);
}
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
jsobj.AddProperty("preview", ToUserCString(40));
if (ref) {
return;
}
// Walk the superclass chain, adding all instance fields.
{
Instance& fieldValue = Instance::Handle();
JSONArray jsarr(&jsobj, "fields");
while (!cls.IsNull()) {
const Array& field_array = Array::Handle(cls.fields());
Field& field = Field::Handle();
if (!field_array.IsNull()) {
for (intptr_t i = 0; i < field_array.Length(); i++) {
field ^= field_array.At(i);
if (!field.is_static()) {
fieldValue ^= GetField(field);
JSONObject jsfield(&jsarr);
jsfield.AddProperty("decl", field);
jsfield.AddProperty("value", fieldValue);
}
}
}
cls = cls.SuperClass();
}
}
if (NumNativeFields() > 0) {
JSONArray jsarr(&jsobj, "nativeFields");
for (intptr_t i = 0; i < NumNativeFields(); i++) {
intptr_t value = GetNativeField(i);
JSONObject jsfield(&jsarr);
jsfield.AddProperty("index", i);
jsfield.AddProperty("value", value);
}
}
jsobj.AddProperty("size", raw()->Size());
}
@ -13493,18 +13500,20 @@ const char* Type::ToCString() const {
void Type::PrintToJSONStream(JSONStream* stream, bool ref) const {
// TODO(koda): Decide whether to assign stable ids to non-canonical types.
if (!IsCanonical()) {
return Object::PrintToJSONStream(stream, ref);
}
ASSERT(IsCanonical());
JSONObject jsobj(stream);
jsobj.AddProperty("type", JSONType(ref));
const Class& type_cls = Class::Handle(type_class());
intptr_t id = type_cls.FindCanonicalTypeIndex(*this);
ASSERT(id >= 0);
intptr_t cid = type_cls.id();
jsobj.AddPropertyF("id", "classes/%" Pd "/types/%" Pd "", cid, id);
PrintSharedInstanceJSON(&jsobj, ref);
if (IsCanonical()) {
const Class& type_cls = Class::Handle(type_class());
intptr_t id = type_cls.FindCanonicalTypeIndex(*this);
ASSERT(id >= 0);
intptr_t cid = type_cls.id();
jsobj.AddPropertyF("id", "classes/%" Pd "/types/%" Pd "", cid, id);
jsobj.AddProperty("type_class", type_cls);
} else {
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
}
const char* name = String::Handle(Name()).ToCString();
const char* user_name = String::Handle(UserVisibleName()).ToCString();
jsobj.AddProperty("name", name);
@ -13512,7 +13521,6 @@ void Type::PrintToJSONStream(JSONStream* stream, bool ref) const {
if (ref) {
return;
}
jsobj.AddProperty("type_class", type_cls);
jsobj.AddProperty("type_arguments", TypeArguments::Handle(arguments()));
}
@ -13682,9 +13690,9 @@ const char* TypeRef::ToCString() const {
void TypeRef::PrintToJSONStream(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
PrintSharedInstanceJSON(&jsobj, ref);
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
jsobj.AddProperty("type", JSONType(ref));
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
const char* name = String::Handle(Name()).ToCString();
const char* user_name = String::Handle(UserVisibleName()).ToCString();
@ -13898,16 +13906,16 @@ const char* TypeParameter::ToCString() const {
void TypeParameter::PrintToJSONStream(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
PrintSharedInstanceJSON(&jsobj, ref);
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
jsobj.AddProperty("type", JSONType(ref));
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
const char* name = String::Handle(Name()).ToCString();
const char* user_name = String::Handle(UserVisibleName()).ToCString();
jsobj.AddProperty("name", name);
jsobj.AddProperty("user_name", user_name);
const Class& cls = Class::Handle(parameterized_class());
jsobj.AddProperty("parameterized_class", cls);
const Class& param_cls = Class::Handle(parameterized_class());
jsobj.AddProperty("parameterized_class", param_cls);
if (ref) {
return;
}
@ -14095,9 +14103,9 @@ const char* BoundedType::ToCString() const {
void BoundedType::PrintToJSONStream(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
PrintSharedInstanceJSON(&jsobj, ref);
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
jsobj.AddProperty("type", JSONType(ref));
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
const char* name = String::Handle(Name()).ToCString();
const char* user_name = String::Handle(UserVisibleName()).ToCString();
@ -16788,12 +16796,10 @@ const char* Array::ToCString() const {
void Array::PrintToJSONStream(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
Class& cls = Class::Handle(this->clazz());
PrintSharedInstanceJSON(&jsobj, ref);
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
jsobj.AddProperty("type", JSONType(ref));
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
jsobj.AddProperty("class", cls);
jsobj.AddProperty("length", Length());
if (ref) {
return;
@ -17127,12 +17133,10 @@ const char* GrowableObjectArray::ToUserCString(intptr_t max_len,
void GrowableObjectArray::PrintToJSONStream(JSONStream* stream,
bool ref) const {
JSONObject jsobj(stream);
Class& cls = Class::Handle(this->clazz());
PrintSharedInstanceJSON(&jsobj, ref);
ObjectIdRing* ring = Isolate::Current()->object_id_ring();
const intptr_t id = ring->GetIdForObject(raw());
jsobj.AddProperty("type", JSONType(ref));
jsobj.AddPropertyF("id", "objects/%" Pd "", id);
jsobj.AddProperty("class", cls);
jsobj.AddProperty("length", Length());
if (ref) {
return;

View file

@ -4075,6 +4075,9 @@ class Instance : public Object {
static RawInstance* New(const Class& cls, Heap::Space space = Heap::kNew);
protected:
virtual void PrintSharedInstanceJSON(JSONObject* jsobj, bool ref) const;
private:
RawObject** FieldAddrAtOffset(intptr_t offset) const {
ASSERT(IsValidFieldOffset(offset));

View file

@ -798,6 +798,54 @@ static bool GetCodeId(const char* s, int64_t* timestamp, uword* address) {
}
static bool HandleInstanceCommands(Isolate* isolate,
const Object& obj,
JSONStream* js,
intptr_t arg_pos) {
ASSERT(js->num_arguments() > arg_pos);
const char* action = js->GetArgument(arg_pos);
if (strcmp(action, "eval") == 0) {
if (js->num_arguments() > (arg_pos + 1)) {
PrintError(js, "expected at most %" Pd " arguments but found %" Pd "\n",
arg_pos + 1,
js->num_arguments());
return true;
}
if (obj.IsNull()) {
PrintErrorWithKind(js, "EvalCollected",
"attempt to evaluate against collected object\n",
js->num_arguments());
return true;
}
if (obj.raw() == Object::sentinel().raw()) {
PrintErrorWithKind(js, "EvalExpired",
"attempt to evaluate against expired object\n",
js->num_arguments());
return true;
}
const char* expr = js->LookupOption("expr");
if (expr == NULL) {
PrintError(js, "eval expects an 'expr' option\n",
js->num_arguments());
return true;
}
const String& expr_str = String::Handle(isolate, String::New(expr));
ASSERT(obj.IsInstance());
const Instance& instance = Instance::Cast(obj);
const Object& result = Object::Handle(instance.Evaluate(expr_str));
if (result.IsNull()) {
Object::null_instance().PrintToJSONStream(js, true);
} else {
result.PrintToJSONStream(js, true);
}
return true;
}
PrintError(js, "unrecognized action '%s'\n", action);
return true;
}
static bool HandleClassesClosures(Isolate* isolate, const Class& cls,
JSONStream* js) {
intptr_t id;
@ -944,11 +992,8 @@ static bool HandleClassesTypes(Isolate* isolate, const Class& cls,
}
return true;
}
ASSERT(js->num_arguments() >= 4);
intptr_t id;
if (js->num_arguments() > 4) {
PrintError(js, "Command too long");
return true;
}
if (!GetIntegerId(js->GetArgument(3), &id)) {
PrintError(js, "Must specify collection object id: types/id");
return true;
@ -959,8 +1004,11 @@ static bool HandleClassesTypes(Isolate* isolate, const Class& cls,
PrintError(js, "Canonical type %" Pd " not found", id);
return true;
}
type.PrintToJSONStream(js, false);
return true;
if (js->num_arguments() == 4) {
type.PrintToJSONStream(js, false);
return true;
}
return HandleInstanceCommands(isolate, type, js, 4);
}
@ -1181,8 +1229,6 @@ static bool HandleObjects(Isolate* isolate, JSONStream* js) {
PrintError(js, "unrecognized object id '%s'", arg);
return true;
}
// Now what should we do with the object?
if (js->num_arguments() == 2) {
// Print.
if (obj.IsNull()) {
@ -1197,47 +1243,7 @@ static bool HandleObjects(Isolate* isolate, JSONStream* js) {
obj.PrintToJSONStream(js, false);
return true;
}
ASSERT(js->num_arguments() > 2);
const char* action = js->GetArgument(2);
if (strcmp(action, "eval") == 0) {
if (js->num_arguments() > 3) {
PrintError(js, "expected at most 3 arguments but found %" Pd "\n",
js->num_arguments());
return true;
}
if (obj.IsNull()) {
PrintErrorWithKind(js, "EvalCollected",
"attempt to evaluate against collected object\n",
js->num_arguments());
return true;
}
if (obj.raw() == Object::sentinel().raw()) {
PrintErrorWithKind(js, "EvalExpired",
"attempt to evaluate against expired object\n",
js->num_arguments());
return true;
}
const char* expr = js->LookupOption("expr");
if (expr == NULL) {
PrintError(js, "eval expects an 'expr' option\n",
js->num_arguments());
return true;
}
const String& expr_str = String::Handle(isolate, String::New(expr));
ASSERT(obj.IsInstance());
const Instance& instance = Instance::Cast(obj);
const Object& result = Object::Handle(instance.Evaluate(expr_str));
if (result.IsNull()) {
Object::null_instance().PrintToJSONStream(js, true);
} else {
result.PrintToJSONStream(js, true);
}
return true;
}
PrintError(js, "unrecognized action '%s'\n", action);
return true;
return HandleInstanceCommands(isolate, obj, js, 2);
}

View file

@ -112,6 +112,9 @@ static RawInstance* EvalF(Dart_Handle lib, const char* fmt, ...) {
// Search for the formatted string in buff.
//
// TODO(turnidge): This function obscures the line number of failing
// EXPECTs. Rework this.
static void ExpectSubstringF(const char* buff, const char* fmt, ...) {
Isolate* isolate = Isolate::Current();
@ -456,10 +459,10 @@ TEST_CASE(Service_Objects) {
handler.filterMsg("name");
handler.filterMsg("size");
EXPECT_STREQ(
"{\"type\":\"String\",\"id\":\"objects\\/1\","
"{\"type\":\"String\","
"\"class\":{\"type\":\"@Class\",\"id\":\"classes\\/60\","
"\"user_name\":\"String\"},\"preview\":\"\\\"value\\\"\","
"\"fields\":[],}",
"\"user_name\":\"String\"},\"fields\":[],"
"\"id\":\"objects\\/1\",\"preview\":\"\\\"value\\\"\"}",
handler.msg());
// object id ring / invalid => expired
@ -781,8 +784,9 @@ TEST_CASE(Service_Types) {
Service::HandleIsolateMessage(isolate, service_msg);
handler.HandleNextMessage();
EXPECT_SUBSTRING("\"type\":\"Class\"", handler.msg());
EXPECT_SUBSTRING("\"name\":\"A\"", handler.msg());
ExpectSubstringF(handler.msg(),
"\"id\":\"classes\\/%" Pd "\",\"name\":\"A\",", cid);
"\"id\":\"classes\\/%" Pd "\"", cid);
// Request canonical type 0 from class A.
service_msg = EvalF(h_lib, "[port, ['classes', '%" Pd "', 'types', '0'],"
@ -790,9 +794,9 @@ TEST_CASE(Service_Types) {
Service::HandleIsolateMessage(isolate, service_msg);
handler.HandleNextMessage();
EXPECT_SUBSTRING("\"type\":\"Type\"", handler.msg());
EXPECT_SUBSTRING("\"name\":\"A<bool>\"", handler.msg());
ExpectSubstringF(handler.msg(),
"\"id\":\"classes\\/%" Pd "\\/types\\/0\","
"\"name\":\"A<bool>\",", cid);
"\"id\":\"classes\\/%" Pd "\\/types\\/0\"", cid);
// Request canonical type 1 from class A.
service_msg = EvalF(h_lib, "[port, ['classes', '%" Pd "', 'types', '1'],"
@ -800,9 +804,9 @@ TEST_CASE(Service_Types) {
Service::HandleIsolateMessage(isolate, service_msg);
handler.HandleNextMessage();
EXPECT_SUBSTRING("\"type\":\"Type\"", handler.msg());
EXPECT_SUBSTRING("\"name\":\"A<A<bool>>\"", handler.msg());
ExpectSubstringF(handler.msg(),
"\"id\":\"classes\\/%" Pd "\\/types\\/1\","
"\"name\":\"A<A<bool>>\",", cid);
"\"id\":\"classes\\/%" Pd "\\/types\\/1\"", cid);
// Request for non-existent canonical type from class A.
service_msg = EvalF(h_lib, "[port, ['classes', '%" Pd "', 'types', '42'],"