Index: test/cctest/test-api.cc |
=================================================================== |
--- test/cctest/test-api.cc (revision 3132) |
+++ test/cctest/test-api.cc (working copy) |
@@ -38,8 +38,6 @@ |
#include "utils.h" |
#include "cctest.h" |
-static const bool kLogThreading = false; |
- |
static bool IsNaN(double x) { |
#ifdef WIN32 |
return _isnan(x); |
@@ -60,7 +58,132 @@ |
namespace i = ::v8::internal; |
+static Local<Value> v8_num(double x) { |
+ return v8::Number::New(x); |
+} |
+ |
+static Local<String> v8_str(const char* x) { |
+ return String::New(x); |
+} |
+ |
+ |
+static Local<Script> v8_compile(const char* x) { |
+ return Script::Compile(v8_str(x)); |
+} |
+ |
+ |
+// A LocalContext holds a reference to a v8::Context. |
+class LocalContext { |
+ public: |
+ LocalContext(v8::ExtensionConfiguration* extensions = 0, |
+ v8::Handle<ObjectTemplate> global_template = |
+ v8::Handle<ObjectTemplate>(), |
+ v8::Handle<Value> global_object = v8::Handle<Value>()) |
+ : context_(Context::New(extensions, global_template, global_object)) { |
+ context_->Enter(); |
+ } |
+ |
+ virtual ~LocalContext() { |
+ context_->Exit(); |
+ context_.Dispose(); |
+ } |
+ |
+ Context* operator->() { return *context_; } |
+ Context* operator*() { return *context_; } |
+ Local<Context> local() { return Local<Context>::New(context_); } |
+ bool IsReady() { return !context_.IsEmpty(); } |
+ |
+ private: |
+ v8::Persistent<Context> context_; |
+}; |
+ |
+ |
+// Switches between all the Api tests using the threading support. |
+// In order to get a surprising but repeatable pattern of thread |
+// switching it has extra semaphores to control the order in which |
+// the tests alternate, not relying solely on the big V8 lock. |
+// |
+// A test is augmented with calls to ApiTestFuzzer::Fuzz() in its |
+// callbacks. This will have no effect when we are not running the |
+// thread fuzzing test. In the thread fuzzing test it will |
+// pseudorandomly select a successor thread and switch execution |
+// to that thread, suspending the current test. |
+class ApiTestFuzzer: public v8::internal::Thread { |
+ public: |
+ void CallTest(); |
+ explicit ApiTestFuzzer(int num) |
+ : test_number_(num), |
+ gate_(v8::internal::OS::CreateSemaphore(0)), |
+ active_(true) { |
+ } |
+ ~ApiTestFuzzer() { delete gate_; } |
+ |
+ // The ApiTestFuzzer is also a Thread, so it has a Run method. |
+ virtual void Run(); |
+ |
+ enum PartOfTest { FIRST_PART, SECOND_PART }; |
+ |
+ static void Setup(PartOfTest part); |
+ static void RunAllTests(); |
+ static void TearDown(); |
+ // This method switches threads if we are running the Threading test. |
+ // Otherwise it does nothing. |
+ static void Fuzz(); |
+ private: |
+ static bool fuzzing_; |
+ static int tests_being_run_; |
+ static int current_; |
+ static int active_tests_; |
+ static bool NextThread(); |
+ int test_number_; |
+ v8::internal::Semaphore* gate_; |
+ bool active_; |
+ void ContextSwitch(); |
+ static int GetNextTestNumber(); |
+ static v8::internal::Semaphore* all_tests_done_; |
+}; |
+ |
+ |
+#define THREADED_TEST(Name) \ |
+ static void Test##Name(); \ |
+ RegisterThreadedTest register_##Name(Test##Name); \ |
+ /* */ TEST(Name) |
+ |
+ |
+class RegisterThreadedTest { |
+ public: |
+ explicit RegisterThreadedTest(CcTest::TestFunction* callback) |
+ : fuzzer_(NULL), callback_(callback) { |
+ prev_ = first_; |
+ first_ = this; |
+ count_++; |
+ } |
+ static int count() { return count_; } |
+ static RegisterThreadedTest* nth(int i) { |
+ CHECK(i < count()); |
+ RegisterThreadedTest* current = first_; |
+ while (i > 0) { |
+ i--; |
+ current = current->prev_; |
+ } |
+ return current; |
+ } |
+ CcTest::TestFunction* callback() { return callback_; } |
+ ApiTestFuzzer* fuzzer_; |
+ |
+ private: |
+ static RegisterThreadedTest* first_; |
+ static int count_; |
+ CcTest::TestFunction* callback_; |
+ RegisterThreadedTest* prev_; |
+}; |
+ |
+ |
+RegisterThreadedTest *RegisterThreadedTest::first_ = NULL; |
+int RegisterThreadedTest::count_ = 0; |
+ |
+ |
static int signature_callback_count; |
static v8::Handle<Value> IncrementingSignatureCallback( |
const v8::Arguments& args) { |
@@ -108,6 +231,11 @@ |
} |
+// Helper function that compiles and runs the source. |
+static Local<Value> CompileRun(const char* source) { |
+ return Script::Compile(String::New(source))->Run(); |
+} |
+ |
THREADED_TEST(ReceiverSignature) { |
v8::HandleScope scope; |
LocalContext env; |
@@ -592,6 +720,27 @@ |
} |
+static v8::Handle<Value> handle_property(Local<String> name, |
+ const AccessorInfo&) { |
+ ApiTestFuzzer::Fuzz(); |
+ return v8_num(900); |
+} |
+ |
+ |
+THREADED_TEST(PropertyHandler) { |
+ v8::HandleScope scope; |
+ Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(); |
+ fun_templ->InstanceTemplate()->SetAccessor(v8_str("foo"), handle_property); |
+ LocalContext env; |
+ Local<Function> fun = fun_templ->GetFunction(); |
+ env->Global()->Set(v8_str("Fun"), fun); |
+ Local<Script> getter = v8_compile("var obj = new Fun(); obj.foo;"); |
+ CHECK_EQ(900, getter->Run()->Int32Value()); |
+ Local<Script> setter = v8_compile("obj.foo = 901;"); |
+ CHECK_EQ(901, setter->Run()->Int32Value()); |
+} |
+ |
+ |
THREADED_TEST(TinyInteger) { |
v8::HandleScope scope; |
LocalContext env; |
@@ -758,6 +907,49 @@ |
} |
+static v8::Handle<Value> GetIntValue(Local<String> property, |
+ const AccessorInfo& info) { |
+ ApiTestFuzzer::Fuzz(); |
+ int* value = |
+ static_cast<int*>(v8::Handle<v8::External>::Cast(info.Data())->Value()); |
+ return v8_num(*value); |
+} |
+ |
+static void SetIntValue(Local<String> property, |
+ Local<Value> value, |
+ const AccessorInfo& info) { |
+ int* field = |
+ static_cast<int*>(v8::Handle<v8::External>::Cast(info.Data())->Value()); |
+ *field = value->Int32Value(); |
+} |
+ |
+int foo, bar, baz; |
+ |
+THREADED_TEST(GlobalVariableAccess) { |
+ foo = 0; |
+ bar = -4; |
+ baz = 10; |
+ v8::HandleScope scope; |
+ v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); |
+ templ->InstanceTemplate()->SetAccessor(v8_str("foo"), |
+ GetIntValue, |
+ SetIntValue, |
+ v8::External::New(&foo)); |
+ templ->InstanceTemplate()->SetAccessor(v8_str("bar"), |
+ GetIntValue, |
+ SetIntValue, |
+ v8::External::New(&bar)); |
+ templ->InstanceTemplate()->SetAccessor(v8_str("baz"), |
+ GetIntValue, |
+ SetIntValue, |
+ v8::External::New(&baz)); |
+ LocalContext env(0, templ->InstanceTemplate()); |
+ v8_compile("foo = (++bar) + baz")->Run(); |
+ CHECK_EQ(bar, -3); |
+ CHECK_EQ(foo, 7); |
+} |
+ |
+ |
THREADED_TEST(ObjectTemplate) { |
v8::HandleScope scope; |
Local<ObjectTemplate> templ1 = ObjectTemplate::New(); |
@@ -1173,6 +1365,50 @@ |
} |
+static v8::Handle<Value> ThrowingGetAccessor(Local<String> name, |
+ const AccessorInfo& info) { |
+ ApiTestFuzzer::Fuzz(); |
+ return v8::ThrowException(v8_str("g")); |
+} |
+ |
+ |
+static void ThrowingSetAccessor(Local<String> name, |
+ Local<Value> value, |
+ const AccessorInfo& info) { |
+ v8::ThrowException(value); |
+} |
+ |
+ |
+THREADED_TEST(Regress1054726) { |
+ v8::HandleScope scope; |
+ v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); |
+ obj->SetAccessor(v8_str("x"), |
+ ThrowingGetAccessor, |
+ ThrowingSetAccessor, |
+ Local<Value>()); |
+ |
+ LocalContext env; |
+ env->Global()->Set(v8_str("obj"), obj->NewInstance()); |
+ |
+ // Use the throwing property setter/getter in a loop to force |
+ // the accessor ICs to be initialized. |
+ v8::Handle<Value> result; |
+ result = Script::Compile(v8_str( |
+ "var result = '';" |
+ "for (var i = 0; i < 5; i++) {" |
+ " try { obj.x; } catch (e) { result += e; }" |
+ "}; result"))->Run(); |
+ CHECK_EQ(v8_str("ggggg"), result); |
+ |
+ result = Script::Compile(String::New( |
+ "var result = '';" |
+ "for (var i = 0; i < 5; i++) {" |
+ " try { obj.x = i; } catch (e) { result += e; }" |
+ "}; result"))->Run(); |
+ CHECK_EQ(v8_str("01234"), result); |
+} |
+ |
+ |
THREADED_TEST(FunctionPrototype) { |
v8::HandleScope scope; |
Local<v8::FunctionTemplate> Foo = v8::FunctionTemplate::New(); |
@@ -2948,6 +3184,53 @@ |
} |
+static int x_register = 0; |
+static v8::Handle<v8::Object> x_receiver; |
+static v8::Handle<v8::Object> x_holder; |
+ |
+ |
+static v8::Handle<Value> XGetter(Local<String> name, const AccessorInfo& info) { |
+ ApiTestFuzzer::Fuzz(); |
+ CHECK_EQ(x_receiver, info.This()); |
+ CHECK_EQ(x_holder, info.Holder()); |
+ return v8_num(x_register); |
+} |
+ |
+ |
+static void XSetter(Local<String> name, |
+ Local<Value> value, |
+ const AccessorInfo& info) { |
+ CHECK_EQ(x_holder, info.This()); |
+ CHECK_EQ(x_holder, info.Holder()); |
+ x_register = value->Int32Value(); |
+} |
+ |
+ |
+THREADED_TEST(AccessorIC) { |
+ v8::HandleScope scope; |
+ v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); |
+ obj->SetAccessor(v8_str("x"), XGetter, XSetter); |
+ LocalContext context; |
+ x_holder = obj->NewInstance(); |
+ context->Global()->Set(v8_str("holder"), x_holder); |
+ x_receiver = v8::Object::New(); |
+ context->Global()->Set(v8_str("obj"), x_receiver); |
+ v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(CompileRun( |
+ "obj.__proto__ = holder;" |
+ "var result = [];" |
+ "for (var i = 0; i < 10; i++) {" |
+ " holder.x = i;" |
+ " result.push(obj.x);" |
+ "}" |
+ "result")); |
+ CHECK_EQ(10, array->Length()); |
+ for (int i = 0; i < 10; i++) { |
+ v8::Handle<Value> entry = array->Get(v8::Integer::New(i)); |
+ CHECK_EQ(v8::Integer::New(i), entry); |
+ } |
+} |
+ |
+ |
static v8::Handle<Value> NoBlockGetterX(Local<String> name, |
const AccessorInfo&) { |
return v8::Handle<Value>(); |
@@ -5811,17 +6094,13 @@ |
// not start immediately. |
bool ApiTestFuzzer::NextThread() { |
int test_position = GetNextTestNumber(); |
- const char* test_name = RegisterThreadedTest::nth(current_)->name(); |
+ int test_number = RegisterThreadedTest::nth(current_)->fuzzer_->test_number_; |
if (test_position == current_) { |
- if (kLogThreading) |
- printf("Stay with %s\n", test_name); |
+ printf("Stay with %d\n", test_number); |
return false; |
} |
- if (kLogThreading) { |
- printf("Switch from %s to %s\n", |
- test_name, |
- RegisterThreadedTest::nth(test_position)->name()); |
- } |
+ printf("Switch from %d to %d\n", |
+ current_ < 0 ? 0 : test_number, test_position < 0 ? 0 : test_number); |
current_ = test_position; |
RegisterThreadedTest::nth(current_)->fuzzer_->gate_->Signal(); |
return true; |
@@ -5930,11 +6209,9 @@ |
void ApiTestFuzzer::CallTest() { |
- if (kLogThreading) |
- printf("Start test %d\n", test_number_); |
+ printf("Start test %d\n", test_number_); |
CallTestNumber(test_number_); |
- if (kLogThreading) |
- printf("End test %d\n", test_number_); |
+ printf("End test %d\n", test_number_); |
} |
@@ -6422,6 +6699,53 @@ |
} |
+static v8::Handle<Value> AccessorProhibitsOverwritingGetter( |
+ Local<String> name, |
+ const AccessorInfo& info) { |
+ ApiTestFuzzer::Fuzz(); |
+ return v8::True(); |
+} |
+ |
+ |
+THREADED_TEST(AccessorProhibitsOverwriting) { |
+ v8::HandleScope scope; |
+ LocalContext context; |
+ Local<ObjectTemplate> templ = ObjectTemplate::New(); |
+ templ->SetAccessor(v8_str("x"), |
+ AccessorProhibitsOverwritingGetter, |
+ 0, |
+ v8::Handle<Value>(), |
+ v8::PROHIBITS_OVERWRITING, |
+ v8::ReadOnly); |
+ Local<v8::Object> instance = templ->NewInstance(); |
+ context->Global()->Set(v8_str("obj"), instance); |
+ Local<Value> value = CompileRun( |
+ "obj.__defineGetter__('x', function() { return false; });" |
+ "obj.x"); |
+ CHECK(value->BooleanValue()); |
+ value = CompileRun( |
+ "var setter_called = false;" |
+ "obj.__defineSetter__('x', function() { setter_called = true; });" |
+ "obj.x = 42;" |
+ "setter_called"); |
+ CHECK(!value->BooleanValue()); |
+ value = CompileRun( |
+ "obj2 = {};" |
+ "obj2.__proto__ = obj;" |
+ "obj2.__defineGetter__('x', function() { return false; });" |
+ "obj2.x"); |
+ CHECK(value->BooleanValue()); |
+ value = CompileRun( |
+ "var setter_called = false;" |
+ "obj2 = {};" |
+ "obj2.__proto__ = obj;" |
+ "obj2.__defineSetter__('x', function() { setter_called = true; });" |
+ "obj2.x = 42;" |
+ "setter_called"); |
+ CHECK(!value->BooleanValue()); |
+} |
+ |
+ |
static bool NamedSetAccessBlocker(Local<v8::Object> obj, |
Local<Value> name, |
v8::AccessType type, |