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 "tensorflow/c/c_test_util.h" 19 #include "tensorflow/core/framework/function.pb.h" 20 #include "tensorflow/core/framework/op_def.pb.h" 21 #include "tensorflow/core/lib/core/status.h" 22 #include "tensorflow/core/lib/hash/hash.h" 23 #include "tensorflow/core/lib/strings/str_util.h" 24 #include "tensorflow/core/lib/strings/strcat.h" 25 #include "tensorflow/core/platform/logging.h" 26 #include "tensorflow/core/platform/test.h" 27 28 namespace tensorflow { 29 namespace { 30 31 // Specification for expected input/output and its type. 32 // DataType value of DT_INVALID signifies that we don't want to 33 // check the data type. 34 typedef std::pair<string, DataType> IOSpec; 35 36 std::vector<IOSpec> M(const std::initializer_list<string>& names) { 37 std::vector<IOSpec> v; 38 for (const string& name : names) { 39 v.push_back(IOSpec(name, DT_INVALID)); 40 } 41 return v; 42 } 43 44 // Specification for an expected edge. 45 // src is either: 46 // - input name (as it appears in FunctionDef) 47 // - name of output tensor (in nested "add:z:0" format) 48 // dst is either: 49 // - output name (as it appears in FunctionDef) 50 // - <name_of_node>:<index_of_this_input_into_node> (this looks the same as 51 // output tensor naming, but it the index is actually an input index) 52 struct EdgeSpec : public std::pair<string, string> { 53 typedef std::pair<string, string> Base; 54 55 // Inherit the set of constructors 56 using Base::pair; 57 58 string ToString() const { return strings::StrCat(first, "->", second); } 59 }; 60 61 class CApiFunctionTest : public ::testing::Test { 62 protected: 63 CApiFunctionTest() 64 : s_(TF_NewStatus()), 65 func_graph_(TF_NewGraph()), 66 host_graph_(TF_NewGraph()), 67 func_(nullptr) {} 68 69 void SetUp() override {} 70 71 ~CApiFunctionTest() override { 72 TF_DeleteFunction(func_); 73 TF_DeleteGraph(host_graph_); 74 TF_DeleteGraph(func_graph_); 75 TF_DeleteStatus(s_); 76 } 77 78 void Run(const std::vector<std::pair<TF_Operation*, TF_Tensor*>>& inputs, 79 TF_Operation* output, int32_t expected_result) { 80 Run(inputs, {{output, 0}}, {expected_result}); 81 } 82 83 // Run the host graph, which now contains a function and check that 84 // outputs are as expected. 85 // 'T' stands for 'tensor' since the outputs are tensors, not scalars. 86 void RunT(const std::vector<std::pair<TF_Operation*, TF_Tensor*>>& inputs, 87 std::initializer_list<TF_Output> outputs, 88 const std::vector<std::vector<int32_t>>& expected_results) { 89 // Create a session for this graph 90 CSession csession(host_graph_, s_); 91 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 92 93 // Run 94 csession.SetInputs(inputs); 95 csession.SetOutputs(outputs); 96 csession.Run(s_); 97 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 98 99 // Check results 100 for (int i = 0; i < expected_results.size(); ++i) { 101 TF_Tensor* out = csession.output_tensor(i); 102 ASSERT_TRUE(out != nullptr); 103 EXPECT_EQ(TF_INT32, TF_TensorType(out)); 104 EXPECT_EQ(1, TF_NumDims(out)); 105 CompareInt32Tensor(expected_results[i], out); 106 } 107 } 108 109 // Run the host graph, which now contains a function and check that 110 // outputs are as expected. 111 void Run(const std::vector<std::pair<TF_Operation*, TF_Tensor*>>& inputs, 112 std::initializer_list<TF_Output> outputs, 113 const std::vector<int32_t>& expected_results) { 114 // Create a session for this graph. 115 CSession csession(host_graph_, s_); 116 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 117 118 csession.SetInputs(inputs); 119 csession.SetOutputs(outputs); 120 csession.Run(s_); 121 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 122 123 for (int i = 0; i < expected_results.size(); ++i) { 124 TF_Tensor* out = csession.output_tensor(i); 125 ASSERT_TRUE(out != nullptr); 126 EXPECT_EQ(TF_INT32, TF_TensorType(out)); 127 EXPECT_EQ(0, TF_NumDims(out)); // scalar 128 ASSERT_EQ(sizeof(int32_t), TF_TensorByteSize(out)); 129 int32_t* output_contents = static_cast<int32_t*>(TF_TensorData(out)); 130 EXPECT_EQ(expected_results[i], *output_contents); 131 } 132 } 133 134 void CompareInt32Tensor(const std::vector<int32_t>& expected, TF_Tensor* t) { 135 int32_t* data = static_cast<int32_t*>(TF_TensorData(t)); 136 size_t size = TF_TensorByteSize(t); 137 ASSERT_EQ(expected.size() * sizeof(int32_t), size); 138 for (int i = 0; i < expected.size(); ++i) { 139 ASSERT_EQ(expected[i], data[i]) << "Different data at index " << i; 140 } 141 } 142 143 std::vector<TF_Output> ToOutput(const std::vector<TF_Operation*> ops) { 144 std::vector<TF_Output> out; 145 for (auto op : ops) { 146 out.push_back({op, 0}); 147 } 148 return out; 149 } 150 151 void Define(int num_opers, const std::vector<TF_Operation*>& opers, 152 const std::vector<TF_Operation*>& inputs, 153 const std::vector<TF_Operation*>& outputs, 154 const std::vector<string>& output_names, 155 bool expect_failure = false) { 156 DefineT(num_opers, opers, ToOutput(inputs), ToOutput(outputs), output_names, 157 expect_failure); 158 } 159 160 // Caller must delete[] the returned value 161 static const char** ToArray(const std::vector<string>& strs) { 162 const char** ptr = nullptr; 163 if (!strs.empty()) { 164 ptr = new const char*[strs.size()]; 165 for (size_t i = 0; i < strs.size(); ++i) { 166 ptr[i] = strs[i].c_str(); 167 } 168 } 169 return ptr; 170 } 171 172 // An explicit `num_opers` is needed so that we can distinguish between the 173 // case of no operations specified (-1) and the case of an empty set of 174 // operations specified (0). 175 void DefineT(int num_opers, const std::vector<TF_Operation*>& opers, 176 const std::vector<TF_Output>& inputs, 177 const std::vector<TF_Output>& outputs, 178 const std::vector<string>& output_names, 179 bool expect_failure = false) { 180 ASSERT_EQ(func_, nullptr); 181 const char** output_names_ptr = ToArray(output_names); 182 func_ = TF_GraphToFunction(func_graph_, func_name_, false, num_opers, 183 num_opers == -1 ? nullptr : opers.data(), 184 inputs.size(), inputs.data(), outputs.size(), 185 outputs.data(), output_names_ptr, 186 /*opts=*/nullptr, /*description=*/nullptr, s_); 187 delete[] output_names_ptr; 188 if (expect_failure) { 189 ASSERT_EQ(func_, nullptr); 190 return; 191 } 192 193 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 194 ASSERT_NE(func_, nullptr); 195 TF_GraphCopyFunction(host_graph_, func_, nullptr, s_); 196 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 197 } 198 199 TF_Operation* Use(const std::vector<TF_Operation*>& inputs) { 200 return UseT(ToOutput(inputs)); 201 } 202 203 TF_Operation* UseT(const std::vector<TF_Output>& inputs) { 204 TF_Operation* op; 205 UseHelper(inputs, &op); 206 return op; 207 } 208 209 // All the *Helper methods are used as a workaround for the restrictions that 210 // one cannot call ASSERT_* methods in non-void-returning functions (when 211 // exceptions are disabled during compilation) 212 void UseHelper(const std::vector<TF_Output>& inputs, TF_Operation** op) { 213 TF_OperationDescription* desc = 214 TF_NewOperation(host_graph_, func_name_, func_node_name_); 215 for (auto input : inputs) { 216 TF_AddInput(desc, input); 217 } 218 // Set device to CPU because some ops inside the function might not be 219 // available on GPU. 220 TF_SetDevice(desc, "/cpu:0"); 221 *op = TF_FinishOperation(desc, s_); 222 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 223 ASSERT_NE(*op, nullptr); 224 } 225 226 FunctionDef fdef() { 227 tensorflow::FunctionDef fdef; 228 EXPECT_TRUE(GetFunctionDef(func_, &fdef)); 229 return fdef; 230 } 231 232 // logging utility 233 template <class Container> 234 string ToString(const Container& v) { 235 std::stringstream ss; 236 ss << "{"; 237 size_t i = 0; 238 for (const auto& e : v) { 239 if (i != 0) { 240 ss << ", "; 241 } 242 ss << e.ToString(); 243 ++i; 244 } 245 ss << "}"; 246 return ss.str(); 247 } 248 249 void VerifyFDefNodes(const tensorflow::FunctionDef& fdef, 250 const std::unordered_set<string>& nodes) { 251 ASSERT_EQ(nodes.size(), fdef.node_def_size()) 252 << "Got unexpected number of nodes. Expected: [" 253 << str_util::Join(nodes, ", ") 254 << "] Actual nodes in fdef: " << fdef.DebugString(); 255 for (const NodeDef& node_def : fdef.node_def()) { 256 ASSERT_TRUE(nodes.find(node_def.name()) != nodes.end()) 257 << "Got unexpected node: " << node_def.name() 258 << " in fdef: " << fdef.DebugString(); 259 } 260 } 261 262 void VerifyFDefInputs(const tensorflow::FunctionDef& fdef, 263 const std::vector<IOSpec>& inputs) { 264 const OpDef& signature = fdef.signature(); 265 ASSERT_EQ(inputs.size(), signature.input_arg_size()); 266 for (int i = 0; i < inputs.size(); ++i) { 267 const OpDef::ArgDef& arg = signature.input_arg(i); 268 const IOSpec& in = inputs[i]; 269 if (in.second != DT_INVALID) { 270 ASSERT_EQ(arg.type(), in.second) 271 << "Got unexpected type for input " << i 272 << ". fdef: " << fdef.DebugString(); 273 } 274 ASSERT_EQ(arg.name(), in.first) << "Got unexpected name for input " << i 275 << ". fdef: " << fdef.DebugString(); 276 } 277 } 278 279 void VerifyFDefOutputs(const tensorflow::FunctionDef& fdef, 280 const std::vector<IOSpec>& outputs) { 281 const OpDef& signature = fdef.signature(); 282 ASSERT_EQ(outputs.size(), signature.output_arg_size()); 283 for (int i = 0; i < outputs.size(); ++i) { 284 const OpDef::ArgDef& arg = signature.output_arg(i); 285 const IOSpec& out = outputs[i]; 286 if (out.second != DT_INVALID) { 287 ASSERT_EQ(arg.type(), out.second) 288 << "Got unexpected type for output " << i 289 << ". fdef: " << fdef.DebugString(); 290 } 291 ASSERT_EQ(arg.name(), out.first) << "Got unexpected name for output " << i 292 << ". fdef: " << fdef.DebugString(); 293 } 294 } 295 296 void VerifyFDefEdges( 297 const tensorflow::FunctionDef& fdef, 298 const std::vector<EdgeSpec>& e_edges, // expected edges 299 const std::vector<EdgeSpec>& c_edges, // expected ctrl edges 300 bool is_exact_edges = true) { 301 // Build a set of edges from fdef 302 std::set<EdgeSpec> a_edges; // actual edges 303 // Get edges from inputs to body nodes and between body nodes 304 for (const NodeDef& node_def : fdef.node_def()) { 305 for (int i = 0; i < node_def.input_size(); ++i) { 306 const string& in = node_def.input(i); 307 const auto& v = 308 a_edges.insert({in, strings::StrCat(node_def.name(), ":", i)}); 309 ASSERT_TRUE(v.second) << "Duplicate edge " << in << " -> " 310 << strings::StrCat(node_def.name(), ":", i) 311 << ". fdef: " << fdef.DebugString(); 312 } 313 } 314 // Get edges from body nodes to outputs and from inputs to outputs 315 for (const OpDef::ArgDef& arg : fdef.signature().output_arg()) { 316 const auto& iter = fdef.ret().find(arg.name()); 317 if (iter != fdef.ret().end()) { 318 const auto& v = a_edges.insert({iter->second, arg.name()}); 319 ASSERT_TRUE(v.second) << "Duplicate edge " << iter->second << " -> " 320 << arg.name() << ". fdef: " << fdef.DebugString(); 321 } else { 322 const auto& v = a_edges.insert({arg.name(), arg.name()}); 323 ASSERT_TRUE(v.second) << "Duplicate edge " << arg.name() << " -> " 324 << arg.name() << ". fdef: " << fdef.DebugString(); 325 } 326 } 327 328 // Verify edges 329 for (const EdgeSpec& e : e_edges) { 330 ASSERT_TRUE(a_edges.find(e) != a_edges.end()) 331 << "Failed to find expected edge " << e.ToString() 332 << " in fdef: " << fdef.DebugString(); 333 } 334 for (const EdgeSpec& e : c_edges) { 335 ASSERT_TRUE(a_edges.find(e) != a_edges.end()) 336 << "Failed to find expected control edge " << e.ToString() 337 << " in fdef: " << fdef.DebugString(); 338 } 339 340 // If caller specified all edges, check that we have seen all 341 if (is_exact_edges) { 342 ASSERT_EQ(e_edges.size() + c_edges.size(), a_edges.size()) 343 << "Expected edges: " << ToString(e_edges) 344 << " Expected Control edges: " << ToString(c_edges) 345 << " Actual edges: " << ToString(a_edges) 346 << " in fdef: " << fdef.DebugString(); 347 } 348 } 349 350 void VerifyFDef(const std::unordered_set<string>& nodes, 351 const std::vector<IOSpec>& inputs, 352 const std::vector<IOSpec>& outputs, 353 const std::vector<EdgeSpec>& e_edges, // expected edges 354 const std::vector<EdgeSpec>& c_edges, // expected ctrl edges 355 bool is_exact_edges = true) { 356 tensorflow::FunctionDef fdef; 357 ASSERT_TRUE(GetFunctionDef(func_, &fdef)); 358 VerifyFDefNodes(fdef, nodes); 359 VerifyFDefInputs(fdef, inputs); 360 VerifyFDefOutputs(fdef, outputs); 361 VerifyFDefEdges(fdef, e_edges, c_edges, is_exact_edges); 362 } 363 364 // Serialize func_ to fdef and import it back 365 void Reincarnate() { 366 // func_ -> fdef 367 tensorflow::FunctionDef fdef; 368 ASSERT_TRUE(GetFunctionDef(func_, &fdef)); 369 TF_DeleteFunction(func_); 370 371 // fdef -> func_ 372 string buf; 373 ASSERT_TRUE(fdef.SerializeToString(&buf)); 374 func_ = TF_FunctionImportFunctionDef(buf.data(), buf.size(), s_); 375 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 376 } 377 378 void GetAttr(const char* attr_name, AttrValue* out_attr) { 379 TF_Buffer* attr_buf = TF_NewBuffer(); 380 TF_FunctionGetAttrValueProto(func_, attr_name, attr_buf, s_); 381 ASSERT_TRUE(out_attr->ParseFromArray(attr_buf->data, attr_buf->length)); 382 TF_DeleteBuffer(attr_buf); 383 } 384 385 const char* func_name_ = "MyFunc"; 386 const char* func_node_name_ = "MyFunc_0"; 387 TF_Status* s_; 388 TF_Graph* func_graph_; 389 TF_Graph* host_graph_; 390 TF_Function* func_; 391 392 // Workaround for not being able to initialize empty map using {} 393 std::unordered_set<string> empty_; 394 }; 395 396 TEST_F(CApiFunctionTest, OneOp_ZeroInputs_OneOutput) { 397 /* 398 * constant 399 * | 400 * v 401 */ 402 // Define 403 TF_Operation* c = ScalarConst(10, func_graph_, s_, "scalar10"); 404 Define(-1, {}, {}, {c}, {}); 405 406 // Use, run, and verify 407 TF_Operation* func_op = Use({}); 408 Run({}, func_op, 10); 409 VerifyFDef({"scalar10_0"}, {}, {{"scalar10", DT_INT32}}, 410 {{"scalar10_0:output:0", "scalar10"}}, {}); 411 } 412 413 TEST_F(CApiFunctionTest, OneOp_OneInput_OneOutput) { 414 /* 415 * | 416 * v 417 * negate 418 * | 419 * v 420 */ 421 // Define 422 TF_Operation* feed = Placeholder(func_graph_, s_); 423 TF_Operation* neg = Neg(feed, func_graph_, s_); 424 Define(-1, {}, {feed}, {neg}, {}); 425 426 // Use, run, and verify 427 TF_Operation* func_feed = Placeholder(host_graph_, s_); 428 TF_Operation* func_op = Use({func_feed}); 429 Run({{func_feed, Int32Tensor(3)}}, func_op, -3); 430 VerifyFDef({"neg_0"}, {{"feed", DT_INT32}}, {{"neg", DT_INT32}}, 431 {{"feed", "neg_0:0"}, {"neg_0:y:0", "neg"}}, {}); 432 } 433 434 TEST_F(CApiFunctionTest, OneOutput_OutputNames) { 435 /* 436 * | 437 * v 438 * negate 439 * | 440 * v 441 */ 442 // Define 443 TF_Operation* feed = Placeholder(func_graph_, s_); 444 TF_Operation* neg = Neg(feed, func_graph_, s_); 445 Define(-1, {}, {feed}, {neg}, {"negated_num"}); 446 447 // Use, run, and verify 448 TF_Operation* func_feed = Placeholder(host_graph_, s_); 449 TF_Operation* func_op = Use({func_feed}); 450 Run({{func_feed, Int32Tensor(3)}}, func_op, -3); 451 VerifyFDef({"neg"}, {{"feed", DT_INT32}}, {{"negated_num", DT_INT32}}, 452 {{"feed", "neg:0"}, {"neg:y:0", "negated_num"}}, {}); 453 } 454 455 TEST_F(CApiFunctionTest, OutputNames_SameNameAsInput) { 456 /* 457 * | 458 * v 459 * negate 460 * | 461 * v 462 */ 463 // Define 464 TF_Operation* feed = Placeholder(func_graph_, s_, "negation"); 465 TF_Operation* neg = Neg(feed, func_graph_, s_, "neg"); 466 Define(-1, {}, {feed}, {neg}, {"negation"}); 467 468 // Use, run, and verify 469 TF_Operation* func_feed = Placeholder(host_graph_, s_); 470 TF_Operation* func_op = Use({func_feed}); 471 Run({{func_feed, Int32Tensor(3)}}, func_op, -3); 472 VerifyFDef({"neg"}, {{"negation_0", DT_INT32}}, {{"negation", DT_INT32}}, 473 {{"negation_0", "neg:0"}, {"neg:y:0", "negation"}}, {}); 474 } 475 476 TEST_F(CApiFunctionTest, ZeroOps_Identity) { 477 /* 478 * | 479 * | 480 * | 481 * v 482 */ 483 // Define 484 TF_Operation* feed = Placeholder(func_graph_, s_); 485 Define(-1, {}, {feed}, {feed}, {}); 486 487 // Use, run, and verify 488 TF_Operation* func_feed = Placeholder(host_graph_, s_); 489 TF_Operation* func_op = Use({func_feed}); 490 Run({{func_feed, Int32Tensor(3)}}, func_op, 3); 491 VerifyFDef(empty_, {{"feed_0", DT_INT32}}, {{"feed", DT_INT32}}, 492 {{"feed_0", "feed"}}, {}); 493 } 494 495 TEST_F(CApiFunctionTest, ZeroOps_Permutation) { 496 /* 497 * | | 498 * \ / 499 * \/ 500 * x 501 * /\ 502 * / \ 503 * | | 504 * v v 505 */ 506 // Define 507 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 508 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 509 Define(-1, {}, {feed1, feed2}, {feed2, feed1}, {}); 510 511 // Use, run, and verify 512 TF_Operation* two = ScalarConst(2, host_graph_, s_); 513 TF_Operation* func_feed = Placeholder(host_graph_, s_); 514 TF_Operation* func_op = Use({two, func_feed}); 515 Run({{func_feed, Int32Tensor(3)}}, {{func_op, 0}, {func_op, 1}}, {3, 2}); 516 VerifyFDef(empty_, M({{"feed1_0"}, {"feed2_0"}}), M({{"feed2"}, {"feed1"}}), 517 {{"feed1_0", "feed1"}, {"feed2_0", "feed2"}}, {}); 518 } 519 520 TEST_F(CApiFunctionTest, ZeroOps_Permutation_OutputNames) { 521 /* 522 * | | 523 * \ / 524 * \/ 525 * x 526 * /\ 527 * / \ 528 * | | 529 * v v 530 */ 531 // Define 532 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 533 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 534 Define(-1, {}, {feed1, feed2}, {feed2, feed1}, {"first", "second"}); 535 536 // Use, run, and verify 537 TF_Operation* two = ScalarConst(2, host_graph_, s_); 538 TF_Operation* func_feed = Placeholder(host_graph_, s_); 539 TF_Operation* func_op = Use({two, func_feed}); 540 Run({{func_feed, Int32Tensor(3)}}, {{func_op, 0}, {func_op, 1}}, {3, 2}); 541 VerifyFDef(empty_, M({{"feed1"}, {"feed2"}}), M({{"first"}, {"second"}}), 542 {{"feed1", "second"}, {"feed2", "first"}}, {}); 543 } 544 545 TEST_F(CApiFunctionTest, OneOp_TwoInputs_OneOutput) { 546 /* 547 * | | 548 * v v 549 * add 550 * | 551 * v 552 */ 553 // Define 554 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 555 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 556 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 557 Define(-1, {}, {feed1, feed2}, {add}, {}); 558 559 // Use, run, and verify 560 TF_Operation* two = ScalarConst(2, host_graph_, s_); 561 TF_Operation* func_feed = Placeholder(host_graph_, s_); 562 TF_Operation* func_op = Use({two, func_feed}); 563 Run({{func_feed, Int32Tensor(3)}}, func_op, 2 + 3); 564 VerifyFDef( 565 {"add_0"}, M({{"feed1"}, {"feed2"}}), M({{"add"}}), 566 {{"feed1", "add_0:0"}, {"feed2", "add_0:1"}, {"add_0:sum:0", "add"}}, {}); 567 } 568 569 TEST_F(CApiFunctionTest, OneOp_TwoInputs_ZeroOutputs) { 570 /* 571 * | | 572 * v v 573 * add 574 * 575 * (output ignored) 576 */ 577 // Define 578 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 579 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 580 Add(feed1, feed2, func_graph_, s_); 581 Define(-1, {}, {feed1, feed2}, {}, {}); 582 583 // Use, run, and verify 584 TF_Operation* two = ScalarConst(2, host_graph_, s_); 585 TF_Operation* func_feed = Placeholder(host_graph_, s_); 586 Use({two, func_feed}); 587 VerifyFDef({"add"}, M({{"feed1"}, {"feed2"}}), {}, 588 {{"feed1", "add:0"}, {"feed2", "add:1"}}, {}); 589 } 590 591 TEST_F(CApiFunctionTest, TwoOps_ThreeInputs_OneOutput) { 592 /* 593 * | | | 594 * v v / 595 * add1 / 596 * | | 597 * v v 598 * add2 599 * | 600 * v 601 */ 602 // Define 603 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 604 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 605 TF_Operation* feed3 = Placeholder(func_graph_, s_, "feed3"); 606 TF_Operation* add1 = Add(feed1, feed2, func_graph_, s_, "add1"); 607 TF_Operation* add2 = Add(add1, feed3, func_graph_, s_, "add2"); 608 Define(-1, {}, {feed1, feed2, feed3}, {add2}, {}); 609 610 // Use, run, and verify 611 TF_Operation* two = ScalarConst(2, host_graph_, s_, "two"); 612 TF_Operation* ten = ScalarConst(10, host_graph_, s_, "ten"); 613 TF_Operation* func_feed = Placeholder(host_graph_, s_); 614 TF_Operation* func_op = Use({two, ten, func_feed}); 615 Run({{func_feed, Int32Tensor(3)}}, func_op, 2 + 10 + 3); 616 VerifyFDef({"add1", "add2_0"}, M({{"feed1"}, {"feed2"}, {"feed3"}}), 617 M({{"add2"}}), 618 {{"feed1", "add1:0"}, 619 {"feed2", "add1:1"}, 620 {"add1:sum:0", "add2_0:0"}, 621 {"feed3", "add2_0:1"}, 622 {"add2_0:sum:0", "add2"}}, 623 {}); 624 } 625 626 TEST_F(CApiFunctionTest, OneOp_TwoInputs_TwoDuplicateOutputs) { 627 /* 628 * | | 629 * v v 630 * add 631 * | 632 * +-+-+ 633 * | | 634 * v v 635 */ 636 // Define 637 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 638 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 639 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 640 Define(-1, {}, {feed1, feed2}, {add, add}, {}); 641 642 // Use, run, and verify 643 TF_Operation* two = ScalarConst(2, host_graph_, s_); 644 TF_Operation* func_feed = Placeholder(host_graph_, s_); 645 TF_Operation* func_op = Use({two, func_feed}); 646 Run({{func_feed, Int32Tensor(3)}}, {{func_op, 0}, {func_op, 1}}, {5, 5}); 647 VerifyFDef({"add_1"}, M({{"feed1"}, {"feed2"}}), M({{"add"}, {"add_0"}}), 648 {{"feed1", "add_1:0"}, 649 {"feed2", "add_1:1"}, 650 {"add_1:sum:0", "add"}, 651 {"add_1:sum:0", "add_0"}}, 652 {}); 653 } 654 655 TEST_F(CApiFunctionTest, TwoDuplicateOutputs_OutputNames) { 656 /* 657 * | | 658 * v v 659 * add 660 * | 661 * +-+-+ 662 * | | 663 * v v 664 */ 665 // Define 666 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 667 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 668 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 669 Define(-1, {}, {feed1, feed2}, {add, add}, {"out1", "out2"}); 670 671 // Use, run, and verify 672 TF_Operation* two = ScalarConst(2, host_graph_, s_); 673 TF_Operation* func_feed = Placeholder(host_graph_, s_); 674 TF_Operation* func_op = Use({two, func_feed}); 675 Run({{func_feed, Int32Tensor(3)}}, {{func_op, 0}, {func_op, 1}}, {5, 5}); 676 VerifyFDef({"add"}, M({{"feed1"}, {"feed2"}}), M({{"out1"}, {"out2"}}), 677 {{"feed1", "add:0"}, 678 {"feed2", "add:1"}, 679 {"add:sum:0", "out1"}, 680 {"add:sum:0", "out2"}}, 681 {}); 682 } 683 684 TEST_F(CApiFunctionTest, TwoOps_ThreeInputs_TwoOutputs) { 685 /* 686 * | | | 687 * v v / 688 * add / 689 * | | 690 * +-+ | 691 * | | | 692 * | v v 693 * | add 694 * | | 695 * v v 696 */ 697 // Define 698 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 699 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 700 TF_Operation* feed3 = Placeholder(func_graph_, s_, "feed3"); 701 TF_Operation* add1 = Add(feed1, feed2, func_graph_, s_, "add1"); 702 TF_Operation* add2 = Add(add1, feed3, func_graph_, s_, "add2"); 703 Define(-1, {}, {feed1, feed2, feed3}, {add1, add2}, {}); 704 705 // Use, run, and verify 706 TF_Operation* two = ScalarConst(2, host_graph_, s_, "two"); 707 TF_Operation* ten = ScalarConst(10, host_graph_, s_, "ten"); 708 TF_Operation* func_feed = Placeholder(host_graph_, s_); 709 TF_Operation* func_op = Use({two, ten, func_feed}); 710 Run({{func_feed, Int32Tensor(3)}}, {{func_op, 0}, {func_op, 1}}, {12, 15}); 711 VerifyFDef({"add1_0", "add2_0"}, M({{"feed1"}, {"feed2"}, {"feed3"}}), 712 M({{"add1"}, {"add2"}}), 713 {{"feed1", "add1_0:0"}, 714 {"feed2", "add1_0:1"}, 715 {"add1_0:sum:0", "add2_0:0"}, 716 {"feed3", "add2_0:1"}, 717 {"add1_0:sum:0", "add1"}, 718 {"add2_0:sum:0", "add2"}}, 719 {}); 720 } 721 722 TEST_F(CApiFunctionTest, FromSubsetOfOps) { 723 /* 724 * | | | 725 * v v / 726 * add / 727 * | | 728 * +---+--+---+ 729 * Ops used | | | | 730 * for func | v v | 731 * | | add | 732 * +-------> | | | 733 * | v | 734 * | | 735 * +----------+ 736 */ 737 // Define 738 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 739 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 740 TF_Operation* feed3 = Placeholder(func_graph_, s_, "feed3"); 741 TF_Operation* add1 = Add(feed1, feed2, func_graph_, s_, "add1"); 742 TF_Operation* add2 = Add(add1, feed3, func_graph_, s_, "add2"); 743 Define(1, {add2}, {add1, feed3}, {add2}, {}); 744 745 // Use, run, and verify 746 TF_Operation* two = ScalarConst(2, host_graph_, s_, "two"); 747 TF_Operation* func_feed = Placeholder(host_graph_, s_); 748 TF_Operation* func_op = Use({two, func_feed}); 749 Run({{func_feed, Int32Tensor(3)}}, func_op, 2 + 3); 750 VerifyFDef( 751 {"add2_0"}, M({{"add1"}, {"feed3"}}), M({{"add2"}}), 752 {{"add1", "add2_0:0"}, {"feed3", "add2_0:1"}, {"add2_0:sum:0", "add2"}}, 753 {}); 754 } 755 756 TEST_F(CApiFunctionTest, UsingOneOutputOfSplit) { 757 /* 758 * feed 759 * | 760 * +---------+---+ 761 * | const0 | | 762 * | | | | 763 * | v / | 764 * | split | 765 * | | | | | 766 * | v | v | 767 * | | | 768 * +------+------+ 769 * | 770 * v 771 * 772 * Only the second output from split is used as function output 773 */ 774 // Define 775 TF_Operation* feed = Placeholder(func_graph_, s_); 776 TF_Operation* split = Split3(feed, func_graph_, s_); 777 DefineT(-1, {}, {{feed, 0}}, {{split, 1}}, {}); 778 779 // Use, run, and verify 780 TF_Operation* func_feed = Placeholder(host_graph_, s_); 781 TF_Operation* func_op = Use({func_feed}); 782 RunT({{func_feed, Int32Tensor({1, 2, 3, 4, 5, 6})}}, {{func_op, 0}}, 783 {{3, 4}}); 784 VerifyFDef({"split3_const0", "split3_0"}, M({{"feed"}}), M({{"split3"}}), 785 {{"split3_const0:output:0", "split3_0:0"}, 786 {"feed", "split3_0:1"}, 787 {"split3_0:output:1", "split3"}}, 788 {}); 789 } 790 791 TEST_F(CApiFunctionTest, UsingTwoOutputsOfSplit) { 792 /* 793 * feed 794 * | 795 * +---------+---+ 796 * | const0 | | 797 * | | | | 798 * | v / | 799 * | split | 800 * | | | | | 801 * | | v | | 802 * | | | | 803 * +---+-----+---+ 804 * | | 805 * v v 806 * 807 * Second output from split is not used as function output 808 */ 809 // Define 810 TF_Operation* feed = Placeholder(func_graph_, s_); 811 TF_Operation* split = Split3(feed, func_graph_, s_); 812 DefineT(-1, {}, {{feed, 0}}, {{split, 0}, {split, 2}}, {}); 813 814 // Use, run, and verify 815 TF_Operation* func_feed = Placeholder(host_graph_, s_); 816 TF_Operation* func_op = Use({func_feed}); 817 RunT({{func_feed, Int32Tensor({1, 2, 3, 4, 5, 6})}}, 818 {{func_op, 0}, {func_op, 1}}, {{1, 2}, {5, 6}}); 819 VerifyFDef({"split3_const0", "split3_1"}, M({{"feed"}}), 820 M({{"split3"}, {"split3_0"}}), 821 {{"split3_const0:output:0", "split3_1:0"}, 822 {"feed", "split3_1:1"}, 823 {"split3_1:output:0", "split3"}, 824 {"split3_1:output:2", "split3_0"}}, 825 {}); 826 } 827 828 TEST_F(CApiFunctionTest, UsingTwoOutputsOfSplitAsInputs) { 829 /* 830 * | 831 * v 832 * split 833 * | | | 834 * | v | 835 * | | 836 * +---+-----+---+ 837 * | | | | 838 * | v v | 839 * | add | 840 * | | | 841 * | | | 842 * +------+------+ 843 * | 844 * v 845 */ 846 // Define 847 TF_Operation* feed = Placeholder(func_graph_, s_); 848 TF_Operation* split = Split3(feed, func_graph_, s_); 849 TF_Operation* add = Add({split, 0}, {split, 2}, func_graph_, s_); 850 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 851 DefineT(1, {add}, {{split, 0}, {split, 2}}, {{add, 0}}, {}); 852 853 // Use, run, and verify 854 TF_Operation* two = ScalarConst(2, host_graph_, s_, "two"); 855 TF_Operation* func_feed = Placeholder(host_graph_, s_); 856 TF_Operation* func_op = Use({two, func_feed}); 857 Run({{func_feed, Int32Tensor(3)}}, func_op, 2 + 3); 858 VerifyFDef( 859 {"add_0"}, M({{"split3"}, {"split3_0"}}), M({{"add"}}), 860 {{"split3", "add_0:0"}, {"split3_0", "add_0:1"}, {"add_0:sum:0", "add"}}, 861 {}); 862 } 863 864 TEST_F(CApiFunctionTest, NodesUsedInInputsMustHaveSingleOutput) { 865 /* 866 * | 867 * v 868 * split 869 * | | | 870 * | v | 871 * | | 872 * input --->| |<--- input 873 * | | 874 * v v 875 * add 876 * | 877 * | 878 * v 879 */ 880 // Define 881 TF_Tensor* tensor_123 = Int32Tensor({1, 2, 3}); 882 TF_Operation* c = Const(tensor_123, func_graph_, s_, "const_array"); 883 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 884 TF_Operation* split = Split3(c, func_graph_, s_); 885 TF_Operation* add = Add({split, 0}, {split, 2}, func_graph_, s_); 886 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 887 DefineT(-1, {}, {{split, 0}, {split, 2}}, {{add, 0}}, {}, true); 888 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 889 EXPECT_EQ(string("When `num_opers` is set to -1, nodes referenced in " 890 "`inputs` must have a single output. Node split3 has " 891 "3 outputs. Encountered while creating function 'MyFunc'"), 892 string(TF_Message(s_))); 893 894 TF_DeleteTensor(tensor_123); 895 } 896 897 TEST_F(CApiFunctionTest, FunctionWithWhileLoop) { 898 // Inputs to the while loop and the function as a whole 899 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 900 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 901 902 // Outputs of the while loop corresponding to the two inputs above 903 // The first one will the function's output 904 std::vector<TF_Output> outputs; 905 906 // Add while loop to func_graph_ 907 { 908 // The inputs to the while loop 909 std::vector<TF_Output> inputs = {{feed1, 0}, {feed2, 0}}; 910 std::unique_ptr<TF_WhileParams> params(new TF_WhileParams( 911 TF_NewWhile(func_graph_, &inputs[0], inputs.size(), s_))); 912 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 913 params->name = "test_loop"; 914 915 // Initialize outputs so we can easily detect errors/bugs 916 outputs.resize(2, {nullptr, -1}); 917 918 // Create loop: while (input1 < input2) input1 += input2 + 1 919 TF_Operation* less_than = LessThan( 920 params->cond_inputs[0], params->cond_inputs[1], params->cond_graph, s_); 921 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 922 params->cond_output = {less_than, 0}; 923 924 TF_Operation* add1 = Add(params->body_inputs[0], params->body_inputs[1], 925 params->body_graph, s_, "add1"); 926 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 927 TF_Operation* one = ScalarConst(1, params->body_graph, s_); 928 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 929 TF_Operation* add2 = Add(add1, one, params->body_graph, s_, "add2"); 930 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 931 params->body_outputs[0] = {add2, 0}; 932 params->body_outputs[1] = params->body_inputs[1]; 933 934 // Finalize while loop 935 TF_FinishWhile(params.get(), s_, &outputs[0]); 936 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 937 } 938 939 // Define function, use it in graph, and run 940 DefineT(-1, {}, {{feed1, 0}, {feed2, 0}}, {outputs[0]}, {}); 941 TF_Operation* five = ScalarConst(5, host_graph_, s_, "five"); 942 TF_Operation* func_feed = Placeholder(host_graph_, s_); 943 TF_Operation* func_op = Use({func_feed, five}); 944 Run({{func_feed, Int32Tensor(2)}}, func_op, 2 /*+=*/ + 5 + 1); 945 946 // Verify input, output, and subset of edges in fdef. 947 // The subset of edges we verify is a chain between feed1 and output to 948 // make sure that the correct output is picked. 949 tensorflow::FunctionDef fdef; 950 ASSERT_TRUE(GetFunctionDef(func_, &fdef)); 951 VerifyFDefInputs(fdef, M({{"feed1"}, {"feed2"}})); 952 VerifyFDefOutputs(fdef, M({{"test_loop_exit"}})); 953 VerifyFDefEdges(fdef, 954 {{"feed1", "test_loop/Enter:0"}, 955 {"test_loop/Enter:output:0", "test_loop/Merge:0"}, 956 {"test_loop/Merge:output:0", "test_loop/Switch:0"}, 957 {"test_loop/Switch:output_false:0", "test_loop/Exit:0"}, 958 {"test_loop/Exit:output:0", "test_loop_exit"}}, 959 {}, false); 960 } 961 962 TEST_F(CApiFunctionTest, ControlDependency) { 963 /* 964 * | | scalar 965 * | | . 966 * v v . <---- control dependency 967 * add < - 968 * | 969 * v 970 */ 971 // Define 972 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 973 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 974 TF_Operation* five = ScalarConst(5, func_graph_, s_); 975 TF_Operation* add = 976 AddWithCtrlDependency(feed1, feed2, func_graph_, five, s_); 977 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 978 Define(-1, {}, {feed1, feed2}, {add}, {}); 979 980 // Use, run, and verify 981 TF_Operation* two = ScalarConst(2, host_graph_, s_); 982 TF_Operation* func_feed = Placeholder(host_graph_, s_); 983 TF_Operation* func_op = Use({two, func_feed}); 984 Run({{func_feed, Int32Tensor(3)}}, func_op, 2 + 3); 985 VerifyFDef( 986 {"add_0", "scalar"}, M({{"feed1"}, {"feed2"}}), M({{"add"}}), 987 {{"feed1", "add_0:0"}, {"feed2", "add_0:1"}, {"add_0:sum:0", "add"}}, 988 {{"^scalar", "add_0:2"}}); 989 } 990 991 TEST_F(CApiFunctionTest, ControlDependencyOutsideOfBody) { 992 /* 993 * | | scalar 994 * | | . 995 * v v . <---- control dependency 996 * add < - 997 * | 998 * v 999 */ 1000 // Define 1001 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1002 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1003 TF_Operation* five = ScalarConst(5, func_graph_, s_); 1004 TF_Operation* add = 1005 AddWithCtrlDependency(feed1, feed2, func_graph_, five, s_); 1006 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1007 Define(1, {add}, {feed1, feed2}, {add}, {}, true); 1008 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1009 EXPECT_EQ(string("The source of control edge [id=3 scalar:-1 -> add:-1] " 1010 "is not in the body. Encountered while creating " 1011 "function 'MyFunc'"), 1012 string(TF_Message(s_))); 1013 } 1014 1015 TEST_F(CApiFunctionTest, ControlDependencyOutsideOfBody_FromInputNode) { 1016 /* 1017 * | |. 1018 * | | . 1019 * | | . 1020 * v v . <---- control dependency 1021 * add < - 1022 * | 1023 * v 1024 */ 1025 // Define 1026 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1027 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1028 TF_Operation* add = 1029 AddWithCtrlDependency(feed1, feed2, func_graph_, feed1, s_); 1030 EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1031 Define(-1, {}, {feed1, feed2}, {add}, {}); 1032 1033 // Use, run, and verify 1034 TF_Operation* two = ScalarConst(2, host_graph_, s_); 1035 TF_Operation* func_feed = Placeholder(host_graph_, s_); 1036 TF_Operation* func_op = Use({two, func_feed}); 1037 Run({{func_feed, Int32Tensor(3)}}, func_op, 2 + 3); 1038 VerifyFDef( 1039 {"add_0"}, M({{"feed1"}, {"feed2"}}), M({{"add"}}), 1040 {{"feed1", "add_0:0"}, {"feed2", "add_0:1"}, {"add_0:sum:0", "add"}}, 1041 {{"^feed1", "add_0:2"}}); 1042 } 1043 1044 TEST_F(CApiFunctionTest, DuplicateInputsAreNotAllowed) { 1045 /* 1046 * feed 1047 * | 1048 * +++ 1049 * | | 1050 * +---+-+---+ 1051 * | | | | 1052 * | v v | 1053 * | add | 1054 * | | | 1055 * | | | 1056 * +----+----+ 1057 * | 1058 * v 1059 */ 1060 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1061 TF_Operation* add = Add(feed1, feed1, func_graph_, s_); 1062 Define(-1, {}, {feed1, feed1}, {add}, {}, true); 1063 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1064 EXPECT_EQ( 1065 string("TF_Output feed1:0 appears more than once in the input list"), 1066 string(TF_Message(s_))); 1067 } 1068 1069 TEST_F(CApiFunctionTest, DuplicateOutputNamesAreNotAllowed) { 1070 /* 1071 * | | | 1072 * v v / 1073 * add / 1074 * | | 1075 * +-+ | 1076 * | | | 1077 * | v v 1078 * | add 1079 * | | 1080 * v v 1081 */ 1082 // Define 1083 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1084 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1085 TF_Operation* feed3 = Placeholder(func_graph_, s_, "feed3"); 1086 TF_Operation* add1 = Add(feed1, feed2, func_graph_, s_, "add1"); 1087 TF_Operation* add2 = Add(add1, feed3, func_graph_, s_, "add2"); 1088 Define(-1, {}, {feed1, feed2, feed3}, {add1, add2}, {"my_out", "my_out"}, 1089 true); 1090 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1091 EXPECT_EQ(string("Cannot have duplicate output names. Name 'my_out' " 1092 "appears more than once in 'output_names' array."), 1093 string(TF_Message(s_))); 1094 } 1095 1096 TEST_F(CApiFunctionTest, InvalidInputTensor_HighIndex) { 1097 /* 1098 * | | 1099 * v v 1100 * add 1101 * | 1102 * v 1103 */ 1104 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1105 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1106 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 1107 DefineT(-1, {}, {{feed1, 0}, {feed2, 2}}, {{add, 0}}, {}, true); 1108 EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(s_)); 1109 EXPECT_EQ(string("Node 'feed2' (type: 'Placeholder', num of outputs: 1) does " 1110 "not have output 2\n\tEncountered while processing " 1111 "input 1 into function 'MyFunc'"), 1112 string(TF_Message(s_))); 1113 } 1114 1115 TEST_F(CApiFunctionTest, InvalidInputTensor_BadNodePtr) { 1116 /* 1117 * | | 1118 * v v 1119 * add 1120 * | 1121 * v 1122 */ 1123 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1124 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1125 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 1126 DefineT(-1, {}, {{feed1, 0}, {nullptr, 0}}, {{add, 0}}, {}, true); 1127 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1128 EXPECT_EQ(string("Node is null\n\tEncountered while processing input 1 " 1129 "into function 'MyFunc'"), 1130 string(TF_Message(s_))); 1131 } 1132 1133 TEST_F(CApiFunctionTest, InvalidOutputTensor_HighIndex) { 1134 /* 1135 * | | 1136 * v v 1137 * add 1138 * | 1139 * v 1140 */ 1141 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1142 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1143 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 1144 DefineT(-1, {}, {{feed1, 0}, {feed2, 0}}, {{add, 3}}, {}, true); 1145 EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(s_)); 1146 EXPECT_EQ(string("Node 'add' (type: 'AddN', num of outputs: 1) does " 1147 "not have output 3\n\tEncountered while processing " 1148 "output 0 from function 'MyFunc'"), 1149 string(TF_Message(s_))); 1150 } 1151 1152 TEST_F(CApiFunctionTest, InvalidOutputTensor_BadNodePtr) { 1153 /* 1154 * | | 1155 * v v 1156 * add 1157 * | 1158 * v 1159 */ 1160 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1161 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1162 Add(feed1, feed2, func_graph_, s_); 1163 DefineT(-1, {}, {{feed1, 0}, {feed2, 0}}, {{nullptr, 3}}, {}, true); 1164 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1165 EXPECT_EQ(string("Node is null\n\tEncountered while processing output 0 " 1166 "from function 'MyFunc'"), 1167 string(TF_Message(s_))); 1168 } 1169 1170 TEST_F(CApiFunctionTest, NodeMissingInput) { 1171 /* 1172 * input---> | | <----missing input 1173 * v v 1174 * body----> add 1175 * | 1176 * v 1177 */ 1178 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1179 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1180 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 1181 DefineT(1, {add}, {{feed1, 0}}, {{add, 0}}, {}, true); 1182 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1183 EXPECT_EQ(string("Input 1, 'feed2:0', of node 'add' in function 'MyFunc' " 1184 "is not available. You might need to include it in inputs " 1185 "or include its source node in the body"), 1186 string(TF_Message(s_))); 1187 } 1188 1189 TEST_F(CApiFunctionTest, OutputOpNotInBody) { 1190 /* 1191 * | | 1192 * v v 1193 * add scalar (scalar not included in body) 1194 * | | 1195 * v v (function has two outputs) 1196 */ 1197 // Define 1198 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1199 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1200 TF_Operation* scalar = ScalarConst(2, func_graph_, s_); 1201 TF_Operation* add = Add(feed1, feed2, func_graph_, s_); 1202 Define(1, {add}, {feed1, feed2}, {add, scalar}, {}, true); 1203 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1204 EXPECT_EQ(string("TF_Output scalar:0 is neither in the function body nor " 1205 "among function inputs. Encountered while creating " 1206 "function 'MyFunc'"), 1207 string(TF_Message(s_))); 1208 } 1209 1210 void DefineFunction(const char* name, TF_Function** func, 1211 const char* description = nullptr, 1212 bool append_hash = false) { 1213 std::unique_ptr<TF_Graph, decltype(&TF_DeleteGraph)> func_graph( 1214 TF_NewGraph(), TF_DeleteGraph); 1215 std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> s(TF_NewStatus(), 1216 TF_DeleteStatus); 1217 1218 TF_Operation* feed = Placeholder(func_graph.get(), s.get()); 1219 TF_Operation* neg = Neg(feed, func_graph.get(), s.get()); 1220 1221 TF_Output inputs[] = {{feed, 0}}; 1222 TF_Output outputs[] = {{neg, 0}}; 1223 *func = TF_GraphToFunction(func_graph.get(), name, append_hash, -1, 1224 /*opers=*/nullptr, 1, inputs, 1, outputs, 1225 /*output_names=*/nullptr, 1226 /*opts=*/nullptr, description, s.get()); 1227 ASSERT_EQ(TF_OK, TF_GetCode(s.get())) << TF_Message(s.get()); 1228 ASSERT_NE(*func, nullptr); 1229 } 1230 1231 TEST_F(CApiFunctionTest, SetGradientAndRun) { 1232 // Define the function and its grad 1233 DefineFunction(func_name_, &func_); 1234 TF_Function* grad_func; 1235 DefineFunction("MyGrad", &grad_func); 1236 1237 // Add func and its gradient to host graph 1238 TF_GraphCopyFunction(host_graph_, func_, grad_func, s_); 1239 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1240 1241 // Verify that function and its grad are in host graph's GraphDef 1242 GraphDef gdef; 1243 GetGraphDef(host_graph_, &gdef); 1244 std::vector<string> func_names = GetFuncNames(gdef); 1245 ASSERT_EQ(2, func_names.size()); 1246 ASSERT_EQ(func_name_, func_names[0]); 1247 ASSERT_EQ("MyGrad", func_names[1]); 1248 std::vector<std::pair<string, string>> grads = GetGradDefs(gdef); 1249 ASSERT_EQ(1, grads.size()); 1250 ASSERT_EQ(func_name_, grads[0].first); 1251 ASSERT_EQ("MyGrad", grads[0].second); 1252 1253 // These calls must be noops 1254 TF_GraphCopyFunction(host_graph_, func_, grad_func, s_); 1255 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1256 TF_GraphCopyFunction(host_graph_, func_, nullptr, s_); 1257 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1258 1259 // Delete the gradient func. 1260 // It is safe to delete after adding a copy to host graph. 1261 TF_DeleteFunction(grad_func); 1262 1263 // Check that GraphDef did not change 1264 GraphDef gdef2; 1265 GetGraphDef(host_graph_, &gdef2); 1266 ASSERT_EQ(gdef.DebugString(), gdef2.DebugString()); 1267 1268 // Use and run func 1269 TF_Operation* func_feed = Placeholder(host_graph_, s_); 1270 TF_Operation* func_op = Use({func_feed}); 1271 Run({{func_feed, Int32Tensor(3)}}, func_op, -3); 1272 } 1273 1274 TEST_F(CApiFunctionTest, SameGradForTwoFunctions) { 1275 // Define the functions 1276 TF_Function* func1; 1277 TF_Function* func2; 1278 TF_Function* grad_func; 1279 DefineFunction("FooFunc1", &func1); 1280 DefineFunction("FooFunc2", &func2); 1281 DefineFunction("MyGrad", &grad_func); 1282 1283 // Make grad_func be a gradient of func1 and func2 1284 TF_GraphCopyFunction(host_graph_, func1, grad_func, s_); 1285 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1286 TF_GraphCopyFunction(host_graph_, func2, grad_func, s_); 1287 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1288 1289 // Verify that functions and their gradients are in host graph's GraphDef 1290 GraphDef gdef; 1291 GetGraphDef(host_graph_, &gdef); 1292 std::vector<std::pair<string, string>> grads = GetGradDefs(gdef); 1293 ASSERT_EQ(2, grads.size()); 1294 ASSERT_EQ("FooFunc1", grads[0].first); 1295 ASSERT_EQ("MyGrad", grads[0].second); 1296 ASSERT_EQ("FooFunc2", grads[1].first); 1297 ASSERT_EQ("MyGrad", grads[1].second); 1298 1299 TF_DeleteFunction(func1); 1300 TF_DeleteFunction(func2); 1301 TF_DeleteFunction(grad_func); 1302 } 1303 1304 TEST_F(CApiFunctionTest, AddFunctionsThenMakeOneGradientOfAnother) { 1305 // Define the functions 1306 TF_Function* func; 1307 TF_Function* grad_func; 1308 DefineFunction("FooFunc", &func); 1309 DefineFunction("MyGrad", &grad_func); 1310 1311 // Add functions individually 1312 TF_GraphCopyFunction(host_graph_, func, nullptr, s_); 1313 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1314 TF_GraphCopyFunction(host_graph_, grad_func, nullptr, s_); 1315 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1316 1317 // Check that functions are added but not linked 1318 GraphDef gdef; 1319 GetGraphDef(host_graph_, &gdef); 1320 std::vector<string> func_names = GetFuncNames(gdef); 1321 ASSERT_EQ(2, func_names.size()); 1322 ASSERT_EQ("FooFunc", func_names[0]); 1323 ASSERT_EQ("MyGrad", func_names[1]); 1324 ASSERT_EQ(0, GetGradDefs(gdef).size()); 1325 1326 // Make grad_func a gradient of func 1327 TF_GraphCopyFunction(host_graph_, func, grad_func, s_); 1328 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1329 1330 // Verify that function and its grad are linked 1331 gdef.Clear(); 1332 GetGraphDef(host_graph_, &gdef); 1333 std::vector<std::pair<string, string>> grads = GetGradDefs(gdef); 1334 ASSERT_EQ(1, grads.size()); 1335 ASSERT_EQ("FooFunc", grads[0].first); 1336 ASSERT_EQ("MyGrad", grads[0].second); 1337 1338 TF_DeleteFunction(func); 1339 TF_DeleteFunction(grad_func); 1340 } 1341 1342 TEST_F(CApiFunctionTest, GradientErrorCases) { 1343 // Define the function 1344 DefineFunction(func_name_, &func_); 1345 TF_Function* grad_func1; 1346 TF_Function* grad_func2; 1347 DefineFunction("MyGrad1", &grad_func1); 1348 DefineFunction("MyGrad2", &grad_func2); 1349 1350 // func cannot be null 1351 TF_GraphCopyFunction(host_graph_, nullptr, func_, s_); 1352 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1353 EXPECT_EQ(string("'func' argument to TF_GraphCopyFunction cannot be null"), 1354 string(TF_Message(s_))); 1355 1356 // Cannot change gradient 1357 TF_GraphCopyFunction(host_graph_, func_, grad_func1, s_); 1358 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1359 TF_GraphCopyFunction(host_graph_, func_, grad_func2, s_); 1360 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1361 EXPECT_EQ(string("Cannot assign gradient function 'MyGrad2' to 'MyFunc' " 1362 "because it already has gradient function 'MyGrad1'"), 1363 string(TF_Message(s_))); 1364 1365 TF_DeleteFunction(grad_func1); 1366 TF_DeleteFunction(grad_func2); 1367 } 1368 1369 TEST_F(CApiFunctionTest, ImportFunctionDef) { 1370 /* 1371 * Using a fairly complex function with output names 1372 * 1373 * | | | 1374 * v v / 1375 * add / 1376 * | | 1377 * +------+ | 1378 * | | | 1379 * | v v 1380 * | add 1381 * | | 1382 * v v 1383 * internal_out final_out 1384 */ 1385 // Define 1386 TF_Operation* feed1 = Placeholder(func_graph_, s_, "feed1"); 1387 TF_Operation* feed2 = Placeholder(func_graph_, s_, "feed2"); 1388 TF_Operation* feed3 = Placeholder(func_graph_, s_, "feed3"); 1389 TF_Operation* add1 = Add(feed1, feed2, func_graph_, s_, "add1"); 1390 TF_Operation* add2 = Add(add1, feed3, func_graph_, s_, "add2"); 1391 Define(-1, {}, {feed1, feed2, feed3}, {add1, add2}, 1392 {"internal_out", "final_out"}); 1393 1394 // Save func_ to FunctionDef and import it back 1395 Reincarnate(); 1396 1397 // Use, run, and verify 1398 TF_Operation* two = ScalarConst(2, host_graph_, s_, "two"); 1399 TF_Operation* ten = ScalarConst(10, host_graph_, s_, "ten"); 1400 TF_Operation* func_feed = Placeholder(host_graph_, s_); 1401 TF_Operation* func_op = Use({two, ten, func_feed}); 1402 Run({{func_feed, Int32Tensor(3)}}, {{func_op, 0}, {func_op, 1}}, {12, 15}); 1403 VerifyFDef({"add1", "add2"}, M({{"feed1"}, {"feed2"}, {"feed3"}}), 1404 M({{"internal_out"}, {"final_out"}}), 1405 {{"feed1", "add1:0"}, 1406 {"feed2", "add1:1"}, 1407 {"add1:sum:0", "add2:0"}, 1408 {"feed3", "add2:1"}, 1409 {"add1:sum:0", "internal_out"}, 1410 {"add2:sum:0", "final_out"}}, 1411 {}); 1412 } 1413 1414 TEST_F(CApiFunctionTest, ImportFunctionDef_InvalidProto) { 1415 // Invalid protobuf data (protos cannot start with 4 bytes of zeros) 1416 char proto[] = {0x0, 0x0, 0x0, 0x0}; 1417 func_ = TF_FunctionImportFunctionDef(proto, 4, s_); 1418 EXPECT_TRUE(func_ == nullptr); 1419 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1420 EXPECT_EQ(string("Invalid FunctionDef given to TF_FunctionImportFunctionDef"), 1421 string(TF_Message(s_))); 1422 } 1423 1424 TEST_F(CApiFunctionTest, Attribute) { 1425 DefineFunction(func_name_, &func_); 1426 1427 // Get non existent attribute 1428 TF_Buffer* attr_buf = TF_NewBuffer(); 1429 TF_FunctionGetAttrValueProto(func_, "foo_attr", attr_buf, s_); 1430 EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)); 1431 EXPECT_EQ(string("Function 'MyFunc' has no attr named 'foo_attr'."), 1432 string(TF_Message(s_))); 1433 TF_DeleteBuffer(attr_buf); 1434 1435 // Set attr 1436 tensorflow::AttrValue attr; 1437 attr.set_s("test_attr_value"); 1438 string bytes; 1439 attr.SerializeToString(&bytes); 1440 TF_FunctionSetAttrValueProto(func_, "test_attr_name", bytes.data(), 1441 bytes.size(), s_); 1442 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1443 1444 // Get attr 1445 AttrValue read_attr; 1446 GetAttr("test_attr_name", &read_attr); 1447 ASSERT_EQ(attr.DebugString(), read_attr.DebugString()); 1448 1449 // Retrieve the same attr after save/restore 1450 Reincarnate(); 1451 AttrValue read_attr2; 1452 GetAttr("test_attr_name", &read_attr2); 1453 ASSERT_EQ(attr.DebugString(), read_attr2.DebugString()); 1454 } 1455 1456 TEST_F(CApiFunctionTest, Description) { 1457 DefineFunction(func_name_, &func_, "Return something"); 1458 tensorflow::FunctionDef fdef; 1459 ASSERT_TRUE(GetFunctionDef(func_, &fdef)); 1460 ASSERT_EQ(string("Return something"), fdef.signature().description()); 1461 } 1462 1463 TEST_F(CApiFunctionTest, Name) { 1464 DefineFunction("long_func_name", &func_, "Return something", 1465 /*append_hash=*/false); 1466 tensorflow::FunctionDef fdef; 1467 ASSERT_TRUE(GetFunctionDef(func_, &fdef)); 1468 ASSERT_EQ(string("long_func_name"), fdef.signature().name()); 1469 } 1470 1471 TEST_F(CApiFunctionTest, AppendHash) { 1472 DefineFunction("func_name_base", &func_, "Return something", 1473 /*append_hash=*/true); 1474 tensorflow::FunctionDef fdef; 1475 ASSERT_TRUE(GetFunctionDef(func_, &fdef)); 1476 #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) 1477 ASSERT_EQ(string("func_name_base_ZpgUD4x8oqk"), fdef.signature().name()); 1478 #else 1479 ASSERT_EQ(string("func_name_base_qaJ8jA8UmGY"), fdef.signature().name()); 1480 #endif 1481 } 1482 1483 TEST_F(CApiFunctionTest, GetOpDef) { 1484 DefineFunction(func_name_, &func_); 1485 TF_GraphCopyFunction(host_graph_, func_, nullptr, s_); 1486 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1487 1488 // Test we can retrieve function OpDef from graph 1489 TF_Buffer* buffer = TF_NewBuffer(); 1490 TF_GraphGetOpDef(host_graph_, func_name_, buffer, s_); 1491 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1492 1493 // Sanity check returned OpDef 1494 string data(static_cast<const char*>(buffer->data), buffer->length); 1495 OpDef op_def; 1496 op_def.ParseFromString(data); 1497 EXPECT_EQ(op_def.name(), func_name_); 1498 EXPECT_EQ(op_def.input_arg_size(), 1); 1499 EXPECT_EQ(op_def.output_arg_size(), 1); 1500 EXPECT_FALSE(op_def.is_stateful()); 1501 1502 TF_DeleteBuffer(buffer); 1503 } 1504 1505 void DefineStatefulFunction(const char* name, TF_Function** func) { 1506 std::unique_ptr<TF_Graph, decltype(&TF_DeleteGraph)> func_graph( 1507 TF_NewGraph(), TF_DeleteGraph); 1508 std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> s(TF_NewStatus(), 1509 TF_DeleteStatus); 1510 1511 TF_Tensor* tensor_shape = Int32Tensor({37, 1}); 1512 TF_Operation* shape = Const(tensor_shape, func_graph.get(), s.get(), "shape"); 1513 TF_Operation* random = 1514 RandomUniform(shape, TF_FLOAT, func_graph.get(), s.get()); 1515 1516 TF_Output inputs[] = {}; 1517 TF_Output outputs[] = {{random, 0}}; 1518 *func = TF_GraphToFunction(func_graph.get(), name, /*append_hash=*/false, -1, 1519 /*opers=*/nullptr, 0, inputs, 1, outputs, 1520 /*output_names=*/nullptr, 1521 /*opts=*/nullptr, "", s.get()); 1522 ASSERT_EQ(TF_OK, TF_GetCode(s.get())) << TF_Message(s.get()); 1523 ASSERT_NE(*func, nullptr); 1524 TF_DeleteTensor(tensor_shape); 1525 } 1526 1527 TEST_F(CApiFunctionTest, StatefulOpDef) { 1528 DefineStatefulFunction(func_name_, &func_); 1529 TF_GraphCopyFunction(host_graph_, func_, nullptr, s_); 1530 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1531 1532 // Test we can retrieve function OpDef from graph 1533 TF_Buffer* buffer = TF_NewBuffer(); 1534 TF_GraphGetOpDef(host_graph_, func_name_, buffer, s_); 1535 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1536 1537 // Sanity check returned OpDef 1538 string data(static_cast<const char*>(buffer->data), buffer->length); 1539 OpDef op_def; 1540 op_def.ParseFromString(data); 1541 EXPECT_EQ(op_def.name(), func_name_); 1542 EXPECT_EQ(op_def.input_arg_size(), 0); 1543 EXPECT_EQ(op_def.output_arg_size(), 1); 1544 EXPECT_TRUE(op_def.is_stateful()); 1545 1546 TF_DeleteBuffer(buffer); 1547 } 1548 1549 void AssertEqual(TF_Function* f1, TF_Function* f2) { 1550 string s1, s2; 1551 tensorflow::FunctionDef fdef1, fdef2; 1552 ASSERT_TRUE(GetFunctionDef(f1, &fdef1)); 1553 ASSERT_TRUE(GetFunctionDef(f2, &fdef2)); 1554 SerializeToStringDeterministic(fdef1, &s1); 1555 SerializeToStringDeterministic(fdef2, &s2); 1556 ASSERT_EQ(s1, s2); 1557 } 1558 1559 string GetName(TF_Function* func) { 1560 tensorflow::FunctionDef fdef; 1561 GetFunctionDef(func, &fdef); 1562 return fdef.signature().name(); 1563 } 1564 1565 TEST_F(CApiFunctionTest, GetFunctionsFromGraph) { 1566 TF_Function* funcs[2]; 1567 1568 // Get functions from empty graph 1569 EXPECT_EQ(TF_GraphNumFunctions(host_graph_), 0); 1570 TF_GraphGetFunctions(host_graph_, nullptr, 0, s_); 1571 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1572 1573 // Define a function and add it to host_graph_ 1574 TF_Function* func0; 1575 DefineFunction("FooFunc0", &func0); 1576 TF_GraphCopyFunction(host_graph_, func0, nullptr, s_); 1577 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1578 1579 // Get this function from host_graph_ 1580 EXPECT_EQ(TF_GraphNumFunctions(host_graph_), 1); 1581 EXPECT_EQ(TF_GraphGetFunctions(host_graph_, funcs, 0, s_), 0); 1582 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1583 EXPECT_EQ(TF_GraphGetFunctions(host_graph_, funcs, 1, s_), 1); 1584 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1585 AssertEqual(func0, funcs[0]); 1586 TF_DeleteFunction(funcs[0]); 1587 EXPECT_EQ(TF_GraphGetFunctions(host_graph_, funcs, 2, s_), 1); 1588 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1589 AssertEqual(func0, funcs[0]); 1590 TF_DeleteFunction(funcs[0]); 1591 1592 // Define a second function 1593 TF_Function* func1; 1594 DefineFunction("FooFunc1", &func1); 1595 TF_GraphCopyFunction(host_graph_, func1, nullptr, s_); 1596 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1597 1598 // Get both function from host_graph_ 1599 EXPECT_EQ(TF_GraphNumFunctions(host_graph_), 2); 1600 EXPECT_EQ(TF_GraphGetFunctions(host_graph_, funcs, 0, s_), 0); 1601 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1602 EXPECT_EQ(TF_GraphGetFunctions(host_graph_, funcs, 2, s_), 2); 1603 ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); 1604 if (GetName(funcs[0]) == GetName(func0)) { 1605 AssertEqual(func0, funcs[0]); 1606 AssertEqual(func1, funcs[1]); 1607 } else { 1608 AssertEqual(func0, funcs[1]); 1609 AssertEqual(func1, funcs[0]); 1610 } 1611 1612 TF_DeleteFunction(funcs[0]); 1613 TF_DeleteFunction(funcs[1]); 1614 1615 TF_DeleteFunction(func0); 1616 TF_DeleteFunction(func1); 1617 } 1618 1619 } // namespace 1620 } // namespace tensorflow 1621