/*
 * Copyright 2016 The Android Open Source Project
 *
 * 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 "ShellDriverTest.h"

#include <errno.h>
#include <gtest/gtest.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <iostream>
#include <sstream>

#include <VtsDriverCommUtil.h>
#include "test/vts/proto/VtsDriverControlMessage.pb.h"

#include "ShellDriver.h"

using namespace std;

namespace android {
namespace vts {

static int kMaxRetry = 3;

/*
 * send a command to the driver on specified UNIX domain socket and print out
 * the outputs from driver.
 */
static string vts_shell_driver_test_client_start(const string& command,
                                                 const string& socket_address) {
  struct sockaddr_un address;
  int socket_fd;

  socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
  if (socket_fd < 0) {
    fprintf(stderr, "socket() failed\n");
    return "";
  }

  VtsDriverCommUtil driverUtil(socket_fd);

  memset(&address, 0, sizeof(struct sockaddr_un));

  address.sun_family = AF_UNIX;
  strncpy(address.sun_path, socket_address.c_str(),
          sizeof(address.sun_path) - 1);

  int conn_success;
  int retry_count = 0;

  conn_success = connect(socket_fd, (struct sockaddr*)&address,
                         sizeof(struct sockaddr_un));
  for (retry_count = 0; retry_count < kMaxRetry && conn_success != 0;
       retry_count++) {  // retry if server not ready
    printf("Client: connection failed, retrying...\n");
    retry_count++;
    if (usleep(50 * pow(retry_count, 3)) != 0) {
      fprintf(stderr, "shell driver unit test: sleep intrupted.");
    }

    conn_success = connect(socket_fd, (struct sockaddr*)&address,
                           sizeof(struct sockaddr_un));
  }

  if (conn_success != 0) {
    fprintf(stderr, "connect() failed\n");
    return "";
  }

  VtsDriverControlCommandMessage cmd_msg;

  cmd_msg.add_shell_command(command);
  cmd_msg.set_command_type(EXECUTE_COMMAND);

  if (!driverUtil.VtsSocketSendMessage(cmd_msg)) {
    return NULL;
  }

  // read driver output
  VtsDriverControlResponseMessage out_msg;

  if (!driverUtil.VtsSocketRecvMessage(
          static_cast<google::protobuf::Message*>(&out_msg))) {
    return "";
  }

  // TODO(yuexima) use vector for output messages
  stringstream ss;
  for (int i = 0; i < out_msg.stdout_size(); i++) {
    string out_str = out_msg.stdout(i);
    cout << "[Shell driver] output for command " << i << ": " << out_str
         << endl;
    ss << out_str;
  }
  close(socket_fd);

  cout << "[Client] receiving output: " << ss.str() << endl;
  return ss.str();
}

/*
 * Prototype unit test helper. It first forks a vts_shell_driver process
 * and then call a client function to execute a command.
 */
static string test_shell_command_output(const string& command,
                                        const string& socket_address) {
  pid_t p_driver;
  string res_client;

  VtsShellDriver shellDriver(socket_address.c_str());

  p_driver = fork();
  if (p_driver == 0) {  // child
    int res_driver = shellDriver.StartListen();

    if (res_driver != 0) {
      fprintf(stderr, "Driver reported error. The error code is: %d.\n",
              res_driver);
      exit(res_driver);
    }

    exit(0);
  } else if (p_driver > 0) {  // parent
    res_client = vts_shell_driver_test_client_start(command, socket_address);
    if (res_client.empty()) {
      fprintf(stderr, "Client reported error.\n");
      exit(1);
    }
    cout << "Client receiving: " << res_client << endl;
  } else {
    fprintf(stderr,
            "shell_driver_test.cpp: create child process failed for driver.");
    exit(-1);
  }

  // send kill signal to insure the process would not block
  kill(p_driver, SIGKILL);

  return res_client;
}

/*
 * This test tests whether the output of "uname" is "Linux\n"
 */
TEST(vts_shell_driver_start, vts_shell_driver_unit_test_uname) {
  string expected = "Linux\n";
  string output =
      test_shell_command_output("uname", "/data/local/tmp/test1_1.tmp");
  ASSERT_EQ(output.compare(expected), 0);
}

/*
 * This test tests whether the output of "which ls" is "/system/bin/ls\n"
 */
TEST(vts_shell_driver_start, vts_shell_driver_unit_test_which_ls) {
  string expected = "/system/bin/ls\n";
  string output =
      test_shell_command_output("which ls", "/data/local/tmp/test1_2.tmp");
  ASSERT_EQ(output.compare(expected), 0);
}

}  // namespace vts
}  // namespace android