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