/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#include "tensorflow/core/kernels/remote_fused_graph_execute_utils.h"
#include "tensorflow/cc/framework/scope.h"
#include "tensorflow/core/common_runtime/shape_refiner.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/framework/remote_fused_graph_execute_info.pb.h"
#include "tensorflow/core/kernels/remote_fused_graph_execute_op_test_utils.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/test.h"

namespace tensorflow {
namespace {

using ClusterInfo = RemoteFusedGraphExecuteUtils::ClusterInfo;

constexpr const char* const NAME_A = "A";
constexpr const char* const NAME_B = "B";
constexpr const char* const NAME_A_PLUS_B = "A_PLUS_B";
constexpr float NODE_A_VAL = 2.0f;
constexpr float NODE_B_VAL = 3.0f;
constexpr float VALUE_TOLERANCE_FLOAT = 1e-8f;
constexpr const char* const REMOTE_FUSED_EXECUTOR_NAME0 =
    "fuse_test_remote_fused_graph_executor0";
constexpr const char* const REMOTE_FUSED_EXECUTOR_NAME1 =
    "fuse_test_remote_fused_graph_executor1";

static NodeDef* GetNodeDef(const string& name, GraphDef* def) {
  CHECK_NE(def, nullptr);
  for (NodeDef& node_def : *def->mutable_node()) {
    if (node_def.name() == name) {
      return &node_def;
    }
  }
  return nullptr;
}

Status BuildRemoteFusedGraphExecutor0(
    std::unique_ptr<IRemoteFusedGraphExecutor>* executor) {
  executor->reset(
      new TestRemoteFusedGraphExecutor({"Mul"}, REMOTE_FUSED_EXECUTOR_NAME0));
  return Status::OK();
}

Status BuildRemoteFusedGraphExecutor1(
    std::unique_ptr<IRemoteFusedGraphExecutor>* executor) {
  executor->reset(new TestRemoteFusedGraphExecutor(
      {"Const", "Mul"}, REMOTE_FUSED_EXECUTOR_NAME1));
  return Status::OK();
}

class FuseRemoteGraphMultipleAddOpsTest : public ::testing::Test {
 protected:
  void SetUp() final {
    TF_ASSERT_OK(
        RemoteFusedGraphExecuteOpTestUtils::BuildMultipleAddGraph(&graph_def_));
    RemoteFusedGraphExecuteUtils::ExecutorBuildRegistrar
        hexagon_remote_fused_graph_executor_build(
            "remote_graph_executor_name",
            [](std::unique_ptr<IRemoteFusedGraphExecutor>* executor) -> Status {
              return Status::OK();
            });
    RemoteFusedGraphExecuteUtils::ExecutorBuildRegistrar
        test_remote_fused_graph_executor_build0(REMOTE_FUSED_EXECUTOR_NAME0,
                                                BuildRemoteFusedGraphExecutor0);

    RemoteFusedGraphExecuteUtils::ExecutorBuildRegistrar
        test_remote_fused_graph_executor_build1(REMOTE_FUSED_EXECUTOR_NAME1,
                                                BuildRemoteFusedGraphExecutor1);
  }

  void TearDown() final {}

  Status FuseByInOut() {
    // Feed output shapes and types
    RemoteFusedGraphExecuteUtils::TensorShapeMap tensor_shape_map;
    GraphDef graph_def_with_shapetype = graph_def_;
    TF_RETURN_IF_ERROR(RemoteFusedGraphExecuteUtils::BuildAndAddTensorShapes(
        input_tensors_, /*dry_run_inference*/ true, &graph_def_with_shapetype));

    return RemoteFusedGraphExecuteUtils::FuseRemoteGraphByBorder(
        graph_def_with_shapetype, inputs_, outputs_,
        "remote_fused_graph_node_names", subgraph_input_names_,
        subgraph_output_names_, "remote_graph_executor_name",
        /*require_shape_type=*/true, &result_graph_def_);
  }

