| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <stddef.h> | 
 | #include <stdint.h> | 
 |  | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/threading/platform_thread.h" | 
 | #include "ppapi/c/pp_var.h" | 
 | #include "ppapi/c/ppb_var.h" | 
 | #include "ppapi/proxy/ppapi_proxy_test.h" | 
 | #include "ppapi/shared_impl/ppb_var_shared.h" | 
 |  | 
 | namespace { | 
 | std::string VarToString(const PP_Var& var, const PPB_Var* ppb_var) { | 
 |   uint32_t len = 0; | 
 |   const char* utf8 = ppb_var->VarToUtf8(var, &len); | 
 |   return std::string(utf8, len); | 
 | } | 
 | const size_t kNumStrings = 100; | 
 | const size_t kNumThreads = 20; | 
 | const int kRefsToAdd = 20; | 
 | }  // namespace | 
 |  | 
 | namespace ppapi { | 
 | namespace proxy { | 
 |  | 
 | class PPB_VarTest : public PluginProxyTest { | 
 |  public: | 
 |   PPB_VarTest() | 
 |       : test_strings_(kNumStrings), vars_(kNumStrings), | 
 |         ppb_var_(ppapi::PPB_Var_Shared::GetVarInterface1_2()) { | 
 |     // Set the value of test_strings_[i] to "i". | 
 |     for (size_t i = 0; i < kNumStrings; ++i) | 
 |       test_strings_[i] = base::IntToString(static_cast<int>(i)); | 
 |   } | 
 |  protected: | 
 |   std::vector<std::string> test_strings_; | 
 |   std::vector<PP_Var> vars_; | 
 |   const PPB_Var* ppb_var_; | 
 | }; | 
 |  | 
 | // Test basic String operations. | 
 | TEST_F(PPB_VarTest, Strings) { | 
 |   for (size_t i = 0; i < kNumStrings; ++i) { | 
 |     vars_[i] = ppb_var_->VarFromUtf8( | 
 |         test_strings_[i].c_str(), | 
 |         static_cast<uint32_t>(test_strings_[i].length())); | 
 |     EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); | 
 |   } | 
 |   // At this point, they should each have a ref count of 1. Add some more. | 
 |   for (int ref = 0; ref < kRefsToAdd; ++ref) { | 
 |     for (size_t i = 0; i < kNumStrings; ++i) { | 
 |       ppb_var_->AddRef(vars_[i]); | 
 |       // Make sure the string is still there with the right value. | 
 |       EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); | 
 |     } | 
 |   } | 
 |   for (int ref = 0; ref < kRefsToAdd; ++ref) { | 
 |     for (size_t i = 0; i < kNumStrings; ++i) { | 
 |       ppb_var_->Release(vars_[i]); | 
 |       // Make sure the string is still there with the right value. | 
 |       EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); | 
 |     } | 
 |   } | 
 |   // Now remove the ref counts for each string and make sure they are gone. | 
 |   for (size_t i = 0; i < kNumStrings; ++i) { | 
 |     ppb_var_->Release(vars_[i]); | 
 |     uint32_t len = 10; | 
 |     const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len); | 
 |     EXPECT_EQ(NULL, utf8); | 
 |     EXPECT_EQ(0u, len); | 
 |   } | 
 | } | 
 |  | 
 | // PPB_VarTest.Threads tests string operations accessed by multiple threads. | 
 | namespace { | 
 | // These three delegate classes which precede the test are for use with | 
 | // PlatformThread. The test goes roughly like this: | 
 | // 1) Spawn kNumThreads 'CreateVar' threads, giving each a roughly equal subset | 
 | //    of test_strings_ to 'create'. Each 'CreateVar' thread also converts its | 
 | //    set of vars back in to strings so that the main test thread can verify | 
 | //    their values were correctly converted. | 
 | // 2) Spawn kNumThreads 'ChangeRefVar' threads. Each of these threads will | 
 | //    incremement & decrement the reference count of ALL vars kRefsToAdd times. | 
 | //    Finally, each thread adds 1 ref count. This leaves each var with a ref- | 
 | //    count of |kNumThreads + 1|. The main test thread removes a ref, leaving | 
 | //    each var with a ref-count of |kNumThreads|. | 
 | // 3) Spawn kNumThreads 'RemoveVar' threads. Each of these threads releases each | 
 | //    var once. Once all the threads have finished, there should be no vars | 
 | //    left. | 
 | class CreateVarThreadDelegate : public base::PlatformThread::Delegate { | 
 |  public: | 
 |   // |strings_in|, |vars|, and |strings_out| are arrays, and size is their size. | 
 |   // For each |strings_in[i]|, we will set |vars[i]| using that value. Then we | 
 |   // read the var back out to |strings_out[i]|. | 
 |   CreateVarThreadDelegate(const std::string* strings_in, | 
 |                           PP_Var* vars_out, | 
 |                           std::string* strings_out, | 
 |                           size_t size) | 
 |       : strings_in_(strings_in), | 
 |         vars_out_(vars_out), | 
 |         strings_out_(strings_out), | 
 |         size_(size) {} | 
 |   virtual ~CreateVarThreadDelegate() {} | 
 |   virtual void ThreadMain() { | 
 |     const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2(); | 
 |     for (size_t i = 0; i < size_; ++i) { | 
 |       vars_out_[i] = ppb_var->VarFromUtf8( | 
 |           strings_in_[i].c_str(), | 
 |           static_cast<uint32_t>(strings_in_[i].length())); | 
 |       strings_out_[i] = VarToString(vars_out_[i], ppb_var); | 
 |     } | 
 |   } | 
 |  private: | 
 |   const std::string* strings_in_; | 
 |   PP_Var* vars_out_; | 
 |   std::string* strings_out_; | 
 |   size_t size_; | 
 | }; | 
 |  | 
 | // A thread that will increment and decrement the reference count of every var | 
 | // multiple times. | 
 | class ChangeRefVarThreadDelegate : public base::PlatformThread::Delegate { | 
 |  public: | 
 |   ChangeRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) { | 
 |   } | 
 |   virtual ~ChangeRefVarThreadDelegate() {} | 
 |   virtual void ThreadMain() { | 
 |     const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2(); | 
 |     // Increment and decrement the reference count for each var kRefsToAdd | 
 |     // times. Note that we always AddRef once before doing the matching Release, | 
 |     // to ensure that we never accidentally release the last reference. | 
 |     for (int ref = 0; ref < kRefsToAdd; ++ref) { | 
 |       for (size_t i = 0; i < kNumStrings; ++i) { | 
 |         ppb_var->AddRef(vars_[i]); | 
 |         ppb_var->Release(vars_[i]); | 
 |       } | 
 |     } | 
 |     // Now add 1 ref to each Var. The net result is that all Vars will have a | 
 |     // ref-count of (kNumThreads + 1) after this. That will allow us to have all | 
 |     // threads release all vars later. | 
 |     for (size_t i = 0; i < kNumStrings; ++i) { | 
 |       ppb_var->AddRef(vars_[i]); | 
 |     } | 
 |   } | 
 |  private: | 
 |   std::vector<PP_Var> vars_; | 
 | }; | 
 |  | 
 | // A thread that will decrement the reference count of every var once. | 
 | class RemoveRefVarThreadDelegate : public base::PlatformThread::Delegate { | 
 |  public: | 
 |   RemoveRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) { | 
 |   } | 
 |   virtual ~RemoveRefVarThreadDelegate() {} | 
 |   virtual void ThreadMain() { | 
 |     const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2(); | 
 |     for (size_t i = 0; i < kNumStrings; ++i) { | 
 |       ppb_var->Release(vars_[i]); | 
 |     } | 
 |   } | 
 |  private: | 
 |   std::vector<PP_Var> vars_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST_F(PPB_VarTest, Threads) { | 
 |   std::vector<base::PlatformThreadHandle> create_var_threads(kNumThreads); | 
 |   std::vector<CreateVarThreadDelegate> create_var_delegates; | 
 |   // The strings that the threads will re-extract from Vars (so we can check | 
 |   // that they match the original strings). | 
 |   std::vector<std::string> strings_out(kNumStrings); | 
 |   size_t strings_per_thread = kNumStrings/kNumThreads; | 
 |   // Give each thread an equal slice of strings to turn in to vars. (Except the | 
 |   // last thread may get fewer if kNumStrings is not evenly divisible by | 
 |   // kNumThreads). | 
 |   for (size_t slice_start= 0; slice_start < kNumStrings; | 
 |        slice_start += strings_per_thread) { | 
 |     create_var_delegates.push_back(CreateVarThreadDelegate( | 
 |         &test_strings_[slice_start], &vars_[slice_start], | 
 |         &strings_out[slice_start], | 
 |         std::min(strings_per_thread, kNumStrings - slice_start))); | 
 |   } | 
 |   // Now run then join all the threads. | 
 |   for (size_t i = 0; i < kNumThreads; ++i) | 
 |     base::PlatformThread::Create(0, &create_var_delegates[i], | 
 |                                  &create_var_threads[i]); | 
 |   for (size_t i = 0; i < kNumThreads; ++i) | 
 |     base::PlatformThread::Join(create_var_threads[i]); | 
 |   // Now check that the strings have the expected values. | 
 |   EXPECT_EQ(test_strings_, strings_out); | 
 |  | 
 |   // Tinker with the reference counts in a multithreaded way. | 
 |   std::vector<base::PlatformThreadHandle> change_ref_var_threads(kNumThreads); | 
 |   std::vector<ChangeRefVarThreadDelegate> change_ref_var_delegates; | 
 |   for (size_t i = 0; i < kNumThreads; ++i) | 
 |     change_ref_var_delegates.push_back(ChangeRefVarThreadDelegate(vars_)); | 
 |   for (size_t i = 0; i < kNumThreads; ++i) { | 
 |     base::PlatformThread::Create(0, &change_ref_var_delegates[i], | 
 |                                  &change_ref_var_threads[i]); | 
 |   } | 
 |   for (size_t i = 0; i < kNumThreads; ++i) | 
 |     base::PlatformThread::Join(change_ref_var_threads[i]); | 
 |  | 
 |   // Now each var has a refcount of (kNumThreads + 1). Let's decrement each var | 
 |   // once so that every 'RemoveRef' thread (spawned below) owns 1 reference, and | 
 |   // when the last one removes a ref, the Var will be deleted. | 
 |   for (size_t i = 0; i < kNumStrings; ++i) { | 
 |     ppb_var_->Release(vars_[i]); | 
 |   } | 
 |  | 
 |   // Check that all vars are still valid and have the values we expect. | 
 |   for (size_t i = 0; i < kNumStrings; ++i) | 
 |     EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); | 
 |  | 
 |   // Remove the last reference counts for all vars. | 
 |   std::vector<base::PlatformThreadHandle> remove_ref_var_threads(kNumThreads); | 
 |   std::vector<RemoveRefVarThreadDelegate> remove_ref_var_delegates; | 
 |   for (size_t i = 0; i < kNumThreads; ++i) | 
 |     remove_ref_var_delegates.push_back(RemoveRefVarThreadDelegate(vars_)); | 
 |   for (size_t i = 0; i < kNumThreads; ++i) { | 
 |     base::PlatformThread::Create(0, &remove_ref_var_delegates[i], | 
 |                                  &remove_ref_var_threads[i]); | 
 |   } | 
 |   for (size_t i = 0; i < kNumThreads; ++i) | 
 |     base::PlatformThread::Join(remove_ref_var_threads[i]); | 
 |  | 
 |   // All the vars should no longer represent valid strings. | 
 |   for (size_t i = 0; i < kNumStrings; ++i) { | 
 |     uint32_t len = 10; | 
 |     const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len); | 
 |     EXPECT_EQ(NULL, utf8); | 
 |     EXPECT_EQ(0u, len); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace proxy | 
 | }  // namespace ppapi |