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 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. 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 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 109 TEST(CAPI, Version) { EXPECT_STRNE("", TF_Version()); } 110 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 121 void Deallocator(void* data, size_t, void* arg) { 122 tensorflow::cpu_allocator()->DeallocateRaw(data); 123 *reinterpret_cast<bool*>(arg) = true; 124 } 125 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 146 void NoOpDeallocator(void* data, size_t, void*) {} 147 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 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 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 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 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 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 264 TEST(CAPI, SessionOptions) { 265 TF_SessionOptions* opt = TF_NewSessionOptions(); 266 TF_DeleteSessionOptions(opt); 267 } 268 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 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 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 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 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 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 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 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 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 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). 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 1041 TEST(CAPI, Session_Min_CPU) { RunMinTest(/*device=*/"", /*use_XLA=*/false); } 1042 1043 TEST(CAPI, Session_Min_XLA_CPU) { RunMinTest(/*device=*/"", /*use_XLA=*/true); } 1044 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 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 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 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 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 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: 1196 CApiColocationTest() : s_(TF_NewStatus()), graph_(TF_NewGraph()) {} 1197 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 1213 ~CApiColocationTest() override { 1214 TF_DeleteGraph(graph_); 1215 TF_DeleteStatus(s_); 1216 } 1217 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 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 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 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 1282 TEST_F(CApiColocationTest, ColocateWith) { 1283 TF_ColocateWith(desc_, feed1_); 1284 FinishAndVerify(desc_, {"loc:@feed1"}); 1285 } 1286 1287 TEST_F(CApiColocationTest, StringList) { 1288 SetViaStringList(desc_, {"loc:@feed1"}); 1289 FinishAndVerify(desc_, {"loc:@feed1"}); 1290 } 1291 1292 TEST_F(CApiColocationTest, Proto) { 1293 SetViaProto(desc_, {"loc:@feed1"}); 1294 FinishAndVerify(desc_, {"loc:@feed1"}); 1295 } 1296 1297 TEST_F(CApiColocationTest, ColocateWith_StringList) { 1298 TF_ColocateWith(desc_, feed1_); 1299 SetViaStringList(desc_, {"loc:@feed2"}); 1300 FinishAndVerify(desc_, {"loc:@feed2"}); 1301 } 1302 1303 TEST_F(CApiColocationTest, ColocateWith_Proto) { 1304 TF_ColocateWith(desc_, feed1_); 1305 SetViaProto(desc_, {"loc:@feed2"}); 1306 FinishAndVerify(desc_, {"loc:@feed2"}); 1307 } 1308 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 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 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 1327 TEST_F(CApiColocationTest, Proto_StringList) { 1328 SetViaProto(desc_, {"loc:@feed1"}); 1329 SetViaStringList(desc_, {"loc:@feed2"}); 1330 FinishAndVerify(desc_, {"loc:@feed2"}); 1331 } 1332 1333 TEST_F(CApiColocationTest, StringList_Proto) { 1334 SetViaStringList(desc_, {"loc:@feed1"}); 1335 SetViaProto(desc_, {"loc:@feed2"}); 1336 FinishAndVerify(desc_, {"loc:@feed2"}); 1337 } 1338 1339 TEST_F(CApiColocationTest, ClearViaStringList) { 1340 TF_ColocateWith(desc_, feed1_); 1341 SetViaStringList(desc_, {}); 1342 FinishAndVerify(desc_, {}); 1343 } 1344 1345 TEST_F(CApiColocationTest, ClearViaProto) { 1346 TF_ColocateWith(desc_, feed1_); 1347 SetViaProto(desc_, {}); 1348 FinishAndVerify(desc_, {}); 1349 } 1350 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 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 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 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: 1520 CApiGradientsTest() 1521 : s_(TF_NewStatus()), 1522 graph_(TF_NewGraph()), 1523 expected_graph_(TF_NewGraph()) {} 1524 1525 ~CApiGradientsTest() override { 1526 TF_DeleteGraph(graph_); 1527 TF_DeleteGraph(expected_graph_); 1528 TF_DeleteStatus(s_); 1529 } 1530 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 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. 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 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 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 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 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 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 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 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 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 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 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 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 1784 TEST_F(CApiGradientsTest, Gradients_GradInputs) { TestGradientsSuccess(true); } 1785 1786 TEST_F(CApiGradientsTest, Gradients_NoGradInputs) { 1787 TestGradientsSuccess(false); 1788 } 1789 1790 TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_GradInputs) { 1791 TestGradientsError(true); 1792 } 1793 1794 TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_NoGradInputs) { 1795 TestGradientsError(false); 1796 } 1797 1798 TEST_F(CApiGradientsTest, GradientsPrefix_PrefixIsOk) { 1799 BuildGraphAndAddGradientsWithPrefixes("gradients"); 1800 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1801 } 1802 1803 TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithDistinctPrefixes) { 1804 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients_1"); 1805 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1806 } 1807 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 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 1818 TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsSubScopeOf1st) { 1819 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/sub"); 1820 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1821 } 1822 1823 TEST_F(CApiGradientsTest, GradientsPrefix_PrefixMatchesExistingNodeName) { 1824 BuildGraphAndAddGradientsWithPrefixes("Const_0"); 1825 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_); 1826 } 1827 1828 TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithIdenticalPrefixes) { 1829 BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients"); 1830 ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_); 1831 } 1832 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 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 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 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 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: 1925 CApiAttributesTest() 1926 : s_(TF_NewStatus()), graph_(TF_NewGraph()), counter_(0) {} 1927 1928 ~CApiAttributesTest() override { 1929 TF_DeleteGraph(graph_); 1930 TF_DeleteStatus(s_); 1931 } 1932 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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: 2450 explicit DummyKernel(tensorflow::OpKernelConstruction* context) 2451 : OpKernel(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 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 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 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