  Status FuseByNodes() {
    return RemoteFusedGraphExecuteUtils::FuseRemoteGraphByNodeNames(
        graph_def_, inputs_, outputs_, "remote_fused_graph_node_names",
        subgraph_node_names_, "remote_graph_executor_name",
        /*require_shape_type=*/false, &result_graph_def_);
  }

  Status FuseByOpTypes() {
    return RemoteFusedGraphExecuteUtils::FuseRemoteGraphByOpTypes(
        graph_def_, inputs_, outputs_, "remote_fused_graph_node_names",
        subgraph_op_types_, "remote_graph_executor_name",
        /*require_shape_type=*/false, &result_graph_def_);
  }

  Status FuseByExecutor0() {
    return RemoteFusedGraphExecuteUtils::FuseRemoteGraphByExecutor(
        graph_def_, inputs_, outputs_, REMOTE_FUSED_EXECUTOR_NAME0,
        &result_graph_def_);
  }

  Status FuseByExecutor1() {
    return RemoteFusedGraphExecuteUtils::FuseRemoteGraphByExecutor(
        graph_def_, inputs_, outputs_, REMOTE_FUSED_EXECUTOR_NAME1,
        &result_graph_def_);
  }

  Status BuildAndAddTensorShape() {
    return RemoteFusedGraphExecuteUtils::BuildAndAddTensorShapes(
        input_tensors_, /*dry_run_inference=*/true, &graph_def_);
  }

  Status PlaceRemoteGraphArguments() {
    return RemoteFusedGraphExecuteUtils::PlaceRemoteGraphArguments(
        inputs_, outputs_, subgraph_node_names_, subgraph_input_names_,
        subgraph_output_names_, subgraph_op_types_,
        "remote_fused_graph_node_names", "remote_graph_executor_name",
        &graph_def_);
  }

  Status FuseByPlacedArguments() {
    const Status status =
        RemoteFusedGraphExecuteUtils::FuseRemoteGraphByPlacedArguments(
            graph_def_, input_tensors_, &graph_def_);
    result_graph_def_ = graph_def_;
    return status;
  }

  bool IsFuseReady() {
    return RemoteFusedGraphExecuteUtils::IsFuseReady(graph_def_,
                                                     input_tensors_);
  }

  void ReplaceOpType(const std::unordered_set<string>& op_name,
                     const string& new_op_type) {
    for (NodeDef& node_def : *graph_def_.mutable_node()) {
      if (op_name.count(node_def.name()) > 0) {
        node_def.set_op(new_op_type);
      }
    }
  }

