1 /* Copyright 2015 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
16 #include "tensorflow/c/c_api.h"
17
18 #include <algorithm>
19 #include <cstddef>
20 #include <iterator>
21 #include <memory>
22 #include <vector>
23
24 #include "tensorflow/c/c_test_util.h"
25 #include "tensorflow/cc/saved_model/signature_constants.h"
26 #include "tensorflow/cc/saved_model/tag_constants.h"
27 #include "tensorflow/core/example/example.pb.h"
28 #include "tensorflow/core/example/feature.pb.h"
29 #include "tensorflow/core/framework/api_def.pb.h"
30 #include "tensorflow/core/framework/common_shape_fns.h"
31 #include "tensorflow/core/framework/graph.pb_text.h"
32 #include "tensorflow/core/framework/kernel_def.pb.h"
33 #include "tensorflow/core/framework/node_def.pb_text.h"
34 #include "tensorflow/core/framework/node_def_util.h"
35 #include "tensorflow/core/framework/op.h"
36 #include "tensorflow/core/framework/op_def.pb.h"
37 #include "tensorflow/core/framework/op_kernel.h"
38 #include "tensorflow/core/framework/partial_tensor_shape.h"
39 #include "tensorflow/core/framework/tensor.h"
40 #include "tensorflow/core/framework/tensor_shape.pb.h"
41 #include "tensorflow/core/framework/types.pb.h"
42 #include "tensorflow/core/graph/tensor_id.h"
43 #include "tensorflow/core/lib/core/error_codes.pb.h"
44 #include "tensorflow/core/lib/core/status_test_util.h"
45 #include "tensorflow/core/lib/io/path.h"
46 #include "tensorflow/core/lib/strings/str_util.h"
47 #include "tensorflow/core/lib/strings/strcat.h"
48 #include "tensorflow/core/platform/test.h"
49 #include "tensorflow/core/protobuf/meta_graph.pb.h"
50 #include "tensorflow/core/util/equal_graph_def.h"
51
52 namespace tensorflow {
53 TF_Tensor* TF_TensorFromTensor(const Tensor& src, TF_Status* status);
54 Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst);
55
56 namespace {
57
ExpectHasSubstr(StringPiece s,StringPiece expected)58 static void ExpectHasSubstr(StringPiece s, StringPiece expected) {
59 EXPECT_TRUE(str_util::StrContains(s, expected))
60 << "'" << s << "' does not contain '" << expected << "'";
61 }
62
63 // Returns the GPU device name if there is one (with arbitrary tie breaking if
64 // there are more than one), or "" otherwise.
GPUDeviceName(TF_Session * session)65 string GPUDeviceName(TF_Session* session) {
66 std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> status(
67 TF_NewStatus(), TF_DeleteStatus);
68 TF_Status* s = status.get();
69 std::unique_ptr<TF_DeviceList, decltype(&TF_DeleteDeviceList)> list(
70 TF_SessionListDevices(session, s), TF_DeleteDeviceList);
71 TF_DeviceList* device_list = list.get();
72
73 CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
74
75 const int num_devices = TF_DeviceListCount(device_list);
76 LOG(INFO) << "There are " << num_devices << " devices.";
77 for (int i = 0; i < num_devices; ++i) {
78 const char* device_name = TF_DeviceListName(device_list, i, s);
79 CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
80 const char* device_type = TF_DeviceListType(device_list, i, s);
81 CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
82 LOG(INFO) << "Device " << i << " has name " << device_name << ", type "
83 << device_type;
84 if (string(device_type) == DEVICE_GPU) {
85 return device_name;
86 }
87 }
88 // No GPU device found.
89 return "";
90 }
91
GPUDeviceName()92 string GPUDeviceName() {
93 std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> status(
94 TF_NewStatus(), TF_DeleteStatus);
95 TF_Status* s = status.get();
96 std::unique_ptr<TF_Graph, decltype(&TF_DeleteGraph)> graph(TF_NewGraph(),
97 TF_DeleteGraph);
98
99 TF_SessionOptions* opts = TF_NewSessionOptions();
100 TF_Session* sess = TF_NewSession(graph.get(), opts, s);
101 TF_DeleteSessionOptions(opts);
102
103 const string gpu_device_name = GPUDeviceName(sess);
104 TF_DeleteSession(sess, s);
105 CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
106 return gpu_device_name;
107 }
108
TEST(CAPI,Version)109 TEST(CAPI, Version) { EXPECT_STRNE("", TF_Version()); }
110
TEST(CAPI,Status)111 TEST(CAPI, Status) {
112 TF_Status* s = TF_NewStatus();
113 EXPECT_EQ(TF_OK, TF_GetCode(s));
114 EXPECT_EQ(string(), TF_Message(s));
115 TF_SetStatus(s, TF_CANCELLED, "cancel");
116 EXPECT_EQ(TF_CANCELLED, TF_GetCode(s));
117 EXPECT_EQ(string("cancel"), TF_Message(s));
118 TF_DeleteStatus(s);
119 }
120
Deallocator(void * data,size_t,void * arg)121 void Deallocator(void* data, size_t, void* arg) {
122 tensorflow::cpu_allocator()->DeallocateRaw(data);
123 *reinterpret_cast<bool*>(arg) = true;
124 }
125
TEST(CAPI,Tensor)126 TEST(CAPI, Tensor) {
127 const int num_bytes = 6 * sizeof(float);
128 float* values =
129 reinterpret_cast<float*>(tensorflow::cpu_allocator()->AllocateRaw(
130 EIGEN_MAX_ALIGN_BYTES, num_bytes));
131 int64_t dims[] = {2, 3};
132 bool deallocator_called = false;
133 TF_Tensor* t = TF_NewTensor(TF_FLOAT, dims, 2, values, num_bytes,
134 &Deallocator, &deallocator_called);
135 EXPECT_FALSE(deallocator_called);
136 EXPECT_EQ(TF_FLOAT, TF_TensorType(t));
137 EXPECT_EQ(2, TF_NumDims(t));
138 EXPECT_EQ(dims[0], TF_Dim(t, 0));
139 EXPECT_EQ(dims[1], TF_Dim(t, 1));
140 EXPECT_EQ(num_bytes, TF_TensorByteSize(t));
141 EXPECT_EQ(static_cast<void*>(values), TF_TensorData(t));
142 TF_DeleteTensor(t);
143 EXPECT_TRUE(deallocator_called);
144 }
145
NoOpDeallocator(void * data,size_t,void *)146 void NoOpDeallocator(void* data, size_t, void*) {}
147
TEST(CAPI,MalformedTensor)148 TEST(CAPI, MalformedTensor) {
149 // See https://github.com/tensorflow/tensorflow/issues/7394
150 // num_dims = 0 implies a scalar, so should be backed by at least 4 bytes of
151 // data.
152 TF_Tensor* t =
153 TF_NewTensor(TF_FLOAT, nullptr, 0, nullptr, 0, &NoOpDeallocator, nullptr);
154 ASSERT_TRUE(t == nullptr);
155 }
156
TEST(CAPI,AllocateTensor)157 TEST(CAPI, AllocateTensor) {
158 const int num_bytes = 6 * sizeof(float);
159 int64_t dims[] = {2, 3};
160 TF_Tensor* t = TF_AllocateTensor(TF_FLOAT, dims, 2, num_bytes);
161 EXPECT_EQ(TF_FLOAT, TF_TensorType(t));
162 EXPECT_EQ(2, TF_NumDims(t));
163 EXPECT_EQ(dims[0], TF_Dim(t, 0));
164 EXPECT_EQ(dims[1], TF_Dim(t, 1));
165 EXPECT_EQ(num_bytes, TF_TensorByteSize(t));
166 EXPECT_EQ(6, TF_TensorElementCount(t));
167 TF_DeleteTensor(t);
168 }
169
TEST(CAPI,MaybeMove)170 TEST(CAPI, MaybeMove) {
171 const int num_bytes = 6 * sizeof(float);
172 float* values =
173 reinterpret_cast<float*>(tensorflow::cpu_allocator()->AllocateRaw(
174 EIGEN_MAX_ALIGN_BYTES, num_bytes));
175 int64_t dims[] = {2, 3};
176 bool deallocator_called = false;
177 TF_Tensor* t = TF_NewTensor(TF_FLOAT, dims, 2, values, num_bytes,
178 &Deallocator, &deallocator_called);
179
180 TF_Tensor* o = TF_TensorMaybeMove(t);
181 ASSERT_TRUE(o == nullptr); // It is unsafe to move memory TF might not own.
182 TF_DeleteTensor(t);
183 EXPECT_TRUE(deallocator_called);
184 }
185
TEST(CAPI,LibraryLoadFunctions)186 TEST(CAPI, LibraryLoadFunctions) {
187 // TODO(b/73318067): Fix linking for the GPU test generated by the
188 // tf_cuda_cc_test() bazel rule and remove the next line.
189 if (!GPUDeviceName().empty()) return;
190
191 #if !defined(TENSORFLOW_NO_SHARED_OBJECTS)
192 {
193 // Load the library.
194 TF_Status* status = TF_NewStatus();
195 TF_Library* lib =
196 TF_LoadLibrary("tensorflow/c/test_op1.so", status);
197 TF_Code code = TF_GetCode(status);
198 string status_msg(TF_Message(status));
199 TF_DeleteStatus(status);
200 ASSERT_EQ(TF_OK, code) << status_msg;
201
202 // Test op list.
203 TF_Buffer op_list_buf = TF_GetOpList(lib);
204 tensorflow::OpList op_list;
205 EXPECT_TRUE(op_list.ParseFromArray(op_list_buf.data, op_list_buf.length));
206 ASSERT_EQ(op_list.op_size(), 1);
207 EXPECT_EQ("TestCApi1", op_list.op(0).name());
208 TF_DeleteLibraryHandle(lib);
209 }
210 #endif // !defined(TENSORFLOW_NO_SHARED_OBJECTS)
211 {
212 TF_Buffer* op_list_buffer = TF_GetAllOpList();
213 tensorflow::OpList op_list;
214 op_list.ParseFromArray(op_list_buffer->data, op_list_buffer->length);
215 ASSERT_GE(op_list.op_size(), 1);
216 typedef tensorflow::protobuf::RepeatedPtrField<tensorflow::OpDef> OpDefs;
217 const OpDefs& ops = op_list.op();
218 bool found = std::find_if(ops.begin(), ops.end(),
219 [](const tensorflow::OpDef& op_def) {
220 return op_def.name() == "TestCApi";
221 }) != ops.end();
222 EXPECT_TRUE(found);
223 TF_DeleteBuffer(op_list_buffer);
224 }
225 }
226
TestEncodeDecode(int line,const std::vector<string> & data)227 void TestEncodeDecode(int line, const std::vector<string>& data) {
228 const tensorflow::int64 n = data.size();
229 TF_Status* status = TF_NewStatus();
230 for (const std::vector<tensorflow::int64>& dims :
231 std::vector<std::vector<tensorflow::int64>>{
232 {n}, {1, n}, {n, 1}, {n / 2, 2}}) {
233 // Create C++ Tensor
234 Tensor src(tensorflow::DT_STRING, TensorShape(dims));
235 for (tensorflow::int64 i = 0; i < src.NumElements(); ++i) {
236 src.flat<string>()(i) = data[i];
237 }
238 TF_Tensor* dst = TF_TensorFromTensor(src, status);
239 ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
240
241 // Convert back to a C++ Tensor and ensure we get expected output.
242 Tensor output;
243 ASSERT_EQ(Status::OK(), TF_TensorToTensor(dst, &output)) << line;
244 ASSERT_EQ(src.NumElements(), output.NumElements()) << line;
245 for (tensorflow::int64 i = 0; i < src.NumElements(); ++i) {
246 ASSERT_EQ(data[i], output.flat<string>()(i)) << line;
247 }
248
249 TF_DeleteTensor(dst);
250 }
251 TF_DeleteStatus(status);
252 }
253
TEST(CAPI,TensorEncodeDecodeStrings)254 TEST(CAPI, TensorEncodeDecodeStrings) {
255 TestEncodeDecode(__LINE__, {});
256 TestEncodeDecode(__LINE__, {"hello"});
257 TestEncodeDecode(__LINE__,
258 {"the", "quick", "brown", "fox", "jumped", "over"});
259
260 string big(1000, 'a');
261 TestEncodeDecode(__LINE__, {"small", big, "small2"});
262 }
263
TEST(CAPI,SessionOptions)264 TEST(CAPI, SessionOptions) {
265 TF_SessionOptions* opt = TF_NewSessionOptions();
266 TF_DeleteSessionOptions(opt);
267 }
268
TEST(CAPI,DeprecatedSession)269 TEST(CAPI, DeprecatedSession) {
270 TF_Status* s = TF_NewStatus();
271 TF_SessionOptions* opt = TF_NewSessionOptions();
272 TF_DeprecatedSession* session = TF_NewDeprecatedSession(opt, s);
273 TF_DeleteSessionOptions(opt);
274 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
275
276 TF_Buffer* run_options = TF_NewBufferFromString("", 0);
277 TF_Buffer* run_metadata = TF_NewBuffer();
278 TF_Run(session, run_options, nullptr, nullptr, 0, nullptr, nullptr, 0,
279 nullptr, 0, run_metadata, s);
280 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s)) << TF_Message(s);
281 EXPECT_EQ("Session was not created with a graph before Run()!",
282 string(TF_Message(s)));
283 TF_DeleteBuffer(run_metadata);
284 TF_DeleteBuffer(run_options);
285
286 TF_DeleteDeprecatedSession(session, s);
287 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
288
289 TF_DeleteStatus(s);
290 }
291
TEST(CAPI,DataTypeEnum)292 TEST(CAPI, DataTypeEnum) {
293 EXPECT_EQ(TF_FLOAT, static_cast<TF_DataType>(tensorflow::DT_FLOAT));
294 EXPECT_EQ(TF_DOUBLE, static_cast<TF_DataType>(tensorflow::DT_DOUBLE));
295 EXPECT_EQ(TF_INT32, static_cast<TF_DataType>(tensorflow::DT_INT32));
296 EXPECT_EQ(TF_UINT8, static_cast<TF_DataType>(tensorflow::DT_UINT8));
297 EXPECT_EQ(TF_INT16, static_cast<TF_DataType>(tensorflow::DT_INT16));
298 EXPECT_EQ(TF_INT8, static_cast<TF_DataType>(tensorflow::DT_INT8));
299 EXPECT_EQ(TF_STRING, static_cast<TF_DataType>(tensorflow::DT_STRING));
300 EXPECT_EQ(TF_COMPLEX64, static_cast<TF_DataType>(tensorflow::DT_COMPLEX64));
301 EXPECT_EQ(TF_COMPLEX, TF_COMPLEX64);
302 EXPECT_EQ(TF_INT64, static_cast<TF_DataType>(tensorflow::DT_INT64));
303 EXPECT_EQ(TF_BOOL, static_cast<TF_DataType>(tensorflow::DT_BOOL));
304 EXPECT_EQ(TF_QINT8, static_cast<TF_DataType>(tensorflow::DT_QINT8));
305 EXPECT_EQ(TF_QUINT8, static_cast<TF_DataType>(tensorflow::DT_QUINT8));
306 EXPECT_EQ(TF_QINT32, static_cast<TF_DataType>(tensorflow::DT_QINT32));
307 EXPECT_EQ(TF_BFLOAT16, static_cast<TF_DataType>(tensorflow::DT_BFLOAT16));
308 EXPECT_EQ(TF_QINT16, static_cast<TF_DataType>(tensorflow::DT_QINT16));
309 EXPECT_EQ(TF_QUINT16, static_cast<TF_DataType>(tensorflow::DT_QUINT16));
310 EXPECT_EQ(TF_UINT16, static_cast<TF_DataType>(tensorflow::DT_UINT16));
311 EXPECT_EQ(TF_COMPLEX128, static_cast<TF_DataType>(tensorflow::DT_COMPLEX128));
312 EXPECT_EQ(TF_HALF, static_cast<TF_DataType>(tensorflow::DT_HALF));
313 EXPECT_EQ(TF_DataTypeSize(TF_DOUBLE),
314 tensorflow::DataTypeSize(tensorflow::DT_DOUBLE));
315 EXPECT_EQ(TF_DataTypeSize(TF_STRING),
316 tensorflow::DataTypeSize(tensorflow::DT_STRING));
317 // Test with invalid type; should always return 0 as documented
318 EXPECT_EQ(TF_DataTypeSize(static_cast<TF_DataType>(0)), 0);
319 }
320
TEST(CAPI,StatusEnum)321 TEST(CAPI, StatusEnum) {
322 EXPECT_EQ(TF_OK, static_cast<TF_Code>(tensorflow::error::OK));
323 EXPECT_EQ(TF_CANCELLED, static_cast<TF_Code>(tensorflow::error::CANCELLED));
324 EXPECT_EQ(TF_UNKNOWN, static_cast<TF_Code>(tensorflow::error::UNKNOWN));
325 EXPECT_EQ(TF_INVALID_ARGUMENT,
326 static_cast<TF_Code>(tensorflow::error::INVALID_ARGUMENT));
327 EXPECT_EQ(TF_DEADLINE_EXCEEDED,
328 static_cast<TF_Code>(tensorflow::error::DEADLINE_EXCEEDED));
329 EXPECT_EQ(TF_NOT_FOUND, static_cast<TF_Code>(tensorflow::error::NOT_FOUND));
330 EXPECT_EQ(TF_ALREADY_EXISTS,
331 static_cast<TF_Code>(tensorflow::error::ALREADY_EXISTS));
332 EXPECT_EQ(TF_PERMISSION_DENIED,
333 static_cast<TF_Code>(tensorflow::error::PERMISSION_DENIED));
334 EXPECT_EQ(TF_UNAUTHENTICATED,
335 static_cast<TF_Code>(tensorflow::error::UNAUTHENTICATED));
336 EXPECT_EQ(TF_RESOURCE_EXHAUSTED,
337 static_cast<TF_Code>(tensorflow::error::RESOURCE_EXHAUSTED));
338 EXPECT_EQ(TF_FAILED_PRECONDITION,
339 static_cast<TF_Code>(tensorflow::error::FAILED_PRECONDITION));
340 EXPECT_EQ(TF_ABORTED, static_cast<TF_Code>(tensorflow::error::ABORTED));
341 EXPECT_EQ(TF_OUT_OF_RANGE,
342 static_cast<TF_Code>(tensorflow::error::OUT_OF_RANGE));
343 EXPECT_EQ(TF_UNIMPLEMENTED,
344 static_cast<TF_Code>(tensorflow::error::UNIMPLEMENTED));
345 EXPECT_EQ(TF_INTERNAL, static_cast<TF_Code>(tensorflow::error::INTERNAL));
346 EXPECT_EQ(TF_UNAVAILABLE,
347 static_cast<TF_Code>(tensorflow::error::UNAVAILABLE));
348 EXPECT_EQ(TF_DATA_LOSS, static_cast<TF_Code>(tensorflow::error::DATA_LOSS));
349 }
350
TEST(CAPI,GetAllOpList)351 TEST(CAPI, GetAllOpList) {
352 TF_Buffer* buf = TF_GetAllOpList();
353 tensorflow::OpList op_list;
354 EXPECT_TRUE(op_list.ParseFromArray(buf->data, buf->length));
355 EXPECT_GT(op_list.op_size(), 0);
356 TF_DeleteBuffer(buf);
357 }
358
TEST(CAPI,SetShape)359 TEST(CAPI, SetShape) {
360 TF_Status* s = TF_NewStatus();
361 TF_Graph* graph = TF_NewGraph();
362
363 TF_Operation* feed = Placeholder(graph, s);
364 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
365 TF_Output feed_out_0 = TF_Output{feed, 0};
366 int num_dims;
367
368 // Fetch the shape, it should be completely unknown.
369 num_dims = TF_GraphGetTensorNumDims(graph, feed_out_0, s);
370 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
371 EXPECT_EQ(-1, num_dims);
372
373 // Set the shape to be unknown, expect no change.
374 TF_GraphSetTensorShape(graph, feed_out_0, /*dims=*/nullptr, -1, s);
375 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
376 num_dims = TF_GraphGetTensorNumDims(graph, feed_out_0, s);
377 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
378 EXPECT_EQ(-1, num_dims);
379
380 // Set the shape to be 2 x Unknown
381 int64_t dims[] = {2, -1};
382 TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
383 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
384
385 // Fetch the shape and validate it is 2 by -1.
386 num_dims = TF_GraphGetTensorNumDims(graph, feed_out_0, s);
387 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
388 EXPECT_EQ(2, num_dims);
389
390 // Resize the dimension vector appropriately.
391 int64_t returned_dims[2];
392 TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
393 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
394 EXPECT_EQ(dims[0], returned_dims[0]);
395 EXPECT_EQ(dims[1], returned_dims[1]);
396
397 // Set to a new valid shape: [2, 3]
398 dims[1] = 3;
399 TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
400 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
401
402 // Fetch and see that the new value is returned.
403 TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
404 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
405 EXPECT_EQ(dims[0], returned_dims[0]);
406 EXPECT_EQ(dims[1], returned_dims[1]);
407
408 // Try to set 'unknown' with unknown rank on the shape and see that
409 // it doesn't change.
410 TF_GraphSetTensorShape(graph, feed_out_0, /*dims=*/nullptr, -1, s);
411 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
412 TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
413 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
414 EXPECT_EQ(2, num_dims);
415 EXPECT_EQ(2, returned_dims[0]);
416 EXPECT_EQ(3, returned_dims[1]);
417
418 // Try to set 'unknown' with same rank on the shape and see that
419 // it doesn't change.
420 dims[0] = -1;
421 dims[1] = -1;
422 TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
423 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
424 // Fetch and see that the new value is returned.
425 TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
426 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
427 EXPECT_EQ(2, num_dims);
428 EXPECT_EQ(2, returned_dims[0]);
429 EXPECT_EQ(3, returned_dims[1]);
430
431 // Try to fetch a shape with the wrong num_dims
432 TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, 5, s);
433 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s)) << TF_Message(s);
434
435 // Try to set an invalid shape (cannot change 2x3 to a 2x5).
436 dims[1] = 5;
437 TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
438 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s)) << TF_Message(s);
439
440 // Test for a scalar.
441 TF_Operation* three = ScalarConst(3, graph, s);
442 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
443 TF_Output three_out_0 = TF_Output{three, 0};
444
445 num_dims = TF_GraphGetTensorNumDims(graph, three_out_0, s);
446 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
447 EXPECT_EQ(0, num_dims);
448 TF_GraphGetTensorShape(graph, three_out_0, returned_dims, num_dims, s);
449 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
450
451 // Clean up
452 TF_DeleteGraph(graph);
453 TF_DeleteStatus(s);
454 }
455
TEST(CAPI,Graph)456 TEST(CAPI, Graph) {
457 TF_Status* s = TF_NewStatus();
458 TF_Graph* graph = TF_NewGraph();
459
460 // Make a placeholder operation.
461 TF_Operation* feed = Placeholder(graph, s);
462 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
463
464 // Test TF_Operation*() query functions.
465 EXPECT_EQ(string("feed"), string(TF_OperationName(feed)));
466 EXPECT_EQ(string("Placeholder"), string(TF_OperationOpType(feed)));
467 EXPECT_EQ(string(""), string(TF_OperationDevice(feed)));
468 EXPECT_EQ(1, TF_OperationNumOutputs(feed));
469 EXPECT_EQ(TF_INT32, TF_OperationOutputType(TF_Output{feed, 0}));
470 EXPECT_EQ(1, TF_OperationOutputListLength(feed, "output", s));
471 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
472 EXPECT_EQ(0, TF_OperationNumInputs(feed));
473 EXPECT_EQ(0, TF_OperationOutputNumConsumers(TF_Output{feed, 0}));
474 EXPECT_EQ(0, TF_OperationNumControlInputs(feed));
475 EXPECT_EQ(0, TF_OperationNumControlOutputs(feed));
476
477 tensorflow::AttrValue attr_value;
478 ASSERT_TRUE(GetAttrValue(feed, "dtype", &attr_value, s)) << TF_Message(s);
479 EXPECT_EQ(attr_value.type(), tensorflow::DT_INT32);
480
481 // Test not found errors in TF_Operation*() query functions.
482 EXPECT_EQ(-1, TF_OperationOutputListLength(feed, "bogus", s));
483 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s));
484
485 ASSERT_FALSE(GetAttrValue(feed, "missing", &attr_value, s));
486 EXPECT_EQ(string("Operation 'feed' has no attr named 'missing'."),
487 string(TF_Message(s)));
488
489 // Make a constant oper with the scalar "3".
490 TF_Operation* three = ScalarConst(3, graph, s);
491 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
492
493 // Add oper.
494 TF_Operation* add = Add(feed, three, graph, s);
495 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
496
497 // Test TF_Operation*() query functions.
498 EXPECT_EQ(string("add"), string(TF_OperationName(add)));
499 EXPECT_EQ(string("AddN"), string(TF_OperationOpType(add)));
500 EXPECT_EQ(string(""), string(TF_OperationDevice(add)));
501 EXPECT_EQ(1, TF_OperationNumOutputs(add));
502 EXPECT_EQ(TF_INT32, TF_OperationOutputType(TF_Output{add, 0}));
503 EXPECT_EQ(1, TF_OperationOutputListLength(add, "sum", s));
504 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
505 EXPECT_EQ(2, TF_OperationNumInputs(add));
506 EXPECT_EQ(2, TF_OperationInputListLength(add, "inputs", s));
507 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
508 EXPECT_EQ(TF_INT32, TF_OperationInputType(TF_Input{add, 0}));
509 EXPECT_EQ(TF_INT32, TF_OperationInputType(TF_Input{add, 1}));
510 TF_Output add_in_0 = TF_OperationInput(TF_Input{add, 0});
511 EXPECT_EQ(feed, add_in_0.oper);
512 EXPECT_EQ(0, add_in_0.index);
513 TF_Output add_in_1 = TF_OperationInput(TF_Input{add, 1});
514 EXPECT_EQ(three, add_in_1.oper);
515 EXPECT_EQ(0, add_in_1.index);
516 EXPECT_EQ(0, TF_OperationOutputNumConsumers(TF_Output{add, 0}));
517 EXPECT_EQ(0, TF_OperationNumControlInputs(add));
518 EXPECT_EQ(0, TF_OperationNumControlOutputs(add));
519
520 ASSERT_TRUE(GetAttrValue(add, "T", &attr_value, s)) << TF_Message(s);
521 EXPECT_EQ(attr_value.type(), tensorflow::DT_INT32);
522 ASSERT_TRUE(GetAttrValue(add, "N", &attr_value, s)) << TF_Message(s);
523 EXPECT_EQ(attr_value.i(), 2);
524
525 // Placeholder oper now has a consumer.
526 ASSERT_EQ(1, TF_OperationOutputNumConsumers(TF_Output{feed, 0}));
527 TF_Input feed_port;
528 EXPECT_EQ(1, TF_OperationOutputConsumers(TF_Output{feed, 0}, &feed_port, 1));
529 EXPECT_EQ(add, feed_port.oper);
530 EXPECT_EQ(0, feed_port.index);
531
532 // The scalar const oper also has a consumer.
533 ASSERT_EQ(1, TF_OperationOutputNumConsumers(TF_Output{three, 0}));
534 TF_Input three_port;
535 EXPECT_EQ(1,
536 TF_OperationOutputConsumers(TF_Output{three, 0}, &three_port, 1));
537 EXPECT_EQ(add, three_port.oper);
538 EXPECT_EQ(1, three_port.index);
539
540 // Serialize to GraphDef.
541 GraphDef graph_def;
542 ASSERT_TRUE(GetGraphDef(graph, &graph_def));
543
544 // Validate GraphDef is what we expect.
545 bool found_placeholder = false;
546 bool found_scalar_const = false;
547 bool found_add = false;
548 for (const auto& n : graph_def.node()) {
549 if (IsPlaceholder(n)) {
550 EXPECT_FALSE(found_placeholder);
551 found_placeholder = true;
552 } else if (IsScalarConst(n, 3)) {
553 EXPECT_FALSE(found_scalar_const);
554 found_scalar_const = true;
555 } else if (IsAddN(n, 2)) {
556 EXPECT_FALSE(found_add);
557 found_add = true;
558 } else {
559 ADD_FAILURE() << "Unexpected NodeDef: " << ProtoDebugString(n);
560 }
561 }
562 EXPECT_TRUE(found_placeholder);
563 EXPECT_TRUE(found_scalar_const);
564 EXPECT_TRUE(found_add);
565
566 // Add another oper to the graph.
567 TF_Operation* neg = Neg(add, graph, s);
568 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
569
570 // Serialize to NodeDef.
571 NodeDef node_def;
572 ASSERT_TRUE(GetNodeDef(neg, &node_def));
573
574 // Validate NodeDef is what we expect.
575 EXPECT_TRUE(IsNeg(node_def, "add"));
576
577 // Serialize to GraphDef.
578 GraphDef graph_def2;
579 ASSERT_TRUE(GetGraphDef(graph, &graph_def2));
580
581 // Compare with first GraphDef + added NodeDef.
582 NodeDef* added_node = graph_def.add_node();
583 *added_node = node_def;
584 EXPECT_EQ(ProtoDebugString(graph_def), ProtoDebugString(graph_def2));
585
586 // Look up some nodes by name.
587 TF_Operation* neg2 = TF_GraphOperationByName(graph, "neg");
588 EXPECT_TRUE(neg == neg2);
589 NodeDef node_def2;
590 ASSERT_TRUE(GetNodeDef(neg2, &node_def2));
591 EXPECT_EQ(ProtoDebugString(node_def), ProtoDebugString(node_def2));
592
593 TF_Operation* feed2 = TF_GraphOperationByName(graph, "feed");
594 EXPECT_TRUE(feed == feed2);
595 ASSERT_TRUE(GetNodeDef(feed, &node_def));
596 ASSERT_TRUE(GetNodeDef(feed2, &node_def2));
597 EXPECT_EQ(ProtoDebugString(node_def), ProtoDebugString(node_def2));
598
599 // Test iterating through the nodes of a graph.
600 found_placeholder = false;
601 found_scalar_const = false;
602 found_add = false;
603 bool found_neg = false;
604 size_t pos = 0;
605 TF_Operation* oper;
606 while ((oper = TF_GraphNextOperation(graph, &pos)) != nullptr) {
607 if (oper == feed) {
608 EXPECT_FALSE(found_placeholder);
609 found_placeholder = true;
610 } else if (oper == three) {
611 EXPECT_FALSE(found_scalar_const);
612 found_scalar_const = true;
613 } else if (oper == add) {
614 EXPECT_FALSE(found_add);
615 found_add = true;
616 } else if (oper == neg) {
617 EXPECT_FALSE(found_neg);
618 found_neg = true;
619 } else {
620 ASSERT_TRUE(GetNodeDef(oper, &node_def));
621 ADD_FAILURE() << "Unexpected Node: " << ProtoDebugString(node_def);
622 }
623 }
624 EXPECT_TRUE(found_placeholder);
625 EXPECT_TRUE(found_scalar_const);
626 EXPECT_TRUE(found_add);
627 EXPECT_TRUE(found_neg);
628
629 // Clean up
630 TF_DeleteGraph(graph);
631 TF_DeleteStatus(s);
632 }
633
634 /*
635 TODO(skyewm): this test currently DCHECKs, change to bad status
636
637 TEST(CAPI, InputFromDifferentGraphError) {
638 TF_Status* s = TF_NewStatus();
639 TF_Graph* g1 = TF_NewGraph();
640 TF_Graph* g2 = TF_NewGraph();
641
642 TF_Operation* feed = Placeholder(g1, s);
643 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
644
645 // Attempt to create node in g2 with input from g1
646 Neg(feed, g2, s);
647 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s));
648 EXPECT_STREQ("foo", TF_Message(s));
649
650 TF_DeleteGraph(g1);
651 TF_DeleteGraph(g2);
652 TF_DeleteStatus(s);
653 }
654 */
655
TEST(CAPI,ImportGraphDef)656 TEST(CAPI, ImportGraphDef) {
657 TF_Status* s = TF_NewStatus();
658 TF_Graph* graph = TF_NewGraph();
659
660 // Create a simple graph.
661 Placeholder(graph, s);
662 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
663 ASSERT_TRUE(TF_GraphOperationByName(graph, "feed") != nullptr);
664 TF_Operation* oper = ScalarConst(3, graph, s);
665 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
666 ASSERT_TRUE(TF_GraphOperationByName(graph, "scalar") != nullptr);
667 Neg(oper, graph, s);
668 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
669 ASSERT_TRUE(TF_GraphOperationByName(graph, "neg") != nullptr);
670
671 // Export to a GraphDef.
672 TF_Buffer* graph_def = TF_NewBuffer();
673 TF_GraphToGraphDef(graph, graph_def, s);
674 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
675
676 // Import it, with a prefix, in a fresh graph.
677 TF_DeleteGraph(graph);
678 graph = TF_NewGraph();
679 TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
680 TF_ImportGraphDefOptionsSetPrefix(opts, "imported");
681 TF_GraphImportGraphDef(graph, graph_def, opts, s);
682 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
683
684 TF_Operation* scalar = TF_GraphOperationByName(graph, "imported/scalar");
685 TF_Operation* feed = TF_GraphOperationByName(graph, "imported/feed");
686 TF_Operation* neg = TF_GraphOperationByName(graph, "imported/neg");
687 ASSERT_TRUE(scalar != nullptr);
688 ASSERT_TRUE(feed != nullptr);
689 ASSERT_TRUE(neg != nullptr);
690
691 // Test basic structure of the imported graph.
692 EXPECT_EQ(0, TF_OperationNumInputs(scalar));
693 EXPECT_EQ(0, TF_OperationNumInputs(feed));
694 ASSERT_EQ(1, TF_OperationNumInputs(neg));
695 TF_Output neg_input = TF_OperationInput({neg, 0});
696 EXPECT_EQ(scalar, neg_input.oper);
697 EXPECT_EQ(0, neg_input.index);
698
699 // Test that we can't see control edges involving the source and sink nodes.
700 TF_Operation* control_ops[100];
701 EXPECT_EQ(0, TF_OperationNumControlInputs(scalar));
702 EXPECT_EQ(0, TF_OperationGetControlInputs(scalar, control_ops, 100));
703 EXPECT_EQ(0, TF_OperationNumControlOutputs(scalar));
704 EXPECT_EQ(0, TF_OperationGetControlOutputs(scalar, control_ops, 100));
705
706 EXPECT_EQ(0, TF_OperationNumControlInputs(feed));
707 EXPECT_EQ(0, TF_OperationGetControlInputs(feed, control_ops, 100));
708 EXPECT_EQ(0, TF_OperationNumControlOutputs(feed));
709 EXPECT_EQ(0, TF_OperationGetControlOutputs(feed, control_ops, 100));
710
711 EXPECT_EQ(0, TF_OperationNumControlInputs(neg));
712 EXPECT_EQ(0, TF_OperationGetControlInputs(neg, control_ops, 100));
713 EXPECT_EQ(0, TF_OperationNumControlOutputs(neg));
714 EXPECT_EQ(0, TF_OperationGetControlOutputs(neg, control_ops, 100));
715
716 // Import it again, with an input mapping, return outputs, and a return
717 // operation, into the same graph.
718 TF_DeleteImportGraphDefOptions(opts);
719 opts = TF_NewImportGraphDefOptions();
720 TF_ImportGraphDefOptionsSetPrefix(opts, "imported2");
721 TF_ImportGraphDefOptionsAddInputMapping(opts, "scalar", 0, {scalar, 0});
722 TF_ImportGraphDefOptionsAddReturnOutput(opts, "feed", 0);
723 TF_ImportGraphDefOptionsAddReturnOutput(opts, "scalar", 0);
724 EXPECT_EQ(2, TF_ImportGraphDefOptionsNumReturnOutputs(opts));
725 TF_ImportGraphDefOptionsAddReturnOperation(opts, "scalar");
726 EXPECT_EQ(1, TF_ImportGraphDefOptionsNumReturnOperations(opts));
727 TF_ImportGraphDefResults* results =
728 TF_GraphImportGraphDefWithResults(graph, graph_def, opts, s);
729 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
730
731 TF_Operation* scalar2 = TF_GraphOperationByName(graph, "imported2/scalar");
732 TF_Operation* feed2 = TF_GraphOperationByName(graph, "imported2/feed");
733 TF_Operation* neg2 = TF_GraphOperationByName(graph, "imported2/neg");
734 ASSERT_TRUE(scalar2 != nullptr);
735 ASSERT_TRUE(feed2 != nullptr);
736 ASSERT_TRUE(neg2 != nullptr);
737
738 // Check input mapping
739 neg_input = TF_OperationInput({neg, 0});
740 EXPECT_EQ(scalar, neg_input.oper);
741 EXPECT_EQ(0, neg_input.index);
742
743 // Check return outputs
744 TF_Output* return_outputs;
745 int num_return_outputs;
746 TF_ImportGraphDefResultsReturnOutputs(results, &num_return_outputs,
747 &return_outputs);
748 ASSERT_EQ(2, num_return_outputs);
749 EXPECT_EQ(feed2, return_outputs[0].oper);
750 EXPECT_EQ(0, return_outputs[0].index);
751 EXPECT_EQ(scalar, return_outputs[1].oper); // remapped
752 EXPECT_EQ(0, return_outputs[1].index);
753
754 // Check return operation
755 TF_Operation** return_opers;
756 int num_return_opers;
757 TF_ImportGraphDefResultsReturnOperations(results, &num_return_opers,
758 &return_opers);
759 ASSERT_EQ(1, num_return_opers);
760 EXPECT_EQ(scalar2, return_opers[0]); // not remapped
761
762 TF_DeleteImportGraphDefResults(results);
763
764 // Import again, with control dependencies, into the same graph.
765 TF_DeleteImportGraphDefOptions(opts);
766 opts = TF_NewImportGraphDefOptions();
767 TF_ImportGraphDefOptionsSetPrefix(opts, "imported3");
768 TF_ImportGraphDefOptionsAddControlDependency(opts, feed);
769 TF_ImportGraphDefOptionsAddControlDependency(opts, feed2);
770 TF_GraphImportGraphDef(graph, graph_def, opts, s);
771 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
772
773 TF_Operation* scalar3 = TF_GraphOperationByName(graph, "imported3/scalar");
774 TF_Operation* feed3 = TF_GraphOperationByName(graph, "imported3/feed");
775 TF_Operation* neg3 = TF_GraphOperationByName(graph, "imported3/neg");
776 ASSERT_TRUE(scalar3 != nullptr);
777 ASSERT_TRUE(feed3 != nullptr);
778 ASSERT_TRUE(neg3 != nullptr);
779
780 // Check that newly-imported scalar and feed have control deps (neg3 will
781 // inherit them from input)
782 TF_Operation* control_inputs[100];
783 int num_control_inputs = TF_OperationGetControlInputs(
784 scalar3, control_inputs, TF_OperationNumControlInputs(scalar3));
785 ASSERT_EQ(2, num_control_inputs);
786 EXPECT_EQ(feed, control_inputs[0]);
787 EXPECT_EQ(feed2, control_inputs[1]);
788
789 num_control_inputs = TF_OperationGetControlInputs(
790 feed3, control_inputs, TF_OperationNumControlInputs(feed3));
791 ASSERT_EQ(2, num_control_inputs);
792 EXPECT_EQ(feed, control_inputs[0]);
793 EXPECT_EQ(feed2, control_inputs[1]);
794
795 // Export to a graph def so we can import a graph with control dependencies
796 TF_DeleteBuffer(graph_def);
797 graph_def = TF_NewBuffer();
798 TF_GraphToGraphDef(graph, graph_def, s);
799 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
800
801 // Import again, with remapped control dependency, into the same graph
802 TF_DeleteImportGraphDefOptions(opts);
803 opts = TF_NewImportGraphDefOptions();
804 TF_ImportGraphDefOptionsSetPrefix(opts, "imported4");
805 TF_ImportGraphDefOptionsRemapControlDependency(opts, "imported/feed", feed);
806 TF_GraphImportGraphDef(graph, graph_def, opts, s);
807 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
808
809 TF_Operation* scalar4 =
810 TF_GraphOperationByName(graph, "imported4/imported3/scalar");
811 TF_Operation* feed4 =
812 TF_GraphOperationByName(graph, "imported4/imported2/feed");
813
814 // Check that imported `imported3/scalar` has remapped control dep from
815 // original graph and imported control dep
816 num_control_inputs = TF_OperationGetControlInputs(
817 scalar4, control_inputs, TF_OperationNumControlInputs(scalar4));
818 ASSERT_EQ(2, num_control_inputs);
819 EXPECT_EQ(feed, control_inputs[0]);
820 EXPECT_EQ(feed4, control_inputs[1]);
821
822 TF_DeleteImportGraphDefOptions(opts);
823 TF_DeleteBuffer(graph_def);
824
825 // Can add nodes to the imported graph without trouble.
826 Add(feed, scalar, graph, s);
827 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
828
829 TF_DeleteGraph(graph);
830 TF_DeleteStatus(s);
831 }
832
TEST(CAPI,ImportGraphDef_WithReturnOutputs)833 TEST(CAPI, ImportGraphDef_WithReturnOutputs) {
834 TF_Status* s = TF_NewStatus();
835 TF_Graph* graph = TF_NewGraph();
836
837 // Create a graph with two nodes: x and 3
838 Placeholder(graph, s);
839 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
840 ASSERT_TRUE(TF_GraphOperationByName(graph, "feed") != nullptr);
841 TF_Operation* oper = ScalarConst(3, graph, s);
842 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
843 ASSERT_TRUE(TF_GraphOperationByName(graph, "scalar") != nullptr);
844 Neg(oper, graph, s);
845 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
846 ASSERT_TRUE(TF_GraphOperationByName(graph, "neg") != nullptr);
847
848 // Export to a GraphDef.
849 TF_Buffer* graph_def = TF_NewBuffer();
850 TF_GraphToGraphDef(graph, graph_def, s);
851 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
852
853 // Import it in a fresh graph with return outputs.
854 TF_DeleteGraph(graph);
855 graph = TF_NewGraph();
856 TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
857 TF_ImportGraphDefOptionsAddReturnOutput(opts, "feed", 0);
858 TF_ImportGraphDefOptionsAddReturnOutput(opts, "scalar", 0);
859 EXPECT_EQ(2, TF_ImportGraphDefOptionsNumReturnOutputs(opts));
860 TF_Output return_outputs[2];
861 TF_GraphImportGraphDefWithReturnOutputs(graph, graph_def, opts,
862 return_outputs, 2, s);
863 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
864
865 TF_Operation* scalar = TF_GraphOperationByName(graph, "scalar");
866 TF_Operation* feed = TF_GraphOperationByName(graph, "feed");
867 TF_Operation* neg = TF_GraphOperationByName(graph, "neg");
868 ASSERT_TRUE(scalar != nullptr);
869 ASSERT_TRUE(feed != nullptr);
870 ASSERT_TRUE(neg != nullptr);
871
872 // Check return outputs
873 EXPECT_EQ(feed, return_outputs[0].oper);
874 EXPECT_EQ(0, return_outputs[0].index);
875 EXPECT_EQ(scalar, return_outputs[1].oper);
876 EXPECT_EQ(0, return_outputs[1].index);
877
878 TF_DeleteImportGraphDefOptions(opts);
879 TF_DeleteBuffer(graph_def);
880 TF_DeleteGraph(graph);
881 TF_DeleteStatus(s);
882 }
883
TEST(CAPI,ImportGraphDef_MissingUnusedInputMappings)884 TEST(CAPI, ImportGraphDef_MissingUnusedInputMappings) {
885 TF_Status* s = TF_NewStatus();
886 TF_Graph* graph = TF_NewGraph();
887
888 // Create a graph with two nodes: x and 3
889 Placeholder(graph, s);
890 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
891 ASSERT_TRUE(TF_GraphOperationByName(graph, "feed") != nullptr);
892 TF_Operation* oper = ScalarConst(3, graph, s);
893 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
894 ASSERT_TRUE(TF_GraphOperationByName(graph, "scalar") != nullptr);
895 Neg(oper, graph, s);
896 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
897 ASSERT_TRUE(TF_GraphOperationByName(graph, "neg") != nullptr);
898
899 // Export to a GraphDef.
900 TF_Buffer* graph_def = TF_NewBuffer();
901 TF_GraphToGraphDef(graph, graph_def, s);
902 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
903
904 // Import it in a fresh graph.
905 TF_DeleteGraph(graph);
906 graph = TF_NewGraph();
907 TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
908 TF_GraphImportGraphDef(graph, graph_def, opts, s);
909 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
910
911 TF_Operation* scalar = TF_GraphOperationByName(graph, "scalar");
912
913 // Import it in a fresh graph with an unused input mapping.
914 TF_DeleteImportGraphDefOptions(opts);
915 opts = TF_NewImportGraphDefOptions();
916 TF_ImportGraphDefOptionsSetPrefix(opts, "imported");
917 TF_ImportGraphDefOptionsAddInputMapping(opts, "scalar", 0, {scalar, 0});
918 TF_ImportGraphDefOptionsAddInputMapping(opts, "fake", 0, {scalar, 0});
919 TF_ImportGraphDefResults* results =
920 TF_GraphImportGraphDefWithResults(graph, graph_def, opts, s);
921 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
922
923 // Check unused input mappings
924 int num_unused_input_mappings;
925 const char** src_names;
926 int* src_indexes;
927 TF_ImportGraphDefResultsMissingUnusedInputMappings(
928 results, &num_unused_input_mappings, &src_names, &src_indexes);
929 ASSERT_EQ(1, num_unused_input_mappings);
930 EXPECT_EQ(string("fake"), string(src_names[0]));
931 EXPECT_EQ(0, src_indexes[0]);
932
933 TF_DeleteImportGraphDefResults(results);
934 TF_DeleteImportGraphDefOptions(opts);
935 TF_DeleteBuffer(graph_def);
936 TF_DeleteGraph(graph);
937 TF_DeleteStatus(s);
938 }
939
TEST(CAPI,Session)940 TEST(CAPI, Session) {
941 TF_Status* s = TF_NewStatus();
942 TF_Graph* graph = TF_NewGraph();
943
944 // Make a placeholder operation.
945 TF_Operation* feed = Placeholder(graph, s);
946 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
947
948 // Make a constant operation with the scalar "2".
949 TF_Operation* two = ScalarConst(2, graph, s);
950 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
951
952 // Add operation.
953 TF_Operation* add = Add(feed, two, graph, s);
954 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
955
956 // Create a session for this graph.
957 CSession csession(graph, s);
958 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
959
960 // Run the graph.
961 csession.SetInputs({{feed, Int32Tensor(3)}});
962 csession.SetOutputs({add});
963 csession.Run(s);
964 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
965 TF_Tensor* out = csession.output_tensor(0);
966 ASSERT_TRUE(out != nullptr);
967 EXPECT_EQ(TF_INT32, TF_TensorType(out));
968 EXPECT_EQ(0, TF_NumDims(out)); // scalar
969 ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out));
970 int32* output_contents = static_cast<int32*>(TF_TensorData(out));
971 EXPECT_EQ(3 + 2, *output_contents);
972
973 // Add another operation to the graph.
974 TF_Operation* neg = Neg(add, graph, s);
975 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
976
977 // Run up to the new operation.
978 csession.SetInputs({{feed, Int32Tensor(7)}});
979 csession.SetOutputs({neg});
980 csession.Run(s);
981 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
982 out = csession.output_tensor(0);
983 ASSERT_TRUE(out != nullptr);
984 EXPECT_EQ(TF_INT32, TF_TensorType(out));
985 EXPECT_EQ(0, TF_NumDims(out)); // scalar
986 ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out));
987 output_contents = static_cast<int32*>(TF_TensorData(out));
988 EXPECT_EQ(-(7 + 2), *output_contents);
989
990 // Clean up
991 csession.CloseAndDelete(s);
992 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
993 TF_DeleteGraph(graph);
994 TF_DeleteStatus(s);
995 }
996
997 // If `device` is non-empty, run Min op on that device.
998 // Otherwise run it on the default device (CPU).
RunMinTest(const string & device,bool use_XLA)999 void RunMinTest(const string& device, bool use_XLA) {
1000 TF_Status* s = TF_NewStatus();
1001 TF_Graph* graph = TF_NewGraph();
1002
1003 // Make a placeholder operation.
1004 TF_Operation* feed = Placeholder(graph, s);
1005 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1006
1007 // Make a constant operation with the scalar "0", for axis.
1008 TF_Operation* one = ScalarConst(0, graph, s);
1009 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1010
1011 // Create a session for this graph.
1012 CSession csession(graph, s, use_XLA);
1013 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1014
1015 if (!device.empty()) {
1016 LOG(INFO) << "Setting op Min on device " << device;
1017 }
1018 TF_Operation* min = MinWithDevice(feed, one, graph, device, s);
1019 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1020
1021 // Run the graph.
1022 csession.SetInputs({{feed, Int32Tensor({3, 2, 5})}});
1023 csession.SetOutputs({min});
1024 csession.Run(s);
1025 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1026 TF_Tensor* out = csession.output_tensor(0);
1027 ASSERT_TRUE(out != nullptr);
1028 EXPECT_EQ(TF_INT32, TF_TensorType(out));
1029 EXPECT_EQ(0, TF_NumDims(out)); // scalar
1030 ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out));
1031 int32* output_contents = static_cast<int32*>(TF_TensorData(out));
1032 EXPECT_EQ(2, *output_contents);
1033
1034 // Clean up
1035 csession.CloseAndDelete(s);
1036 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1037 TF_DeleteGraph(graph);
1038 TF_DeleteStatus(s);
1039 }
1040
TEST(CAPI,Session_Min_CPU)1041 TEST(CAPI, Session_Min_CPU) { RunMinTest(/*device=*/"", /*use_XLA=*/false); }
1042
TEST(CAPI,Session_Min_XLA_CPU)1043 TEST(CAPI, Session_Min_XLA_CPU) { RunMinTest(/*device=*/"", /*use_XLA=*/true); }
1044
TEST(CAPI,Session_Min_GPU)1045 TEST(CAPI, Session_Min_GPU) {
1046 const string gpu_device = GPUDeviceName();
1047 // Skip this test if no GPU is available.
1048 if (gpu_device.empty()) return;
1049
1050 RunMinTest(gpu_device, /*use_XLA=*/false);
1051 }
1052
TEST(CAPI,Session_Min_XLA_GPU)1053 TEST(CAPI, Session_Min_XLA_GPU) {
1054 const string gpu_device = GPUDeviceName();
1055 // Skip this test if no GPU is available.
1056 if (gpu_device.empty()) return;
1057
1058 RunMinTest(gpu_device, /*use_XLA=*/true);
1059 }
1060
TEST(CAPI,SessionPRun)1061 TEST(CAPI, SessionPRun) {
1062 TF_Status* s = TF_NewStatus();
1063 TF_Graph* graph = TF_NewGraph();
1064
1065 // Construct the graph: A + 2 + B
1066 TF_Operation* a = Placeholder(graph, s, "A");
1067 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1068
1069 TF_Operation* b = Placeholder(graph, s, "B");
1070 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1071
1072 TF_Operation* two = ScalarConst(2, graph, s);
1073 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1074
1075 TF_Operation* plus2 = Add(a, two, graph, s, "plus2");
1076 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1077
1078 TF_Operation* plusB = Add(plus2, b, graph, s, "plusB");
1079 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1080
1081 // Setup a session and a partial run handle. The partial run will allow
1082 // computation of A + 2 + B in two phases (calls to TF_SessionPRun):
1083 // 1. Feed A and get (A+2)
1084 // 2. Feed B and get (A+2)+B
1085 TF_SessionOptions* opts = TF_NewSessionOptions();
1086 TF_Session* sess = TF_NewSession(graph, opts, s);
1087 TF_DeleteSessionOptions(opts);
1088
1089 TF_Output feeds[] = {TF_Output{a, 0}, TF_Output{b, 0}};
1090 TF_Output fetches[] = {TF_Output{plus2, 0}, TF_Output{plusB, 0}};
1091
1092 const char* handle = nullptr;
1093 TF_SessionPRunSetup(sess, feeds, TF_ARRAYSIZE(feeds), fetches,
1094 TF_ARRAYSIZE(fetches), nullptr, 0, &handle, s);
1095 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1096
1097 // Feed A and fetch A + 2.
1098 TF_Output feeds1[] = {TF_Output{a, 0}};
1099 TF_Output fetches1[] = {TF_Output{plus2, 0}};
1100 TF_Tensor* feedValues1[] = {Int32Tensor(1)};
1101 TF_Tensor* fetchValues1[1];
1102 TF_SessionPRun(sess, handle, feeds1, feedValues1, 1, fetches1, fetchValues1,
1103 1, nullptr, 0, s);
1104 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1105 EXPECT_EQ(3, *(static_cast<int32*>(TF_TensorData(fetchValues1[0]))));
1106 TF_DeleteTensor(feedValues1[0]);
1107 TF_DeleteTensor(fetchValues1[0]);
1108
1109 // Feed B and fetch (A + 2) + B.
1110 TF_Output feeds2[] = {TF_Output{b, 0}};
1111 TF_Output fetches2[] = {TF_Output{plusB, 0}};
1112 TF_Tensor* feedValues2[] = {Int32Tensor(4)};
1113 TF_Tensor* fetchValues2[1];
1114 TF_SessionPRun(sess, handle, feeds2, feedValues2, 1, fetches2, fetchValues2,
1115 1, nullptr, 0, s);
1116 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1117 EXPECT_EQ(7, *(static_cast<int32*>(TF_TensorData(fetchValues2[0]))));
1118 TF_DeleteTensor(feedValues2[0]);
1119 TF_DeleteTensor(fetchValues2[0]);
1120
1121 // Clean up.
1122 TF_DeletePRunHandle(handle);
1123 TF_DeleteSession(sess, s);
1124 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1125 TF_DeleteGraph(graph);
1126 TF_DeleteStatus(s);
1127 }
1128
TEST(CAPI,ShapeInferenceError)1129 TEST(CAPI, ShapeInferenceError) {
1130 // TF_FinishOperation should fail if the shape of the added operation cannot
1131 // be inferred.
1132 TF_Status* status = TF_NewStatus();
1133 TF_Graph* graph = TF_NewGraph();
1134
1135 // Create this failure by trying to add two nodes with incompatible shapes
1136 // (A tensor with shape [2] and a tensor with shape [3] cannot be added).
1137 const char data[] = {1, 2, 3};
1138 const int64_t vec2_dims[] = {2};
1139 unique_tensor_ptr vec2_tensor(
1140 Int8Tensor(vec2_dims, TF_ARRAYSIZE(vec2_dims), data), TF_DeleteTensor);
1141 TF_Operation* vec2 = Const(vec2_tensor.get(), graph, status, "vec2");
1142 ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
1143
1144 const int64_t vec3_dims[] = {3};
1145 unique_tensor_ptr vec3_tensor(
1146 Int8Tensor(vec3_dims, TF_ARRAYSIZE(vec3_dims), data), TF_DeleteTensor);
1147 TF_Operation* vec3 = Const(vec3_tensor.get(), graph, status, "vec3");
1148 ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
1149
1150 TF_Operation* add = AddNoCheck(vec2, vec3, graph, status);
1151 ASSERT_NE(TF_OK, TF_GetCode(status));
1152 ASSERT_TRUE(add == nullptr);
1153
1154 TF_DeleteGraph(graph);
1155 TF_DeleteStatus(status);
1156 }
1157
TEST(CAPI,GetOpDef)1158 TEST(CAPI, GetOpDef) {
1159 TF_Status* status = TF_NewStatus();
1160 TF_Graph* graph = TF_NewGraph();
1161 TF_Buffer* buffer = TF_NewBuffer();
1162
1163 TF_GraphGetOpDef(graph, "Add", buffer, status);
1164 ASSERT_EQ(TF_OK, TF_GetCode(status));
1165 const OpDef* expected_op_def;
1166 TF_ASSERT_OK(OpRegistry::Global()->LookUpOpDef("Add", &expected_op_def));
1167 string expected_serialized;
1168 expected_op_def->SerializeToString(&expected_serialized);
1169 string actual_string(reinterpret_cast<const char*>(buffer->data),
1170 buffer->length);
1171 EXPECT_EQ(expected_serialized, actual_string);
1172
1173 TF_GraphGetOpDef(graph, "MyFakeOp", buffer, status);
1174 EXPECT_EQ(TF_NOT_FOUND, TF_GetCode(status));
1175 ExpectHasSubstr(TF_Message(status),
1176 "Op type not registered 'MyFakeOp' in binary");
1177
1178 TF_DeleteBuffer(buffer);
1179 TF_DeleteGraph(graph);
1180 TF_DeleteStatus(status);
1181 }
1182
StringVectorToArrays(const std::vector<string> & v,std::unique_ptr<const void * []> * ptrs,std::unique_ptr<size_t[]> * lens)1183 void StringVectorToArrays(const std::vector<string>& v,
1184 std::unique_ptr<const void*[]>* ptrs,
1185 std::unique_ptr<size_t[]>* lens) {
1186 ptrs->reset(new const void*[v.size()]);
1187 lens->reset(new size_t[v.size()]);
1188 for (size_t i = 0; i < v.size(); ++i) {
1189 (*ptrs)[i] = v[i].data();
1190 (*lens)[i] = v[i].size();
1191 }
1192 }
1193
1194 class CApiColocationTest : public ::testing::Test {
1195 protected:
CApiColocationTest()1196 CApiColocationTest() : s_(TF_NewStatus()), graph_(TF_NewGraph()) {}
1197
SetUp()1198 void SetUp() override {
1199 feed1_ = Placeholder(graph_, s_, "feed1");
1200 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1201
1202 feed2_ = Placeholder(graph_, s_, "feed2");
1203 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1204
1205 constant_ = ScalarConst(10, graph_, s_);
1206 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1207
1208 desc_ = TF_NewOperation(graph_, "AddN", "add");
1209 TF_Output inputs[] = {{feed1_, 0}, {constant_, 0}};
1210 TF_AddInputList(desc_, inputs, TF_ARRAYSIZE(inputs));
1211 }
1212
~CApiColocationTest()1213 ~CApiColocationTest() override {
1214 TF_DeleteGraph(graph_);
1215 TF_DeleteStatus(s_);
1216 }
1217
SetViaStringList(TF_OperationDescription * desc,const std::vector<string> & list)1218 void SetViaStringList(TF_OperationDescription* desc,
1219 const std::vector<string>& list) {
1220 std::unique_ptr<const void*[]> list_ptrs;
1221 std::unique_ptr<size_t[]> list_lens;
1222 StringVectorToArrays(list, &list_ptrs, &list_lens);
1223 TF_SetAttrStringList(desc, tensorflow::kColocationAttrName, list_ptrs.get(),
1224 list_lens.get(), list.size());
1225 }
1226
SetViaProto(TF_OperationDescription * desc,const std::vector<string> & list)1227 void SetViaProto(TF_OperationDescription* desc,
1228 const std::vector<string>& list) {
1229 tensorflow::AttrValue attr;
1230 for (const string& v : list) {
1231 attr.mutable_list()->add_s(v);
1232 }
1233 string bytes;
1234 attr.SerializeToString(&bytes);
1235 TF_SetAttrValueProto(desc, tensorflow::kColocationAttrName, bytes.data(),
1236 bytes.size(), s_);
1237 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1238 }
1239
VerifyCollocation(TF_Operation * op,const std::vector<string> & expected)1240 void VerifyCollocation(TF_Operation* op,
1241 const std::vector<string>& expected) {
1242 TF_AttrMetadata m =
1243 TF_OperationGetAttrMetadata(op, tensorflow::kColocationAttrName, s_);
1244 if (expected.empty()) {
1245 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
1246 EXPECT_EQ("Operation 'add' has no attr named '_class'.",
1247 string(TF_Message(s_)));
1248 return;
1249 }
1250 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1251 EXPECT_EQ(1, m.is_list);
1252 EXPECT_EQ(expected.size(), m.list_size);
1253 EXPECT_EQ(TF_ATTR_STRING, m.type);
1254 std::vector<void*> values(expected.size());
1255 std::vector<size_t> lens(expected.size());
1256 std::unique_ptr<char[]> storage(new char[m.total_size]);
1257 TF_OperationGetAttrStringList(op, tensorflow::kColocationAttrName,
1258 values.data(), lens.data(), expected.size(),
1259 storage.get(), m.total_size, s_);
1260 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1261 for (int i = 0; i < expected.size(); ++i) {
1262 EXPECT_EQ(expected[i],
1263 string(static_cast<const char*>(values[i]), lens[i]));
1264 }
1265 }
1266
FinishAndVerify(TF_OperationDescription * desc,const std::vector<string> & expected)1267 void FinishAndVerify(TF_OperationDescription* desc,
1268 const std::vector<string>& expected) {
1269 TF_Operation* op = TF_FinishOperation(desc_, s_);
1270 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1271 VerifyCollocation(op, expected);
1272 }
1273
1274 TF_Status* s_;
1275 TF_Graph* graph_;
1276 TF_Operation* feed1_;
1277 TF_Operation* feed2_;
1278 TF_Operation* constant_;
1279 TF_OperationDescription* desc_;
1280 };
1281
TEST_F(CApiColocationTest,ColocateWith)1282 TEST_F(CApiColocationTest, ColocateWith) {
1283 TF_ColocateWith(desc_, feed1_);
1284 FinishAndVerify(desc_, {"loc:@feed1"});
1285 }
1286
TEST_F(CApiColocationTest,StringList)1287 TEST_F(CApiColocationTest, StringList) {
1288 SetViaStringList(desc_, {"loc:@feed1"});
1289 FinishAndVerify(desc_, {"loc:@feed1"});
1290 }
1291
TEST_F(CApiColocationTest,Proto)1292 TEST_F(CApiColocationTest, Proto) {
1293 SetViaProto(desc_, {"loc:@feed1"});
1294 FinishAndVerify(desc_, {"loc:@feed1"});
1295 }
1296
TEST_F(CApiColocationTest,ColocateWith_StringList)1297 TEST_F(CApiColocationTest, ColocateWith_StringList) {
1298 TF_ColocateWith(desc_, feed1_);
1299 SetViaStringList(desc_, {"loc:@feed2"});
1300 FinishAndVerify(desc_, {"loc:@feed2"});
1301 }
1302
TEST_F(CApiColocationTest,ColocateWith_Proto)1303 TEST_F(CApiColocationTest, ColocateWith_Proto) {
1304 TF_ColocateWith(desc_, feed1_);
1305 SetViaProto(desc_, {"loc:@feed2"});
1306 FinishAndVerify(desc_, {"loc:@feed2"});
1307 }
1308
TEST_F(CApiColocationTest,StringList_ColocateWith)1309 TEST_F(CApiColocationTest, StringList_ColocateWith) {
1310 SetViaStringList(desc_, {"loc:@feed2"});
1311 TF_ColocateWith(desc_, feed1_);
1312 FinishAndVerify(desc_, {"loc:@feed1", "loc:@feed2"});
1313 }
1314
TEST_F(CApiColocationTest,Proto_ColocateWith)1315 TEST_F(CApiColocationTest, Proto_ColocateWith) {
1316 SetViaProto(desc_, {"loc:@feed2"});
1317 TF_ColocateWith(desc_, feed1_);
1318 FinishAndVerify(desc_, {"loc:@feed1", "loc:@feed2"});
1319 }
1320
TEST_F(CApiColocationTest,ColocateWith_ColocateWith)1321 TEST_F(CApiColocationTest, ColocateWith_ColocateWith) {
1322 TF_ColocateWith(desc_, feed1_);
1323 TF_ColocateWith(desc_, feed2_);
1324 FinishAndVerify(desc_, {"loc:@feed1", "loc:@feed2"});
1325 }
1326
TEST_F(CApiColocationTest,Proto_StringList)1327 TEST_F(CApiColocationTest, Proto_StringList) {
1328 SetViaProto(desc_, {"loc:@feed1"});
1329 SetViaStringList(desc_, {"loc:@feed2"});
1330 FinishAndVerify(desc_, {"loc:@feed2"});
1331 }
1332
TEST_F(CApiColocationTest,StringList_Proto)1333 TEST_F(CApiColocationTest, StringList_Proto) {
1334 SetViaStringList(desc_, {"loc:@feed1"});
1335 SetViaProto(desc_, {"loc:@feed2"});
1336 FinishAndVerify(desc_, {"loc:@feed2"});
1337 }
1338
TEST_F(CApiColocationTest,ClearViaStringList)1339 TEST_F(CApiColocationTest, ClearViaStringList) {
1340 TF_ColocateWith(desc_, feed1_);
1341 SetViaStringList(desc_, {});
1342 FinishAndVerify(desc_, {});
1343 }
1344
TEST_F(CApiColocationTest,ClearViaProto)1345 TEST_F(CApiColocationTest, ClearViaProto) {
1346 TF_ColocateWith(desc_, feed1_);
1347 SetViaProto(desc_, {});
1348 FinishAndVerify(desc_, {});
1349 }
1350
TEST(CAPI,SavedModel)1351 TEST(CAPI, SavedModel) {
1352 // Load the saved model.
1353 const char kSavedModel[] = "cc/saved_model/testdata/half_plus_two/00000123";
1354 const string saved_model_dir = tensorflow::io::JoinPath(
1355 tensorflow::testing::TensorFlowSrcRoot(), kSavedModel);
1356 TF_SessionOptions* opt = TF_NewSessionOptions();
1357 TF_Buffer* run_options = TF_NewBufferFromString("", 0);
1358 TF_Buffer* metagraph = TF_NewBuffer();
1359 TF_Status* s = TF_NewStatus();
1360 const char* tags[] = {tensorflow::kSavedModelTagServe};
1361 TF_Graph* graph = TF_NewGraph();
1362 TF_Session* session = TF_LoadSessionFromSavedModel(
1363 opt, run_options, saved_model_dir.c_str(), tags, 1, graph, metagraph, s);
1364 TF_DeleteBuffer(run_options);
1365 TF_DeleteSessionOptions(opt);
1366 tensorflow::MetaGraphDef metagraph_def;
1367 metagraph_def.ParseFromArray(metagraph->data, metagraph->length);
1368 TF_DeleteBuffer(metagraph);
1369
1370 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1371 CSession csession(session);
1372
1373 // Retrieve the regression signature from meta graph def.
1374 const auto signature_def_map = metagraph_def.signature_def();
1375 const auto signature_def = signature_def_map.at("regress_x_to_y");
1376
1377 const string input_name =
1378 signature_def.inputs().at(tensorflow::kRegressInputs).name();
1379 const string output_name =
1380 signature_def.outputs().at(tensorflow::kRegressOutputs).name();
1381
1382 // Write {0, 1, 2, 3} as tensorflow::Example inputs.
1383 Tensor input(tensorflow::DT_STRING, TensorShape({4}));
1384 for (tensorflow::int64 i = 0; i < input.NumElements(); ++i) {
1385 tensorflow::Example example;
1386 auto* feature_map = example.mutable_features()->mutable_feature();
1387 (*feature_map)["x"].mutable_float_list()->add_value(i);
1388 input.flat<string>()(i) = example.SerializeAsString();
1389 }
1390
1391 const tensorflow::string input_op_name(
1392 tensorflow::ParseTensorName(input_name).first);
1393 TF_Operation* input_op =
1394 TF_GraphOperationByName(graph, input_op_name.c_str());
1395 ASSERT_TRUE(input_op != nullptr);
1396 csession.SetInputs({{input_op, TF_TensorFromTensor(input, s)}});
1397 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1398
1399 const tensorflow::string output_op_name(
1400 tensorflow::ParseTensorName(output_name).first);
1401 TF_Operation* output_op =
1402 TF_GraphOperationByName(graph, output_op_name.c_str());
1403 ASSERT_TRUE(output_op != nullptr);
1404 csession.SetOutputs({output_op});
1405 csession.Run(s);
1406 ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1407
1408 TF_Tensor* out = csession.output_tensor(0);
1409 ASSERT_TRUE(out != nullptr);
1410 EXPECT_EQ(TF_FLOAT, TF_TensorType(out));
1411 EXPECT_EQ(2, TF_NumDims(out));
1412 EXPECT_EQ(4, TF_Dim(out, 0));
1413 EXPECT_EQ(1, TF_Dim(out, 1));
1414 float* values = static_cast<float*>(TF_TensorData(out));
1415 // These values are defined to be (input / 2) + 2.
1416 EXPECT_EQ(2, values[0]);
1417 EXPECT_EQ(2.5, values[1]);
1418 EXPECT_EQ(3, values[2]);
1419 EXPECT_EQ(3.5, values[3]);
1420
1421 csession.CloseAndDelete(s);
1422 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1423 TF_DeleteGraph(graph);
1424 TF_DeleteStatus(s);
1425 }
1426
TEST(CAPI,SavedModelNullArgsAreValid)1427 TEST(CAPI, SavedModelNullArgsAreValid) {
1428 const char kSavedModel[] = "cc/saved_model/testdata/half_plus_two/00000123";
1429 const string saved_model_dir = tensorflow::io::JoinPath(
1430 tensorflow::testing::TensorFlowSrcRoot(), kSavedModel);
1431 TF_SessionOptions* opt = TF_NewSessionOptions();
1432 TF_Status* s = TF_NewStatus();
1433 const char* tags[] = {tensorflow::kSavedModelTagServe};
1434 TF_Graph* graph = TF_NewGraph();
1435 // NULL run_options and meta_graph_def should work.
1436 TF_Session* session = TF_LoadSessionFromSavedModel(
1437 opt, nullptr, saved_model_dir.c_str(), tags, 1, graph, nullptr, s);
1438 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1439 TF_DeleteSessionOptions(opt);
1440 TF_CloseSession(session, s);
1441 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1442 TF_DeleteSession(session, s);
1443 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1444 TF_DeleteGraph(graph);
1445 TF_DeleteStatus(s);
1446 }
1447
TEST(CAPI,DeletingNullPointerIsSafe)1448 TEST(CAPI, DeletingNullPointerIsSafe) {
1449 TF_Status* status = TF_NewStatus();
1450
1451 TF_DeleteStatus(nullptr);
1452 TF_DeleteBuffer(nullptr);
1453 TF_DeleteTensor(nullptr);
1454 TF_DeleteSessionOptions(nullptr);
1455 TF_DeleteGraph(nullptr);
1456 TF_DeleteImportGraphDefOptions(nullptr);
1457 TF_DeleteImportGraphDefResults(nullptr);
1458 TF_DeleteFunction(nullptr);
1459 TF_DeleteSession(nullptr, status);
1460 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
1461 TF_DeletePRunHandle(nullptr);
1462 TF_DeleteDeprecatedSession(nullptr, status);
1463 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
1464 TF_DeleteDeviceList(nullptr);
1465 TF_DeleteLibraryHandle(nullptr);
1466 TF_DeleteApiDefMap(nullptr);
1467
1468 TF_DeleteStatus(status);
1469 }
1470
TEST(CAPI,TestBitcastFrom_Reshape)1471 TEST(CAPI, TestBitcastFrom_Reshape) {
1472 int64_t dims[] = {2, 3};
1473 TF_Tensor* a =
1474 TF_AllocateTensor(TF_UINT64, dims, 2, 6 * TF_DataTypeSize(TF_UINT64));
1475 TF_Tensor* b =
1476 TF_AllocateTensor(TF_UINT64, nullptr, 0, TF_DataTypeSize(TF_UINT64));
1477 EXPECT_NE(a, nullptr);
1478 EXPECT_NE(b, nullptr);
1479
1480 EXPECT_EQ(6, TF_TensorElementCount(a));
1481 EXPECT_EQ(1, TF_TensorElementCount(b));
1482 EXPECT_EQ(6 * TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(a));
1483 EXPECT_EQ(TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(b));
1484
1485 int64_t new_dims[] = {3, 2};
1486 TF_Status* status = TF_NewStatus();
1487 TF_TensorBitcastFrom(a, TF_UINT64, b, new_dims, 2, status);
1488 ASSERT_EQ(TF_OK, TF_GetCode(status));
1489 TF_DeleteStatus(status);
1490
1491 EXPECT_EQ(6, TF_TensorElementCount(a));
1492 EXPECT_EQ(6, TF_TensorElementCount(b));
1493 EXPECT_EQ(6 * TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(a));
1494 EXPECT_EQ(6 * TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(b));
1495
1496 // Check that a write to one tensor shows up in the other.
1497 *(static_cast<int64_t*>(TF_TensorData(a))) = 4;
1498 EXPECT_EQ(4, *(static_cast<int64_t*>(TF_TensorData(b))));
1499 *(static_cast<int64_t*>(TF_TensorData(b))) = 6;
1500 EXPECT_EQ(6, *(static_cast<int64_t*>(TF_TensorData(a))));
1501
1502 TF_DeleteTensor(a);
1503 TF_DeleteTensor(b);
1504 }
1505
1506 REGISTER_OP("TestOpWithNoGradient")
1507 .Input("x: T")
1508 .Output("y: T")
1509 .Attr("T: {float, double}")
1510 .Doc(R"doc(
1511 Test op with no grad registered.
1512
1513 x: input
1514 y: output
1515 )doc")
1516 .SetShapeFn(tensorflow::shape_inference::UnknownShape);
1517
1518 class CApiGradientsTest : public ::testing::Test {
1519 protected:
CApiGradientsTest()1520 CApiGradientsTest()
1521 : s_(TF_NewStatus()),
1522 graph_(TF_NewGraph()),
1523 expected_graph_(TF_NewGraph()) {}
1524
~CApiGradientsTest()1525 ~CApiGradientsTest() override {
1526 TF_DeleteGraph(graph_);
1527 TF_DeleteGraph(expected_graph_);
1528 TF_DeleteStatus(s_);
1529 }
1530
TestGradientsSuccess(bool grad_inputs_provided)1531 void TestGradientsSuccess(bool grad_inputs_provided) {
1532 TF_Output inputs[2];
1533 TF_Output outputs[1];
1534 TF_Output grad_outputs[2];
1535 TF_Output expected_grad_outputs[2];
1536
1537 BuildSuccessGraph(inputs, outputs);
1538 BuildExpectedGraph(grad_inputs_provided, expected_grad_outputs);
1539
1540 AddGradients(grad_inputs_provided, nullptr, inputs, 2, outputs, 1,
1541 grad_outputs);
1542 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1543
1544 // Compare that the graphs match.
1545 GraphDef expected_gdef;
1546 GraphDef gdef;
1547 EXPECT_TRUE(GetGraphDef(expected_graph_, &expected_gdef));
1548 EXPECT_TRUE(GetGraphDef(graph_, &gdef));
1549 TF_EXPECT_GRAPH_EQ(expected_gdef, gdef);
1550
1551 // Compare that the output of the gradients of both graphs match.
1552 RunGraphsAndCompareOutputs(grad_outputs, expected_grad_outputs);
1553 }
1554
TestGradientsError(bool grad_inputs_provided)1555 void TestGradientsError(bool grad_inputs_provided) {
1556 TF_Output inputs[1];
1557 TF_Output outputs[1];
1558 TF_Output grad_outputs[1];
1559
1560 BuildErrorGraph(inputs, outputs);
1561
1562 AddGradients(grad_inputs_provided, nullptr, inputs, 1, outputs, 1,
1563 grad_outputs);
1564
1565 string expected_msg =
1566 "No gradient defined for op: TestOpWithNoGradient. Please see "
1567 "https://www.tensorflow.org/code/"
1568 "tensorflow/cc/gradients/README.md"
1569 " for instructions on how to add C++ gradients.";
1570 EXPECT_EQ(expected_msg, TF_Message(s_));
1571 }
1572
1573 // Run the graph and ensure that the gradient values are as expected.
RunGraphsAndCompareOutputs(TF_Output * grad_outputs,TF_Output * expected_grad_outputs)1574 void RunGraphsAndCompareOutputs(TF_Output* grad_outputs,
1575 TF_Output* expected_grad_outputs) {
1576 std::unique_ptr<CSession> csession(new CSession(graph_, s_));
1577 std::unique_ptr<CSession> expected_csession(
1578 new CSession(expected_graph_, s_));
1579
1580 std::vector<TF_Output> grad_outputs_vec;
1581 grad_outputs_vec.assign(grad_outputs, grad_outputs + 2);
1582 csession->SetOutputs(grad_outputs_vec);
1583 csession->Run(s_);
1584 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1585 TF_Tensor* out0 = csession->output_tensor(0);
1586 TF_Tensor* out1 = csession->output_tensor(1);
1587
1588 std::vector<TF_Output> expected_grad_outputs_vec;
1589 expected_grad_outputs_vec.assign(expected_grad_outputs,
1590 expected_grad_outputs + 2);
1591 expected_csession->SetOutputs(expected_grad_outputs_vec);
1592 expected_csession->Run(s_);
1593 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1594 TF_Tensor* expected_out0 = expected_csession->output_tensor(0);
1595 TF_Tensor* expected_out1 = expected_csession->output_tensor(1);
1596
1597 CompareTensors(out0, expected_out0);
1598 CompareTensors(out1, expected_out1);
1599 }
1600
CompareTensors(TF_Tensor * a,TF_Tensor * b)1601 void CompareTensors(TF_Tensor* a, TF_Tensor* b) {
1602 float* a_data = static_cast<float*>(TF_TensorData(a));
1603 float* b_data = static_cast<float*>(TF_TensorData(b));
1604 EXPECT_EQ(*a_data, *b_data);
1605 }
1606
AddGradients(bool grad_inputs_provided,const char * prefix,TF_Output * inputs,int ninputs,TF_Output * outputs,int noutputs,TF_Output * grad_outputs)1607 void AddGradients(bool grad_inputs_provided, const char* prefix,
1608 TF_Output* inputs, int ninputs, TF_Output* outputs,
1609 int noutputs, TF_Output* grad_outputs) {
1610 if (grad_inputs_provided) {
1611 TF_Output grad_inputs[1];
1612 const float grad_inputs_val[] = {1.0, 1.0, 1.0, 1.0};
1613 TF_Operation* grad_inputs_op =
1614 FloatConst2x2(graph_, s_, grad_inputs_val, "GradInputs");
1615 grad_inputs[0] = TF_Output{grad_inputs_op, 0};
1616 TF_AddGradientsWithPrefix(graph_, prefix, outputs, noutputs, inputs,
1617 ninputs, grad_inputs, s_, grad_outputs);
1618 } else {
1619 TF_AddGradientsWithPrefix(graph_, prefix, outputs, noutputs, inputs,
1620 ninputs, nullptr, s_, grad_outputs);
1621 }
1622 }
1623
BuildErrorGraph(TF_Output * inputs,TF_Output * outputs)1624 void BuildErrorGraph(TF_Output* inputs, TF_Output* outputs) {
1625 const float const0_val[] = {1.0, 2.0, 3.0, 4.0};
1626 TF_Operation* const0 = FloatConst2x2(graph_, s_, const0_val, "Const_0");
1627 TF_Operation* nograd = NoGradientOp(graph_, s_, const0, "NoGrad");
1628 inputs[0] = TF_Output{const0, 0};
1629 outputs[0] = TF_Output{nograd, 0};
1630 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1631 }
1632
BuildSuccessGraph(TF_Output * inputs,TF_Output * outputs)1633 void BuildSuccessGraph(TF_Output* inputs, TF_Output* outputs) {
1634 // Construct the following graph:
1635 // |
1636 // z|
1637 // |
1638 // MatMul
1639 // / \
1640 // ^ ^
1641 // | |
1642 // x| y|
1643 // | |
1644 // | |
1645 // Const_0 Const_1
1646 //
1647 const float const0_val[] = {1.0, 2.0, 3.0, 4.0};
1648 const float const1_val[] = {1.0, 0.0, 0.0, 1.0};
1649 TF_Operation* const0 = FloatConst2x2(graph_, s_, const0_val, "Const_0");
1650 TF_Operation* const1 = FloatConst2x2(graph_, s_, const1_val, "Const_1");
1651 TF_Operation* matmul = MatMul(graph_, s_, const0, const1, "MatMul");
1652 inputs[0] = TF_Output{const0, 0};
1653 inputs[1] = TF_Output{const1, 0};
1654 outputs[0] = TF_Output{matmul, 0};
1655 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1656 }
1657
BuildExpectedGraph(bool grad_inputs_provided,TF_Output * expected_grad_outputs)1658 void BuildExpectedGraph(bool grad_inputs_provided,
1659 TF_Output* expected_grad_outputs) {
1660 // The expected graph looks like this if grad_inputs_provided.
1661 // If grad_inputs_provided is false, Const_0 will be a OnesLike op.
1662 // ^ ^
1663 // dy| dx| // MatMul Gradient Graph
1664 // | |
1665 // MatMul_2 MatMul_1
1666 // ^ ^ ^ ^
1667 // | |----------| |
1668 // | ^ |
1669 // | dz| |
1670 // | | |
1671 // | Const_3 |
1672 // | |
1673 // | ^ |
1674 // | z| | // MatMul Forward Graph
1675 // | | |
1676 // | MatMul |
1677 // | / \ |
1678 // | ^ ^ |
1679 // | | | |
1680 // |---x| y|----|
1681 // | |
1682 // | |
1683 // Const_0 Const_1
1684 //
1685 const float const0_val[] = {1.0, 2.0, 3.0, 4.0};
1686 const float const1_val[] = {1.0, 0.0, 0.0, 1.0};
1687 TF_Operation* const0 =
1688 FloatConst2x2(expected_graph_, s_, const0_val, "Const_0");
1689 TF_Operation* const1 =
1690 FloatConst2x2(expected_graph_, s_, const1_val, "Const_1");
1691 TF_Operation* matmul =
1692 MatMul(expected_graph_, s_, const0, const1, "MatMul");
1693
1694 TF_Operation* const3;
1695 if (grad_inputs_provided) {
1696 const float const3_val[] = {1.0, 1.0, 1.0, 1.0};
1697 const3 = FloatConst2x2(expected_graph_, s_, const3_val, "GradInputs");
1698 } else {
1699 const3 = OnesLike(expected_graph_, s_, matmul, "gradients/OnesLike");
1700 }
1701
1702 TF_Operation* matmul1 = MatMul(expected_graph_, s_, const3, const1,
1703 "gradients/MatMul", false, true);
1704 TF_Operation* matmul2 = MatMul(expected_graph_, s_, const0, const3,
1705 "gradients/MatMul_1", true, false);
1706 expected_grad_outputs[0] = {matmul1, 0};
1707 expected_grad_outputs[1] = {matmul2, 0};
1708 }
1709
FloatTensor2x2(const float * values)1710 TF_Tensor* FloatTensor2x2(const float* values) {
1711 const int64_t dims[2] = {2, 2};
1712 TF_Tensor* t = TF_AllocateTensor(TF_FLOAT, dims, 2, sizeof(float) * 4);
1713 memcpy(TF_TensorData(t), values, sizeof(float) * 4);
1714 return t;
1715 }
1716
FloatConst2x2(TF_Graph * graph,TF_Status * s,const float * values,const char * name)1717 TF_Operation* FloatConst2x2(TF_Graph* graph, TF_Status* s,
1718 const float* values, const char* name) {
1719 unique_tensor_ptr tensor(FloatTensor2x2(values), TF_DeleteTensor);
1720 TF_OperationDescription* desc = TF_NewOperation(graph, "Const", name);
1721 TF_SetAttrTensor(desc, "value", tensor.get(), s);
1722 if (TF_GetCode(s) != TF_OK) return nullptr;
1723 TF_SetAttrType(desc, "dtype", TF_FLOAT);
1724 TF_Operation* op = TF_FinishOperation(desc, s);
1725 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1726 return op;
1727 }
1728
MatMul(TF_Graph * graph,TF_Status * s,TF_Operation * l,TF_Operation * r,const char * name,bool transpose_a=false,bool transpose_b=false)1729 TF_Operation* MatMul(TF_Graph* graph, TF_Status* s, TF_Operation* l,
1730 TF_Operation* r, const char* name,
1731 bool transpose_a = false, bool transpose_b = false) {
1732 TF_OperationDescription* desc = TF_NewOperation(graph, "MatMul", name);
1733 if (transpose_a) {
1734 TF_SetAttrBool(desc, "transpose_a", 1);
1735 }
1736 if (transpose_b) {
1737 TF_SetAttrBool(desc, "transpose_b", 1);
1738 }
1739 TF_AddInput(desc, {l, 0});
1740 TF_AddInput(desc, {r, 0});
1741 TF_Operation* op = TF_FinishOperation(desc, s);
1742 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1743 return op;
1744 }
1745
OnesLike(TF_Graph * graph,TF_Status * s,TF_Operation * in,const char * name)1746 TF_Operation* OnesLike(TF_Graph* graph, TF_Status* s, TF_Operation* in,
1747 const char* name) {
1748 TF_OperationDescription* desc = TF_NewOperation(graph, "OnesLike", name);
1749 TF_AddInput(desc, {in, 0});
1750 TF_Operation* op = TF_FinishOperation(desc, s);
1751 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1752 return op;
1753 }
1754
NoGradientOp(TF_Graph * graph,TF_Status * s,TF_Operation * in,const char * name)1755 TF_Operation* NoGradientOp(TF_Graph* graph, TF_Status* s, TF_Operation* in,
1756 const char* name) {
1757 TF_OperationDescription* desc =
1758 TF_NewOperation(graph, "TestOpWithNoGradient", name);
1759 TF_AddInput(desc, {in, 0});
1760 TF_Operation* op = TF_FinishOperation(desc, s);
1761 EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
1762 return op;
1763 }
1764
BuildGraphAndAddGradientsWithPrefixes(const char * prefix1,const char * prefix2=nullptr)1765 void BuildGraphAndAddGradientsWithPrefixes(const char* prefix1,
1766 const char* prefix2 = nullptr) {
1767 TF_Output inputs[2];
1768 TF_Output outputs[1];
1769 TF_Output grad_outputs[2];
1770
1771 BuildSuccessGraph(inputs, outputs);
1772
1773 AddGradients(false, prefix1, inputs, 2, outputs, 1, grad_outputs);
1774 if (prefix2 != nullptr) {
1775 AddGradients(false, prefix2, inputs, 2, outputs, 1, grad_outputs);
1776 }
1777 }
1778
1779 TF_Status* s_;
1780 TF_Graph* graph_;
1781 TF_Graph* expected_graph_;
1782 };
1783
TEST_F(CApiGradientsTest,Gradients_GradInputs)1784 TEST_F(CApiGradientsTest, Gradients_GradInputs) { TestGradientsSuccess(true); }
1785
TEST_F(CApiGradientsTest,Gradients_NoGradInputs)1786 TEST_F(CApiGradientsTest, Gradients_NoGradInputs) {
1787 TestGradientsSuccess(false);
1788 }
1789
TEST_F(CApiGradientsTest,OpWithNoGradientRegistered_GradInputs)1790 TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_GradInputs) {
1791 TestGradientsError(true);
1792 }
1793
TEST_F(CApiGradientsTest,OpWithNoGradientRegistered_NoGradInputs)1794 TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_NoGradInputs) {
1795 TestGradientsError(false);
1796 }
1797
TEST_F(CApiGradientsTest,GradientsPrefix_PrefixIsOk)1798 TEST_F(CApiGradientsTest, GradientsPrefix_PrefixIsOk) {
1799 BuildGraphAndAddGradientsWithPrefixes("gradients");
1800 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1801 }
1802
TEST_F(CApiGradientsTest,GradientsPrefix_TwoGradientsWithDistinctPrefixes)1803 TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithDistinctPrefixes) {
1804 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients_1");
1805 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1806 }
1807
TEST_F(CApiGradientsTest,GradientsPrefix_TwoGradientsInSameScope)1808 TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsInSameScope) {
1809 BuildGraphAndAddGradientsWithPrefixes("scope/gradients", "scope/gradients_1");
1810 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1811 }
1812
TEST_F(CApiGradientsTest,GradientsPrefix_TwoGradientsInDifferentScopes)1813 TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsInDifferentScopes) {
1814 BuildGraphAndAddGradientsWithPrefixes("scope/gradients", "scope_1/gradients");
1815 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1816 }
1817
TEST_F(CApiGradientsTest,GradientsPrefix_2ndGradientsAsSubScopeOf1st)1818 TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsSubScopeOf1st) {
1819 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/sub");
1820 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1821 }
1822
TEST_F(CApiGradientsTest,GradientsPrefix_PrefixMatchesExistingNodeName)1823 TEST_F(CApiGradientsTest, GradientsPrefix_PrefixMatchesExistingNodeName) {
1824 BuildGraphAndAddGradientsWithPrefixes("Const_0");
1825 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
1826 }
1827
TEST_F(CApiGradientsTest,GradientsPrefix_TwoGradientsWithIdenticalPrefixes)1828 TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithIdenticalPrefixes) {
1829 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients");
1830 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
1831 }
1832
TEST_F(CApiGradientsTest,GradientsPrefix_2ndGradientsMatchingNodeOf1st)1833 TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsMatchingNodeOf1st) {
1834 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/MatMul");
1835 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
1836 }
1837
TEST_F(CApiGradientsTest,GradientsPrefix_1stGradientsMatchingNodeOf2nd)1838 TEST_F(CApiGradientsTest, GradientsPrefix_1stGradientsMatchingNodeOf2nd) {
1839 BuildGraphAndAddGradientsWithPrefixes("gradients/MatMul", "gradients");
1840 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
1841 }
1842
TEST_F(CApiGradientsTest,GradientsPrefix_2ndGradientsAsParentScopeOf1st)1843 TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsParentScopeOf1st) {
1844 BuildGraphAndAddGradientsWithPrefixes("gradients/sub", "gradients");
1845 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
1846 }
1847
ScalarFloatFromTensor(const TF_Tensor * t,float * f)1848 void ScalarFloatFromTensor(const TF_Tensor* t, float* f) {
1849 ASSERT_TRUE(t != nullptr);
1850 ASSERT_EQ(TF_FLOAT, TF_TensorType(t));
1851 ASSERT_EQ(0, TF_NumDims(t));
1852 ASSERT_EQ(4, TF_TensorByteSize(t));
1853 float* p = static_cast<float*>(TF_TensorData(t));
1854 *f = *p;
1855 }
1856
TEST_F(CApiGradientsTest,MultipleCallsToAddGradients)1857 TEST_F(CApiGradientsTest, MultipleCallsToAddGradients) {
1858 const float X = 3.0f, Y = 7.0f;
1859 TF_Operation* x = Placeholder(graph_, s_, "x", TF_FLOAT);
1860 TF_Operation* y = Placeholder(graph_, s_, "y", TF_FLOAT);
1861 TF_Operation* xy = Mul(x, y, graph_, s_, "xy");
1862 TF_Output dxy_dx, dxy_dy;
1863
1864 TF_Output outputs[1] = {{xy, 0}};
1865 TF_Output inputs[1] = {{x, 0}};
1866 TF_AddGradients(graph_, outputs, 1, inputs, 1, nullptr, s_, &dxy_dx);
1867 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1868
1869 inputs[0] = {y, 0};
1870 TF_AddGradients(graph_, outputs, 1, inputs, 1, nullptr, s_, &dxy_dy);
1871 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1872
1873 TF_SessionOptions* opts = TF_NewSessionOptions();
1874 TF_Session* sess = TF_NewSession(graph_, opts, s_);
1875 TF_DeleteSessionOptions(opts);
1876 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1877
1878 TF_Output feeds[] = {{x, 0}, {y, 0}};
1879 TF_Tensor* feedValues[] = {FloatTensor(X), FloatTensor(Y)};
1880 TF_Output fetches[] = {dxy_dx, dxy_dy};
1881 TF_Tensor* fetchValues[] = {nullptr, nullptr};
1882
1883 TF_SessionRun(sess, nullptr /* run_options */, feeds, feedValues, 2, fetches,
1884 fetchValues, 2, nullptr /* target_opers */, 0,
1885 nullptr /* run_metadata */, s_);
1886 TF_DeleteTensor(feedValues[0]);
1887 TF_DeleteTensor(feedValues[1]);
1888 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1889 TF_DeleteSession(sess, s_);
1890 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1891
1892 float dxy_dxValue = 0.0f, dxy_dyValue = 0.0f;
1893 ScalarFloatFromTensor(fetchValues[0], &dxy_dxValue);
1894 EXPECT_EQ(Y, dxy_dxValue);
1895
1896 ScalarFloatFromTensor(fetchValues[1], &dxy_dyValue);
1897 EXPECT_EQ(X, dxy_dyValue);
1898
1899 TF_DeleteTensor(fetchValues[0]);
1900 TF_DeleteTensor(fetchValues[1]);
1901 }
1902
1903 // REGISTER_OP for CApiAttributesTest test cases.
1904 // Registers two ops, each with a single attribute called 'v'.
1905 // The attribute in one op will have a type 'type', the other
1906 // will have list(type).
1907 #define ATTR_TEST_REGISTER_OP(type) \
1908 REGISTER_OP("CApiAttributesTestOp" #type) \
1909 .Attr("v: " #type) \
1910 .SetShapeFn(tensorflow::shape_inference::UnknownShape); \
1911 REGISTER_OP("CApiAttributesTestOpList" #type) \
1912 .Attr("v: list(" #type ")") \
1913 .SetShapeFn(tensorflow::shape_inference::UnknownShape)
1914 ATTR_TEST_REGISTER_OP(string);
1915 ATTR_TEST_REGISTER_OP(int);
1916 ATTR_TEST_REGISTER_OP(float);
1917 ATTR_TEST_REGISTER_OP(bool);
1918 ATTR_TEST_REGISTER_OP(type);
1919 ATTR_TEST_REGISTER_OP(shape);
1920 ATTR_TEST_REGISTER_OP(tensor);
1921 #undef ATTR_TEST_REGISTER_OP
1922
1923 class CApiAttributesTest : public ::testing::Test {
1924 protected:
CApiAttributesTest()1925 CApiAttributesTest()
1926 : s_(TF_NewStatus()), graph_(TF_NewGraph()), counter_(0) {}
1927
~CApiAttributesTest()1928 ~CApiAttributesTest() override {
1929 TF_DeleteGraph(graph_);
1930 TF_DeleteStatus(s_);
1931 }
1932
init(string type)1933 TF_OperationDescription* init(string type) {
1934 // Construct op_name to match the name used by REGISTER_OP in the
1935 // ATTR_TEST_REGISTER calls above.
1936 string op_name = "CApiAttributesTestOp";
1937 if (type.find("list(") == 0) {
1938 op_name += "List";
1939 type = type.replace(0, 5, "");
1940 type = type.replace(type.size() - 1, 1, "");
1941 }
1942 op_name += type;
1943 return TF_NewOperation(
1944 graph_, op_name.c_str(),
1945 ::tensorflow::strings::StrCat("name", counter_++).c_str());
1946 }
1947
1948 TF_Status* s_;
1949
1950 private:
1951 TF_Graph* graph_;
1952 int counter_;
1953 };
1954
1955 // Helper macros for the TF_OperationGetAttr* tests.
1956 // TODO(ashankar): Use gmock matchers instead?
1957 // (https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#writing-new-parameterized-matchers-quickly)
1958 // That will require setting up the tensorflow build with gmock.
1959 #define EXPECT_TF_META(attr_name, expected_list_size, expected_type, \
1960 expected_total_size) \
1961 do { \
1962 auto m = TF_OperationGetAttrMetadata(oper, attr_name, s_); \
1963 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); \
1964 const unsigned char e = expected_list_size >= 0 ? 1 : 0; \
1965 EXPECT_EQ(e, m.is_list); \
1966 EXPECT_EQ(expected_list_size, m.list_size); \
1967 EXPECT_EQ(expected_type, m.type); \
1968 EXPECT_EQ(expected_total_size, m.total_size); \
1969 } while (0)
1970
TEST_F(CApiAttributesTest,String)1971 TEST_F(CApiAttributesTest, String) {
1972 auto desc = init("string");
1973 TF_SetAttrString(desc, "v", "bunny", 5);
1974
1975 auto oper = TF_FinishOperation(desc, s_);
1976 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1977 EXPECT_TF_META("v", -1, TF_ATTR_STRING, 5);
1978 std::unique_ptr<char[]> value(new char[5]);
1979
1980 TF_OperationGetAttrString(oper, "v", value.get(), 5, s_);
1981 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
1982 EXPECT_EQ("bunny", string(static_cast<const char*>(value.get()), 5));
1983 }
1984
TEST_F(CApiAttributesTest,StringList)1985 TEST_F(CApiAttributesTest, StringList) {
1986 std::vector<string> list = {"bugs", "bunny", "duck"};
1987 std::unique_ptr<const void*[]> list_ptrs;
1988 std::unique_ptr<size_t[]> list_lens;
1989 StringVectorToArrays(list, &list_ptrs, &list_lens);
1990 int list_total_size = 0;
1991 for (const auto& s : list) {
1992 list_total_size += s.size();
1993 }
1994
1995 auto desc = init("list(string)");
1996 TF_SetAttrStringList(desc, "v", list_ptrs.get(), list_lens.get(),
1997 list.size());
1998
1999 auto oper = TF_FinishOperation(desc, s_);
2000 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2001
2002 EXPECT_TF_META("v", list.size(), TF_ATTR_STRING, list_total_size);
2003 std::unique_ptr<void*[]> values(new void*[list.size()]);
2004 std::unique_ptr<size_t[]> lens(new size_t[list.size()]);
2005 std::unique_ptr<char[]> storage(new char[list_total_size]);
2006 TF_OperationGetAttrStringList(oper, "v", values.get(), lens.get(),
2007 list.size(), storage.get(), list_total_size,
2008 s_);
2009 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2010 for (size_t i = 0; i < list.size(); ++i) {
2011 EXPECT_EQ(list[i].size(), lens[i]) << i;
2012 EXPECT_EQ(list[i], string(static_cast<const char*>(values[i]), lens[i]))
2013 << i;
2014 }
2015 }
2016
TEST_F(CApiAttributesTest,Int)2017 TEST_F(CApiAttributesTest, Int) {
2018 auto desc = init("int");
2019 TF_SetAttrInt(desc, "v", 31415);
2020
2021 auto oper = TF_FinishOperation(desc, s_);
2022 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2023 EXPECT_TF_META("v", -1, TF_ATTR_INT, -1);
2024
2025 int64_t value;
2026 TF_OperationGetAttrInt(oper, "v", &value, s_);
2027 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2028 EXPECT_EQ(31415, value);
2029 }
2030
TEST_F(CApiAttributesTest,IntList)2031 TEST_F(CApiAttributesTest, IntList) {
2032 const int64_t list[] = {1, 2, 3, 4};
2033 const size_t list_size = TF_ARRAYSIZE(list);
2034
2035 auto desc = init("list(int)");
2036 TF_SetAttrIntList(desc, "v", list, list_size);
2037
2038 auto oper = TF_FinishOperation(desc, s_);
2039 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2040
2041 int64_t values[list_size];
2042 EXPECT_TF_META("v", list_size, TF_ATTR_INT, -1);
2043 TF_OperationGetAttrIntList(oper, "v", values, list_size, s_);
2044 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2045 EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
2046 }
2047
TEST_F(CApiAttributesTest,Float)2048 TEST_F(CApiAttributesTest, Float) {
2049 auto desc = init("float");
2050 TF_SetAttrFloat(desc, "v", 2.718);
2051
2052 auto oper = TF_FinishOperation(desc, s_);
2053 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2054 EXPECT_TF_META("v", -1, TF_ATTR_FLOAT, -1);
2055
2056 float value;
2057 TF_OperationGetAttrFloat(oper, "v", &value, s_);
2058 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2059 EXPECT_FLOAT_EQ(2.718, value);
2060 }
2061
TEST_F(CApiAttributesTest,FloatList)2062 TEST_F(CApiAttributesTest, FloatList) {
2063 const float list[] = {1.414, 2.718, 3.1415};
2064 const size_t list_size = TF_ARRAYSIZE(list);
2065
2066 auto desc = init("list(float)");
2067 TF_SetAttrFloatList(desc, "v", list, list_size);
2068
2069 auto oper = TF_FinishOperation(desc, s_);
2070 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2071
2072 float values[list_size];
2073 EXPECT_TF_META("v", list_size, TF_ATTR_FLOAT, -1);
2074 TF_OperationGetAttrFloatList(oper, "v", values, list_size, s_);
2075 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2076 EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
2077 }
2078
TEST_F(CApiAttributesTest,Bool)2079 TEST_F(CApiAttributesTest, Bool) {
2080 auto desc = init("bool");
2081 TF_SetAttrBool(desc, "v", 1);
2082
2083 auto oper = TF_FinishOperation(desc, s_);
2084 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2085 EXPECT_TF_META("v", -1, TF_ATTR_BOOL, -1);
2086
2087 unsigned char value;
2088 TF_OperationGetAttrBool(oper, "v", &value, s_);
2089 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2090 EXPECT_EQ(1, value);
2091 }
2092
TEST_F(CApiAttributesTest,BoolList)2093 TEST_F(CApiAttributesTest, BoolList) {
2094 const unsigned char list[] = {0, 1, 1, 0, 0, 1, 1};
2095 const size_t list_size = TF_ARRAYSIZE(list);
2096
2097 auto desc = init("list(bool)");
2098 TF_SetAttrBoolList(desc, "v", list, list_size);
2099
2100 auto oper = TF_FinishOperation(desc, s_);
2101 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2102
2103 unsigned char values[list_size];
2104 EXPECT_TF_META("v", list_size, TF_ATTR_BOOL, -1);
2105 TF_OperationGetAttrBoolList(oper, "v", values, list_size, s_);
2106 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2107 EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
2108 }
2109
TEST_F(CApiAttributesTest,Type)2110 TEST_F(CApiAttributesTest, Type) {
2111 auto desc = init("type");
2112 TF_SetAttrType(desc, "v", TF_COMPLEX128);
2113
2114 auto oper = TF_FinishOperation(desc, s_);
2115 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2116 EXPECT_TF_META("v", -1, TF_ATTR_TYPE, -1);
2117
2118 TF_DataType value;
2119 TF_OperationGetAttrType(oper, "v", &value, s_);
2120 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2121 EXPECT_EQ(TF_COMPLEX128, value);
2122 }
2123
TEST_F(CApiAttributesTest,TypeList)2124 TEST_F(CApiAttributesTest, TypeList) {
2125 const TF_DataType list[] = {TF_FLOAT, TF_DOUBLE, TF_HALF, TF_COMPLEX128};
2126 const size_t list_size = TF_ARRAYSIZE(list);
2127
2128 auto desc = init("list(type)");
2129 TF_SetAttrTypeList(desc, "v", list, list_size);
2130
2131 auto oper = TF_FinishOperation(desc, s_);
2132 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2133
2134 TF_DataType values[list_size];
2135 EXPECT_TF_META("v", list_size, TF_ATTR_TYPE, -1);
2136 TF_OperationGetAttrTypeList(oper, "v", values, list_size, s_);
2137 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2138 EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
2139 }
2140
TEST_F(CApiAttributesTest,Shape)2141 TEST_F(CApiAttributesTest, Shape) {
2142 // Unknown shape
2143 auto desc = init("shape");
2144 TF_SetAttrShape(desc, "v", nullptr, -1);
2145 auto oper = TF_FinishOperation(desc, s_);
2146 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2147 EXPECT_TF_META("v", -1, TF_ATTR_SHAPE, -1);
2148 TF_OperationGetAttrShape(oper, "v", nullptr, 10, s_);
2149 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2150
2151 // Partially specified shape
2152 const int64_t partial_shape[] = {17, -1};
2153 const size_t sz = TF_ARRAYSIZE(partial_shape);
2154 desc = init("shape");
2155 TF_SetAttrShape(desc, "v", partial_shape, sz);
2156 oper = TF_FinishOperation(desc, s_);
2157 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2158 EXPECT_TF_META("v", -1, TF_ATTR_SHAPE, sz);
2159 int64_t values[sz];
2160 TF_OperationGetAttrShape(oper, "v", values, sz, s_);
2161 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2162 EXPECT_TRUE(
2163 std::equal(std::begin(partial_shape), std::end(partial_shape), values));
2164 }
2165
TEST_F(CApiAttributesTest,ShapeList)2166 TEST_F(CApiAttributesTest, ShapeList) {
2167 const int64_t shape_1[] = {1, 3};
2168 const int64_t shape_2[] = {2, 4, 6};
2169 const int64_t* list[] = {&shape_1[0], &shape_2[0]};
2170 const size_t list_size = TF_ARRAYSIZE(list);
2171 const int ndims[] = {TF_ARRAYSIZE(shape_1), TF_ARRAYSIZE(shape_2)};
2172 const int total_ndims = 5; // ndims[0] + ndims[1]
2173
2174 auto desc = init("list(shape)");
2175 TF_SetAttrShapeList(desc, "v", list, ndims, list_size);
2176 auto oper = TF_FinishOperation(desc, s_);
2177 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2178
2179 EXPECT_TF_META("v", list_size, TF_ATTR_SHAPE, total_ndims);
2180 int64_t* values[list_size];
2181 int values_ndims[list_size];
2182 int64_t storage[total_ndims];
2183 TF_OperationGetAttrShapeList(oper, "v", values, values_ndims, list_size,
2184 storage, total_ndims, s_);
2185 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2186 for (size_t i = 0; i < list_size; ++i) {
2187 EXPECT_EQ(ndims[i], values_ndims[i]) << i;
2188 for (int j = 0; j < values_ndims[i]; ++j) {
2189 EXPECT_EQ(list[i][j], values[i][j]) << "(" << i << ", " << j << ")";
2190 }
2191 }
2192 }
2193
TEST_F(CApiAttributesTest,TensorShapeProto)2194 TEST_F(CApiAttributesTest, TensorShapeProto) {
2195 const tensorflow::int64 pts[] = {2, 4, -1, 8};
2196 tensorflow::TensorShapeProto proto;
2197 tensorflow::PartialTensorShape(pts).AsProto(&proto);
2198 string bytes;
2199 proto.SerializeToString(&bytes);
2200
2201 auto desc = init("shape");
2202 TF_SetAttrTensorShapeProto(desc, "v", bytes.data(), bytes.length(), s_);
2203 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2204 auto oper = TF_FinishOperation(desc, s_);
2205 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2206
2207 EXPECT_TF_META("v", -1, TF_ATTR_SHAPE, 4);
2208 TF_Buffer* value = TF_NewBuffer();
2209 TF_OperationGetAttrTensorShapeProto(oper, "v", value, s_);
2210 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2211 EXPECT_EQ(bytes.length(), value->length);
2212 EXPECT_EQ(0, memcmp(bytes.data(), value->data, value->length));
2213 TF_DeleteBuffer(value);
2214 }
2215
TEST_F(CApiAttributesTest,TensorShapeProtoList)2216 TEST_F(CApiAttributesTest, TensorShapeProtoList) {
2217 string bytes1, bytes2;
2218 tensorflow::TensorShapeProto proto;
2219
2220 const tensorflow::int64 pts1[] = {2, 4, -1, 8};
2221 tensorflow::PartialTensorShape(pts1).AsProto(&proto);
2222 proto.SerializeToString(&bytes1);
2223
2224 const tensorflow::int64 pts2[] = {1, 3, 5, 7};
2225 tensorflow::PartialTensorShape(pts2).AsProto(&proto);
2226 proto.SerializeToString(&bytes2);
2227
2228 std::unique_ptr<const void*[]> list_ptrs;
2229 std::unique_ptr<size_t[]> list_lens;
2230 const std::vector<string> list = {bytes1, bytes2};
2231 StringVectorToArrays(list, &list_ptrs, &list_lens);
2232
2233 auto desc = init("list(shape)");
2234 TF_SetAttrTensorShapeProtoList(desc, "v", list_ptrs.get(), list_lens.get(),
2235 list.size(), s_);
2236 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2237 auto oper = TF_FinishOperation(desc, s_);
2238 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2239
2240 EXPECT_TF_META("v", 2, TF_ATTR_SHAPE, 8);
2241 TF_Buffer* values[2];
2242 TF_OperationGetAttrTensorShapeProtoList(oper, "v", values, 2, s_);
2243 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2244 for (int i = 0; i < 2; ++i) {
2245 int le = list_lens[i];
2246 int la = values[i]->length;
2247 const void* e = list_ptrs[i];
2248 const void* a = values[i]->data;
2249 EXPECT_EQ(le, la) << i;
2250 EXPECT_EQ(0, memcmp(e, a, std::min(le, la))) << i;
2251 TF_DeleteBuffer(values[i]);
2252 }
2253 }
2254
TEST_F(CApiAttributesTest,Tensor)2255 TEST_F(CApiAttributesTest, Tensor) {
2256 const char tensor[] = {5, 7};
2257 const int64_t dims[] = {1, 2};
2258 const size_t ndims = TF_ARRAYSIZE(dims);
2259
2260 auto desc = init("tensor");
2261 unique_tensor_ptr v(Int8Tensor(dims, ndims, tensor), TF_DeleteTensor);
2262 TF_SetAttrTensor(desc, "v", v.get(), s_);
2263 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2264
2265 auto oper = TF_FinishOperation(desc, s_);
2266 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2267
2268 EXPECT_TF_META("v", -1, TF_ATTR_TENSOR, -1);
2269 TF_Tensor* value;
2270 TF_OperationGetAttrTensor(oper, "v", &value, s_);
2271 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2272 ASSERT_NE(nullptr, value);
2273 EXPECT_EQ(TF_INT8, TF_TensorType(value));
2274 EXPECT_EQ(ndims, TF_NumDims(value));
2275 for (int i = 0; i < TF_NumDims(value); ++i) {
2276 EXPECT_EQ(dims[i], TF_Dim(value, i)) << i;
2277 }
2278 EXPECT_EQ(sizeof(char) * TF_ARRAYSIZE(tensor), TF_TensorByteSize(value));
2279 EXPECT_EQ(0, memcmp(tensor, TF_TensorData(value), TF_TensorByteSize(value)));
2280 TF_DeleteTensor(value);
2281 }
2282
TEST_F(CApiAttributesTest,StringTensor)2283 TEST_F(CApiAttributesTest, StringTensor) {
2284 // Create the string-Tensor "attribute" value.
2285 char encoded[] = {
2286 0, 0, 0, 0, 0, 0, 0, 0, // array[uint64] offsets
2287 1, // varint encoded string length
2288 'A',
2289 };
2290 auto deallocator = [](void* data, size_t len, void* arg) {};
2291 unique_tensor_ptr t_in(TF_NewTensor(TF_STRING, nullptr, 0, &encoded[0],
2292 sizeof(encoded), deallocator, nullptr),
2293 TF_DeleteTensor);
2294
2295 // Create a TF_Operation with the attribute t_in
2296 auto desc = init("tensor");
2297 TF_SetAttrTensor(desc, "v", t_in.get(), s_);
2298 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2299
2300 auto oper = TF_FinishOperation(desc, s_);
2301 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2302
2303 // Fetch the attribute back.
2304 EXPECT_TF_META("v", -1, TF_ATTR_TENSOR, -1);
2305 TF_Tensor* t_out = nullptr;
2306 TF_OperationGetAttrTensor(oper, "v", &t_out, s_);
2307 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2308 EXPECT_EQ(TF_STRING, TF_TensorType(t_out));
2309 EXPECT_EQ(0, TF_NumDims(t_out));
2310 ASSERT_EQ(TF_TensorByteSize(t_in.get()), TF_TensorByteSize(t_out));
2311 EXPECT_EQ(0, memcmp(TF_TensorData(t_in.get()), TF_TensorData(t_out),
2312 TF_TensorByteSize(t_out)));
2313 TF_DeleteTensor(t_out);
2314 }
2315
TEST_F(CApiAttributesTest,TensorList)2316 TEST_F(CApiAttributesTest, TensorList) {
2317 const char tensor1[] = {5, 7};
2318 const int64_t dims1[] = {1, 2};
2319 const size_t ndims1 = TF_ARRAYSIZE(dims1);
2320
2321 const char tensor2[] = {2, 4, 6, 8};
2322 const int64_t dims2[] = {2, 2};
2323 const size_t ndims2 = TF_ARRAYSIZE(dims2);
2324
2325 auto desc = init("list(tensor)");
2326 TF_Tensor* tmp[] = {
2327 Int8Tensor(dims1, ndims1, tensor1),
2328 Int8Tensor(dims2, ndims2, tensor2),
2329 };
2330 TF_SetAttrTensorList(desc, "v", tmp, TF_ARRAYSIZE(tmp), s_);
2331 for (int i = 0; i < TF_ARRAYSIZE(tmp); ++i) {
2332 TF_DeleteTensor(tmp[i]);
2333 }
2334 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2335 auto oper = TF_FinishOperation(desc, s_);
2336 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2337
2338 EXPECT_TF_META("v", 2, TF_ATTR_TENSOR, -1);
2339 TF_Tensor* values[2];
2340 TF_OperationGetAttrTensorList(oper, "v", &values[0], TF_ARRAYSIZE(values),
2341 s_);
2342 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2343
2344 const char* tensor_data[] = {&tensor1[0], &tensor2[0]};
2345 const size_t tensor_size[] = {TF_ARRAYSIZE(tensor1), TF_ARRAYSIZE(tensor2)};
2346 const int64_t* tensor_dims[] = {&dims1[0], &dims2[0]};
2347 const size_t tensor_ndims[] = {ndims1, ndims2};
2348 for (int i = 0; i < 2; ++i) {
2349 TF_Tensor* v = values[i];
2350 ASSERT_NE(nullptr, v) << i;
2351 EXPECT_EQ(TF_INT8, TF_TensorType(v)) << i;
2352 EXPECT_EQ(tensor_ndims[i], TF_NumDims(v)) << i;
2353 for (int j = 0; j < TF_NumDims(v); ++j) {
2354 EXPECT_EQ(tensor_dims[i][j], TF_Dim(v, j))
2355 << "Tensor #" << i << ", dimension #" << j;
2356 }
2357 EXPECT_EQ(sizeof(char) * tensor_size[i], TF_TensorByteSize(v)) << i;
2358 EXPECT_EQ(0,
2359 memcmp(tensor_data[i], TF_TensorData(v), TF_TensorByteSize(v)));
2360 TF_DeleteTensor(v);
2361 }
2362 }
2363
TEST_F(CApiAttributesTest,EmptyList)2364 TEST_F(CApiAttributesTest, EmptyList) {
2365 auto desc = init("list(int)");
2366 TF_SetAttrIntList(desc, "v", nullptr, 0);
2367 auto oper = TF_FinishOperation(desc, s_);
2368 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2369 EXPECT_TF_META("v", 0, TF_ATTR_INT, -1);
2370 }
2371
TEST_F(CApiAttributesTest,Errors)2372 TEST_F(CApiAttributesTest, Errors) {
2373 auto desc = init("int");
2374 TF_SetAttrInt(desc, "v", 3);
2375 auto oper = TF_FinishOperation(desc, s_);
2376 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
2377 TF_OperationGetAttrString(oper, "v", nullptr, 0, s_);
2378 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
2379 }
2380
TEST(TestApiDef,TestCreateApiDef)2381 TEST(TestApiDef, TestCreateApiDef) {
2382 // TODO(b/73318067): Fix linking for the GPU test generated by the
2383 // tf_cuda_cc_test() bazel rule and remove the next line.
2384 if (!GPUDeviceName().empty()) return;
2385
2386 TF_Buffer* op_list_buf = TF_GetAllOpList();
2387 TF_Status* status = TF_NewStatus();
2388 auto* api_def_map = TF_NewApiDefMap(op_list_buf, status);
2389 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2390 TF_DeleteStatus(status);
2391
2392 string op_name = "TestCApi";
2393 status = TF_NewStatus();
2394 auto* api_def_buf =
2395 TF_ApiDefMapGet(api_def_map, op_name.c_str(), op_name.size(), status);
2396 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2397 TF_DeleteStatus(status);
2398
2399 tensorflow::ApiDef api_def;
2400 EXPECT_TRUE(api_def.ParseFromArray(api_def_buf->data, api_def_buf->length));
2401 EXPECT_EQ(op_name, api_def.graph_op_name());
2402 EXPECT_EQ(R"doc(Used to test C API)doc", api_def.summary());
2403
2404 TF_DeleteBuffer(api_def_buf);
2405 TF_DeleteApiDefMap(api_def_map);
2406 TF_DeleteBuffer(op_list_buf);
2407 }
2408
TEST(TestApiDef,TestCreateApiDefWithOverwrites)2409 TEST(TestApiDef, TestCreateApiDefWithOverwrites) {
2410 // TODO(b/73318067): Fix linking for the GPU test generated by the
2411 // tf_cuda_cc_test() bazel rule and remove the next line.
2412 if (!GPUDeviceName().empty()) return;
2413
2414 TF_Buffer* op_list_buf = TF_GetAllOpList();
2415 TF_Status* status = TF_NewStatus();
2416 auto* api_def_map = TF_NewApiDefMap(op_list_buf, status);
2417 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2418 TF_DeleteStatus(status);
2419
2420 string api_def_overwrites = R"(op: <
2421 graph_op_name: "TestCApi"
2422 summary: "New summary"
2423 >
2424 )";
2425 status = TF_NewStatus();
2426 TF_ApiDefMapPut(api_def_map, api_def_overwrites.c_str(),
2427 api_def_overwrites.size(), status);
2428 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2429 TF_DeleteStatus(status);
2430
2431 string op_name = "TestCApi";
2432 status = TF_NewStatus();
2433 auto* api_def_buf =
2434 TF_ApiDefMapGet(api_def_map, op_name.c_str(), op_name.size(), status);
2435 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2436 TF_DeleteStatus(status);
2437
2438 tensorflow::ApiDef api_def;
2439 EXPECT_TRUE(api_def.ParseFromArray(api_def_buf->data, api_def_buf->length));
2440 EXPECT_EQ(op_name, api_def.graph_op_name());
2441 EXPECT_EQ("New summary", api_def.summary());
2442
2443 TF_DeleteBuffer(api_def_buf);
2444 TF_DeleteApiDefMap(api_def_map);
2445 TF_DeleteBuffer(op_list_buf);
2446 }
2447
2448 class DummyKernel : public tensorflow::OpKernel {
2449 public:
DummyKernel(tensorflow::OpKernelConstruction * context)2450 explicit DummyKernel(tensorflow::OpKernelConstruction* context)
2451 : OpKernel(context) {}
Compute(tensorflow::OpKernelContext * context)2452 void Compute(tensorflow::OpKernelContext* context) override {}
2453 };
2454
2455 // Test we can query kernels
2456 REGISTER_OP("TestOpWithSingleKernel")
2457 .Input("a: float")
2458 .Input("b: float")
2459 .Output("o: float");
2460 REGISTER_KERNEL_BUILDER(
2461 Name("TestOpWithSingleKernel").Device(tensorflow::DEVICE_CPU), DummyKernel);
2462
TEST(TestKernel,TestGetAllRegisteredKernels)2463 TEST(TestKernel, TestGetAllRegisteredKernels) {
2464 TF_Status* status = TF_NewStatus();
2465 TF_Buffer* kernel_list_buf = TF_GetAllRegisteredKernels(status);
2466 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2467 KernelList kernel_list;
2468 kernel_list.ParseFromArray(kernel_list_buf->data, kernel_list_buf->length);
2469 ASSERT_GT(kernel_list.kernel_size(), 0);
2470 TF_DeleteBuffer(kernel_list_buf);
2471 TF_DeleteStatus(status);
2472 }
2473
TEST(TestKernel,TestGetRegisteredKernelsForOp)2474 TEST(TestKernel, TestGetRegisteredKernelsForOp) {
2475 TF_Status* status = TF_NewStatus();
2476 TF_Buffer* kernel_list_buf =
2477 TF_GetRegisteredKernelsForOp("TestOpWithSingleKernel", status);
2478 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2479 KernelList kernel_list;
2480 kernel_list.ParseFromArray(kernel_list_buf->data, kernel_list_buf->length);
2481 ASSERT_EQ(kernel_list.kernel_size(), 1);
2482 EXPECT_EQ(kernel_list.kernel(0).op(), "TestOpWithSingleKernel");
2483 EXPECT_EQ(kernel_list.kernel(0).device_type(), "CPU");
2484 TF_DeleteBuffer(kernel_list_buf);
2485 TF_DeleteStatus(status);
2486 }
2487
TEST(TestKernel,TestGetRegisteredKernelsForOpNoKernels)2488 TEST(TestKernel, TestGetRegisteredKernelsForOpNoKernels) {
2489 TF_Status* status = TF_NewStatus();
2490 TF_Buffer* kernel_list_buf = TF_GetRegisteredKernelsForOp("Unknown", status);
2491 EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
2492 KernelList kernel_list;
2493 kernel_list.ParseFromArray(kernel_list_buf->data, kernel_list_buf->length);
2494 ASSERT_EQ(kernel_list.kernel_size(), 0);
2495 TF_DeleteBuffer(kernel_list_buf);
2496 TF_DeleteStatus(status);
2497 }
2498
2499 #undef EXPECT_TF_META
2500
2501 } // namespace
2502 } // namespace tensorflow
2503
2504 // TODO(josh11b): Test:
2505 // * TF_SetDevice(desc, "/job:worker");
2506 // * control inputs / outputs
2507 // * targets
2508 // * TF_DeleteGraph() before TF_DeleteSession()
2509