TensorFlow Serving C++ API Documentation
simple_loader_test.cc
1 /* Copyright 2016 Google Inc. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7  http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #include "tensorflow_serving/core/simple_loader.h"
17 
18 #include <memory>
19 #include <string>
20 #include <vector>
21 
22 #include <gtest/gtest.h>
23 #include "absl/memory/memory.h"
24 #include "tensorflow/core/lib/core/errors.h"
25 #include "tensorflow/core/lib/core/status_test_util.h"
26 #include "tensorflow/core/lib/core/stringpiece.h"
27 #include "tensorflow/core/lib/strings/strcat.h"
28 #include "tensorflow/core/protobuf/error_codes.pb.h"
29 #include "tensorflow_serving/core/servable_data.h"
30 #include "tensorflow_serving/core/servable_id.h"
31 #include "tensorflow_serving/test_util/test_util.h"
32 
33 namespace tensorflow {
34 namespace serving {
35 namespace {
36 
37 using test_util::CreateProto;
38 using test_util::EqualsProto;
39 
40 // State reflects the current state of a Caller object.
41 enum class State {
42  kNone,
43  kCtor,
44  kDoStuff,
45  kDtor,
46 };
47 
48 // Caller updates a handle to State as it is created, used and destroyed.
49 class Caller {
50  public:
51  explicit Caller(State* state) : state_(state) { *state_ = State::kCtor; }
52  void DoStuff() { *state_ = State::kDoStuff; }
53  ~Caller() { *state_ = State::kDtor; }
54 
55  private:
56  State* state_;
57 };
58 
59 Loader::Metadata CreateMetadata() { return {ServableId{"name", 42}}; }
60 
61 class LoaderCreatorWithoutMetadata {
62  public:
63  template <typename ServableType, typename... Args>
64  static std::unique_ptr<Loader> CreateSimpleLoader(
65  typename SimpleLoader<ServableType>::Creator creator, Args... args) {
66  return absl::make_unique<SimpleLoader<ServableType>>(creator, args...);
67  }
68 
69  static Status Load(Loader* loader) { return loader->Load(); }
70 };
71 
72 class LoaderCreatorWithMetadata {
73  public:
74  template <typename ServableType, typename... Args>
75  static std::unique_ptr<Loader> CreateSimpleLoader(
76  typename SimpleLoader<ServableType>::Creator creator, Args... args) {
77  return absl::make_unique<SimpleLoader<ServableType>>(
78  [creator](const Loader::Metadata& metadata,
79  std::unique_ptr<ServableType>* servable) {
80  const auto& expected_metadata = CreateMetadata();
81  EXPECT_EQ(expected_metadata.servable_id, metadata.servable_id);
82  return creator(servable);
83  },
84  args...);
85  }
86 
87  static Status Load(Loader* loader) {
88  return loader->LoadWithMetadata(CreateMetadata());
89  }
90 };
91 
92 template <typename T>
93 class SimpleLoaderTest : public ::testing::Test {};
94 using LoaderCreatorTypes =
95  ::testing::Types<LoaderCreatorWithoutMetadata, LoaderCreatorWithMetadata>;
96 TYPED_TEST_SUITE(SimpleLoaderTest, LoaderCreatorTypes);
97 
98 // Move a Loader through its lifetime and ensure the servable is in the state
99 // we expect.
100 TYPED_TEST(SimpleLoaderTest, VerifyServableStates) {
101  State state = State::kNone;
102  auto loader = TypeParam::template CreateSimpleLoader<Caller>(
103  [&state](std::unique_ptr<Caller>* caller) {
104  caller->reset(new Caller(&state));
105  return OkStatus();
106  },
107  SimpleLoader<Caller>::EstimateNoResources());
108  EXPECT_EQ(State::kNone, state);
109  const Status status = TypeParam::Load(loader.get());
110  TF_EXPECT_OK(status);
111  EXPECT_EQ(State::kCtor, state);
112  AnyPtr servable = loader->servable();
113  ASSERT_TRUE(servable.get<Caller>() != nullptr);
114  servable.get<Caller>()->DoStuff();
115  EXPECT_EQ(State::kDoStuff, state);
116  loader->Unload();
117  EXPECT_EQ(State::kDtor, state);
118  state = State::kNone;
119  loader.reset(nullptr);
120  EXPECT_EQ(State::kNone, state);
121 }
122 
123 TYPED_TEST(SimpleLoaderTest, ResourceEstimation) {
124  const auto want = CreateProto<ResourceAllocation>(
125  "resource_quantities { "
126  " resource { "
127  " device: 'main' "
128  " kind: 'processing' "
129  " } "
130  " quantity: 42 "
131  "} ");
132  auto loader = TypeParam::template CreateSimpleLoader<int>(
133  [](std::unique_ptr<int>* servable) {
134  servable->reset(new int);
135  return OkStatus();
136  },
137  [&want](ResourceAllocation* estimate) {
138  *estimate = want;
139  return OkStatus();
140  });
141 
142  {
143  ResourceAllocation got;
144  TF_ASSERT_OK(loader->EstimateResources(&got));
145  EXPECT_THAT(got, EqualsProto(want));
146  }
147 
148  // The estimate should remain the same after load.
149  TF_ASSERT_OK(TypeParam::Load(loader.get()));
150  {
151  ResourceAllocation got;
152  TF_ASSERT_OK(loader->EstimateResources(&got));
153  EXPECT_THAT(got, EqualsProto(want));
154  }
155 }
156 
157 TYPED_TEST(SimpleLoaderTest, ResourceEstimationWithPostLoadRelease) {
158  const auto pre_load_resources = CreateProto<ResourceAllocation>(
159  "resource_quantities { "
160  " resource { "
161  " device: 'main' "
162  " kind: 'processing' "
163  " } "
164  " quantity: 42 "
165  "} ");
166  const auto post_load_resources = CreateProto<ResourceAllocation>(
167  "resource_quantities { "
168  " resource { "
169  " device: 'main' "
170  " kind: 'processing' "
171  " } "
172  " quantity: 17 "
173  "} ");
174  auto loader = TypeParam::template CreateSimpleLoader<int>(
175  [](std::unique_ptr<int>* servable) {
176  servable->reset(new int);
177  return OkStatus();
178  },
179  [&pre_load_resources](ResourceAllocation* estimate) {
180  *estimate = pre_load_resources;
181  return OkStatus();
182  },
183  absl::make_optional([&post_load_resources](ResourceAllocation* estimate) {
184  *estimate = post_load_resources;
185  return OkStatus();
186  }));
187 
188  // Run it twice, to exercise memoization.
189  for (int i = 0; i < 2; ++i) {
190  ResourceAllocation got;
191  TF_ASSERT_OK(loader->EstimateResources(&got));
192  EXPECT_THAT(got, EqualsProto(pre_load_resources));
193  }
194 
195  // The estimate should switch to the post-load one after load.
196  TF_ASSERT_OK(TypeParam::Load(loader.get()));
197  {
198  ResourceAllocation got;
199  TF_ASSERT_OK(loader->EstimateResources(&got));
200  EXPECT_THAT(got, EqualsProto(post_load_resources));
201  }
202 }
203 
204 // Verify that the error returned by the Creator is propagates back through
205 // Load.
206 TYPED_TEST(SimpleLoaderTest, LoadError) {
207  auto loader = TypeParam::template CreateSimpleLoader<Caller>(
208  [](std::unique_ptr<Caller>* caller) {
209  return errors::InvalidArgument("No way!");
210  },
211  SimpleLoader<Caller>::EstimateNoResources());
212  const Status status = TypeParam::Load(loader.get());
213  EXPECT_EQ(error::INVALID_ARGUMENT, status.code());
214  EXPECT_EQ("No way!", status.message());
215 }
216 
217 TEST(SimpleLoaderCompatibilityTest, WithoutMetadata) {
218  auto loader_without_metadata = absl::make_unique<SimpleLoader<int>>(
219  [](std::unique_ptr<int>* servable) {
220  servable->reset(new int);
221  return OkStatus();
222  },
223  SimpleLoader<int>::EstimateNoResources());
224  // If the creator without metadata is used, both Load() and LoadWithMetadata()
225  // are fine, for compatibility.
226  TF_EXPECT_OK(loader_without_metadata->Load());
227  TF_EXPECT_OK(loader_without_metadata->LoadWithMetadata(CreateMetadata()));
228 }
229 
230 TEST(SimpleLoaderCompatibilityTest, WithMetadata) {
231  auto loader_with_metadata = absl::make_unique<SimpleLoader<int>>(
232  [](const Loader::Metadata& metadata, std::unique_ptr<int>* servable) {
233  const auto& expected_metadata = CreateMetadata();
234  EXPECT_EQ(expected_metadata.servable_id, metadata.servable_id);
235  servable->reset(new int);
236  return OkStatus();
237  },
238  SimpleLoader<int>::EstimateNoResources());
239  // If the creator with metadata is used, we allow only LoadWithMetadata()
240  // to be invoked.
241  const Status error_status = loader_with_metadata->Load();
242  EXPECT_EQ(error::FAILED_PRECONDITION, error_status.code());
243  TF_EXPECT_OK(loader_with_metadata->LoadWithMetadata(CreateMetadata()));
244 }
245 
246 // A pass-through implementation of SimpleLoaderSourceAdapter, which can be
247 // instantiated.
248 template <typename DataType, typename ServableType>
249 class SimpleLoaderSourceAdapterImpl final
250  : public SimpleLoaderSourceAdapter<DataType, ServableType> {
251  public:
252  SimpleLoaderSourceAdapterImpl(
253  typename SimpleLoaderSourceAdapter<DataType, ServableType>::Creator
254  creator,
255  typename SimpleLoaderSourceAdapter<
256  DataType, ServableType>::ResourceEstimator resource_estimator)
257  : SimpleLoaderSourceAdapter<DataType, ServableType>(creator,
258  resource_estimator) {}
259  ~SimpleLoaderSourceAdapterImpl() override { TargetBase<DataType>::Detach(); }
260 };
261 
262 TEST(SimpleLoaderSourceAdapterTest, Basic) {
263  SimpleLoaderSourceAdapterImpl<string, string> adapter(
264  [](const string& data, std::unique_ptr<string>* servable) {
265  servable->reset(new string);
266  **servable = strings::StrCat(data, "_was_here");
267  return OkStatus();
268  },
269  [](const string& data, ResourceAllocation* output) {
270  ResourceAllocation::Entry* entry = output->add_resource_quantities();
271  entry->mutable_resource()->set_device(data);
272  entry->set_quantity(42);
273  return OkStatus();
274  });
275 
276  const string kServableName = "test_servable_name";
277  bool callback_called;
278  adapter.SetAspiredVersionsCallback(
279  [&](const StringPiece servable_name,
280  std::vector<ServableData<std::unique_ptr<Loader>>> versions) {
281  callback_called = true;
282  EXPECT_EQ(kServableName, servable_name);
283  EXPECT_EQ(1, versions.size());
284  TF_ASSERT_OK(versions[0].status());
285  std::unique_ptr<Loader> loader = versions[0].ConsumeDataOrDie();
286  ResourceAllocation estimate_given;
287  TF_ASSERT_OK(loader->EstimateResources(&estimate_given));
288  EXPECT_THAT(estimate_given, EqualsProto(CreateProto<ResourceAllocation>(
289  "resource_quantities { "
290  " resource { "
291  " device: 'test_data' "
292  " } "
293  " quantity: 42 "
294  "} ")));
295  TF_ASSERT_OK(loader->Load());
296  AnyPtr servable = loader->servable();
297  ASSERT_TRUE(servable.get<string>() != nullptr);
298  EXPECT_EQ("test_data_was_here", *servable.get<string>());
299  });
300  adapter.SetAspiredVersions(
301  kServableName, {ServableData<string>({kServableName, 0}, "test_data")});
302  EXPECT_TRUE(callback_called);
303 }
304 
305 // This test verifies that deleting a SimpleLoaderSourceAdapter doesn't affect
306 // the loaders it has emitted. This is a regression test for b/30189916.
307 TEST(SimpleLoaderSourceAdapterTest, OkayToDeleteAdapter) {
308  std::unique_ptr<Loader> loader;
309  {
310  // Allocate 'adapter' on the heap so ASAN will catch a use-after-free.
311  auto adapter = std::unique_ptr<SimpleLoaderSourceAdapter<string, string>>(
312  new SimpleLoaderSourceAdapterImpl<string, string>(
313  [](const string& data, std::unique_ptr<string>* servable) {
314  servable->reset(new string);
315  **servable = strings::StrCat(data, "_was_here");
316  return OkStatus();
317  },
318  SimpleLoaderSourceAdapter<string, string>::EstimateNoResources()));
319 
320  const string kServableName = "test_servable_name";
321  adapter->SetAspiredVersionsCallback(
322  [&](const StringPiece servable_name,
323  std::vector<ServableData<std::unique_ptr<Loader>>> versions) {
324  ASSERT_EQ(1, versions.size());
325  TF_ASSERT_OK(versions[0].status());
326  loader = versions[0].ConsumeDataOrDie();
327  });
328  adapter->SetAspiredVersions(
329  kServableName, {ServableData<string>({kServableName, 0}, "test_data")});
330 
331  // Let 'adapter' fall out of scope and be deleted.
332  }
333 
334  // We should be able to invoke the resource-estimation and servable-creation
335  // callbacks, despite the fact that 'adapter' has been deleted.
336  ResourceAllocation estimate_given;
337  TF_ASSERT_OK(loader->EstimateResources(&estimate_given));
338  TF_ASSERT_OK(loader->Load());
339 }
340 
341 } // namespace
342 } // namespace serving
343 } // namespace tensorflow