 public:
  const std::vector<std::pair<string, Tensor>> input_tensors_{
      {"A", {DT_FLOAT, {1, 1, 1, 1}}}};
  const std::vector<string> inputs_{"A"};
  const std::vector<string> outputs_{"K"};
  GraphDef graph_def_;
  GraphDef result_graph_def_;
  std::vector<string> subgraph_input_names_;
  std::vector<string> subgraph_output_names_;
  std::unordered_set<string> subgraph_node_names_;
  std::unordered_set<string> subgraph_op_types_;
};

void SetSubgraphArguments(const std::vector<string>& input_names,
                          const std::vector<string>& output_names,
                          FuseRemoteGraphMultipleAddOpsTest* fixture) {
  for (const string& input_name : input_names) {
    fixture->subgraph_input_names_.emplace_back(input_name);
  }

  fixture->subgraph_output_names_ = output_names;
}

template <typename T>
static string IterToString(const T& set) {
  string out;
  for (const string& val : set) {
    if (!out.empty()) {
      out += ", ";
    }
    out += val;
  }
  return out;
}

static string SummarizeGraphDef(const GraphDef& graph_def) {
  string out;
  for (const NodeDef& node : graph_def.node()) {
    out += strings::StrCat("node: ", node.name(), "\n    input: ");
    for (const string& input : node.input()) {
      out += strings::StrCat(input, ", ");
    }
    out += "\n";
  }
  return out;
}

static string DumpInOutNames(const std::vector<ClusterInfo>& ci_vec) {
  for (int i = 0; i < ci_vec.size(); ++i) {
    LOG(INFO) << "Cluster(" << i << ")";
    LOG(INFO) << "input: " << IterToString(std::get<1>(ci_vec.at(i)));
    LOG(INFO) << "output: " << IterToString(std::get<2>(ci_vec.at(i)));
  }
  return "";
}

static void ClearCluster(ClusterInfo* cluster) {
  std::get<0>(*cluster).clear();
  std::get<1>(*cluster).clear();
  std::get<2>(*cluster).clear();
}

TEST(RemoteFusedGraphExecuteUtils, DryRunAddGraphA) {
  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));
  std::pair<string, Tensor> input_node_info;
  input_node_info.first = NAME_A;
  input_node_info.second = Tensor(DT_FLOAT, {});
  input_node_info.second.scalar<float>()() = 1.0f;
  const std::vector<std::pair<string, Tensor>> inputs{input_node_info};
  std::vector<string> outputs = {NAME_B, NAME_A_PLUS_B};
  std::vector<tensorflow::Tensor> output_tensors;
  Status status = RemoteFusedGraphExecuteUtils::DryRunInference(
      def, inputs, outputs, false /* initialize_by_zero */, &output_tensors);
  ASSERT_TRUE(status.ok()) << status;
  EXPECT_EQ(outputs.size(), output_tensors.size());
  EXPECT_NEAR(NODE_B_VAL, output_tensors.at(0).scalar<float>()(),
              VALUE_TOLERANCE_FLOAT);
  EXPECT_NEAR(1.0f + NODE_B_VAL, output_tensors.at(1).scalar<float>()(),
              VALUE_TOLERANCE_FLOAT);
}

TEST(RemoteFusedGraphExecuteUtils, DryRunAddGraphAUninitialized) {
  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));
  std::pair<string, Tensor> input_node_info;
  input_node_info.first = NAME_A;
  input_node_info.second = Tensor(DT_FLOAT, {});
  const std::vector<std::pair<string, Tensor>> inputs{input_node_info};
  std::vector<string> outputs = {NAME_B, NAME_A_PLUS_B};
  std::vector<tensorflow::Tensor> output_tensors;
  Status status = RemoteFusedGraphExecuteUtils::DryRunInference(
      def, inputs, outputs, true /* initialize_by_zero */, &output_tensors);
  ASSERT_TRUE(status.ok()) << status;
  EXPECT_EQ(outputs.size(), output_tensors.size());
  EXPECT_NEAR(NODE_B_VAL, output_tensors.at(0).scalar<float>()(),
              VALUE_TOLERANCE_FLOAT);
  EXPECT_NEAR(NODE_B_VAL, output_tensors.at(1).scalar<float>()(),
              VALUE_TOLERANCE_FLOAT);
}

TEST(RemoteFusedGraphExecuteUtils, DryRunAddGraphAB) {
  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));
  std::pair<string, Tensor> input_node_info_a;
  input_node_info_a.first = NAME_A;
  input_node_info_a.second = Tensor(DT_FLOAT, {});
  input_node_info_a.second.scalar<float>()() = NODE_A_VAL;
  std::pair<string, Tensor> input_node_info_b;
  input_node_info_b.first = NAME_B;
  input_node_info_b.second = Tensor(DT_FLOAT, {});
  input_node_info_b.second.scalar<float>()() = NODE_B_VAL;
  const std::vector<std::pair<string, Tensor>> inputs{input_node_info_a,
                                                      input_node_info_b};
  std::vector<string> outputs = {NAME_A_PLUS_B};
  std::vector<tensorflow::Tensor> output_tensors;
  Status status = RemoteFusedGraphExecuteUtils::DryRunInference(
      def, inputs, outputs, false /* initialize_by_zero */, &output_tensors);
  ASSERT_TRUE(status.ok()) << status;
  EXPECT_EQ(outputs.size(), output_tensors.size());
  EXPECT_NEAR(NODE_A_VAL + NODE_B_VAL, output_tensors.at(0).scalar<float>()(),
              VALUE_TOLERANCE_FLOAT);
}

