1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <string>
6#include <vector>
7
8#include "base/strings/string_number_conversions.h"
9#include "base/threading/platform_thread.h"
10#include "ppapi/c/pp_var.h"
11#include "ppapi/c/ppb_var.h"
12#include "ppapi/proxy/ppapi_proxy_test.h"
13#include "ppapi/shared_impl/ppb_var_shared.h"
14
15namespace {
16std::string VarToString(const PP_Var& var, const PPB_Var* ppb_var) {
17  uint32_t len = 0;
18  const char* utf8 = ppb_var->VarToUtf8(var, &len);
19  return std::string(utf8, len);
20}
21const size_t kNumStrings = 100;
22const size_t kNumThreads = 20;
23const int kRefsToAdd = 20;
24}  // namespace
25
26namespace ppapi {
27namespace proxy {
28
29class PPB_VarTest : public PluginProxyTest {
30 public:
31  PPB_VarTest()
32      : test_strings_(kNumStrings), vars_(kNumStrings),
33        ppb_var_(ppapi::PPB_Var_Shared::GetVarInterface1_2()) {
34    // Set the value of test_strings_[i] to "i".
35    for (size_t i = 0; i < kNumStrings; ++i)
36      test_strings_[i] = base::IntToString(i);
37  }
38 protected:
39  std::vector<std::string> test_strings_;
40  std::vector<PP_Var> vars_;
41  const PPB_Var* ppb_var_;
42};
43
44// Test basic String operations.
45TEST_F(PPB_VarTest, Strings) {
46  for (size_t i = 0; i < kNumStrings; ++i) {
47    vars_[i] = ppb_var_->VarFromUtf8(test_strings_[i].c_str(),
48                                     test_strings_[i].length());
49    EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
50  }
51  // At this point, they should each have a ref count of 1. Add some more.
52  for (int ref = 0; ref < kRefsToAdd; ++ref) {
53    for (size_t i = 0; i < kNumStrings; ++i) {
54      ppb_var_->AddRef(vars_[i]);
55      // Make sure the string is still there with the right value.
56      EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
57    }
58  }
59  for (int ref = 0; ref < kRefsToAdd; ++ref) {
60    for (size_t i = 0; i < kNumStrings; ++i) {
61      ppb_var_->Release(vars_[i]);
62      // Make sure the string is still there with the right value.
63      EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
64    }
65  }
66  // Now remove the ref counts for each string and make sure they are gone.
67  for (size_t i = 0; i < kNumStrings; ++i) {
68    ppb_var_->Release(vars_[i]);
69    uint32_t len = 10;
70    const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len);
71    EXPECT_EQ(NULL, utf8);
72    EXPECT_EQ(0u, len);
73  }
74}
75
76// PPB_VarTest.Threads tests string operations accessed by multiple threads.
77namespace {
78// These three delegate classes which precede the test are for use with
79// PlatformThread. The test goes roughly like this:
80// 1) Spawn kNumThreads 'CreateVar' threads, giving each a roughly equal subset
81//    of test_strings_ to 'create'. Each 'CreateVar' thread also converts its
82//    set of vars back in to strings so that the main test thread can verify
83//    their values were correctly converted.
84// 2) Spawn kNumThreads 'ChangeRefVar' threads. Each of these threads will
85//    incremement & decrement the reference count of ALL vars kRefsToAdd times.
86//    Finally, each thread adds 1 ref count. This leaves each var with a ref-
87//    count of |kNumThreads + 1|. The main test thread removes a ref, leaving
88//    each var with a ref-count of |kNumThreads|.
89// 3) Spawn kNumThreads 'RemoveVar' threads. Each of these threads releases each
90//    var once. Once all the threads have finished, there should be no vars
91//    left.
92class CreateVarThreadDelegate : public base::PlatformThread::Delegate {
93 public:
94  // |strings_in|, |vars|, and |strings_out| are arrays, and size is their size.
95  // For each |strings_in[i]|, we will set |vars[i]| using that value. Then we
96  // read the var back out to |strings_out[i]|.
97  CreateVarThreadDelegate(PP_Module pp_module, const std::string* strings_in,
98                          PP_Var* vars_out, std::string* strings_out,
99                          size_t size)
100      : pp_module_(pp_module), strings_in_(strings_in), vars_out_(vars_out),
101        strings_out_(strings_out), size_(size) {
102  }
103  virtual ~CreateVarThreadDelegate() {}
104  virtual void ThreadMain() {
105    const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
106    for (size_t i = 0; i < size_; ++i) {
107      vars_out_[i] = ppb_var->VarFromUtf8(strings_in_[i].c_str(),
108                                          strings_in_[i].length());
109      strings_out_[i] = VarToString(vars_out_[i], ppb_var);
110    }
111  }
112 private:
113  PP_Module pp_module_;
114  const std::string* strings_in_;
115  PP_Var* vars_out_;
116  std::string* strings_out_;
117  size_t size_;
118};
119
120// A thread that will increment and decrement the reference count of every var
121// multiple times.
122class ChangeRefVarThreadDelegate : public base::PlatformThread::Delegate {
123 public:
124  ChangeRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) {
125  }
126  virtual ~ChangeRefVarThreadDelegate() {}
127  virtual void ThreadMain() {
128    const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
129    // Increment and decrement the reference count for each var kRefsToAdd
130    // times. Note that we always AddRef once before doing the matching Release,
131    // to ensure that we never accidentally release the last reference.
132    for (int ref = 0; ref < kRefsToAdd; ++ref) {
133      for (size_t i = 0; i < kNumStrings; ++i) {
134        ppb_var->AddRef(vars_[i]);
135        ppb_var->Release(vars_[i]);
136      }
137    }
138    // Now add 1 ref to each Var. The net result is that all Vars will have a
139    // ref-count of (kNumThreads + 1) after this. That will allow us to have all
140    // threads release all vars later.
141    for (size_t i = 0; i < kNumStrings; ++i) {
142      ppb_var->AddRef(vars_[i]);
143    }
144  }
145 private:
146  std::vector<PP_Var> vars_;
147};
148
149// A thread that will decrement the reference count of every var once.
150class RemoveRefVarThreadDelegate : public base::PlatformThread::Delegate {
151 public:
152  RemoveRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) {
153  }
154  virtual ~RemoveRefVarThreadDelegate() {}
155  virtual void ThreadMain() {
156    const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
157    for (size_t i = 0; i < kNumStrings; ++i) {
158      ppb_var->Release(vars_[i]);
159    }
160  }
161 private:
162  std::vector<PP_Var> vars_;
163};
164
165}  // namespace
166
167TEST_F(PPB_VarTest, Threads) {
168  std::vector<base::PlatformThreadHandle> create_var_threads(kNumThreads);
169  std::vector<CreateVarThreadDelegate> create_var_delegates;
170  // The strings that the threads will re-extract from Vars (so we can check
171  // that they match the original strings).
172  std::vector<std::string> strings_out(kNumStrings);
173  size_t strings_per_thread = kNumStrings/kNumThreads;
174  // Give each thread an equal slice of strings to turn in to vars. (Except the
175  // last thread may get fewer if kNumStrings is not evenly divisible by
176  // kNumThreads).
177  for (size_t slice_start= 0; slice_start < kNumStrings;
178       slice_start += strings_per_thread) {
179    create_var_delegates.push_back(
180        CreateVarThreadDelegate(pp_module(),
181                                &test_strings_[slice_start],
182                                &vars_[slice_start],
183                                &strings_out[slice_start],
184                                std::min(strings_per_thread,
185                                         kNumStrings - slice_start)));
186  }
187  // Now run then join all the threads.
188  for (size_t i = 0; i < kNumThreads; ++i)
189    base::PlatformThread::Create(0, &create_var_delegates[i],
190                                 &create_var_threads[i]);
191  for (size_t i = 0; i < kNumThreads; ++i)
192    base::PlatformThread::Join(create_var_threads[i]);
193  // Now check that the strings have the expected values.
194  EXPECT_EQ(test_strings_, strings_out);
195
196  // Tinker with the reference counts in a multithreaded way.
197  std::vector<base::PlatformThreadHandle> change_ref_var_threads(kNumThreads);
198  std::vector<ChangeRefVarThreadDelegate> change_ref_var_delegates;
199  for (size_t i = 0; i < kNumThreads; ++i)
200    change_ref_var_delegates.push_back(ChangeRefVarThreadDelegate(vars_));
201  for (size_t i = 0; i < kNumThreads; ++i) {
202    base::PlatformThread::Create(0, &change_ref_var_delegates[i],
203                                 &change_ref_var_threads[i]);
204  }
205  for (size_t i = 0; i < kNumThreads; ++i)
206    base::PlatformThread::Join(change_ref_var_threads[i]);
207
208  // Now each var has a refcount of (kNumThreads + 1). Let's decrement each var
209  // once so that every 'RemoveRef' thread (spawned below) owns 1 reference, and
210  // when the last one removes a ref, the Var will be deleted.
211  for (size_t i = 0; i < kNumStrings; ++i) {
212    ppb_var_->Release(vars_[i]);
213  }
214
215  // Check that all vars are still valid and have the values we expect.
216  for (size_t i = 0; i < kNumStrings; ++i)
217    EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
218
219  // Remove the last reference counts for all vars.
220  std::vector<base::PlatformThreadHandle> remove_ref_var_threads(kNumThreads);
221  std::vector<RemoveRefVarThreadDelegate> remove_ref_var_delegates;
222  for (size_t i = 0; i < kNumThreads; ++i)
223    remove_ref_var_delegates.push_back(RemoveRefVarThreadDelegate(vars_));
224  for (size_t i = 0; i < kNumThreads; ++i) {
225    base::PlatformThread::Create(0, &remove_ref_var_delegates[i],
226                                 &remove_ref_var_threads[i]);
227  }
228  for (size_t i = 0; i < kNumThreads; ++i)
229    base::PlatformThread::Join(remove_ref_var_threads[i]);
230
231  // All the vars should no longer represent valid strings.
232  for (size_t i = 0; i < kNumStrings; ++i) {
233    uint32_t len = 10;
234    const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len);
235    EXPECT_EQ(NULL, utf8);
236    EXPECT_EQ(0u, len);
237  }
238}
239
240}  // namespace proxy
241}  // namespace ppapi
242