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