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

Licensed under the Apache License, Version 2.0 (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/framework/node_def_builder.h"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/shape_inference_testutil.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_testutil.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/test.h"

namespace tensorflow {

TEST(DataFlowOpsTest, LookupTableFind) {
  ShapeInferenceTestOp op("LookupTableFind");
  INFER_OK(op, "?;?;?", "?");
  INFER_OK(op, "[2];[];[]", "?");
  INFER_OK(op, "[?];[1,2,3];[]", "?");

  // Last input must be a scalar or vector
  INFER_ERROR("Shape must be at most rank 1 but is rank 2", op,
              "[?];[1,2,3];[1,2]");
}

TEST(DataFlowOpsTest, LookupTableInsert) {
  ShapeInferenceTestOp op("LookupTableInsert");
  INFER_OK(op, "?;?;?", "");
  INFER_OK(op, "[2];[];[]", "");

  // Dim 0 (table handle) must be a vector.
  INFER_ERROR("Shape must be rank 1 but is rank 0", op, "[];[1,2,3];[]");
  INFER_OK(op, "[2];[1,?,3];[?,2,?]", "");
}

TEST(DataFlowOpsTest, LookupTableSize) {
  ShapeInferenceTestOp op("LookupTableSize");
  // Always scalar output.
  INFER_OK(op, "?", "[]");
  INFER_OK(op, "[2]", "[]");

  // Dim 0 (table handle) must be a vector of length 2
  INFER_ERROR("Shape must be rank 1 but is rank 0", op, "[]");
  INFER_ERROR("Dimension must be 2 but is 3", op, "[3]");
}

TEST(DataFlowOpsTest, LookupTableExport) {
  ShapeInferenceTestOp op("LookupTableExport");
  // Always one vector output and one unknown size output.
  INFER_OK(op, "?", "[?];?");
  INFER_OK(op, "[2]", "[?];?");

  // Dim 0 (table handle) must be a vector.
  INFER_ERROR("Shape must be rank 1 but is rank 0", op, "[]");
}

TEST(DataFlowOpsTest, InitializeTable) {
  ShapeInferenceTestOp op("InitializeTable");
  // Always no output.
  INFER_OK(op, "?;?;?", "");

  // Dim 0 (table handle) must be a vector.
  INFER_ERROR("Shape must be rank 1 but is rank 0", op, "[];[];[]");

  // Dims 1 and 2 (keys and values) are the same size and must be vectors.
  INFER_ERROR("Dimension 0 in both shapes must be equal, but are 1 and 2", op,
              "?;[1];[2]");
  INFER_ERROR("Shape must be rank 1 but is rank 2", op, "[2];[1,2];[1,2]");
}

TEST(DataFlowOpsTest, InitializeTableFromTextFile) {
  ShapeInferenceTestOp op("InitializeTableFromTextFile");
  // Always no output.
  INFER_OK(op, "?;?", "");

  // Dim 1 (filename) must be scalar, dim 0 vector
  INFER_ERROR("Shape must be rank 1 but is rank 0", op, "[];[]");
  INFER_ERROR("Shape must be rank 0 but is rank 1", op, "[2];[1]");
}

TEST(DataFlowOpsTest, DynamicPartition) {
  ShapeInferenceTestOp op("DynamicPartition");
  TF_ASSERT_OK(NodeDefBuilder("test", "DynamicPartition")
                   .Input("data", 0, DT_FLOAT_REF)
                   .Input("indices", 0, DT_INT32)
                   .Attr("num_partitions", 4)
                   .Finalize(&op.node_def));

  // Unknown rank for indices, so unknown shape.
  INFER_OK(op, "?;?", "?;?;?;?");

  // 3 dimensional data, 2 dimensional indices.
  INFER_OK(op, "[3,4,5];[3,4]", "[?,d0_2];[?,d0_2];[?,d0_2];[?,d0_2]");

  TF_ASSERT_OK(NodeDefBuilder("test", "DynamicPartition")
                   .Input("data", 0, DT_FLOAT)
                   .Input("indices", 0, DT_INT32)
                   .Attr("num_partitions", 2)
                   .Finalize(&op.node_def));

  // Suffix after matching prefix is copied over.
  INFER_OK(op, "[3,4,5,6];[3,4]", "[?,d0_2,d0_3];[?,d0_2,d0_3]");

  // Does not start with proper prefix
  INFER_ERROR("Dimensions must be equal, but are 4 and 100", op,
              "[3,4,5];[3,100]");
}

TEST(DataFlowOpsTest, DynamicStitch) {
  ShapeInferenceTestOp op("DynamicStitch");
  TF_ASSERT_OK(
      NodeDefBuilder("test", "DynamicStitch")
          .Input({{"indices", 0, DT_INT32}, {"indices_2", 1, DT_INT32}})
          .Input({{"data", 0, DT_FLOAT}, {"data_2", 1, DT_FLOAT}})
          .Attr("N", 2)
          .Finalize(&op.node_def));

  // Bad prefix for the second data input.
  INFER_ERROR("Dimensions must be equal, but are 10 and 5", op,
              "[2,3];[5,6];[2,3,4,5];[10,11,4,5]");

  // Inconsistent suffix dimensions
  INFER_ERROR("Dimension 0 in both shapes must be equal, but are 4 and 13", op,
              "[2,3];[5,6];[2,3,4,5];[5,6,13,14]");

  // Good case, but no known input tensors.
  INFER_OK(op, "[2,3];[5,6];[2,3,4,5];[5,6,4,5]", "[?,d2_2,d2_3]");

  // 1 known input tensors, not enough to change answer.
  Tensor tensor_2 = test::AsTensor<int32>(
      std::vector<int32>{2, 4, 6, 0, 10, 11}, TensorShape({2, 3}));
  Tensor tensor_5 = test::AsTensor<int32>(
      std::vector<int32>{0,    1,  2,  3,  4,  5,  6,  7,  8,  9,
                         10,   11, 12, 13, 14, 15, 16, 17, 18, 19,
                         1000, 21, 22, 23, 24, 25, 26, 27, 28, 29},
      TensorShape({5, 6}));
  op.input_tensors.push_back(nullptr);
  op.input_tensors.push_back(&tensor_5);
  INFER_OK(op, "[2,3];[5,6];[2,3,4,5];[5,6,4,5]", "[?,d2_2,d2_3]");

  op.input_tensors[0] = &tensor_2;
  op.input_tensors[1] = nullptr;
  INFER_OK(op, "[2,3];[5,6];[2,3,4,5];[5,6,4,5]", "[?,d2_2,d2_3]");
  INFER_OK(op, "[2,3];?;[2,3,4,5];[5,6,4,5]", "[?,d2_2,d2_3]");

  op.input_tensors[1] = &tensor_5;
  INFER_OK(op, "[2,3];[5,6];[2,3,4,5];[5,6,4,5]", "[1001,d2_2,d2_3]");

  tensor_2.flat<int32>()(3) = 10000;
  INFER_OK(op, "[2,3];[5,6];[2,3,4,5];[5,6,4,5]", "[10001,d2_2,d2_3]");
}

TEST(DataFlowOpsTest, ParallelDynamicStitch) {
  ShapeInferenceTestOp op("ParallelDynamicStitch");
  TF_ASSERT_OK(
      NodeDefBuilder("test", "ParallelDynamicStitch")
          .Input({{"indices", 0, DT_INT32}, {"indices_2", 1, DT_INT32}})
          .Input({{"data", 0, DT_FLOAT}, {"data_2", 1, DT_FLOAT}})
          .Attr("N", 2)
          .Finalize(&op.node_def));

  INFER_OK(op, "[2,3];[5,6];[2,3,4,5];[5,6,4,5]", "[?,d2_2,d2_3]");

  // Bad prefix for the second data input.
  INFER_ERROR("Dimensions must be equal, but are 10 and 5", op,
              "[2,3];[5,6];[2,3,4,5];[10,11,4,5]");

  // Inconsistent suffix dimensions
  INFER_ERROR("Dimension 0 in both shapes must be equal, but are 4 and 13", op,
              "[2,3];[5,6];[2,3,4,5];[5,6,13,14]");
}

TEST(DataFlowOpsTest, TensorArrayV3) {
  ShapeInferenceTestOp op("TensorArrayV3");
  TF_ASSERT_OK(NodeDefBuilder("test", "TensorArrayV3")
                   .Input({"size", 0, DT_INT32})
                   .Attr("dtype", DT_FLOAT)
                   .Finalize(&op.node_def));

  INFER_OK(op, "[]", "[2];[]");
  INFER_OK(op, "?", "[2];[]");
  INFER_ERROR("Shape must be rank 0 but is rank 1", op, "[2]");
}

TEST(DataFlowOpsTest, QueueDequeueV2ShapeFn) {
  ShapeInferenceTestOp op("QueueDequeueV2");
  TF_ASSERT_OK(NodeDefBuilder("test", op.name)
                   .Input("handle", 0, DT_RESOURCE)
                   .Attr("component_types", {DT_FLOAT, DT_INT32})
                   .Finalize(&op.node_def));

  INFER_OK(op, "?", "?;?");

  std::vector<ShapeInferenceTestOp::ShapeAndType> shapes_and_types;
  op.input_resource_handle_shapes_and_types.push_back(&shapes_and_types);
  INFER_OK(op, "?", "?;?");

  // Wrong number of shapes provided by handle.
  shapes_and_types.emplace_back("[1,?,3]", DT_FLOAT);
  INFER_OK(op, "?", "?;?");

  // Correct number of shapes provided by handle.
  shapes_and_types.emplace_back("[?,2]", DT_FLOAT);
  INFER_OK(op, "?", "[1,?,3];[?,2]");
}

TEST(DataFlowOpsTest, QueueDequeueManyV2ShapeFn) {
  ShapeInferenceTestOp op("QueueDequeueManyV2");
  TF_ASSERT_OK(NodeDefBuilder("test", op.name)
                   .Input("handle", 0, DT_RESOURCE)
                   .Input("n", 0, DT_INT32)
                   .Attr("component_types", {DT_FLOAT, DT_INT32})
                   .Finalize(&op.node_def));

  ////////////////////////////
  // Input n is not a constant.
  INFER_OK(op, "?;?", "?;?");
  std::vector<ShapeInferenceTestOp::ShapeAndType> shapes_and_types;
  op.input_resource_handle_shapes_and_types.push_back(&shapes_and_types);
  op.input_resource_handle_shapes_and_types.push_back(nullptr);
  // Wrong number of shapes provided by handle.
  shapes_and_types.emplace_back("[1,?,3]", DT_FLOAT);
  INFER_OK(op, "?;?", "?;?");
  // Correct number of shapes provided by handle.
  shapes_and_types.emplace_back("[?,2]", DT_FLOAT);
  INFER_OK(op, "?;?", "[?,1,?,3];[?,?,2]");

  ////////////////////////////
  // Input n is a constant. (set up test and repeat the cases from above).
  Tensor n_tensor = test::AsScalar(12);
  op.input_tensors.push_back(nullptr);
  op.input_tensors.push_back(&n_tensor);
  op.input_resource_handle_shapes_and_types.clear();
  shapes_and_types.clear();

  INFER_OK(op, "?;?", "?;?");
  op.input_resource_handle_shapes_and_types.push_back(&shapes_and_types);
  op.input_resource_handle_shapes_and_types.push_back(nullptr);
  // Wrong number of shapes provided by handle.
  shapes_and_types.emplace_back("[1,?,3]", DT_FLOAT);
  INFER_OK(op, "?;?", "?;?");
  // Correct number of shapes provided by handle.
  shapes_and_types.emplace_back("[?,2]", DT_FLOAT);
  INFER_OK(op, "?;?", "[12,1,?,3];[12,?,2]");

  n_tensor = test::AsScalar<int32>(-1);  // invalid value of n.
  INFER_ERROR("must be >= 0", op, "?;?");
}

TEST(DataFlowOpsTest, QueueDequeueUpToV2ShapeFn) {
  // Results are the same regardless of what value is passed for n.
  for (int pass = 0; pass < 2; ++pass) {
    ShapeInferenceTestOp op("QueueDequeueUpToV2");
    TF_ASSERT_OK(NodeDefBuilder("test", op.name)
                     .Input("handle", 0, DT_RESOURCE)
                     .Input("n", 0, DT_INT32)
                     .Attr("component_types", {DT_FLOAT, DT_INT32})
                     .Finalize(&op.node_def));

    Tensor n_tensor = test::AsScalar(12);
    if (pass == 1) {
      // Second pass, pass value of <n> as a constant.
      op.input_tensors.push_back(nullptr);
      op.input_tensors.push_back(&n_tensor);
    }

    INFER_OK(op, "?;?", "?;?");
    std::vector<ShapeInferenceTestOp::ShapeAndType> shapes_and_types;
    op.input_resource_handle_shapes_and_types.push_back(&shapes_and_types);
    op.input_resource_handle_shapes_and_types.push_back(nullptr);
    // Wrong number of shapes provided by handle.
    shapes_and_types.emplace_back("[1,?,3]", DT_FLOAT);
    INFER_OK(op, "?;?", "?;?");
    // Correct number of shapes provided by handle.
    shapes_and_types.emplace_back("[?,2]", DT_FLOAT);
    INFER_OK(op, "?;?", "[?,1,?,3];[?,?,2]");
  }
}

}  // end namespace tensorflow