TEST(RemoteFusedGraphExecuteUtils, DryRunAddGraphForAllNodes) {
  // Set Node "A" as an input with value (= 1.0f)
  std::pair<string, Tensor> input_node_info_a;
  input_node_info_a.first = NAME_A;
  input_node_info_a.second = Tensor(DT_FLOAT, {});
  input_node_info_a.second.scalar<float>()() = 1.0f;

  // Setup dryrun arguments
  const std::vector<std::pair<string, Tensor>> inputs{input_node_info_a};
  RemoteFusedGraphExecuteUtils::TensorShapeMap tensor_shape_map;

  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));

  // dryrun
  const Status status = RemoteFusedGraphExecuteUtils::DryRunInferenceForAllNode(
      def, inputs, false /* initialize_by_zero */, &tensor_shape_map);

  ASSERT_TRUE(status.ok()) << status;

  // Assert output node count
  ASSERT_EQ(3, tensor_shape_map.size());
  ASSERT_EQ(1, tensor_shape_map.count(NAME_A));
  ASSERT_EQ(1, tensor_shape_map.count(NAME_B));
  ASSERT_EQ(1, tensor_shape_map.count(NAME_A_PLUS_B));

  const RemoteFusedGraphExecuteUtils::TensorShapeType* tst =
      RemoteFusedGraphExecuteUtils::GetTensorShapeType(tensor_shape_map,
                                                       NAME_B);
  EXPECT_NE(tst, nullptr);
  EXPECT_EQ(DT_FLOAT, tst->first);
  EXPECT_EQ(0, tst->second.dims());

  tst = RemoteFusedGraphExecuteUtils::GetTensorShapeType(tensor_shape_map,
                                                         NAME_A_PLUS_B);
  EXPECT_NE(tst, nullptr);
  EXPECT_EQ(DT_FLOAT, tst->first);
  EXPECT_EQ(0, tst->second.dims());
}

