TensorFlow Serving C++ API Documentation
json_tensor_test.cc
1 /* Copyright 2018 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/util/json_tensor.h"
17 
18 #include <functional>
19 #include <string>
20 
21 #include "google/protobuf/message.h"
22 #include "google/protobuf/text_format.h"
23 #include "google/protobuf/util/message_differencer.h"
24 #include "rapidjson/document.h"
25 #include "rapidjson/error/en.h"
26 #include <gmock/gmock.h>
27 #include <gtest/gtest.h>
28 #include "absl/strings/substitute.h"
29 #include "tensorflow/core/lib/core/errors.h"
30 #include "tensorflow/core/lib/core/status_test_util.h"
31 #include "tensorflow/core/platform/protobuf.h"
32 #include "tensorflow_serving/apis/model.pb.h"
33 #include "tensorflow_serving/apis/predict.pb.h"
34 #include "tensorflow_serving/test_util/test_util.h"
35 
36 namespace tensorflow {
37 namespace serving {
38 namespace {
39 
40 using protobuf::TextFormat;
41 using protobuf::util::DefaultFieldComparator;
42 using protobuf::util::MessageDifferencer;
43 using test_util::EqualsProto;
44 using ::testing::HasSubstr;
45 
46 using TensorInfoMap = ::google::protobuf::Map<string, TensorInfo>;
47 using TensorMap = ::google::protobuf::Map<string, TensorProto>;
48 
49 std::function<tensorflow::Status(const string&, TensorInfoMap*)> getmap(
50  const TensorInfoMap& map) {
51  return [&map](const string&, TensorInfoMap* m) {
52  *m = map;
53  return OkStatus();
54  };
55 }
56 
57 TEST(JsontensorTest, SingleUnnamedTensor) {
58  TensorInfoMap infomap;
59  ASSERT_TRUE(
60  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["default"]));
61 
62  PredictRequest req;
63  JsonPredictRequestFormat format;
64  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
65  {
66  "instances": [[1,2],[3,4],[5,6]]
67  })",
68  getmap(infomap), &req, &format));
69  auto tmap = req.inputs();
70  EXPECT_EQ(tmap.size(), 1);
71  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
72  EXPECT_THAT(tmap["default"], EqualsProto(R"(
73  dtype: DT_INT32
74  tensor_shape {
75  dim { size: 3 }
76  dim { size: 2 }
77  }
78  int_val: 1
79  int_val: 2
80  int_val: 3
81  int_val: 4
82  int_val: 5
83  int_val: 6
84  )"));
85 }
86 
87 TEST(JsontensorTest, DeeplyNestedWellFormed) {
88  TensorInfoMap infomap;
89  ASSERT_TRUE(
90  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["default"]));
91 
92  PredictRequest req;
93  JsonPredictRequestFormat format;
94  std::string json_req = R"({"instances":[1], "nested":)";
95  json_req.append(500000, '[');
96  json_req.append(500000, ']');
97  json_req.append("}");
98  TF_EXPECT_OK(
99  FillPredictRequestFromJson(json_req, getmap(infomap), &req, &format));
100  auto tmap = req.inputs();
101  EXPECT_EQ(tmap.size(), 1);
102 }
103 
104 TEST(JsontensorTest, DeeplyNestedMalformed) {
105  TensorInfoMap infomap;
106  ASSERT_TRUE(
107  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["default"]));
108 
109  PredictRequest req;
110  JsonPredictRequestFormat format;
111  std::string json_req = R"({"signature_name":)";
112  json_req.append(500000, '[');
113  json_req.append(500000, ']');
114  json_req.append("}");
115  auto status =
116  FillPredictRequestFromJson(json_req, getmap(infomap), &req, &format);
117  ASSERT_TRUE(errors::IsInvalidArgument(status));
118  EXPECT_THAT(status.message(), HasSubstr("key must be a string value"));
119 }
120 
121 TEST(JsontensorTest, MixedInputForFloatTensor) {
122  TensorInfoMap infomap;
123  ASSERT_TRUE(
124  TextFormat::ParseFromString("dtype: DT_FLOAT", &infomap["default"]));
125 
126  PredictRequest req;
127  JsonPredictRequestFormat format;
128  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
129  {
130  "instances": [1, 2.0, 3, 4, 5.003, 0.007, 0.0]
131  })",
132  getmap(infomap), &req, &format));
133  auto tmap = req.inputs();
134  EXPECT_EQ(tmap.size(), 1);
135  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
136  EXPECT_THAT(tmap["default"], EqualsProto(R"(
137  dtype: DT_FLOAT
138  tensor_shape { dim { size: 7 } }
139  float_val: 1
140  float_val: 2.0
141  float_val: 3
142  float_val: 4
143  float_val: 5.003
144  float_val: 0.007
145  float_val: 0.0
146  )"));
147 }
148 
149 TEST(JsontensorTest, MixedInputForDoubleTensor) {
150  TensorInfoMap infomap;
151  ASSERT_TRUE(
152  TextFormat::ParseFromString("dtype: DT_DOUBLE", &infomap["default"]));
153 
154  PredictRequest req;
155  JsonPredictRequestFormat format;
156  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
157  {
158  "instances": [1.0, 2, 3, 4, 0.662, 0, 0.0]
159  })",
160  getmap(infomap), &req, &format));
161  auto tmap = req.inputs();
162  EXPECT_EQ(tmap.size(), 1);
163  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
164  EXPECT_THAT(tmap["default"], EqualsProto(R"(
165  dtype: DT_DOUBLE
166  tensor_shape { dim { size: 7 } }
167  double_val: 1.0
168  double_val: 2
169  double_val: 3
170  double_val: 4
171  double_val: 0.662
172  double_val: 0
173  double_val: 0.0
174  )"));
175 }
176 
177 // This test was borne out of b/181409782. Essentially, we expect that
178 // inputs 1435774380 and 1435774380.0 behave exactly the same. Prior to the
179 // bug fix, the former was rejected because it couldn't be exactly
180 // represented by DT_FLOAT, but the latter was accepted, even though it, too,
181 // could not be exactly represented by DT_FLOAT. Post-fix, they are both
182 // accepted.
183 TEST(JsontensorTest, FloatTensorWithPrecisionLoss) {
184  TensorInfoMap infomap;
185  ASSERT_TRUE(
186  TextFormat::ParseFromString("dtype: DT_FLOAT", &infomap["default"]));
187 
188  PredictRequest req;
189  JsonPredictRequestFormat format;
190  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
191  {
192  "instances": [1435774380]
193  })",
194  getmap(infomap), &req, &format));
195  auto tmap = req.inputs();
196  EXPECT_EQ(tmap.size(), 1);
197  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
198 
199  // Please note that the float_val has changed due to precision loss.
200  EXPECT_THAT(tmap["default"], EqualsProto(R"(
201  dtype: DT_FLOAT
202  tensor_shape { dim { size: 1 } }
203  float_val: 1435774380
204  )"));
205 
206  // Now, add a decimal point to the end, and expect exactly the same behavior.
207  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
208  {
209  "instances": [1435774380.0]
210  })",
211  getmap(infomap), &req, &format));
212  tmap = req.inputs();
213  EXPECT_EQ(tmap.size(), 1);
214  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
215 
216  // As before, the float_val has changed due to precision loss.
217  EXPECT_THAT(tmap["default"], EqualsProto(R"(
218  dtype: DT_FLOAT
219  tensor_shape { dim { size: 1 } }
220  float_val: 1435774380.0
221  )"));
222 }
223 
224 TEST(JsontensorTest, FloatTensorThatExceedsMaxReturnsInf) {
225  TensorInfoMap infomap;
226  ASSERT_TRUE(
227  TextFormat::ParseFromString("dtype: DT_FLOAT", &infomap["default"]));
228 
229  PredictRequest req;
230  JsonPredictRequestFormat format;
231  TF_EXPECT_OK(FillPredictRequestFromJson(
232  absl::Substitute(R"({ "instances": [$0] })",
233  std::numeric_limits<double>::max()),
234  getmap(infomap), &req, &format));
235  auto tmap = req.inputs();
236  EXPECT_EQ(tmap.size(), 1);
237  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
238 
239  EXPECT_THAT(tmap["default"], EqualsProto(R"(
240  dtype: DT_FLOAT
241  tensor_shape { dim { size: 1 } }
242  float_val: inf
243  )"));
244 
245  // Test for minimum, too (gets converted to -inf)
246  TF_EXPECT_OK(FillPredictRequestFromJson(
247  absl::Substitute(R"({ "instances": [$0] })",
248  -std::numeric_limits<double>::max()),
249  getmap(infomap), &req, &format));
250  tmap = req.inputs();
251  EXPECT_EQ(tmap.size(), 1);
252  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
253 
254  EXPECT_THAT(tmap["default"], EqualsProto(R"(
255  dtype: DT_FLOAT
256  tensor_shape { dim { size: 1 } }
257  float_val: -inf
258  )"));
259 }
260 
261 TEST(JsontensorTest, FloatTensorThatExceedsMinReturnsZero) {
262  TensorInfoMap infomap;
263  ASSERT_TRUE(
264  TextFormat::ParseFromString("dtype: DT_FLOAT", &infomap["default"]));
265 
266  PredictRequest req;
267  JsonPredictRequestFormat format;
268  TF_EXPECT_OK(FillPredictRequestFromJson(
269  absl::Substitute(R"({ "instances": [$0] })",
270  std::numeric_limits<double>::min()),
271  getmap(infomap), &req, &format));
272  auto tmap = req.inputs();
273  EXPECT_EQ(tmap.size(), 1);
274  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
275 
276  EXPECT_THAT(tmap["default"], EqualsProto(R"(
277  dtype: DT_FLOAT
278  tensor_shape { dim { size: 1 } }
279  float_val: 0
280  )"));
281 
282  // Test for minimum, too (gets converted to -inf)
283  TF_EXPECT_OK(FillPredictRequestFromJson(
284  absl::Substitute(R"({ "instances": [$0] })",
285  -std::numeric_limits<double>::min()),
286  getmap(infomap), &req, &format));
287  tmap = req.inputs();
288  EXPECT_EQ(tmap.size(), 1);
289  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
290 
291  EXPECT_THAT(tmap["default"], EqualsProto(R"(
292  dtype: DT_FLOAT
293  tensor_shape { dim { size: 1 } }
294  float_val: -0
295  )"));
296 }
297 
298 TEST(JsontensorTest, SingleUnnamedTensorWithSignature) {
299  TensorInfoMap infomap;
300  ASSERT_TRUE(
301  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["default"]));
302 
303  PredictRequest req;
304  JsonPredictRequestFormat format;
305  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
306  {
307  "signature_name": "predict_images",
308  "instances": [[1,2]]
309  })",
310  getmap(infomap), &req, &format));
311  EXPECT_EQ(req.model_spec().signature_name(), "predict_images");
312  auto tmap = req.inputs();
313  EXPECT_EQ(tmap.size(), 1);
314  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
315  EXPECT_THAT(tmap["default"], EqualsProto(R"(
316  dtype: DT_INT32
317  tensor_shape {
318  dim { size: 1 }
319  dim { size: 2 }
320  }
321  int_val: 1
322  int_val: 2
323  )"));
324 }
325 
326 TEST(JsontensorTest, TensorFromNonNullTerminatedBuffer) {
327  TensorInfoMap infomap;
328  ASSERT_TRUE(
329  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["default"]));
330 
331  // Note, last character is a 'X' to for non-null termination.
332  const string jsonstr = R"({"instances": [[1,2],[3,4],[5,6]]}X)";
333  PredictRequest req;
334  JsonPredictRequestFormat format;
335  TF_EXPECT_OK(FillPredictRequestFromJson(
336  // Process over a buffer that is not null terminated.
337  absl::string_view(jsonstr.data(), jsonstr.length() - 1), getmap(infomap),
338  &req, &format));
339  auto tmap = req.inputs();
340  EXPECT_EQ(tmap.size(), 1);
341  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
342  EXPECT_THAT(tmap["default"], EqualsProto(R"(
343  dtype: DT_INT32
344  tensor_shape {
345  dim { size: 3 }
346  dim { size: 2 }
347  }
348  int_val: 1
349  int_val: 2
350  int_val: 3
351  int_val: 4
352  int_val: 5
353  int_val: 6
354  )"));
355 }
356 
357 TEST(JsontensorTest, SingleUnnamedTensorBase64Scalars) {
358  TensorInfoMap infomap;
359  ASSERT_TRUE(
360  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["default"]));
361 
362  PredictRequest req;
363  JsonPredictRequestFormat format;
364  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
365  {
366  "instances": [ { "b64" : "aGVsbG8=" }, { "b64": "d29ybGQ=" } ]
367  })",
368  getmap(infomap), &req, &format));
369  auto tmap = req.inputs();
370  EXPECT_EQ(tmap.size(), 1);
371  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
372  EXPECT_THAT(tmap["default"], EqualsProto(R"(
373  dtype: DT_STRING
374  tensor_shape {
375  dim { size: 2 }
376  }
377  string_val: "hello"
378  string_val: "world"
379  )"));
380 }
381 
382 TEST(JsontensorTest, SingleUnnamedTensorBase64Lists) {
383  TensorInfoMap infomap;
384  ASSERT_TRUE(
385  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["default"]));
386 
387  PredictRequest req;
388  JsonPredictRequestFormat format;
389  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
390  {
391  "instances": [ [{ "b64" : "aGVsbG8=" }], [{ "b64": "d29ybGQ=" }] ]
392  })",
393  getmap(infomap), &req, &format));
394  auto tmap = req.inputs();
395  EXPECT_EQ(tmap.size(), 1);
396  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
397  EXPECT_THAT(tmap["default"], EqualsProto(R"(
398  dtype: DT_STRING
399  tensor_shape {
400  dim { size: 2 }
401  dim { size: 1 }
402  }
403  string_val: "hello"
404  string_val: "world"
405  )"));
406 }
407 
408 TEST(JsontensorTest, SingleNamedTensorBase64) {
409  TensorInfoMap infomap;
410  ASSERT_TRUE(
411  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["default"]));
412 
413  PredictRequest req;
414  JsonPredictRequestFormat format;
415  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
416  {
417  "instances": [
418  {
419  "default": [ [{ "b64" : "aGVsbG8=" }], [{ "b64": "d29ybGQ=" }] ]
420  }
421  ]
422  })",
423  getmap(infomap), &req, &format));
424  auto tmap = req.inputs();
425  EXPECT_EQ(tmap.size(), 1);
426  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
427  EXPECT_THAT(tmap["default"], EqualsProto(R"(
428  dtype: DT_STRING
429  tensor_shape {
430  dim { size: 1 }
431  dim { size: 2 }
432  dim { size: 1 }
433  }
434  string_val: "hello"
435  string_val: "world"
436  )"));
437 }
438 
439 TEST(JsontensorTest, MultipleNamedTensor) {
440  TensorInfoMap infomap;
441 
442  // 3 named tensors with different types.
443  ASSERT_TRUE(
444  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["int_tensor"]));
445  ASSERT_TRUE(
446  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["str_tensor"]));
447  ASSERT_TRUE(
448  TextFormat::ParseFromString("dtype: DT_FLOAT", &infomap["float_tensor"]));
449  ASSERT_TRUE(
450  TextFormat::ParseFromString("dtype: DT_DOUBLE", &infomap["dbl_tensor"]));
451 
452  PredictRequest req;
453  JsonPredictRequestFormat format;
454  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
455  {
456  "instances": [
457  {
458  "int_tensor": [[1,2],[3,4],[5,6]],
459  "str_tensor": ["foo", "bar"],
460  "float_tensor": [1.0, NaN],
461  "dbl_tensor": [NaN]
462  },
463  {
464  "int_tensor": [[7,8],[9,0],[1,2]],
465  "str_tensor": ["baz", "bat"],
466  "float_tensor": [2.0, Infinity],
467  "dbl_tensor": [2.0]
468  }
469  ]
470  })",
471  getmap(infomap), &req, &format));
472 
473  auto tmap = req.inputs();
474  EXPECT_EQ(tmap.size(), 4);
475  EXPECT_EQ(format, JsonPredictRequestFormat::kRow);
476  EXPECT_THAT(tmap["int_tensor"], EqualsProto(R"(
477  dtype: DT_INT32
478  tensor_shape {
479  dim { size: 2 }
480  dim { size: 3 }
481  dim { size: 2 }
482  }
483  int_val: 1
484  int_val: 2
485  int_val: 3
486  int_val: 4
487  int_val: 5
488  int_val: 6
489  int_val: 7
490  int_val: 8
491  int_val: 9
492  int_val: 0
493  int_val: 1
494  int_val: 2
495  )"));
496  EXPECT_THAT(tmap["str_tensor"], EqualsProto(R"(
497  dtype: DT_STRING
498  tensor_shape {
499  dim { size: 2 }
500  dim { size: 2 }
501  }
502  string_val: "foo"
503  string_val: "bar"
504  string_val: "baz"
505  string_val: "bat"
506  )"));
507  EXPECT_THAT(tmap["float_tensor"], EqualsProto(R"(
508  dtype: DT_FLOAT
509  tensor_shape {
510  dim { size: 2 }
511  dim { size: 2 }
512  }
513  float_val: 1.0
514  float_val: NaN
515  float_val: 2.0
516  float_val: Infinity
517  )"));
518  EXPECT_THAT(tmap["dbl_tensor"], EqualsProto(R"(
519  dtype: DT_DOUBLE
520  tensor_shape {
521  dim { size: 2 }
522  dim { size: 1 }
523  }
524  double_val: NaN
525  double_val: 2.0
526  )"));
527 }
528 
529 TEST(JsontensorTest, SingleUnnamedTensorColumnarFormat) {
530  TensorInfoMap infomap;
531  ASSERT_TRUE(
532  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["int_tensor"]));
533 
534  PredictRequest req;
535  JsonPredictRequestFormat format;
536  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
537  {
538  "inputs": {
539  "int_tensor": [[[1,2],[3,4],[5,6]]]
540  }
541  })",
542  getmap(infomap), &req, &format));
543  auto tmap = req.inputs();
544  EXPECT_EQ(tmap.size(), 1);
545  EXPECT_EQ(format, JsonPredictRequestFormat::kColumnar);
546  EXPECT_THAT(tmap["int_tensor"], EqualsProto(R"(
547  dtype: DT_INT32
548  tensor_shape {
549  dim { size: 1 }
550  dim { size: 3 }
551  dim { size: 2 }
552  }
553  int_val: 1
554  int_val: 2
555  int_val: 3
556  int_val: 4
557  int_val: 5
558  int_val: 6
559  )"));
560 
561  //
562  // Skip explicitly specifying named input.
563  //
564  req.Clear();
565  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
566  {
567  "inputs": [[[1,2],[3,4],[5,6]]]
568  })",
569  getmap(infomap), &req, &format));
570  auto tmap2 = req.inputs();
571  EXPECT_EQ(tmap2.size(), 1);
572  EXPECT_EQ(format, JsonPredictRequestFormat::kColumnar);
573  EXPECT_THAT(tmap2["int_tensor"], EqualsProto(R"(
574  dtype: DT_INT32
575  tensor_shape {
576  dim { size: 1 }
577  dim { size: 3 }
578  dim { size: 2 }
579  }
580  int_val: 1
581  int_val: 2
582  int_val: 3
583  int_val: 4
584  int_val: 5
585  int_val: 6
586  )"));
587 }
588 
589 TEST(JsontensorTest, MultipleNamedTensorColumnarFormat) {
590  TensorInfoMap infomap;
591 
592  // 3 named tensors with different types.
593  ASSERT_TRUE(
594  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["int_tensor"]));
595  ASSERT_TRUE(
596  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["str_tensor"]));
597  ASSERT_TRUE(
598  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["bin_tensor"]));
599 
600  PredictRequest req;
601  JsonPredictRequestFormat format;
602  TF_EXPECT_OK(FillPredictRequestFromJson(R"(
603  {
604  "inputs": {
605  "int_tensor": [[[1,2],[3,4],[5,6]]],
606  "str_tensor": ["foo", "bar"],
607  "bin_tensor": { "b64" : "aGVsbG8=" }
608  }
609  })",
610  getmap(infomap), &req, &format));
611 
612  auto tmap = req.inputs();
613  EXPECT_EQ(tmap.size(), 3);
614  EXPECT_EQ(format, JsonPredictRequestFormat::kColumnar);
615  EXPECT_THAT(tmap["int_tensor"], EqualsProto(R"(
616  dtype: DT_INT32
617  tensor_shape {
618  dim { size: 1 }
619  dim { size: 3 }
620  dim { size: 2 }
621  }
622  int_val: 1
623  int_val: 2
624  int_val: 3
625  int_val: 4
626  int_val: 5
627  int_val: 6
628  )"));
629  EXPECT_THAT(tmap["str_tensor"], EqualsProto(R"(
630  dtype: DT_STRING
631  tensor_shape { dim { size: 2 } }
632  string_val: "foo"
633  string_val: "bar"
634  )"));
635  EXPECT_THAT(tmap["bin_tensor"], EqualsProto(R"(
636  dtype: DT_STRING
637  tensor_shape {}
638  string_val: "hello"
639  )"));
640 }
641 
642 TEST(JsontensorTest, SingleUnnamedTensorErrors) {
643  TensorInfoMap infomap;
644  ASSERT_TRUE(
645  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["default"]));
646 
647  PredictRequest req;
648  JsonPredictRequestFormat format;
649  Status status;
650  status = FillPredictRequestFromJson("", getmap(infomap), &req, &format);
651  ASSERT_TRUE(errors::IsInvalidArgument(status));
652  EXPECT_THAT(status.message(), HasSubstr("document is empty"));
653 
654  status = FillPredictRequestFromJson(R"(
655  {
656  "signature_name": 5,
657  "instances": [[1,2],[3,4],[5,6,7]]
658  })",
659  getmap(infomap), &req, &format);
660  ASSERT_TRUE(errors::IsInvalidArgument(status));
661  EXPECT_THAT(status.message(), HasSubstr("must be a string value"));
662 
663  status = FillPredictRequestFromJson(R"(
664  {
665  "instances": [[1,2],[3,4],[5,6,7]],
666  "inputs": [[1,2],[3,4],[5,6,7]]
667  })",
668  getmap(infomap), &req, &format);
669  ASSERT_TRUE(errors::IsInvalidArgument(status));
670  EXPECT_THAT(status.message(), HasSubstr("Not formatted correctly"));
671 
672  status = FillPredictRequestFromJson(R"(
673  {
674  "instances": [[1,2],[3,4],[5,6,7]]
675  })",
676  getmap(infomap), &req, &format);
677  ASSERT_TRUE(errors::IsInvalidArgument(status));
678  EXPECT_THAT(status.message(), HasSubstr("Expecting tensor size"));
679 
680  status = FillPredictRequestFromJson(R"(
681  {
682  "instances": [[1,2],[3,4],[[5,6]]]
683  })",
684  getmap(infomap), &req, &format);
685  ASSERT_TRUE(errors::IsInvalidArgument(status));
686  EXPECT_THAT(status.message(), HasSubstr("Expecting shape"));
687 
688  status = FillPredictRequestFromJson(R"(
689  {
690  "instances": [1, [1]]
691  })",
692  getmap(infomap), &req, &format);
693  ASSERT_TRUE(errors::IsInvalidArgument(status));
694  EXPECT_THAT(status.message(), HasSubstr("Expecting shape"));
695 
696  status = FillPredictRequestFromJson(R"(
697  {
698  "instances": [[1,2],["a", "b"]]
699  })",
700  getmap(infomap), &req, &format);
701  ASSERT_TRUE(errors::IsInvalidArgument(status));
702  EXPECT_THAT(status.message(), HasSubstr("not of expected type"));
703 }
704 
705 TEST(JsontensorTest, MultipleNamedTensorErrors) {
706  TensorInfoMap infomap;
707 
708  // 2 named tensors with different types.
709  ASSERT_TRUE(
710  TextFormat::ParseFromString("dtype: DT_INT32", &infomap["int_tensor"]));
711  ASSERT_TRUE(
712  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["str_tensor"]));
713 
714  PredictRequest req;
715  JsonPredictRequestFormat format;
716  Status status;
717  // Different shapes across int_tensor instances.
718  status = FillPredictRequestFromJson(R"(
719  {
720  "instances": [
721  {
722  "int_tensor": [[1,2],[3,4],[5,6]],
723  "str_tensor": ["foo", "bar"]
724  },
725  {
726  "int_tensor": [[[7,8],[9,0],[1,2]]],
727  "str_tensor": ["baz", "bat"]
728  }
729  ]
730  })",
731  getmap(infomap), &req, &format);
732  ASSERT_TRUE(errors::IsInvalidArgument(status));
733  EXPECT_THAT(status.message(), HasSubstr("Expecting shape"));
734 
735  // Different size/length across int_tensor instances.
736  req.Clear();
737  status = FillPredictRequestFromJson(R"(
738  {
739  "instances": [
740  {
741  "int_tensor": [[1,2],[3,4],[5,6],[7,8]],
742  "str_tensor": ["foo", "bar"]
743  },
744  {
745  "int_tensor": [[7,8],[9,0],[1,2]],
746  "str_tensor": ["baz", "bat"]
747  }
748  ]
749  })",
750  getmap(infomap), &req, &format);
751  ASSERT_TRUE(errors::IsInvalidArgument(status));
752  EXPECT_THAT(status.message(), HasSubstr("Expecting tensor size"));
753 
754  // Mix of object and value/list in "instances" list.
755  // First element is an object. Rest are expected to be objects too.
756  req.Clear();
757  status = FillPredictRequestFromJson(R"(
758  {
759  "instances": [
760  {
761  "int_tensor": [[1,2],[3,4],[5,6]],
762  "str_tensor": ["foo", "bar"]
763  },
764  [1, 20, 30],
765  {
766  "int_tensor": [[[7,8],[9,0],[1,2]]],
767  "str_tensor": ["baz", "bat"]
768  }
769  ]
770  })",
771  getmap(infomap), &req, &format);
772  ASSERT_TRUE(errors::IsInvalidArgument(status));
773  EXPECT_THAT(status.message(), HasSubstr("Expecting object but got list"));
774 
775  // Mix of object and value/list in "instances" list.
776  // First element is a list. Rest are expected to be list too.
777  infomap.clear();
778  ASSERT_TRUE(
779  TextFormat::ParseFromString("dtype: DT_STRING", &infomap["str_tensor"]));
780  req.Clear();
781  status = FillPredictRequestFromJson(R"(
782  {
783  "instances": [
784  ["baz", "bar"],
785  {
786  "str_tensor": ["baz", "bat"]
787  }
788  ]
789  })",
790  getmap(infomap), &req, &format);
791  ASSERT_TRUE(errors::IsInvalidArgument(status));
792  EXPECT_THAT(status.message(),
793  HasSubstr("Expecting value/list but got object"));
794 }
795 
796 template <const unsigned int parseflags = rapidjson::kParseNanAndInfFlag>
797 Status CompareJson(const string& json1, const string& json2) {
798  rapidjson::Document doc1;
799  if (doc1.Parse<parseflags>(json1.c_str()).HasParseError()) {
800  return errors::InvalidArgument(
801  "LHS JSON Parse error: ",
802  rapidjson::GetParseError_En(doc1.GetParseError()),
803  " at offset: ", doc1.GetErrorOffset(), " JSON: ", json1);
804  }
805  rapidjson::Document doc2;
806  if (doc2.Parse<parseflags>(json2.c_str()).HasParseError()) {
807  return errors::InvalidArgument(
808  "RHS JSON Parse error: ",
809  rapidjson::GetParseError_En(doc2.GetParseError()),
810  " at offset: ", doc2.GetErrorOffset(), " JSON: ", json2);
811  }
812  if (doc1 != doc2) {
813  return errors::InvalidArgument("JSON Different. JSON1: ", json1,
814  "JSON2: ", json2);
815  }
816  return OkStatus();
817 }
818 
819 // Compare two JSON documents treating values (including numbers) as strings.
820 //
821 // Use this if you are comparing JSON with decimal numbers that can have
822 // NaN or +/-Inf numbers, as comparing them numerically will not work, due
823 // to the approximate nature of decimal representation.
824 //
825 // For most use cases, prefer CompareJson() defined above.
826 Status CompareJsonAllValuesAsStrings(const string& json1, const string& json2) {
827  return CompareJson<rapidjson::kParseNanAndInfFlag |
828  rapidjson::kParseNumbersAsStringsFlag>(json1, json2);
829 }
830 
831 TEST(JsontensorTest, FromJsonSingleTensor) {
832  TensorMap tensormap;
833  ASSERT_TRUE(TextFormat::ParseFromString(R"(
834  dtype: DT_INT32
835  tensor_shape {
836  dim { size: 2 }
837  dim { size: 3 }
838  dim { size: 2 }
839  }
840  int_val: 1
841  int_val: 2
842  int_val: 3
843  int_val: 4
844  int_val: 5
845  int_val: 6
846  int_val: 7
847  int_val: 8
848  int_val: 9
849  int_val: 0
850  int_val: 1
851  int_val: 2
852  )",
853  &tensormap["int_tensor"]));
854 
855  string json;
856  TF_EXPECT_OK(
857  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
858  TF_EXPECT_OK(CompareJson(json, R"({
859  "predictions": [[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 0], [1, 2]]
860  ]})"));
861 
862  json.clear();
863  TF_EXPECT_OK(MakeJsonFromTensors(tensormap,
864  JsonPredictRequestFormat::kColumnar, &json));
865  TF_EXPECT_OK(CompareJson(json, R"({
866  "outputs": [[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 0], [1, 2]]
867  ]})"));
868 }
869 
870 TEST(JsontensorTest, FromJsonSingleScalarTensor) {
871  TensorMap tensormap;
872  ASSERT_TRUE(TextFormat::ParseFromString(R"(
873  dtype: DT_INT32
874  tensor_shape {
875  dim { size: 5 }
876  }
877  int_val: 1
878  int_val: 2
879  int_val: 3
880  int_val: 4
881  int_val: 5
882  )",
883  &tensormap["int_tensor"]));
884 
885  string json;
886  TF_EXPECT_OK(
887  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
888  TF_EXPECT_OK(CompareJson(json, R"({ "predictions": [1, 2, 3, 4, 5] })"));
889 
890  json.clear();
891  TF_EXPECT_OK(MakeJsonFromTensors(tensormap,
892  JsonPredictRequestFormat::kColumnar, &json));
893  TF_EXPECT_OK(CompareJson(json, R"({ "outputs": [1, 2, 3, 4, 5] })"));
894 }
895 
896 TEST(JsontensorTest, FromJsonSingleBytesTensor) {
897  TensorMap tensormap;
898  ASSERT_TRUE(TextFormat::ParseFromString(R"(
899  dtype: DT_STRING
900  tensor_shape {
901  dim { size: 2 }
902  dim { size: 2 }
903  }
904  string_val: "hello"
905  string_val: "world"
906  string_val: "tf"
907  string_val: "serving"
908  )",
909  &tensormap["str_tensor_bytes"]));
910 
911  string json;
912  TF_EXPECT_OK(
913  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
914  TF_EXPECT_OK(CompareJson(json, R"({
915  "predictions": [
916  [{"b64": "aGVsbG8="}, {"b64": "d29ybGQ="}],
917  [{"b64": "dGY="}, {"b64": "c2VydmluZw=="}]
918  ]})"));
919 }
920 
921 // Tests StrCat() six-digit precision float output is correctly
922 // represented in the final (output) JSON string.
923 TEST(JsontensorTest, FromJsonSingleFloatTensorSixDigitPrecision) {
924  TensorMap tensormap;
925  ASSERT_TRUE(TextFormat::ParseFromString(R"(
926  dtype: DT_FLOAT
927  tensor_shape {
928  dim { size: 2 }
929  dim { size: 3 }
930  }
931  float_val: 9000000
932  float_val: 999999
933  float_val: 0.50000
934  float_val: .0003
935  float_val: .00003
936  float_val: 555557.5
937  )",
938  &tensormap["float_tensor"]));
939 
940  string json;
941  TF_EXPECT_OK(
942  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
943  TF_EXPECT_OK(CompareJsonAllValuesAsStrings(json, R"({
944  "predictions": [
945  [9e+06, 999999.0, 0.5],
946  [0.0003, 3e-05, 555557.5]
947  ]})"));
948 }
949 
950 TEST(JsontensorTest, FromJsonSingleFloatTensorNonFinite) {
951  TensorMap tensormap;
952  ASSERT_TRUE(TextFormat::ParseFromString(R"(
953  dtype: DT_FLOAT
954  tensor_shape {
955  dim { size: 2 }
956  dim { size: 2 }
957  }
958  float_val: NaN
959  float_val: Infinity
960  float_val: 3
961  float_val: -Infinity
962  )",
963  &tensormap["float_tensor"]));
964 
965  string json;
966  TF_EXPECT_OK(
967  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
968  TF_EXPECT_OK(CompareJsonAllValuesAsStrings(json, R"({
969  "predictions": [
970  [NaN, Infinity],
971  [3.0, -Infinity]
972  ]})"));
973 }
974 
975 TEST(JsontensorTest, FromJsonSingleTensorErrors) {
976  TensorMap tensormap;
977  string json;
978  Status status;
979 
980  status =
981  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json);
982  ASSERT_TRUE(errors::IsInvalidArgument(status));
983  EXPECT_THAT(status.message(), HasSubstr("empty tensor map"));
984 
985  ASSERT_TRUE(TextFormat::ParseFromString(R"(
986  dtype: DT_COMPLEX64
987  tensor_shape {
988  dim { size: 2 }
989  }
990  scomplex_val: 1.0
991  scomplex_val: 2.0
992  )",
993  &tensormap["tensor"]));
994  status =
995  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json);
996  ASSERT_TRUE(errors::IsInvalidArgument(status));
997  EXPECT_THAT(status.message(), HasSubstr("tensor type: complex64"));
998 
999  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1000  dtype: DT_INT32
1001  int_val: 1
1002  )",
1003  &tensormap["tensor"]));
1004  status =
1005  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json);
1006  ASSERT_TRUE(errors::IsInvalidArgument(status));
1007  EXPECT_THAT(status.message(), HasSubstr("no shape information"));
1008 }
1009 
1010 TEST(JsontensorTest, FromJsonMultipleNamedTensors) {
1011  TensorMap tensormap;
1012  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1013  dtype: DT_INT32
1014  tensor_shape {
1015  dim { size: 2 }
1016  dim { size: 3 }
1017  dim { size: 2 }
1018  }
1019  int_val: 1
1020  int_val: 2
1021  int_val: 3
1022  int_val: 4
1023  int_val: 5
1024  int_val: 6
1025  int_val: 7
1026  int_val: 8
1027  int_val: 9
1028  int_val: 0
1029  int_val: 1
1030  int_val: 2
1031  )",
1032  &tensormap["int_tensor"]));
1033 
1034  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1035  dtype: DT_STRING
1036  tensor_shape {
1037  dim { size: 2 }
1038  dim { size: 2 }
1039  }
1040  string_val: "foo"
1041  string_val: "bar"
1042  string_val: "baz"
1043  string_val: "bat"
1044  )",
1045  &tensormap["str_tensor"]));
1046 
1047  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1048  dtype: DT_FLOAT
1049  tensor_shape {
1050  dim { size: 2 }
1051  dim { size: 1 }
1052  }
1053  float_val: 1.0
1054  float_val: 2.0
1055  )",
1056  &tensormap["float_tensor"]));
1057 
1058  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1059  dtype: DT_DOUBLE
1060  tensor_shape {
1061  dim { size: 2 }
1062  }
1063  double_val: 8.0
1064  double_val: 9.0
1065  )",
1066  &tensormap["double_scalar_tensor"]));
1067 
1068  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1069  dtype: DT_STRING
1070  tensor_shape {
1071  dim { size: 2 }
1072  dim { size: 2 }
1073  }
1074  string_val: "hello"
1075  string_val: "world"
1076  string_val: "tf"
1077  string_val: "serving"
1078  )",
1079  &tensormap["str_tensor_bytes"]));
1080 
1081  string json;
1082  TF_EXPECT_OK(
1083  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
1084  TF_EXPECT_OK(CompareJson(json, R"({
1085  "predictions": [
1086  {
1087  "double_scalar_tensor": 8.0,
1088  "float_tensor": [1.0],
1089  "int_tensor": [[1, 2], [3, 4], [5, 6]],
1090  "str_tensor": ["foo", "bar"],
1091  "str_tensor_bytes": [ {"b64": "aGVsbG8="}, {"b64": "d29ybGQ="} ]
1092  },
1093  {
1094  "double_scalar_tensor": 9.0,
1095  "float_tensor": [2.0],
1096  "int_tensor": [[7, 8], [9, 0], [1, 2]],
1097  "str_tensor": ["baz", "bat"],
1098  "str_tensor_bytes": [ {"b64": "dGY="}, {"b64": "c2VydmluZw=="} ]
1099  }
1100  ]})"));
1101 
1102  json.clear();
1103  TF_EXPECT_OK(MakeJsonFromTensors(tensormap,
1104  JsonPredictRequestFormat::kColumnar, &json));
1105  TF_EXPECT_OK(CompareJson(json, R"({
1106  "outputs": {
1107  "double_scalar_tensor": [8.0, 9.0],
1108  "float_tensor": [[1.0], [2.0]],
1109  "int_tensor": [[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 0], [1, 2]]],
1110  "str_tensor": [["foo", "bar"], ["baz", "bat"]],
1111  "str_tensor_bytes": [[{"b64": "aGVsbG8="}, {"b64": "d29ybGQ="}],
1112  [{"b64": "dGY="}, {"b64": "c2VydmluZw=="}]]
1113  }})"));
1114 }
1115 
1116 TEST(JsontensorTest, FromJsonMultipleNamedTensorsErrors) {
1117  TensorMap tensormap;
1118  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1119  dtype: DT_INT32
1120  tensor_shape {
1121  dim { size: 2 }
1122  dim { size: 3 }
1123  }
1124  int_val: 1
1125  int_val: 2
1126  int_val: 3
1127  int_val: 4
1128  int_val: 5
1129  int_val: 6
1130  )",
1131  &tensormap["int_tensor"]));
1132 
1133  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1134  dtype: DT_STRING
1135  tensor_shape {
1136  dim { size: 1 }
1137  dim { size: 3 }
1138  }
1139  string_val: "foo"
1140  string_val: "bar"
1141  string_val: "baz"
1142  )",
1143  &tensormap["str_tensor"]));
1144 
1145  string json;
1146  const auto& status =
1147  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json);
1148  ASSERT_TRUE(errors::IsInvalidArgument(status));
1149  EXPECT_THAT(status.message(), HasSubstr("inconsistent batch size"));
1150 }
1151 
1152 TEST(JsontensorTest, FromJsonSingleZeroBatchTensor) {
1153  TensorMap tensormap;
1154  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1155  dtype: DT_INT32
1156  tensor_shape {
1157  dim { size: 0 }
1158  dim { size: 2 }
1159  }
1160  )",
1161  &tensormap["int_tensor"]));
1162 
1163  string json;
1164  TF_EXPECT_OK(
1165  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
1166  TF_EXPECT_OK(CompareJson(json, R"({ "predictions": [] })"));
1167 }
1168 
1169 TEST(JsontensorTest, FromJsonMultipleZeroBatchTensors) {
1170  TensorMap tensormap;
1171  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1172  dtype: DT_STRING
1173  tensor_shape {
1174  dim { size: 0 }
1175  dim { size: 1 }
1176  dim { size: 2 }
1177  }
1178  )",
1179  &tensormap["str_tensor"]));
1180 
1181  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1182  dtype: DT_FLOAT
1183  tensor_shape {
1184  dim { size: 0 }
1185  dim { size: 3 }
1186  dim { size: 4 }
1187  }
1188  )",
1189  &tensormap["float_tensor"]));
1190 
1191  string json;
1192  TF_EXPECT_OK(
1193  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json));
1194  TF_EXPECT_OK(CompareJson(json, R"({ "predictions": [] })"));
1195 }
1196 
1197 TEST(JsontensorTest, FromJsonMultipleZeroBatchTensorsErrors) {
1198  TensorMap tensormap;
1199  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1200  dtype: DT_INT32
1201  tensor_shape {
1202  dim { size: 0 }
1203  dim { size: 2 }
1204  }
1205  )",
1206  &tensormap["int_tensor"]));
1207 
1208  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1209  dtype: DT_STRING
1210  tensor_shape {
1211  dim { size: 1 }
1212  dim { size: 2 }
1213  }
1214  string_val: "foo"
1215  string_val: "bar"
1216  )",
1217  &tensormap["str_tensor"]));
1218 
1219  string json;
1220  const auto& status =
1221  MakeJsonFromTensors(tensormap, JsonPredictRequestFormat::kRow, &json);
1222  ASSERT_TRUE(errors::IsInvalidArgument(status));
1223  EXPECT_THAT(status.message(), HasSubstr("inconsistent batch size"));
1224 }
1225 
1226 template <typename RequestType>
1227 class ClassifyRegressRequestTest : public ::testing::Test {
1228  protected:
1229  Status FillRequest(const string& json, ClassificationRequest* req) {
1230  return FillClassificationRequestFromJson(json, req);
1231  }
1232 
1233  Status FillRequest(const string& json, RegressionRequest* req) {
1234  return FillRegressionRequestFromJson(json, req);
1235  }
1236 };
1237 
1238 typedef ::testing::Types<ClassificationRequest, RegressionRequest> RequestTypes;
1239 TYPED_TEST_CASE(ClassifyRegressRequestTest, RequestTypes);
1240 
1241 TYPED_TEST(ClassifyRegressRequestTest, RequestNoContext) {
1242  TypeParam req;
1243  TF_EXPECT_OK(this->FillRequest(R"(
1244  {
1245  "examples": [
1246  {
1247  "names": [ "foo", "bar" ],
1248  "ratings": [ 8.0, 9.0 ],
1249  "age": 20
1250  },
1251  {
1252  "names": [ "baz" ],
1253  "ratings": 6.0
1254  }
1255  ]
1256  })",
1257  &req));
1258 
1259  TypeParam expected_req;
1260  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1261  input {
1262  example_list {
1263  examples {
1264  features {
1265  feature {
1266  key: "names"
1267  value { bytes_list {
1268  value: "foo"
1269  value: "bar"
1270  }}
1271  }
1272  feature {
1273  key: "ratings"
1274  value { float_list {
1275  value: 8.0
1276  value: 9.0
1277  }}
1278  }
1279  feature {
1280  key: "age"
1281  value { int64_list {
1282  value: 20
1283  }}
1284  }
1285  }
1286  }
1287  examples {
1288  features {
1289  feature {
1290  key: "names"
1291  value { bytes_list {
1292  value: "baz"
1293  }}
1294  }
1295  feature {
1296  key: "ratings"
1297  value { float_list {
1298  value: 6.0
1299  }}
1300  }
1301  }
1302  }
1303  }
1304  }
1305  )",
1306  &expected_req));
1307  EXPECT_TRUE(MessageDifferencer::ApproximatelyEquals(req, expected_req))
1308  << "Expected Proto: " << expected_req.DebugString();
1309 }
1310 
1311 TYPED_TEST(ClassifyRegressRequestTest, RequestWithContextAndSignature) {
1312  TypeParam req;
1313  TF_EXPECT_OK(this->FillRequest(R"(
1314  {
1315  "signature_name": "custom_signture",
1316  "context": {
1317  "query": "pizza",
1318  "location": [ "sfo" ]
1319  },
1320  "examples": [
1321  {
1322  "names": [ "foo", "bar" ],
1323  "ratings": [ 8.0, 9.0, NaN, Infinity ],
1324  "age": 20
1325  },
1326  {
1327  "names": [ "baz", { "b64": "aGVsbG8=" } ],
1328  "ratings": 6.0,
1329  "intboolmix": [ 1, true, false ]
1330  },
1331  {
1332  "names": "bar",
1333  "ratings": -Infinity
1334  }
1335  ]
1336  })",
1337  &req));
1338 
1339  TypeParam expected_req;
1340  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1341  model_spec {
1342  signature_name: "custom_signture"
1343  }
1344  input {
1345  example_list_with_context {
1346  context {
1347  features {
1348  feature {
1349  key: "query"
1350  value { bytes_list {
1351  value: "pizza"
1352  }}
1353  }
1354  feature {
1355  key: "location"
1356  value { bytes_list {
1357  value: "sfo"
1358  }}
1359  }
1360  }
1361  }
1362  examples {
1363  features {
1364  feature {
1365  key: "names"
1366  value { bytes_list {
1367  value: "foo"
1368  value: "bar"
1369  }}
1370  }
1371  feature {
1372  key: "ratings"
1373  value { float_list {
1374  value: 8.0
1375  value: 9.0
1376  value: NaN
1377  value: Infinity
1378  }}
1379  }
1380  feature {
1381  key: "age"
1382  value { int64_list {
1383  value: 20
1384  }}
1385  }
1386  }
1387  }
1388  examples {
1389  features {
1390  feature {
1391  key: "names"
1392  value { bytes_list {
1393  value: "baz"
1394  value: "hello"
1395  }}
1396  }
1397  feature {
1398  key: "ratings"
1399  value { float_list {
1400  value: 6.0
1401  }}
1402  }
1403  feature {
1404  key: "intboolmix"
1405  value { int64_list {
1406  value: 1
1407  value: 1
1408  value: 0
1409  }}
1410  }
1411  }
1412  }
1413  examples {
1414  features {
1415  feature {
1416  key: "names"
1417  value { bytes_list {
1418  value: "bar"
1419  }}
1420  }
1421  feature {
1422  key: "ratings"
1423  value { float_list {
1424  value: -Infinity
1425  }}
1426  }
1427  }
1428  }
1429 
1430  }
1431  }
1432  )",
1433  &expected_req));
1434  DefaultFieldComparator comparator;
1435  comparator.set_float_comparison(DefaultFieldComparator::APPROXIMATE);
1436  comparator.set_treat_nan_as_equal(true);
1437  MessageDifferencer differencer;
1438  differencer.set_field_comparator(&comparator);
1439  EXPECT_TRUE(differencer.Compare(req, expected_req))
1440  << "Expected proto: " << expected_req.DebugString()
1441  << "But got proto: " << req.DebugString();
1442 }
1443 
1444 TYPED_TEST(ClassifyRegressRequestTest, JsonErrors) {
1445  TypeParam req;
1446  auto status = this->FillRequest(R"(
1447  {
1448  "signature_name": [ "hello" ],
1449  "examples": [ { "names": [ "foo", "bar" ] } ]
1450  })",
1451  &req);
1452  ASSERT_TRUE(errors::IsInvalidArgument(status));
1453  EXPECT_THAT(status.message(),
1454  HasSubstr("'signature_name' key must be a string"));
1455 
1456  req.Clear();
1457  status = this->FillRequest(R"(
1458  {
1459  "context": [ { "names": [ "foo", "bar" ] } ]
1460  })",
1461  &req);
1462  ASSERT_TRUE(errors::IsInvalidArgument(status));
1463  EXPECT_THAT(status.message(), HasSubstr("Example must be JSON object"));
1464 
1465  req.Clear();
1466  status = this->FillRequest(R"(
1467  {
1468  "examples": { "names": [ "foo", "bar" ] }
1469  })",
1470  &req);
1471  ASSERT_TRUE(errors::IsInvalidArgument(status));
1472  EXPECT_THAT(status.message(), HasSubstr("list/array"));
1473 
1474  req.Clear();
1475  status = this->FillRequest(R"(
1476  {
1477  "examples": [ [ { "names": [ "foo", "bar" ] } ] ]
1478  })",
1479  &req);
1480  ASSERT_TRUE(errors::IsInvalidArgument(status));
1481  EXPECT_THAT(status.message(), HasSubstr("Example must be JSON object"));
1482 
1483  req.Clear();
1484  status = this->FillRequest(R"(
1485  {
1486  "examples": [ { "names": [ 10, null ] } ]
1487  })",
1488  &req);
1489  ASSERT_TRUE(errors::IsInvalidArgument(status));
1490  EXPECT_THAT(status.message(),
1491  HasSubstr("names has element with unexpected JSON type: Null"));
1492 
1493  req.Clear();
1494  status = this->FillRequest(R"(
1495  {
1496  "examples": [ { "names": [ 10, 10.0 ] } ]
1497  })",
1498  &req);
1499  ASSERT_TRUE(errors::IsInvalidArgument(status));
1500  EXPECT_THAT(status.message(),
1501  HasSubstr("feature: names expecting type: int64"));
1502 
1503  req.Clear();
1504  status = this->FillRequest(R"(
1505  {
1506  "examples": [ { "names": [ 10, { "test": 10 } ] } ]
1507  })",
1508  &req);
1509  ASSERT_TRUE(errors::IsInvalidArgument(status));
1510  EXPECT_THAT(status.message(),
1511  HasSubstr("names has element with unexpected JSON type: Object"));
1512 
1513  req.Clear();
1514  status = this->FillRequest(R"(
1515  {
1516  "examples": [ { "names": [ [10], 20 ] } ]
1517  })",
1518  &req);
1519  ASSERT_TRUE(errors::IsInvalidArgument(status));
1520  EXPECT_THAT(status.message(),
1521  HasSubstr("names has element with unexpected JSON type: Array"));
1522 
1523  req.Clear();
1524  status = this->FillRequest(R"(
1525  {
1526  "examples": [ { "names": [ 20, 18446744073709551603 ] } ]
1527  })",
1528  &req);
1529  ASSERT_TRUE(errors::IsInvalidArgument(status));
1530  EXPECT_THAT(status.message(), HasSubstr("Only int64_t is supported"));
1531 }
1532 
1533 TEST(ClassifyRegressnResultTest, JsonFromClassificationResult) {
1534  ClassificationResult result;
1535  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1536  classifications {
1537  classes { label: "car" score: 0.2 }
1538  classes { label: "bike" score: 0.7 }
1539  classes { label: "bus" score: 0.1 }
1540  }
1541  classifications {
1542  classes { label: "car" score: 0.7 }
1543  classes { label: "bike" score: 0.1 }
1544  classes { label: "bus" score: 0.2 }
1545  })",
1546  &result));
1547 
1548  string json;
1549  TF_EXPECT_OK(MakeJsonFromClassificationResult(result, &json));
1550  TF_EXPECT_OK(CompareJson(json, R"({
1551  "results": [
1552  [ ["car", 0.2], ["bike", 0.7], ["bus", 0.1] ],
1553  [ ["car", 0.7], ["bike", 0.1], ["bus", 0.2] ]
1554  ]
1555  })"));
1556 }
1557 
1558 TEST(ClassifyRegressnResultTest, JsonFromRegressionResult) {
1559  RegressionResult result;
1560  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1561  regressions { value: 0.2 }
1562  regressions { value: 0.9 }
1563  regressions { value: 1.0 }
1564  )",
1565  &result));
1566 
1567  string json;
1568  TF_EXPECT_OK(MakeJsonFromRegressionResult(result, &json));
1569  TF_EXPECT_OK(CompareJson(json, R"({ "results": [ 0.2, 0.9, 1.0 ] })"));
1570 }
1571 
1572 TEST(ClassifyRegressnResultTest, JsonFromRegressionResultWithNonFinite) {
1573  RegressionResult result;
1574  ASSERT_TRUE(TextFormat::ParseFromString(R"(
1575  regressions { value: 0.2 }
1576  regressions { value: Infinity }
1577  regressions { value: 1.0 }
1578  regressions { value: -Infinity }
1579  regressions { value: NaN }
1580  )",
1581  &result));
1582 
1583  string json;
1584  TF_EXPECT_OK(MakeJsonFromRegressionResult(result, &json));
1585  TF_EXPECT_OK(CompareJsonAllValuesAsStrings(
1586  json, R"({ "results": [ 0.2, Infinity, 1.0, -Infinity, NaN ] })"));
1587 }
1588 
1589 TEST(ClassifyRegressnResultTest, JsonFromResultErrors) {
1590  string json;
1591  auto status = MakeJsonFromClassificationResult(ClassificationResult(), &json);
1592  ASSERT_TRUE(errors::IsInvalidArgument(status));
1593  EXPECT_THAT(status.message(), HasSubstr("empty ClassificationResults"));
1594 
1595  status = MakeJsonFromRegressionResult(RegressionResult(), &json);
1596  ASSERT_TRUE(errors::IsInvalidArgument(status));
1597  EXPECT_THAT(status.message(), HasSubstr("empty RegressionResults"));
1598 }
1599 
1600 TEST(MakeJsonFromTensors, StatusOK) {
1601  string json;
1602  MakeJsonFromStatus(OkStatus(), &json);
1603  EXPECT_EQ(json, "");
1604 }
1605 
1606 TEST(MakeJsonFromTensors, StatusError) {
1607  string json;
1608  MakeJsonFromStatus(errors::InvalidArgument("Bad key: \"key\""), &json);
1609  TF_EXPECT_OK(CompareJson(json, R"({ "error": "Bad key: \"key\"" })"));
1610 
1611  json.clear();
1612  MakeJsonFromStatus(errors::InvalidArgument("Bad key: 'key'"), &json);
1613  TF_EXPECT_OK(CompareJson(json, R"({ "error": "Bad key: 'key'" })"));
1614 }
1615 
1616 } // namespace
1617 } // namespace serving
1618 } // namespace tensorflow