Index: test/cctest/test-heap-profiler.cc |
diff --git a/test/cctest/test-heap-profiler.cc b/test/cctest/test-heap-profiler.cc |
index 6dc49c0e54911adba3981d9fb565dda9e2d087b0..606336c906af817a3464d9e6925b2ca6eca0cb14 100644 |
--- a/test/cctest/test-heap-profiler.cc |
+++ b/test/cctest/test-heap-profiler.cc |
@@ -989,4 +989,137 @@ TEST(AggregatedHeapSnapshot) { |
CHECK(IsNodeRetainedAs(a_from_b, 1)); // B has 1 ref to A. |
} |
+namespace { |
+ |
+class TestJSONStream : public v8::OutputStream { |
+ public: |
+ TestJSONStream() : eos_signaled_(0) {} |
+ virtual ~TestJSONStream() {} |
+ virtual void EndOfStream() { ++eos_signaled_; } |
+ virtual void WriteAsciiChunk(char* buffer, int chars_written) { |
+ CHECK_GT(chars_written, 0); |
+ i::Vector<char> chunk = buffer_.AddBlock(chars_written, '\0'); |
+ memcpy(chunk.start(), buffer, chars_written); |
+ } |
+ void WriteTo(i::Vector<char> dest) { buffer_.WriteTo(dest); } |
+ int eos_signaled() { return eos_signaled_; } |
+ int size() { return buffer_.size(); } |
+ private: |
+ i::Collector<char> buffer_; |
+ int eos_signaled_; |
+}; |
+ |
+class AsciiResource: public v8::String::ExternalAsciiStringResource { |
+ public: |
+ explicit AsciiResource(i::Vector<char> string): data_(string.start()) { |
+ length_ = string.length(); |
+ } |
+ virtual const char* data() const { return data_; } |
+ virtual size_t length() const { return length_; } |
+ private: |
+ const char* data_; |
+ size_t length_; |
+}; |
+ |
+} // namespace |
+ |
+TEST(HeapSnapshotJSONSerialization) { |
+ v8::HandleScope scope; |
+ LocalContext env; |
+ |
+#define STRING_LITERAL_FOR_TEST \ |
+ "\"String \\n\\r\\u0008\\u0081\\u0101\\u0801\\u8001\"" |
+ CompileAndRunScript( |
+ "function A(s) { this.s = s; }\n" |
+ "function B(x) { this.x = x; }\n" |
+ "var a = new A(" STRING_LITERAL_FOR_TEST ");\n" |
+ "var b = new B(a);"); |
+ const v8::HeapSnapshot* snapshot = |
+ v8::HeapProfiler::TakeSnapshot(v8::String::New("json")); |
+ TestJSONStream stream; |
+ snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON); |
+ CHECK_GT(stream.size(), 0); |
+ CHECK_EQ(1, stream.eos_signaled()); |
+ i::ScopedVector<char> json(stream.size()); |
+ stream.WriteTo(json); |
+ |
+ // Verify that snapshot string is valid JSON. |
+ AsciiResource json_res(json); |
+ v8::Local<v8::String> json_string = v8::String::NewExternal(&json_res); |
+ env->Global()->Set(v8::String::New("json_snapshot"), json_string); |
+ v8::Local<v8::Value> snapshot_parse_result = CompileRun( |
+ "var parsed = JSON.parse(json_snapshot); true;"); |
+ CHECK(!snapshot_parse_result.IsEmpty()); |
+ |
+ // Verify that snapshot object has required fields. |
+ v8::Local<v8::Object> parsed_snapshot = |
+ env->Global()->Get(v8::String::New("parsed"))->ToObject(); |
+ CHECK(parsed_snapshot->Has(v8::String::New("snapshot"))); |
+ CHECK(parsed_snapshot->Has(v8::String::New("nodes"))); |
+ CHECK(parsed_snapshot->Has(v8::String::New("strings"))); |
+ |
+ // Verify that nodes meta-info is valid JSON. |
+ v8::Local<v8::Value> nodes_meta_parse_result = CompileRun( |
+ "var parsed_meta = JSON.parse(parsed.nodes[0]); true;"); |
+ CHECK(!nodes_meta_parse_result.IsEmpty()); |
+ |
+ // Get node and edge "member" offsets. |
+ v8::Local<v8::Value> meta_analysis_result = CompileRun( |
+ "var children_count_offset =" |
+ " parsed_meta.fields.indexOf('children_count');\n" |
+ "var children_offset =" |
+ " parsed_meta.fields.indexOf('children');\n" |
+ "var children_meta =" |
+ " parsed_meta.types[children_offset];\n" |
+ "var child_fields_count = children_meta.fields.length;\n" |
+ "var child_type_offset =" |
+ " children_meta.fields.indexOf('type');\n" |
+ "var child_name_offset =" |
+ " children_meta.fields.indexOf('name_or_index');\n" |
+ "var child_to_node_offset =" |
+ " children_meta.fields.indexOf('to_node');\n" |
+ "var property_type =" |
+ " children_meta.types[child_type_offset].indexOf('property');"); |
+ CHECK(!meta_analysis_result.IsEmpty()); |
+ |
+ // A helper function for processing encoded nodes. |
+ CompileRun( |
+ "function GetChildPosByProperty(pos, prop_name) {\n" |
+ " var nodes = parsed.nodes;\n" |
+ " var strings = parsed.strings;\n" |
+ " for (var i = 0,\n" |
+ " count = nodes[pos + children_count_offset] * child_fields_count;\n" |
+ " i < count; i += child_fields_count) {\n" |
+ " var child_pos = pos + children_offset + i;\n" |
+ " if (nodes[child_pos + child_type_offset] === property_type\n" |
+ " && strings[nodes[child_pos + child_name_offset]] === prop_name)\n" |
+ " return nodes[child_pos + child_to_node_offset];\n" |
+ " }\n" |
+ " return null;\n" |
+ "}\n"); |
+ // Get the string index using the path: <root> -> <global>.b.x.s |
+ v8::Local<v8::Value> string_obj_pos_val = CompileRun( |
+ "GetChildPosByProperty(\n" |
+ " GetChildPosByProperty(\n" |
+ " GetChildPosByProperty(" |
+ " parsed.nodes[1 + children_offset + child_to_node_offset],\"b\"),\n" |
+ " \"x\")," |
+ " \"s\")"); |
+ CHECK(!string_obj_pos_val.IsEmpty()); |
+ int string_obj_pos = string_obj_pos_val->ToNumber()->Value(); |
+ v8::Local<v8::Object> nodes_array = |
+ parsed_snapshot->Get(v8::String::New("nodes"))->ToObject(); |
+ int string_index = |
+ nodes_array->Get(string_obj_pos + 1)->ToNumber()->Value(); |
+ CHECK_GT(string_index, 0); |
+ v8::Local<v8::Object> strings_array = |
+ parsed_snapshot->Get(v8::String::New("strings"))->ToObject(); |
+ v8::Local<v8::String> string = strings_array->Get(string_index)->ToString(); |
+ v8::Local<v8::String> ref_string = |
+ CompileRun(STRING_LITERAL_FOR_TEST)->ToString(); |
+#undef STRING_LITERAL_FOR_TEST |
+ CHECK_EQ(*v8::String::Utf8Value(ref_string), |
+ *v8::String::Utf8Value(string)); |
+} |
+ |
#endif // ENABLE_LOGGING_AND_PROFILING |