TEST(RemoteFusedGraphExecuteUtils, PropagateAndBuildTensorShapeMap) {
  std::pair<string, Tensor> input_node_info_a;
  input_node_info_a.first = NAME_A;
  input_node_info_a.second = Tensor(DT_FLOAT, {});
  input_node_info_a.second.scalar<float>()() = NODE_A_VAL;
  std::pair<string, Tensor> input_node_info_b;
  input_node_info_b.first = NAME_B;
  input_node_info_b.second = Tensor(DT_FLOAT, {});
  input_node_info_b.second.scalar<float>()() = NODE_B_VAL;
  const std::vector<std::pair<string, Tensor>> inputs{input_node_info_a,
                                                      input_node_info_b};

  RemoteFusedGraphExecuteUtils::TensorShapeMap tensor_shape_map;
  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));
  ImportGraphDefOptions opts;
  Graph graph(OpRegistry::Global());
  ShapeRefiner shape_refiner(graph.versions(), graph.op_registry());
  Status status = ImportGraphDef(opts, def, &graph, &shape_refiner);
  ASSERT_TRUE(RemoteFusedGraphExecuteUtils::PropagateShapeInference(
                  def, inputs, &graph, &shape_refiner)
                  .ok());
  ASSERT_TRUE(RemoteFusedGraphExecuteUtils::BuildTensorShapeMapFromGraph(
                  graph, shape_refiner, &tensor_shape_map)
                  .ok());

  ASSERT_EQ(3, tensor_shape_map.size());
  ASSERT_EQ(1, tensor_shape_map.count(NAME_A));
  ASSERT_EQ(1, tensor_shape_map.count(NAME_B));
  ASSERT_EQ(1, tensor_shape_map.count(NAME_A_PLUS_B));

  const RemoteFusedGraphExecuteUtils::TensorShapeType* tst =
      RemoteFusedGraphExecuteUtils::GetTensorShapeType(tensor_shape_map,
                                                       NAME_B);
  EXPECT_NE(tst, nullptr);
  EXPECT_EQ(DT_FLOAT, tst->first);
  EXPECT_EQ(0, tst->second.dims());

  tst = RemoteFusedGraphExecuteUtils::GetTensorShapeType(tensor_shape_map,
                                                         NAME_A_PLUS_B);
  EXPECT_NE(tst, nullptr);
  EXPECT_EQ(DT_FLOAT, tst->first);
  EXPECT_EQ(0, tst->second.dims());

  {
    NodeDef* node_def = GetNodeDef(NAME_B, &def);
    TF_ASSERT_OK(
        RemoteFusedGraphExecuteUtils::AddOutputTensorShapeTypeByTensorShapeMap(
            tensor_shape_map, node_def));
    std::vector<DataType> data_types;
    TF_ASSERT_OK(GetNodeAttr(
        *node_def, RemoteFusedGraphExecuteUtils::ATTR_OUTPUT_DATA_TYPES,
        &data_types));
    ASSERT_EQ(1, data_types.size());
    EXPECT_EQ(DT_FLOAT, data_types.at(0));

    std::vector<TensorShape> shapes;
    TF_ASSERT_OK(GetNodeAttr(
        *node_def, RemoteFusedGraphExecuteUtils::ATTR_OUTPUT_SHAPES, &shapes));
    ASSERT_EQ(1, shapes.size());
    EXPECT_EQ(0, shapes.at(0).dims());
  }

  {
    NodeDef* node_def = GetNodeDef(NAME_A_PLUS_B, &def);
    TF_ASSERT_OK(
        RemoteFusedGraphExecuteUtils::AddOutputTensorShapeTypeByTensorShapeMap(
            tensor_shape_map, node_def));
    std::vector<DataType> data_types;
    TF_ASSERT_OK(GetNodeAttr(
        *node_def, RemoteFusedGraphExecuteUtils::ATTR_OUTPUT_DATA_TYPES,
        &data_types));
    ASSERT_EQ(1, data_types.size());
    EXPECT_EQ(DT_FLOAT, data_types.at(0));

    std::vector<TensorShape> shapes;
    TF_ASSERT_OK(GetNodeAttr(
        *node_def, RemoteFusedGraphExecuteUtils::ATTR_OUTPUT_SHAPES, &shapes));
    ASSERT_EQ(1, shapes.size());
    EXPECT_EQ(0, shapes.at(0).dims());
  }
}

TEST(RemoteFusedGraphExecuteUtils,
     BuildRemoteFusedGraphExecuteInfoWithShapeInference) {
  // Build inputs
  std::pair<string, Tensor> input_node_info_a;
  input_node_info_a.first = NAME_A;
  input_node_info_a.second = Tensor(DT_FLOAT, {});
  input_node_info_a.second.scalar<float>()() = NODE_A_VAL;
  std::pair<string, Tensor> input_node_info_b;
  input_node_info_b.first = NAME_B;
  input_node_info_b.second = Tensor(DT_FLOAT, {});
  input_node_info_b.second.scalar<float>()() = NODE_B_VAL;
  const std::vector<std::pair<string, Tensor>> input_tensors{input_node_info_a,
                                                             input_node_info_b};
  const std::vector<string> inputs{NAME_A, NAME_B};

  // Build outputs
  const std::vector<string> outputs = {NAME_A_PLUS_B};

  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildAndAddTensorShapes(
      input_tensors, /*dry_run_inference*/ true, &def));

  RemoteFusedGraphExecuteInfo execute_info0;
  DataTypeVector input_types0;
  DataTypeVector output_types0;

  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildRemoteFusedGraphExecuteInfo(
      "executor", def, inputs, outputs, /*require_shape_type=*/true,
      &execute_info0, &input_types0, &output_types0));

  EXPECT_EQ(inputs.size(),
            execute_info0.default_graph_input_tensor_shape_size());
  EXPECT_EQ(outputs.size(),
            execute_info0.default_graph_output_tensor_shape_size());
  EXPECT_EQ(inputs.size(), input_types0.size());
  EXPECT_EQ(outputs.size(), output_types0.size());

  EXPECT_EQ(def.node_size(), execute_info0.remote_graph().node_size());
}

