| 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);
|
| +}
|
|
|