1 /* Copyright 2019 The TensorFlow Authors. 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 #include <memory>
16 #include <numeric>
17 #include <vector>
18 
19 #include "tensorflow/core/framework/fake_input.h"
20 #include "tensorflow/core/framework/node_def_builder.h"
21 #include "tensorflow/core/framework/shape_inference_testutil.h"
22 #include "tensorflow/core/framework/tensor.h"
23 #include "tensorflow/core/framework/tensor_shape.h"
24 #include "tensorflow/core/framework/types.h"
25 #include "tensorflow/core/framework/types.pb.h"
26 #include "tensorflow/core/kernels/ops_testutil.h"
27 #include "tensorflow/core/lib/core/errors.h"
28 #include "tensorflow/core/lib/core/status.h"
29 #include "tensorflow/core/lib/core/status_test_util.h"
30 #include "tensorflow/core/platform/test.h"
31 
32 namespace tensorflow {
33 namespace {
MakeNodeDef(DataType dtype,NodeDef * node_def)34 Status MakeNodeDef(DataType dtype, NodeDef* node_def) {
35   return NodeDefBuilder("fingerprint", "Fingerprint")
36       .Input(FakeInput(dtype))
37       .Input(FakeInput(DT_STRING))
38       .Finalize(node_def);
39 }
40 
41 class FingerprintOpTest : public OpsTestBase {
42  protected:
MakeFingerprintOp(Tensor * tensor)43   Status MakeFingerprintOp(Tensor* tensor) {
44     return MakeFingerprintOp(tensor, "farmhash64");
45   }
46 
MakeFingerprintOp(Tensor * data,const string & method)47   Status MakeFingerprintOp(Tensor* data, const string& method) {
48     TF_RETURN_IF_ERROR(MakeNodeDef(data->dtype(), node_def()));
49     TF_RETURN_IF_ERROR(InitOp());
50 
51     inputs_.clear();
52     inputs_.push_back(TensorValue(data));
53 
54     method_ = Tensor(DT_STRING, TensorShape{});
55     method_.scalar<tstring>()() = method;
56     inputs_.push_back(TensorValue(&method_));
57     return Status::OK();
58   }
59 
60   Tensor batch_dims_;
61   Tensor method_;
62 };
63 
TEST_F(FingerprintOpTest,Empty)64 TEST_F(FingerprintOpTest, Empty) {
65   Tensor tensor(DT_UINT8, {0});
66 
67   TF_ASSERT_OK(MakeFingerprintOp(&tensor));
68   TF_ASSERT_OK(RunOpKernel());
69   EXPECT_EQ(GetOutput(0)->shape(), (TensorShape{0, 8}));
70   EXPECT_EQ(GetOutput(0)->tensor_data(), "");
71 }
72 
73 // This test detects changes in fingerprint method.
TEST_F(FingerprintOpTest,GoldenValue)74 TEST_F(FingerprintOpTest, GoldenValue) {
75   Tensor tensor(DT_UINT8, {1, 3, 4, 5, 6, 7});
76   auto buffer = tensor.flat<uint8>();
77   std::iota(buffer.data(), buffer.data() + buffer.size(),
78             static_cast<uint8>(47));
79 
80   TF_ASSERT_OK(MakeFingerprintOp(&tensor));
81   TF_ASSERT_OK(RunOpKernel());
82   EXPECT_EQ(GetOutput(0)->shape(), (TensorShape{1, 8}));
83   EXPECT_EQ(GetOutput(0)->tensor_data(), "\x2d\x90\xdf\x03\x79\x36\x3c\x43");
84 }
85 
86 // String types have a different compute path. This test detects changes in this
87 // special-case handling.
TEST_F(FingerprintOpTest,StringGoldenValue)88 TEST_F(FingerprintOpTest, StringGoldenValue) {
89   Tensor data(DT_STRING, {1, 2, 2});
90   auto buffer = data.flat<tstring>();
91   buffer(0).resize(10);
92   buffer(1).resize(7);
93   buffer(2).resize(0);
94   buffer(3).resize(19);
95   std::iota(&buffer(0)[0], &buffer(0)[0] + buffer(0).size(), 0);
96   std::iota(&buffer(1)[0], &buffer(1)[0] + buffer(1).size(), 7);
97   std::iota(&buffer(2)[0], &buffer(2)[0] + buffer(2).size(), 71);
98   std::iota(&buffer(3)[0], &buffer(3)[0] + buffer(3).size(), 41);
99 
100   TF_ASSERT_OK(MakeFingerprintOp(&data));
101   TF_ASSERT_OK(RunOpKernel());
102   ASSERT_EQ(GetOutput(0)->shape(), (TensorShape{1, 8}));
103   EXPECT_EQ(GetOutput(0)->tensor_data(), "\x92\x43\x28\x52\xa3\x7c\x48\x18");
104 
105   // When each batch item has exactly one string, Fingerprint op avoids
106   // double-fingerprint. Adding a test to detect any change in this logic.
107   ASSERT_TRUE(data.CopyFrom(data, TensorShape{4}));
108   TF_ASSERT_OK(MakeFingerprintOp(&data));
109   TF_ASSERT_OK(RunOpKernel());
110   ASSERT_EQ(GetOutput(0)->shape(), (TensorShape{4, 8}));
111   EXPECT_EQ(GetOutput(0)->tensor_data(),
112             "\xea\xff\xd6\xb2\xb2\x4d\x70\x9b"
113             "\x6e\x9d\xed\x21\xc6\x4a\x61\x52"
114             "\x4f\x40\x90\x2f\x3b\x6a\xe1\x9a"
115             "\x0d\x9b\x7f\x63\x23\x14\x1c\xb8");
116 }
117 
TEST_F(FingerprintOpTest,Collision)118 TEST_F(FingerprintOpTest, Collision) {
119   const TensorShape shape = {1, 2, 4, 6};
120   for (DataType dtype : kRealNumberTypes) {
121     const int64 size = shape.num_elements() * DataTypeSize(dtype);
122 
123     Tensor tensor(dtype, shape);
124     auto buffer = tensor.bit_casted_shaped<uint8, 1>({size});
125     buffer.setRandom();
126 
127     TF_ASSERT_OK(MakeFingerprintOp(&tensor));
128     TF_ASSERT_OK(RunOpKernel());
129     const Tensor fingerprint0 = *GetOutput(0);
130 
131     // Alter a byte value in the buffer.
132     const int offset = buffer(0) % buffer.size();
133     buffer(offset) = ~buffer(offset);
134 
135     TF_ASSERT_OK(MakeFingerprintOp(&tensor));
136     TF_ASSERT_OK(RunOpKernel());
137     const Tensor fingerprint1 = *GetOutput(0);
138 
139     EXPECT_NE(fingerprint0.tensor_data(), fingerprint1.tensor_data());
140   }
141 }
142 
TEST_F(FingerprintOpTest,CollisionString)143 TEST_F(FingerprintOpTest, CollisionString) {
144   constexpr int64 size = 256;
145 
146   Tensor tensor(DT_STRING, {1});
147   auto& input = tensor.vec<tstring>()(0);
148   input.resize(size);
149 
150   TTypes<uint8>::UnalignedFlat buffer(reinterpret_cast<uint8*>(&input[0]),
151                                       input.size());
152   buffer.setRandom();
153 
154   TF_ASSERT_OK(MakeFingerprintOp(&tensor));
155   TF_ASSERT_OK(RunOpKernel());
156   const Tensor fingerprint0 = *GetOutput(0);
157 
158   // Alter a byte value in the buffer.
159   const int offset = buffer(0) % buffer.size();
160   buffer(offset) = ~buffer(offset);
161 
162   TF_ASSERT_OK(MakeFingerprintOp(&tensor));
163   TF_ASSERT_OK(RunOpKernel());
164   const Tensor fingerprint1 = *GetOutput(0);
165 
166   EXPECT_NE(fingerprint0.tensor_data(), fingerprint1.tensor_data());
167 }
168 
TEST_F(FingerprintOpTest,CompareBytesAndString)169 TEST_F(FingerprintOpTest, CompareBytesAndString) {
170   Tensor pods_tensor(DT_FLOAT, {4, 64});
171   Tensor strings_tensor(DT_STRING, {4});
172 
173   auto pods = pods_tensor.matrix<float>();
174   pods.setRandom();
175 
176   auto strings = strings_tensor.vec<tstring>();
177   for (int64 i = 0; i < strings.size(); ++i) {
178     strings(i).assign(reinterpret_cast<const char*>(&pods(i, 0)),
179                       pods.dimension(1) * sizeof(pods(i, 0)));
180   }
181 
182   TF_ASSERT_OK(MakeFingerprintOp(&pods_tensor));
183   TF_ASSERT_OK(RunOpKernel());
184   Tensor pods_fingerprints = *GetOutput(0);
185 
186   TF_ASSERT_OK(MakeFingerprintOp(&strings_tensor));
187   TF_ASSERT_OK(RunOpKernel());
188   Tensor strings_fingerprints = *GetOutput(0);
189 
190   EXPECT_EQ(pods_fingerprints.tensor_data(),
191             strings_fingerprints.tensor_data());
192 }
193 
TEST_F(FingerprintOpTest,SupportedMethods)194 TEST_F(FingerprintOpTest, SupportedMethods) {
195   Tensor tensor(DT_STRING, TensorShape{1});
196   TF_ASSERT_OK(MakeFingerprintOp(&tensor, "unsupported_method"));
197 
198   const Status status = RunOpKernel();
199   EXPECT_FALSE(status.ok());
200   EXPECT_NE(status.error_message().find("unsupported_method"), string::npos);
201 }
202 
TEST_F(FingerprintOpTest,SupportedTypes)203 TEST_F(FingerprintOpTest, SupportedTypes) {
204   Tensor input(DT_RESOURCE, TensorShape{1});
205   EXPECT_FALSE(MakeFingerprintOp(&input).ok());
206 }
207 
TEST(FingerprintOpShapeFnTest,MethodKnownStatically)208 TEST(FingerprintOpShapeFnTest, MethodKnownStatically) {
209   ShapeInferenceTestOp op("Fingerprint");
210 
211   Tensor method(DT_STRING, TensorShape{});
212   method.scalar<tstring>()() = "farmhash64";
213   op.input_tensors.assign({nullptr, &method});
214 
215   TF_ASSERT_OK(MakeNodeDef(DT_UINT8, &op.node_def));
216   INFER_OK(op, "?;?", "[?,8]");
217   INFER_ERROR("must be at least rank 1", op, "[];?");
218   INFER_OK(op, "[?];?", "[d0_0,8]");
219   INFER_OK(op, "[1,?];?", "[d0_0,8]");
220   INFER_OK(op, "[?,2,3];?", "[d0_0,8]");
221 }
222 
TEST(FingerprintOpShapeFnTest,MethodUnknownStatically)223 TEST(FingerprintOpShapeFnTest, MethodUnknownStatically) {
224   ShapeInferenceTestOp op("Fingerprint");
225 
226   TF_ASSERT_OK(MakeNodeDef(DT_FLOAT, &op.node_def));
227   INFER_OK(op, "?;?", "[?,?]");
228   INFER_ERROR("must be at least rank 1", op, "[];?");
229   INFER_OK(op, "[?];?", "[d0_0,?]");
230   INFER_OK(op, "[1,?];?", "[d0_0,?]");
231   INFER_OK(op, "[?,2,3];?", "[d0_0,?]");
232 }
233 
TEST(FingerprintOpShapeFnTest,InvalidMethod)234 TEST(FingerprintOpShapeFnTest, InvalidMethod) {
235   ShapeInferenceTestOp op("Fingerprint");
236 
237   // When `method` shape is known statically.
238   INFER_ERROR("must be rank 0", op, "[1];[1]");
239 
240   // When `method` shape is unknown statically.
241   Tensor method(DT_STRING, TensorShape{1});
242   method.vec<tstring>()(0) = "farmhash64";
243   op.input_tensors.assign({nullptr, &method});
244   INFER_ERROR("must be rank 0", op, "?;?");
245 
246   method = Tensor(DT_STRING, TensorShape{});
247   method.scalar<tstring>()() = "unsupported_method";
248   op.input_tensors.assign({nullptr, &method});
249   INFER_ERROR("unsupported_method", op, "?;?");
250 }
251 }  // namespace
252 }  // namespace tensorflow
253