TEST(RemoteFusedGraphExecuteUtils, BuildRemoteFusedGraphExecuteOpNode) {
  const std::vector<string> inputs{NAME_A, NAME_B};

  // Build outputs
  const std::vector<string> outputs = {NAME_A_PLUS_B};

  GraphDef def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteOpTestUtils::BuildAddGraph(
      NAME_A, NODE_A_VAL, NAME_B, NODE_B_VAL, NAME_A_PLUS_B, &def));

  Graph graph(OpRegistry::Global());
  ShapeRefiner shape_refiner(graph.versions(), graph.op_registry());
  TF_ASSERT_OK(ImportGraphDef({}, def, &graph, &shape_refiner));

  Node* node;
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildRemoteFusedGraphExecuteOpNode(
      "fused_name", "executor", def, inputs, outputs,
      /*require_shape_type=*/false, &graph, &node));
}

TEST(RemoteFusedGraphExecuteUtils, ExtractSubgraphNodes) {
  GraphDef graph_def;
  TF_ASSERT_OK(
      RemoteFusedGraphExecuteOpTestUtils::BuildMultipleAddGraph(&graph_def));
  ClusterInfo cluster;
  const std::unordered_set<string>& node_names = std::get<0>(cluster);
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      {"H", "I"}, {"J"}, graph_def, &cluster));
  EXPECT_EQ(1, node_names.size()) << IterToString(node_names);

  ClearCluster(&cluster);
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      {"F", "C", "G"}, {"J"}, graph_def, &cluster));
  EXPECT_EQ(3, node_names.size()) << IterToString(node_names);

  ClearCluster(&cluster);
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      {"A", "B", "C", "D", "E"}, {"J"}, graph_def, &cluster));
  EXPECT_EQ(5, node_names.size()) << IterToString(node_names);

  ClearCluster(&cluster);
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      {"A", "B", "C", "D", "E"}, {"K"}, graph_def, &cluster));
  EXPECT_EQ(6, node_names.size()) << IterToString(node_names);

  ClearCluster(&cluster);
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      {"F"}, {"H"}, graph_def, &cluster));
  EXPECT_EQ(2, node_names.size()) << IterToString(node_names);
}

TEST(RemoteFusedGraphExecuteUtils, ClusterizeNodes) {
  GraphDef graph_def;
  TF_ASSERT_OK(
      RemoteFusedGraphExecuteOpTestUtils::BuildMultipleAddGraph(&graph_def));

  std::vector<ClusterInfo> ci_vec;
  TF_ASSERT_OK(
      RemoteFusedGraphExecuteUtils::ClusterizeNodes({"J"}, graph_def, &ci_vec));
  ASSERT_EQ(1, ci_vec.size());
  EXPECT_EQ(2, std::get<1>(ci_vec.at(0)).size()) << DumpInOutNames(ci_vec);
  EXPECT_EQ(1, std::get<2>(ci_vec.at(0)).size()) << DumpInOutNames(ci_vec);

  ci_vec.clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::ClusterizeNodes(
      {"H", "I", "J"}, graph_def, &ci_vec));
  ASSERT_EQ(1, ci_vec.size());
  EXPECT_EQ(3, std::get<1>(ci_vec.at(0)).size()) << DumpInOutNames(ci_vec);
  EXPECT_EQ(1, std::get<2>(ci_vec.at(0)).size()) << DumpInOutNames(ci_vec);

  ci_vec.clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::ClusterizeNodes(
      {"F", "C", "G", "H", "I", "J"}, graph_def, &ci_vec));
  ASSERT_EQ(1, ci_vec.size());
  EXPECT_EQ(4, std::get<1>(ci_vec.at(0)).size()) << DumpInOutNames(ci_vec);
  EXPECT_EQ(2, std::get<2>(ci_vec.at(0)).size()) << DumpInOutNames(ci_vec);

  ci_vec.clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::ClusterizeNodes(
      {"A", "B", "C", "D", "E"}, graph_def, &ci_vec));
  ASSERT_EQ(5, ci_vec.size());

  ci_vec.clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::ClusterizeNodes(
      {"A", "B", "D", "E", "F", "G"}, graph_def, &ci_vec));
  ASSERT_EQ(2, ci_vec.size());
}

