//===-- PlatformAndroidTest.cpp -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Plugins/Platform/Android/PlatformAndroid.h"
#include "Plugins/Platform/Android/PlatformAndroidRemoteGDBServer.h"
#include "TestingSupport/SubsystemRAII.h"
#include "TestingSupport/TestUtilities.h"
#include "lldb/Utility/Connection.h"
#include "gmock/gmock.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::platform_android;
using namespace testing;

namespace {

class MockSyncService : public AdbClient::SyncService {
public:
  MockSyncService() : SyncService(std::unique_ptr<Connection>()) {}

  MOCK_METHOD2(PullFile,
               Status(const FileSpec &remote_file, const FileSpec &local_file));
  MOCK_METHOD4(Stat, Status(const FileSpec &remote_file, uint32_t &mode,
                            uint32_t &size, uint32_t &mtime));
};

typedef std::unique_ptr<AdbClient::SyncService> SyncServiceUP;

class MockAdbClient : public AdbClient {
public:
  explicit MockAdbClient() : AdbClient("mock") {}

  MOCK_METHOD3(ShellToFile,
               Status(const char *command, std::chrono::milliseconds timeout,
                      const FileSpec &output_file_spec));
  MOCK_METHOD1(GetSyncService, SyncServiceUP(Status &error));
};

class PlatformAndroidTest : public PlatformAndroid, public ::testing::Test {
public:
  PlatformAndroidTest() : PlatformAndroid(false) {
    m_remote_platform_sp = PlatformSP(new PlatformAndroidRemoteGDBServer());
  }

  MOCK_METHOD1(GetAdbClient, AdbClientUP(Status &error));
  MOCK_METHOD0(GetPropertyPackageName, llvm::StringRef());
};

} // namespace

TEST_F(PlatformAndroidTest, DownloadModuleSliceWithAdbClientError) {
  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(1)
      .WillOnce(DoAll(WithArg<0>([](auto &arg) {
                        arg = Status::FromErrorString(
                            "Failed to create AdbClient");
                      }),
                      Return(ByMove(AdbClientUP()))));

  EXPECT_TRUE(
      DownloadModuleSlice(
          FileSpec("/system/app/Test/Test.apk!/lib/arm64-v8a/libtest.so"), 4096,
          3600, FileSpec())
          .Fail());
}

TEST_F(PlatformAndroidTest, DownloadModuleSliceWithNormalFile) {
  auto sync_service = new MockSyncService();
  EXPECT_CALL(*sync_service, Stat(FileSpec("/system/lib64/libc.so"), _, _, _))
      .Times(1)
      .WillOnce(DoAll(SetArgReferee<1>(1), Return(Status())));
  EXPECT_CALL(*sync_service, PullFile(FileSpec("/system/lib64/libc.so"), _))
      .Times(1)
      .WillOnce(Return(Status()));

  auto adb_client = new MockAdbClient();
  EXPECT_CALL(*adb_client, GetSyncService(_))
      .Times(1)
      .WillOnce(Return(ByMove(SyncServiceUP(sync_service))));

  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(1)
      .WillOnce(Return(ByMove(AdbClientUP(adb_client))));

  EXPECT_TRUE(
      DownloadModuleSlice(FileSpec("/system/lib64/libc.so"), 0, 0, FileSpec())
          .Success());
}

TEST_F(PlatformAndroidTest, DownloadModuleSliceWithZipFile) {
  auto adb_client = new MockAdbClient();
  EXPECT_CALL(*adb_client,
              ShellToFile(StrEq("dd if='/system/app/Test/Test.apk' "
                                "iflag=skip_bytes,count_bytes "
                                "skip=4096 count=3600 status=none"),
                          _, _))
      .Times(1)
      .WillOnce(Return(Status()));

  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(1)
      .WillOnce(Return(ByMove(AdbClientUP(adb_client))));

  EXPECT_TRUE(
      DownloadModuleSlice(
          FileSpec("/system/app/Test/Test.apk!/lib/arm64-v8a/libtest.so"), 4096,
          3600, FileSpec())
          .Success());
}

