Index: test/cctest/test-api.cc |
diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc |
index 75c5b8e2a330f8306bd220e561d70c5b2ed4d534..e32089398b97dae02079d53ec7eb0fd52711044f 100644 |
--- a/test/cctest/test-api.cc |
+++ b/test/cctest/test-api.cc |
@@ -23035,3 +23035,307 @@ TEST(GetHiddenPropertyTableAfterAccessCheck) { |
obj->SetHiddenValue(v8_str("hidden key 2"), v8_str("hidden value 2")); |
} |
+ |
+ |
+class TestSourceStream : public v8::ScriptCompiler::ExternalSourceStream { |
+ public: |
+ explicit TestSourceStream(const char** chunks) : chunks_(chunks), index_(0) {} |
+ |
+ virtual size_t GetMoreData(const uint8_t** src) { |
+ // Unlike in real use cases, this function will never block. |
+ if (chunks_[index_] == NULL) { |
+ return 0; |
+ } |
+ // Copy the data, since the caller takes ownership of it. |
+ size_t len = strlen(chunks_[index_]); |
+ // We don't need to zero-terminate since we return the length. |
+ uint8_t* copy = new uint8_t[len]; |
+ memcpy(copy, chunks_[index_], len); |
+ *src = copy; |
+ ++index_; |
+ return len; |
+ } |
+ |
+ // Helper for constructing a string from chunks (the compilation needs it |
+ // too). |
+ static char* FullSourceString(const char** chunks) { |
+ size_t total_len = 0; |
+ for (size_t i = 0; chunks[i] != NULL; ++i) { |
+ total_len += strlen(chunks[i]); |
+ } |
+ char* full_string = new char[total_len + 1]; |
+ size_t offset = 0; |
+ for (size_t i = 0; chunks[i] != NULL; ++i) { |
+ size_t len = strlen(chunks[i]); |
+ memcpy(full_string + offset, chunks[i], len); |
+ offset += len; |
+ } |
+ full_string[total_len] = 0; |
+ return full_string; |
+ } |
+ |
+ private: |
+ const char** chunks_; |
+ unsigned index_; |
+}; |
+ |
+ |
+// Helper function for running streaming tests. |
+void RunStreamingTest(const char** chunks, |
+ v8::ScriptCompiler::StreamedSource::Encoding encoding = |
+ v8::ScriptCompiler::StreamedSource::ONE_BYTE, |
+ bool expected_success = true) { |
+ LocalContext env; |
+ v8::Isolate* isolate = env->GetIsolate(); |
+ v8::HandleScope scope(isolate); |
+ v8::TryCatch try_catch; |
+ |
+ v8::ScriptCompiler::StreamedSource source(new TestSourceStream(chunks), |
+ encoding); |
+ v8::ScriptCompiler::ScriptStreamingTask* task = |
+ v8::ScriptCompiler::StartStreamingScript(isolate, &source); |
+ |
+ // TestSourceStream::GetMoreData won't block, so it's OK to just run the |
+ // task here in the main thread. |
+ task->Run(); |
+ delete task; |
+ |
+ v8::ScriptOrigin origin(v8_str("http://foo.com")); |
+ char* full_source = TestSourceStream::FullSourceString(chunks); |
+ |
+ // The possible errors are only produced while compiling. |
+ CHECK_EQ(false, try_catch.HasCaught()); |
+ |
+ v8::Handle<Script> script = v8::ScriptCompiler::Compile( |
+ isolate, &source, v8_str(full_source), origin); |
+ if (expected_success) { |
+ CHECK(!script.IsEmpty()); |
+ v8::Handle<Value> result(script->Run()); |
+ // All scripts are supposed to return the fixed value 13 when ran. |
+ CHECK_EQ(13, result->Int32Value()); |
+ } else { |
+ CHECK(script.IsEmpty()); |
+ CHECK(try_catch.HasCaught()); |
+ } |
+ delete[] full_source; |
+} |
+ |
+ |
+TEST(StreamingSimpleScript) { |
+ // This script is unrealistically small, since no one chunk is enough to fill |
+ // the backing buffer of Scanner, let alone overflow it. |
+ const char* chunks[] = {"function foo() { ret", "urn 13; } f", "oo(); ", |
+ NULL}; |
+ RunStreamingTest(chunks); |
+} |
+ |
+ |
+TEST(StreamingBiggerScript) { |
+ const char* chunk1 = |
+ "function foo() {\n" |
+ " // Make this chunk sufficiently long so that it will overflow the\n" |
+ " // backing buffer of the Scanner.\n" |
+ " var i = 0;\n" |
+ " var result = 0;\n" |
+ " for (i = 0; i < 13; ++i) { result = result + 1; }\n" |
+ " result = 0;\n" |
+ " for (i = 0; i < 13; ++i) { result = result + 1; }\n" |
+ " result = 0;\n" |
+ " for (i = 0; i < 13; ++i) { result = result + 1; }\n" |
+ " result = 0;\n" |
+ " for (i = 0; i < 13; ++i) { result = result + 1; }\n" |
+ " return result;\n" |
+ "}\n"; |
+ const char* chunks[] = {chunk1, "foo(); ", NULL}; |
+ RunStreamingTest(chunks); |
+} |
+ |
+ |
+TEST(StreamingScriptWithParseError) { |
+ // Test that parse errors from streamed scripts are propagated correctly. |
+ { |
+ char chunk1[] = |
+ " // This will result in a parse error.\n" |
+ " var if else then foo"; |
+ char chunk2[] = " 13\n"; |
+ const char* chunks[] = {chunk1, chunk2, "foo();", NULL}; |
+ |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::ONE_BYTE, |
+ false); |
+ } |
+ // Test that the next script succeeds normally. |
+ { |
+ char chunk1[] = |
+ " // This will be parsed successfully.\n" |
+ " function foo() { return "; |
+ char chunk2[] = " 13; }\n"; |
+ const char* chunks[] = {chunk1, chunk2, "foo();", NULL}; |
+ |
+ RunStreamingTest(chunks); |
+ } |
+} |
+ |
+ |
+TEST(StreamingUtf8Script) { |
+ const char* chunk1 = |
+ "function foo() {\n" |
+ " // This function will contain an UTF-8 character which is not in\n" |
+ " // ASCII.\n" |
+ " var foob\uc481r = 13;\n" |
+ " return foob\uc481r;\n" |
+ "}\n"; |
+ const char* chunks[] = {chunk1, "foo(); ", NULL}; |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8); |
+} |
+ |
+ |
+TEST(StreamingUtf8ScriptWithSplitCharactersSanityCheck) { |
+ // A sanity check to prove that the approach of splitting UTF-8 |
+ // characters is correct. Here is an UTF-8 character which will take three |
+ // bytes. |
+ const char* reference = "\uc481"; |
+ CHECK_EQ(3, strlen(reference)); |
+ char chunk1[] = |
+ "function foo() {\n" |
+ " // This function will contain an UTF-8 character which is not in\n" |
+ " // ASCII.\n" |
+ " var foob"; |
+ char chunk2[] = |
+ "XXXr = 13;\n" |
+ " return foob\uc481r;\n" |
+ "}\n"; |
+ for (int i = 0; i < 3; ++i) { |
+ chunk2[i] = reference[i]; |
+ } |
+ const char* chunks[] = {chunk1, chunk2, "foo();", NULL}; |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8); |
+} |
+ |
+ |
+TEST(StreamingUtf8ScriptWithSplitCharacters) { |
+ // Stream data where a multi-byte UTF-8 character is split between two data |
+ // chunks. |
+ const char* reference = "\uc481"; |
+ char chunk1[] = |
+ "function foo() {\n" |
+ " // This function will contain an UTF-8 character which is not in\n" |
+ " // ASCII.\n" |
+ " var foobX"; |
+ char chunk2[] = |
+ "XXr = 13;\n" |
+ " return foob\uc481r;\n" |
+ "}\n"; |
+ chunk1[strlen(chunk1) - 1] = reference[0]; |
+ chunk2[0] = reference[1]; |
+ chunk2[1] = reference[2]; |
+ const char* chunks[] = {chunk1, chunk2, "foo();", NULL}; |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8); |
+} |
+ |
+ |
+TEST(StreamingUtf8ScriptWithSplitCharactersValidEdgeCases) { |
+ // Tests edge cases which should still be decoded correctly. |
+ |
+ // Case 1: a chunk contains only bytes for a split character (and no other |
+ // data). This kind of a chunk would be exceptionally small, but we should |
+ // still decode it correctly. |
+ const char* reference = "\uc481"; |
+ fprintf(stderr, "%d %d %d\n", reference[0], reference[1], reference[2]); |
+ // The small chunk is at the beginning of the split character |
+ { |
+ char chunk1[] = |
+ "function foo() {\n" |
+ " // This function will contain an UTF-8 character which is not in\n" |
+ " // ASCII.\n" |
+ " var foob"; |
+ char chunk2[] = "XX"; |
+ char chunk3[] = |
+ "Xr = 13;\n" |
+ " return foob\uc481r;\n" |
+ "}\n"; |
+ chunk2[0] = reference[0]; |
+ chunk2[1] = reference[1]; |
+ chunk3[0] = reference[2]; |
+ const char* chunks[] = {chunk1, chunk2, chunk3, "foo();", NULL}; |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8); |
+ } |
+ // The small chunk is at the end of a character |
+ { |
+ char chunk1[] = |
+ "function foo() {\n" |
+ " // This function will contain an UTF-8 character which is not in\n" |
+ " // ASCII.\n" |
+ " var foobX"; |
+ char chunk2[] = "XX"; |
+ char chunk3[] = |
+ "r = 13;\n" |
+ " return foob\uc481r;\n" |
+ "}\n"; |
+ chunk1[strlen(chunk1) - 1] = reference[0]; |
+ chunk2[0] = reference[1]; |
+ chunk2[1] = reference[2]; |
+ const char* chunks[] = {chunk1, chunk2, chunk3, "foo();", NULL}; |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8); |
+ } |
+ // Case 2: the script ends with a multi-byte character. Make sure that it's |
+ // decoded correctly and not just ignored. |
+ { |
+ char chunk1[] = |
+ "var foob\uc481 = 13;\n" |
+ "foob\uc481"; |
+ const char* chunks[] = {chunk1, NULL}; |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8); |
+ } |
+} |
+ |
+ |
+TEST(StreamingUtf8ScriptWithSplitCharactersInvalidEdgeCases) { |
+ // Test cases where a UTF-8 character is split over several chunks. Those |
+ // cases are not supported (the embedder should give the data in big enough |
+ // chunks), but we shouldn't crash, just produce a parse error. |
+ const char* reference = "\uc481"; |
+ char chunk1[] = |
+ "function foo() {\n" |
+ " // This function will contain an UTF-8 character which is not in\n" |
+ " // ASCII.\n" |
+ " var foobX"; |
+ char chunk2[] = "X"; |
+ char chunk3[] = |
+ "Xr = 13;\n" |
+ " return foob\uc481r;\n" |
+ "}\n"; |
+ chunk1[strlen(chunk1) - 1] = reference[0]; |
+ chunk2[0] = reference[1]; |
+ chunk3[0] = reference[2]; |
+ const char* chunks[] = {chunk1, chunk2, chunk3, "foo();", NULL}; |
+ |
+ RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8, false); |
+} |
+ |
+ |
+TEST(StreamingProducesParserCache) { |
+ i::FLAG_min_preparse_length = 0; |
+ const char* chunks[] = {"function foo() { ret", "urn 13; } f", "oo(); ", |
+ NULL}; |
+ |
+ LocalContext env; |
+ v8::Isolate* isolate = env->GetIsolate(); |
+ v8::HandleScope scope(isolate); |
+ |
+ v8::ScriptCompiler::StreamedSource source( |
+ new TestSourceStream(chunks), |
+ v8::ScriptCompiler::StreamedSource::ONE_BYTE); |
+ v8::ScriptCompiler::ScriptStreamingTask* task = |
+ v8::ScriptCompiler::StartStreamingScript( |
+ isolate, &source, v8::ScriptCompiler::kProduceParserCache); |
+ |
+ // TestSourceStream::GetMoreData won't block, so it's OK to just run the |
+ // task here in the main thread. |
+ task->Run(); |
+ delete task; |
+ |
+ const v8::ScriptCompiler::CachedData* cached_data = source.GetCachedData(); |
+ CHECK(cached_data != NULL); |
+ CHECK(cached_data->data != NULL); |
+ CHECK_GT(cached_data->length, 0); |
+} |