| // 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 |