TEST_F(PlatformAndroidTest, DownloadModuleSliceWithZipFileAndRunAs) {
  auto adb_client = new MockAdbClient();
  EXPECT_CALL(*adb_client,
              ShellToFile(StrEq("run-as 'com.example.test' "
                                "dd if='/system/app/Test/Test.apk' "
                                "iflag=skip_bytes,count_bytes "
                                "skip=4096 count=3600 status=none"),
                          _, _))
      .Times(1)
      .WillOnce(Return(Status()));

  EXPECT_CALL(*this, GetPropertyPackageName())
      .Times(1)
      .WillOnce(Return(llvm::StringRef("com.example.test")));

  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(1)
      .WillOnce(Return(ByMove(AdbClientUP(adb_client))));

  EXPECT_TRUE(
      DownloadModuleSlice(
          FileSpec("/system/app/Test/Test.apk!/lib/arm64-v8a/libtest.so"), 4096,
          3600, FileSpec())
          .Success());
}

TEST_F(PlatformAndroidTest, GetFileWithNormalFile) {
  auto sync_service = new MockSyncService();
  EXPECT_CALL(*sync_service, Stat(FileSpec("/data/local/tmp/test"), _, _, _))
      .Times(1)
      .WillOnce(DoAll(SetArgReferee<1>(1), Return(Status())));
  EXPECT_CALL(*sync_service, PullFile(FileSpec("/data/local/tmp/test"), _))
      .Times(1)
      .WillOnce(Return(Status()));

  auto adb_client = new MockAdbClient();
  EXPECT_CALL(*adb_client, GetSyncService(_))
      .Times(1)
      .WillOnce(Return(ByMove(SyncServiceUP(sync_service))));

  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(1)
      .WillOnce(Return(ByMove(AdbClientUP(adb_client))));

  EXPECT_TRUE(GetFile(FileSpec("/data/local/tmp/test"), FileSpec()).Success());
}

TEST_F(PlatformAndroidTest, GetFileWithCatFallback) {
  auto sync_service = new MockSyncService();
  EXPECT_CALL(
      *sync_service,
      Stat(FileSpec("/data/data/com.example.app/lib-main/libtest.so"), _, _, _))
      .Times(1)
      .WillOnce(DoAll(SetArgReferee<1>(0), Return(Status())));

  auto adb_client0 = new MockAdbClient();
  EXPECT_CALL(*adb_client0, GetSyncService(_))
      .Times(1)
      .WillOnce(Return(ByMove(SyncServiceUP(sync_service))));

  auto adb_client1 = new MockAdbClient();
  EXPECT_CALL(
      *adb_client1,
      ShellToFile(StrEq("cat '/data/data/com.example.app/lib-main/libtest.so'"),
                  _, _))
      .Times(1)
      .WillOnce(Return(Status()));

  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(2)
      .WillOnce(Return(ByMove(AdbClientUP(adb_client0))))
      .WillOnce(Return(ByMove(AdbClientUP(adb_client1))));

  EXPECT_TRUE(
      GetFile(FileSpec("/data/data/com.example.app/lib-main/libtest.so"),
              FileSpec())
          .Success());
}

TEST_F(PlatformAndroidTest, GetFileWithCatFallbackAndRunAs) {
  auto sync_service = new MockSyncService();
  EXPECT_CALL(
      *sync_service,
      Stat(FileSpec("/data/data/com.example.app/lib-main/libtest.so"), _, _, _))
      .Times(1)
      .WillOnce(DoAll(SetArgReferee<1>(0), Return(Status())));

  auto adb_client0 = new MockAdbClient();
  EXPECT_CALL(*adb_client0, GetSyncService(_))
      .Times(1)
      .WillOnce(Return(ByMove(SyncServiceUP(sync_service))));

  auto adb_client1 = new MockAdbClient();
  EXPECT_CALL(
      *adb_client1,
      ShellToFile(StrEq("run-as 'com.example.app' "
                        "cat '/data/data/com.example.app/lib-main/libtest.so'"),
                  _, _))
      .Times(1)
      .WillOnce(Return(Status()));

  EXPECT_CALL(*this, GetPropertyPackageName())
      .Times(1)
      .WillOnce(Return(llvm::StringRef("com.example.app")));

  EXPECT_CALL(*this, GetAdbClient(_))
      .Times(2)
      .WillOnce(Return(ByMove(AdbClientUP(adb_client0))))
      .WillOnce(Return(ByMove(AdbClientUP(adb_client1))));

  EXPECT_TRUE(
      GetFile(FileSpec("/data/data/com.example.app/lib-main/libtest.so"),
              FileSpec())
          .Success());
}