TEST(RemoteFusedGraphExecuteUtils, BuildSubgraphDefByInOut) {
  GraphDef graph_def;
  TF_ASSERT_OK(
      RemoteFusedGraphExecuteOpTestUtils::BuildMultipleAddGraph(&graph_def));

  ClusterInfo cluster;
  GraphDef subgraph_def;
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      std::vector<string>{"H", "I"}, std::vector<string>{"J"}, graph_def,
      &cluster));
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterSubgraphDef(
      cluster, graph_def, &subgraph_def));
  EXPECT_EQ(3, subgraph_def.node_size());

  ClearCluster(&cluster);
  subgraph_def.Clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      std::vector<string>{"F", "C", "G"}, std::vector<string>{"J"}, graph_def,
      &cluster));
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterSubgraphDef(
      cluster, graph_def, &subgraph_def));
  EXPECT_EQ(6, subgraph_def.node_size());

  ClearCluster(&cluster);
  subgraph_def.Clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      std::vector<string>{"A", "B", "C", "D", "E"}, std::vector<string>{"J"},
      graph_def, &cluster));
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterSubgraphDef(
      cluster, graph_def, &subgraph_def));
  EXPECT_EQ(10, subgraph_def.node_size());

  ClearCluster(&cluster);
  subgraph_def.Clear();

  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      std::vector<string>{"A", "B", "C", "D", "E"}, std::vector<string>{"K"},
      graph_def, &cluster));
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterSubgraphDef(
      cluster, graph_def, &subgraph_def));
  EXPECT_EQ(11, subgraph_def.node_size());

  ClearCluster(&cluster);
  subgraph_def.Clear();
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterByBorder(
      std::vector<string>{"F"}, std::vector<string>{"H"}, graph_def, &cluster));
  TF_ASSERT_OK(RemoteFusedGraphExecuteUtils::BuildClusterSubgraphDef(
      cluster, graph_def, &subgraph_def));
  EXPECT_EQ(3, subgraph_def.node_size());
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByInOut_HI_J) {
  SetSubgraphArguments(std::vector<string>{"H", "I"}, std::vector<string>{"J"},
                       this);

  TF_ASSERT_OK(FuseByInOut());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(11, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByInOut_FCG_J) {
  SetSubgraphArguments(std::vector<string>{"F", "C", "G"},
                       std::vector<string>{"J"}, this);

  TF_ASSERT_OK(FuseByInOut());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(9, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByInOut_ABCDE_J) {
  SetSubgraphArguments(std::vector<string>{"A", "B", "C", "D", "E"},
                       std::vector<string>{"J"}, this);

  TF_ASSERT_OK(FuseByInOut());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(8, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByInOut_ABCDE_K) {
  SetSubgraphArguments(std::vector<string>{"A", "B", "C", "D", "E"},
                       std::vector<string>{"K"}, this);

  TF_ASSERT_OK(FuseByInOut());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(7, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByNodes_H) {
  subgraph_node_names_ = {"H"};

  TF_ASSERT_OK(FuseByNodes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(11, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByNodes_HIJ) {
  subgraph_node_names_ = {"H", "I", "J"};

  TF_ASSERT_OK(FuseByNodes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(9, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByNodes_CFGHIJ) {
  subgraph_node_names_ = {"C", "F", "G", "H", "I", "J"};

  TF_ASSERT_OK(FuseByNodes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(6, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByNodes_ABCDEFGHIJ) {
  subgraph_node_names_ = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"};

  TF_ASSERT_OK(FuseByNodes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(3, result_graph_def_.node_size())  // "A", "RFG", "K"
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByNodes_ABCDEFGHIJK) {
  subgraph_node_names_ = {"A", "B", "C", "D", "E", "F",
                          "G", "H", "I", "J", "K"};

  TF_ASSERT_OK(FuseByNodes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(3, result_graph_def_.node_size())  // "A", "RFG", "K"
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByOpTypes_HIJ) {
  subgraph_op_types_ = {"Mul"};
  ReplaceOpType({"H", "I", "J"}, "Mul");

  TF_ASSERT_OK(FuseByOpTypes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(9, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByOpTypes_FGHIJ) {
  subgraph_op_types_ = {"Const", "Mul"};
  ReplaceOpType({"F", "G", "H", "I", "J"}, "Mul");

  TF_ASSERT_OK(FuseByOpTypes());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(3, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByExecutor_HIJ) {
  ReplaceOpType({"H", "I", "J"}, "Mul");

  TF_ASSERT_OK(FuseByExecutor0());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(9, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, FuseSubgraphByExecutor_FGHIJ) {
  ReplaceOpType({"F", "G", "H", "I", "J"}, "Mul");

  TF_ASSERT_OK(FuseByExecutor1());

  EXPECT_EQ(11, graph_def_.node_size());
  EXPECT_EQ(3, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_H) {
  subgraph_node_names_ = {"H"};

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(11, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_CFGHIJ) {
  subgraph_node_names_ = {"C", "F", "G", "H", "I", "J"};

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(6, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_ABCDEFGHIJK) {
  subgraph_node_names_ = {"A", "B", "C", "D", "E", "F",
                          "G", "H", "I", "J", "K"};

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(3, result_graph_def_.node_size())  // "A", "RFG", "K"
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_HI_J) {
  SetSubgraphArguments(std::vector<string>{"H", "I"}, std::vector<string>{"J"},
                       this);

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(11, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_FCG_J) {
  SetSubgraphArguments(std::vector<string>{"F", "C", "G"},
                       std::vector<string>{"J"}, this);

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(9, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_ABCDE_K) {
  SetSubgraphArguments(std::vector<string>{"A", "B", "C", "D", "E"},
                       std::vector<string>{"K"}, this);

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(7, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_MUL_HIJ) {
  ReplaceOpType({"H", "I", "J"}, "Mul");
  subgraph_op_types_ = {"Mul"};

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(9, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

TEST_F(FuseRemoteGraphMultipleAddOpsTest, PlaceAndFuse_CONST_MUL_FGHIJ) {
  ReplaceOpType({"F", "G", "H", "I", "J"}, "Mul");
  subgraph_op_types_ = {"Const", "Mul"};

  TF_ASSERT_OK(PlaceRemoteGraphArguments());
  ASSERT_TRUE(IsFuseReady());
  TF_ASSERT_OK(BuildAndAddTensorShape());

  EXPECT_EQ(11, graph_def_.node_size());

  TF_ASSERT_OK(FuseByPlacedArguments());

  EXPECT_EQ(3, result_graph_def_.node_size())
      << "=== Before: \n"
      << SummarizeGraphDef(graph_def_) << "\n\n\n=== After: \n"
      << SummarizeGraphDef(result_graph_def_);
}

}  // namespace
}  // namespace tensorflow
