TensorFlow Serving C++ API Documentation
caching_manager_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/caching_manager.h"
17 
18 #include <map>
19 #include <memory>
20 #include <utility>
21 #include <vector>
22 
23 #include <gmock/gmock.h>
24 #include <gtest/gtest.h>
25 #include "tensorflow/core/lib/core/errors.h"
26 #include "tensorflow/core/lib/core/status.h"
27 #include "tensorflow/core/lib/core/status_test_util.h"
28 #include "tensorflow/core/lib/strings/strcat.h"
29 #include "tensorflow/core/platform/env.h"
30 #include "tensorflow_serving/core/servable_data.h"
31 #include "tensorflow_serving/core/servable_handle.h"
32 #include "tensorflow_serving/core/servable_id.h"
33 #include "tensorflow_serving/core/servable_state.h"
34 #include "tensorflow_serving/core/servable_state_monitor.h"
35 #include "tensorflow_serving/core/simple_loader.h"
36 #include "tensorflow_serving/core/test_util/fake_loader_source_adapter.h"
37 #include "tensorflow_serving/core/test_util/manager_test_util.h"
38 #include "tensorflow_serving/util/event_bus.h"
39 #include "tensorflow_serving/util/threadpool_executor.h"
40 
41 namespace tensorflow {
42 namespace serving {
43 namespace {
44 
45 using ::testing::HasSubstr;
46 using ::testing::UnorderedElementsAreArray;
47 
48 // A simple loader-factory that concatenates requested servable name and
49 // version.
50 class StringLoaderFactory : public CachingManager::LoaderFactory {
51  public:
52  explicit StringLoaderFactory(const int64_t starting_version)
53  : latest_version_(starting_version) {}
54 
55  ~StringLoaderFactory() override = default;
56 
57  ServableData<std::unique_ptr<Loader>> CreateLoader(
58  const ServableId& id) override {
59  // Update state to indicate a new loader was created.
60  {
61  mutex_lock l(mu_);
62  num_loaders_dispensed_++;
63  }
64 
65  auto servable_creator = [&](std::unique_ptr<string>* servable) {
66  servable->reset(new string);
67  **servable = strings::StrCat(id.name, "-", id.version);
68  return OkStatus();
69  };
70  std::unique_ptr<Loader> loader;
71  loader.reset(new SimpleLoader<string>(
72  servable_creator, SimpleLoader<string>::EstimateNoResources()));
73  return ServableData<std::unique_ptr<Loader>>(id, std::move(loader));
74  }
75 
76  // Returns the earliest/latest version corresponding to the servable name.
77  int64_t GetServableVersion(
78  const string& request_name,
79  ServableRequest::AutoVersionPolicy policy) const override {
80  mutex_lock l(mu_);
81  switch (policy) {
82  case ServableRequest::AutoVersionPolicy::kEarliest:
83  return earliest_version_;
84  case ServableRequest::AutoVersionPolicy::kLatest:
85  return latest_version_;
86  }
87  }
88 
89  // Update the earliest available version.
90  void set_earliest_version(int64_t version) {
91  mutex_lock l(mu_);
92  earliest_version_ = version;
93  }
94 
95  // Update the latest available version.
96  void set_latest_version(int64_t version) {
97  mutex_lock l(mu_);
98  latest_version_ = version;
99  }
100 
101  // Returns the number of loaders created by the loader-factory.
102  int64_t num_loaders_dispensed() const {
103  mutex_lock l(mu_);
104  return num_loaders_dispensed_;
105  }
106 
107  private:
108  // Used to protect updates to 'earliest_version_' and 'latest_version_'.
109  mutable mutex mu_;
110 
111  // The current earliest version.
112  int64_t earliest_version_ TF_GUARDED_BY(mu_) = 0;
113 
114  // The current latest version.
115  int64_t latest_version_ TF_GUARDED_BY(mu_) = 0;
116 
117  // Tracks the number of loaders dispensed by the loader-factory.
118  int64_t num_loaders_dispensed_ TF_GUARDED_BY(mu_) = 0;
119 
120  TF_DISALLOW_COPY_AND_ASSIGN(StringLoaderFactory);
121 };
122 
123 // A simple loader-factory that always returns a loader with an error for every
124 // request.
125 class ErrorLoaderFactory : public CachingManager::LoaderFactory {
126  public:
127  ErrorLoaderFactory() = default;
128  ~ErrorLoaderFactory() override = default;
129 
130  ServableData<std::unique_ptr<Loader>> CreateLoader(
131  const ServableId& id) override {
132  auto servable_creator = [&](std::unique_ptr<string>* servable) {
133  return errors::Unknown("error loader-factory");
134  };
135  std::unique_ptr<Loader> loader;
136  loader.reset(new SimpleLoader<string>(
137  servable_creator, SimpleLoader<string>::EstimateNoResources()));
138  return ServableData<std::unique_ptr<Loader>>(id, std::move(loader));
139  }
140 
141  int64_t GetServableVersion(
142  const string& request_name,
143  ServableRequest::AutoVersionPolicy policy) const override {
144  // A simple policy interpretation that always returns version 42.
145  return 42;
146  }
147 
148  private:
149  TF_DISALLOW_COPY_AND_ASSIGN(ErrorLoaderFactory);
150 };
151 
152 // Commonly used servable names.
153 constexpr char kServableName[] = "kServableName";
154 constexpr char kServableName2[] = "kServableName2";
155 
156 constexpr int kNumThreads = 10;
157 
158 // We parameterize this test with the number of load & unload threads. (Zero
159 // means use an in-line executor instead of a thread pool.)
160 struct ThreadPoolSizes {
161  uint64_t num_load_threads;
162  uint64_t num_unload_threads;
163 };
164 class CachingManagerTest : public ::testing::TestWithParam<ThreadPoolSizes> {
165  protected:
166  CachingManagerTest()
167  : servable_event_bus_(EventBus<ServableState>::CreateEventBus()),
168  servable_state_monitor_(servable_event_bus_.get()) {
169  CachingManager::Options options;
170  options.env = Env::Default();
171  options.servable_event_bus = servable_event_bus_.get();
172  options.num_load_threads = GetParam().num_load_threads;
173  options.num_unload_threads = GetParam().num_unload_threads;
174  options.max_num_load_retries = 1;
175  options.load_retry_interval_micros = 0;
176 
177  std::unique_ptr<StringLoaderFactory> string_loader_factory;
178  string_loader_factory.reset(new StringLoaderFactory(0));
179  string_loader_factory_ = string_loader_factory.get();
180 
181  TF_CHECK_OK(CachingManager::Create(
182  std::move(options), std::move(string_loader_factory), &manager_));
183  }
184 
185  // Creates a manager with a loader-factory that generates errors for all
186  // requests. This is to simplify testing for cases related to erroneous
187  // handles.
188  std::unique_ptr<CachingManager> CreateManagerWithErrorLoaderFactory() {
189  CachingManager::Options options;
190  options.env = Env::Default();
191  options.servable_event_bus = servable_event_bus_.get();
192  options.num_load_threads = GetParam().num_load_threads;
193  options.num_unload_threads = GetParam().num_unload_threads;
194  options.max_num_load_retries = 1;
195  options.load_retry_interval_micros = 0;
196 
197  std::unique_ptr<ErrorLoaderFactory> error_loader_factory;
198  error_loader_factory.reset(new ErrorLoaderFactory);
199 
200  std::unique_ptr<CachingManager> error_manager;
201  TF_CHECK_OK(CachingManager::Create(
202  std::move(options), std::move(error_loader_factory), &error_manager));
203  return error_manager;
204  }
205 
206  // Helper function to return the size of the load-mutex map from the
207  // caching-manager.
208  int64_t GetLoadMutexMapSize() {
209  return test_util::CachingManagerTestAccess(manager_.get())
210  .GetLoadMutexMapSize();
211  }
212 
213  std::shared_ptr<EventBus<ServableState>> servable_event_bus_;
214  ServableStateMonitor servable_state_monitor_;
215  std::unique_ptr<CachingManager> manager_;
216  StringLoaderFactory* string_loader_factory_;
217 };
218 
219 INSTANTIATE_TEST_CASE_P(
220  WithOrWithoutThreadPools, CachingManagerTest,
221  ::testing::Values(
222  ThreadPoolSizes{0, 0} /* without load or unload threadpools */,
223  ThreadPoolSizes{4, 4} /* with load and unload threadpools */));
224 
226 // Servable handles.
227 
228 TEST_P(CachingManagerTest, ServableHandleSingleRequest) {
229  // Single request for a servable handle.
230  const ServableId id = {kServableName, 30};
231  ServableHandle<string> handle;
232  TF_ASSERT_OK(
233  manager_->GetServableHandle(ServableRequest::FromId(id), &handle));
234  EXPECT_EQ("kServableName-30", *handle);
235  EXPECT_EQ(id, handle.id());
236 }
237 
238 TEST_P(CachingManagerTest, ServableHandleMultipleRequests) {
239  // Multiple requests in sequence to different servable handles.
240  // Scoped to destruct handles at the end of it.
241  {
242  // Request with servable name and version.
243  const ServableId id = {kServableName, 30};
244  ServableHandle<string> handle;
245  TF_ASSERT_OK(
246  manager_->GetServableHandle(ServableRequest::FromId(id), &handle));
247  EXPECT_EQ("kServableName-30", *handle);
248  EXPECT_EQ(id, handle.id());
249  }
250  {
251  // Request with the same servable name and a higher version.
252  const ServableId id = {kServableName, 31};
253  ServableHandle<string> handle;
254  TF_ASSERT_OK(
255  manager_->GetServableHandle(ServableRequest::FromId(id), &handle));
256  EXPECT_EQ("kServableName-31", *handle);
257  EXPECT_EQ(id, handle.id());
258  }
259 }
260 
261 // Tests functionality when the version corresponding to the "earliest" needs to
262 // be newly managed and loaded by the manager.
263 TEST_P(CachingManagerTest, ServableHandleSingleRequestEarliest) {
264  string_loader_factory_->set_earliest_version(30);
265  ServableHandle<string> handle;
266  TF_ASSERT_OK(manager_->GetServableHandle(
267  ServableRequest::Earliest({kServableName}), &handle));
268  EXPECT_EQ("kServableName-30", *handle);
269  const ServableId id = {kServableName, 30};
270  EXPECT_EQ(id, handle.id());
271 }
272 
273 // Tests functionality when the version corresponding to the "latest" needs to
274 // be newly managed and loaded by the manager.
275 TEST_P(CachingManagerTest, ServableHandleSingleRequestLatest) {
276  string_loader_factory_->set_latest_version(30);
277  ServableHandle<string> handle;
278  TF_ASSERT_OK(manager_->GetServableHandle(
279  ServableRequest::Latest({kServableName}), &handle));
280  EXPECT_EQ("kServableName-30", *handle);
281  const ServableId id = {kServableName, 30};
282  EXPECT_EQ(id, handle.id());
283 }
284 
285 // Tests functionality when the version corresponding to the "earliest" is
286 // already managed and loaded by the caching-manager.
287 TEST_P(CachingManagerTest, ServableHandleMultipleRequestsEarliest) {
288  const ServableId id = {kServableName, 42};
289  {
290  // Make an explicit request for version 42.
291  ServableHandle<string> handle;
292  TF_ASSERT_OK(
293  manager_->GetServableHandle(ServableRequest::FromId(id), &handle));
294  EXPECT_EQ("kServableName-42", *handle);
295  EXPECT_EQ(id, handle.id());
296  // We expect a new loader to be created for this request.
297  EXPECT_EQ(1, string_loader_factory_->num_loaders_dispensed());
298  // Update the earliest available version.
299  string_loader_factory_->set_earliest_version(42);
300  }
301  {
302  // Now request for the earliest. The returned handle should have an id
303  // corresponding to version 42.
304  ServableHandle<string> handle;
305  TF_ASSERT_OK(manager_->GetServableHandle(
306  ServableRequest::Earliest({kServableName}), &handle));
307  EXPECT_EQ("kServableName-42", *handle);
308  EXPECT_EQ(id, handle.id());
309  // We do not expect a new loader to be created for this request, since it is
310  // identical to the previous request.
311  EXPECT_EQ(1, string_loader_factory_->num_loaders_dispensed());
312  }
313 }
314 
315 // Tests functionality when the version corresponding to the "latest" is
316 // already managed and loaded by the caching-manager.
317 TEST_P(CachingManagerTest, ServableHandleMultipleRequestsLatest) {
318  const ServableId id = {kServableName, 42};
319  {
320  // Make an explicit request for version 42.
321  ServableHandle<string> handle;
322  TF_ASSERT_OK(
323  manager_->GetServableHandle(ServableRequest::FromId(id), &handle));
324  EXPECT_EQ("kServableName-42", *handle);
325  EXPECT_EQ(id, handle.id());
326  // We expect a new loader to be created for this request.
327  EXPECT_EQ(1, string_loader_factory_->num_loaders_dispensed());
328  // Update the latest available version.
329  string_loader_factory_->set_latest_version(42);
330  }
331  {
332  // Now request for the latest. The returned handle should have an id
333  // corresponding to version 42.
334  ServableHandle<string> handle;
335  TF_ASSERT_OK(manager_->GetServableHandle(
336  ServableRequest::Latest({kServableName}), &handle));
337  EXPECT_EQ("kServableName-42", *handle);
338  EXPECT_EQ(id, handle.id());
339  // We do not expect a new loader to be created for this request, since it is
340  // identical to the previous request.
341  EXPECT_EQ(1, string_loader_factory_->num_loaders_dispensed());
342  }
343 }
344 
345 TEST_P(CachingManagerTest, ServableHandleWrongType) {
346  // The servable is supposed to be of type string, but we request for a handle
347  // of type int. This should cause an invalid argument error.
348  ServableHandle<int> handle;
349  const Status status = manager_->GetServableHandle(
350  ServableRequest::FromId({kServableName, 30}), &handle);
351  ASSERT_FALSE(status.ok()) << status;
352  EXPECT_EQ(error::INVALID_ARGUMENT, status.code());
353 }
354 
355 TEST_P(CachingManagerTest, ServableHandleError) {
356  // Create the manager to use the error loader-factory that produces errors.
357  std::unique_ptr<CachingManager> error_manager =
358  CreateManagerWithErrorLoaderFactory();
359  ServableHandle<string> handle;
360  const Status status = error_manager->GetServableHandle(
361  ServableRequest::FromId({kServableName, 30}), &handle);
362  EXPECT_FALSE(status.ok()) << status;
363 }
364 
366 // Get available servable handles.
367 
368 TEST_P(CachingManagerTest, AvailableServableHandlesNoRequests) {
369  std::map<ServableId, ServableHandle<string>> handles =
370  manager_->GetAvailableServableHandles<string>();
371  // Since there are no requests yet, the handles map is empty.
372  EXPECT_EQ(0, handles.size());
373 }
374 
375 TEST_P(CachingManagerTest, AvailableServableHandlesMultipleRequests) {
376  // Multiple requests in sequence to different servable handles.
377  // Scoped to destruct handles at the end of it.
378  {
379  // Request with servable name and version.
380  ServableHandle<string> handle;
381  TF_ASSERT_OK(manager_->GetServableHandle(
382  ServableRequest::FromId({kServableName, 30}), &handle));
383  }
384  {
385  // Request with different version.
386  ServableHandle<string> handle;
387  TF_ASSERT_OK(manager_->GetServableHandle(
388  ServableRequest::FromId({kServableName, 31}), &handle));
389  }
390  {
391  // Request with a different servable name.
392  ServableHandle<string> handle;
393  TF_ASSERT_OK(manager_->GetServableHandle(
394  ServableRequest::FromId({kServableName2, 32}), &handle));
395  }
396  const std::map<ServableId, ServableHandle<string>> handles =
397  manager_->GetAvailableServableHandles<string>();
398  std::vector<ServableId> actual_keys;
399  for (const auto& it_handle : handles) {
400  actual_keys.push_back(it_handle.first);
401  }
402 
403  const std::vector<ServableId> expected_keys = {
404  {kServableName, 30}, {kServableName, 31}, {kServableName2, 32}};
405  EXPECT_THAT(actual_keys, UnorderedElementsAreArray(expected_keys));
406 }
407 
408 TEST_P(CachingManagerTest, AvailableServableHandlesWrongType) {
409  ServableHandle<string> handle;
410  TF_ASSERT_OK(manager_->GetServableHandle(
411  ServableRequest::FromId({kServableName, 30}), &handle));
412  std::map<ServableId, ServableHandle<int>> handles =
413  manager_->GetAvailableServableHandles<int>();
414  EXPECT_EQ(0, handles.size());
415 }
416 
417 TEST_P(CachingManagerTest, AvailableServableHandlesError) {
418  // Create the manager to use the error loader-factory that produces errors.
419  std::unique_ptr<CachingManager> error_manager =
420  CreateManagerWithErrorLoaderFactory();
421  ServableHandle<string> handle;
422  const Status status = error_manager->GetServableHandle(
423  ServableRequest::FromId({kServableName, 30}), &handle);
424  ASSERT_FALSE(status.ok()) << status;
425  std::map<ServableId, ServableHandle<string>> handles =
426  error_manager->GetAvailableServableHandles<string>();
427  EXPECT_EQ(0, handles.size());
428 }
429 
431 // List available servable ids.
432 
433 TEST_P(CachingManagerTest, ListAvailableServableIdsMultipleRequests) {
434  {
435  // Request with servable name and version.
436  ServableHandle<string> handle;
437  TF_ASSERT_OK(manager_->GetServableHandle(
438  ServableRequest::FromId({kServableName, 30}), &handle));
439  }
440  {
441  // Request with a different version.
442  ServableHandle<string> handle;
443  TF_ASSERT_OK(manager_->GetServableHandle(
444  ServableRequest::FromId({kServableName, 31}), &handle));
445  }
446  {
447  // Request with a different servable name.
448  ServableHandle<string> handle;
449  TF_ASSERT_OK(manager_->GetServableHandle(
450  ServableRequest::FromId({kServableName2, 32}), &handle));
451  }
452  const std::vector<ServableId> expected = {
453  {kServableName, 30}, {kServableName, 31}, {kServableName2, 32}};
454  EXPECT_THAT(manager_->ListAvailableServableIds(),
455  UnorderedElementsAreArray(expected));
456 }
457 
459 // Event bus.
460 
461 MATCHER_P(EqualsServableState, servable_state, servable_state.DebugString()) {
462  if (arg == servable_state) {
463  return true;
464  }
465  *result_listener << arg.DebugString();
466  return false;
467 }
468 
469 TEST_P(CachingManagerTest, EventBusSingleRequest) {
470  ServableHandle<string> handle;
471  const ServableId id = {kServableName, 30};
472  TF_ASSERT_OK(
473  manager_->GetServableHandle(ServableRequest::FromId(id), &handle));
474  // Check that the state published on the event-bus matches produced by the
475  // loader-factory for a successful request.
476  const ServableState expected_published_state = {
477  id, ServableState::ManagerState::kAvailable, OkStatus()};
478  EXPECT_THAT(*servable_state_monitor_.GetState(id),
479  EqualsServableState(expected_published_state));
480 }
481 
482 TEST_P(CachingManagerTest, EventBusErrorHandle) {
483  // Create the manager to use the error loader-factory that produces errors.
484  std::unique_ptr<CachingManager> error_manager =
485  CreateManagerWithErrorLoaderFactory();
486  ServableHandle<string> handle;
487  const ServableId id = {kServableName, 30};
488  const Status status =
489  error_manager->GetServableHandle(ServableRequest::FromId(id), &handle);
490  // Check that the state published on the event-bus matches that produced
491  // by the loader-factory for an error.
492  const ServableState expected_published_state = {
493  id, ServableState::ManagerState::kEnd,
494  errors::Unknown("error loader-factory")};
495  EXPECT_THAT(*servable_state_monitor_.GetState(id),
496  EqualsServableState(expected_published_state));
497 }
498 
500 // Concurrent requests.
501 
502 TEST_P(CachingManagerTest, ConcurrentDisjointRequests) {
503  // Track the status of each request.
504  mutex status_mu;
505  std::vector<Status> statuses(4);
506  {
507  ThreadPoolExecutor request_executor(Env::Default(), "GetHandles",
508  kNumThreads);
509  for (int i = 0; i < 4; i++) {
510  request_executor.Schedule([this, i, &statuses, &status_mu]() {
511  ServableHandle<string> handle;
512  const Status status =
513  manager_->GetServableHandle({kServableName, i + 30}, &handle);
514  mutex_lock l(status_mu);
515  statuses[i] = status;
516  });
517  }
518  }
519  // Check that all requests returned with an ok status.
520  for (int i = 0; i < 4; i++) {
521  mutex_lock l(status_mu);
522  EXPECT_EQ(OkStatus(), statuses[i]);
523  }
524  // Check that the available servable handles now includes all requested
525  // servables.
526  const std::map<ServableId, ServableHandle<string>> handles =
527  manager_->GetAvailableServableHandles<string>();
528  std::vector<ServableId> actual_keys;
529  for (const auto& it_handle : handles) {
530  actual_keys.push_back(it_handle.first);
531  }
532 
533  const std::vector<ServableId> expected_keys = {{kServableName, 30},
534  {kServableName, 31},
535  {kServableName, 32},
536  {kServableName, 33}};
537  EXPECT_THAT(actual_keys, UnorderedElementsAreArray(expected_keys));
538  // Since the map entries in load_mutex_map_ are garbage-collected, we expect
539  // no remaining entries in the map.
540  EXPECT_EQ(0, GetLoadMutexMapSize());
541 }
542 
543 TEST_P(CachingManagerTest, ConcurrentIntersectingRequests) {
544  mutex status_mu;
545  std::vector<Status> statuses(8);
546  {
547  ThreadPoolExecutor request_executor(Env::Default(), "GetHandles",
548  kNumThreads);
549  for (int i = 0; i < 8; i++) {
550  // Use two different versions to send concurrent requests.
551  const int version = i % 2 + 30;
552  const ServableId id = {kServableName, version};
553  request_executor.Schedule([this, i, id, &statuses, &status_mu]() {
554  ServableHandle<string> handle;
555  const Status status =
556  manager_->GetServableHandle(ServableRequest::FromId(id), &handle);
557  mutex_lock l(status_mu);
558  statuses[i] = status;
559  });
560  }
561  }
562  // Check that all requests returned with an ok status.
563  for (int i = 0; i < 8; i++) {
564  mutex_lock l(status_mu);
565  EXPECT_EQ(OkStatus(), statuses[i]);
566  }
567  // Check that the available servable handles now includes all requested
568  // servables.
569  const std::map<ServableId, ServableHandle<string>> handles =
570  manager_->GetAvailableServableHandles<string>();
571  std::vector<ServableId> actual_keys;
572  for (const auto& it_handle : handles) {
573  actual_keys.push_back(it_handle.first);
574  }
575  const std::vector<ServableId> expected_keys = {{kServableName, 30},
576  {kServableName, 31}};
577  EXPECT_THAT(actual_keys, UnorderedElementsAreArray(expected_keys));
578  // Since the map entries in load_mutex_map_ are garbage-collected, we expect
579  // no remaining entries in the map.
580  EXPECT_EQ(0, GetLoadMutexMapSize());
581 }
582 
584 
585 TEST(PathPrefixLoaderFactoryTest, Basic) {
586  auto adapter = std::unique_ptr<StoragePathSourceAdapter>(
587  new test_util::FakeLoaderSourceAdapter("suffix"));
588  PathPrefixLoaderFactory factory("prefix", std::move(adapter));
589 
590  ServableData<std::unique_ptr<Loader>> loader_data =
591  factory.CreateLoader({"servable_name", 0});
592  TF_ASSERT_OK(loader_data.status());
593  std::unique_ptr<Loader> loader = loader_data.ConsumeDataOrDie();
594  TF_ASSERT_OK(loader->Load());
595  EXPECT_EQ("prefix/servable_name/suffix", *loader->servable().get<string>());
596 
597  EXPECT_EQ(0, factory.GetServableVersion(
598  "blah", ServableRequest::AutoVersionPolicy::kEarliest));
599  EXPECT_EQ(0, factory.GetServableVersion(
600  "blah", ServableRequest::AutoVersionPolicy::kLatest));
601 }
602 
603 TEST(PathPrefixLoaderFactoryTest, VersionOtherThanZeroYieldsError) {
604  auto adapter = std::unique_ptr<StoragePathSourceAdapter>(
605  new test_util::FakeLoaderSourceAdapter("suffix"));
606  PathPrefixLoaderFactory factory("prefix", std::move(adapter));
607 
608  ServableData<std::unique_ptr<Loader>> loader_data =
609  factory.CreateLoader({"servable_name", 42});
610  ASSERT_FALSE(loader_data.status().ok());
611  EXPECT_THAT(loader_data.status().ToString(),
612  HasSubstr("PathPrefixLoaderFactory only supports single-version "
613  "servables at version 0"));
614 }
615 
616 } // namespace
617 } // namespace serving
618 } // namespace tensorflow
absl::optional< ServableState > GetState(const ServableId &servable_id) const TF_LOCKS_EXCLUDED(mu_)