/*
 * Copyright (C) 2019 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 "perfetto/base/logging.h"
#include "perfetto/ext/base/scoped_file.h"
#include "src/traced/probes/packages_list/packages_list_data_source.h"

#include <stdio.h>
#include <unistd.h>

#include <cstdint>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>

#include "perfetto/ext/base/pipe.h"
#include "src/traced/probes/packages_list/packages_list_parser.h"
#include "test/gtest_and_gmock.h"

namespace perfetto {
namespace {

TEST(PackagesListDataSourceTest, ParseLineNonProfileNonDebug) {
  char kLine[] =
      "com.test.app 1234 0 /data/user/0/com.test.app "
      "default:targetSdkVersion=12452 1234,5678 0 1111\n";
  Package pkg;
  ASSERT_TRUE(ReadPackagesListLine(kLine, &pkg));
  EXPECT_EQ(pkg.name, "com.test.app");
  EXPECT_EQ(pkg.debuggable, false);
  EXPECT_EQ(pkg.profileable_from_shell, false);
  EXPECT_EQ(pkg.version_code, 1111);
}

TEST(PackagesListDataSourceTest, ParseLineProfileNonDebug) {
  char kLine[] =
      "com.test.app 1234 0 /data/user/0/com.test.app "
      "default:targetSdkVersion=12452 1234,5678 1 1111\n";
  Package pkg;
  ASSERT_TRUE(ReadPackagesListLine(kLine, &pkg));
  EXPECT_EQ(pkg.name, "com.test.app");
  EXPECT_EQ(pkg.debuggable, false);
  EXPECT_EQ(pkg.profileable_from_shell, true);
  EXPECT_EQ(pkg.version_code, 1111);
}

TEST(PackagesListDataSourceTest, ParseLineNonProfileDebug) {
  char kLine[] =
      "com.test.app 1234 1 /data/user/0/com.test.app "
      "default:targetSdkVersion=12452 1234,5678 0 1111\n";
  Package pkg;
  ASSERT_TRUE(ReadPackagesListLine(kLine, &pkg));
  EXPECT_EQ(pkg.name, "com.test.app");
  EXPECT_EQ(pkg.debuggable, true);
  EXPECT_EQ(pkg.profileable_from_shell, false);
  EXPECT_EQ(pkg.version_code, 1111);
}

TEST(PackagesListDataSourceTest, ParseLineProfileDebug) {
  char kLine[] =
      "com.test.app 1234 1 /data/user/0/com.test.app "
      "default:targetSdkVersion=12452 1234,5678 1 1111\n";
  Package pkg;
  ASSERT_TRUE(ReadPackagesListLine(kLine, &pkg));
  EXPECT_EQ(pkg.name, "com.test.app");
  EXPECT_EQ(pkg.debuggable, true);
  EXPECT_EQ(pkg.profileable_from_shell, true);
  EXPECT_EQ(pkg.version_code, 1111);
}

TEST(PackagesListDataSourceTest, EmptyNameFilterIncludesAll) {
  char buf[] =
      "com.test.one 1000 0 /data/user/0/com.test.one "
      "default:targetSdkVersion=10 none 0 10\n"
      "com.test.two 1001 0 /data/user/0/com.test.two "
      "default:targetSdkVersion=10 1065,3002 0 20\n"
      "com.test.three 1002 0 /data/user/0/com.test.three "
      "default:targetSdkVersion=10 1065,3002 0 30\n";

  // Create a stream from |buf|, up to the null byte. Avoid fmemopen as it
  // requires a higher target API (23) than we use for portability.
  auto pipe = base::Pipe::Create();
  PERFETTO_CHECK(write(pipe.wr.get(), buf, sizeof(buf) - 1) == sizeof(buf) - 1);
  pipe.wr.reset();
  auto fs = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
  pipe.rd.release();  // now owned by |fs|

  std::unordered_multimap<uint64_t, Package> packages;
  std::set<std::string> filter{};

  ASSERT_FALSE(ParsePackagesListStream(packages, fs, filter, {}));

  // all entries
  EXPECT_EQ(packages.size(), 3lu);
  EXPECT_EQ(packages.find(1000)->second.name, "com.test.one");
  EXPECT_EQ(packages.find(1000)->second.version_code, 10);
  EXPECT_EQ(packages.find(1001)->second.name, "com.test.two");
  EXPECT_EQ(packages.find(1001)->second.version_code, 20);
  EXPECT_EQ(packages.find(1002)->second.name, "com.test.three");
  EXPECT_EQ(packages.find(1002)->second.version_code, 30);
}

TEST(PackagesListDataSourceTest, NameFilter) {
  char buf[] =
      "com.test.one 1000 0 /data/user/0/com.test.one "
      "default:targetSdkVersion=10 none 0 10\n"
      "com.test.two 1001 0 /data/user/0/com.test.two "
      "default:targetSdkVersion=10 1065,3002 0 20\n"
      "com.test.three 1002 0 /data/user/0/com.test.three "
      "default:targetSdkVersion=10 1065,3002 0 30\n";

  // Create a stream from |buf|, up to the null byte. Avoid fmemopen as it
  // requires a higher target API (23) than we use for portability.
  auto pipe = base::Pipe::Create();
  PERFETTO_CHECK(write(pipe.wr.get(), buf, sizeof(buf) - 1) == sizeof(buf) - 1);
  pipe.wr.reset();
  auto fs = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
  pipe.rd.release();  // now owned by |fs|

  std::unordered_multimap<uint64_t, Package> packages;
  std::set<std::string> filter{"com.test.one", "com.test.three"};

  ASSERT_FALSE(ParsePackagesListStream(packages, fs, filter, {}));

  // two named entries
  EXPECT_EQ(packages.size(), 2lu);
  EXPECT_EQ(packages.find(1000)->second.name, "com.test.one");
  EXPECT_EQ(packages.find(1000)->second.version_code, 10);
  EXPECT_EQ(packages.find(1002)->second.name, "com.test.three");
  EXPECT_EQ(packages.find(1002)->second.version_code, 30);
}

TEST(PackagesListDataSourceTest, RegexFilter) {
  char buf[] =
      "com.test.one 1000 0 /data/user/0/com.test.one "
      "default:targetSdkVersion=10 none 0 10\n"
      "com.test.two 1001 0 /data/user/0/com.test.two "
      "default:targetSdkVersion=10 1065,3002 0 20\n"
      "com.example.three 1002 0 /data/user/0/com.example.three "
      "default:targetSdkVersion=10 1065,3002 0 30\n";

  auto pipe = base::Pipe::Create();
  PERFETTO_CHECK(write(pipe.wr.get(), buf, sizeof(buf) - 1) == sizeof(buf) - 1);
  pipe.wr.reset();
  auto fs = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
  pipe.rd.release();

  std::unordered_multimap<uint64_t, Package> packages;
  std::set<std::string> name_filter{};
  std::vector<std::string> regex_filter{"com\\.test\\..*"};

  ASSERT_FALSE(
      ParsePackagesListStream(packages, fs, name_filter, regex_filter));

  // Only the two com.test.* packages should match.
  EXPECT_EQ(packages.size(), 2lu);
  EXPECT_EQ(packages.find(1000)->second.name, "com.test.one");
  EXPECT_EQ(packages.find(1001)->second.name, "com.test.two");
}

TEST(PackagesListDataSourceTest, RegexFilterMultiplePatterns) {
  char buf[] =
      "com.test.one 1000 0 /data/user/0/com.test.one "
      "default:targetSdkVersion=10 none 0 10\n"
      "com.test.two 1001 0 /data/user/0/com.test.two "
      "default:targetSdkVersion=10 1065,3002 0 20\n"
      "com.example.three 1002 0 /data/user/0/com.example.three "
      "default:targetSdkVersion=10 1065,3002 0 30\n";

  auto pipe = base::Pipe::Create();
  PERFETTO_CHECK(write(pipe.wr.get(), buf, sizeof(buf) - 1) == sizeof(buf) - 1);
  pipe.wr.reset();
  auto fs = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
  pipe.rd.release();

  std::unordered_multimap<uint64_t, Package> packages;
  std::set<std::string> name_filter{};
  std::vector<std::string> regex_filter{"com\\.test\\.one",
                                        "com\\.example\\..*"};

  ASSERT_FALSE(
      ParsePackagesListStream(packages, fs, name_filter, regex_filter));

  // com.test.one and com.example.three should match.
  EXPECT_EQ(packages.size(), 2lu);
  EXPECT_EQ(packages.find(1000)->second.name, "com.test.one");
  EXPECT_EQ(packages.find(1002)->second.name, "com.example.three");
}

TEST(PackagesListDataSourceTest, ExactAndRegexFilterCombined) {
  char buf[] =
      "com.test.one 1000 0 /data/user/0/com.test.one "
      "default:targetSdkVersion=10 none 0 10\n"
      "com.test.two 1001 0 /data/user/0/com.test.two "
      "default:targetSdkVersion=10 1065,3002 0 20\n"
      "com.example.three 1002 0 /data/user/0/com.example.three "
      "default:targetSdkVersion=10 1065,3002 0 30\n";

  auto pipe = base::Pipe::Create();
  PERFETTO_CHECK(write(pipe.wr.get(), buf, sizeof(buf) - 1) == sizeof(buf) - 1);
  pipe.wr.reset();
  auto fs = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
  pipe.rd.release();

  std::unordered_multimap<uint64_t, Package> packages;
  std::set<std::string> name_filter{"com.test.one"};
  std::vector<std::string> regex_filter{"com\\.example\\..*"};

  ASSERT_FALSE(
      ParsePackagesListStream(packages, fs, name_filter, regex_filter));

  // com.test.one (exact) and com.example.three (regex) should match.
  EXPECT_EQ(packages.size(), 2lu);
  EXPECT_EQ(packages.find(1000)->second.name, "com.test.one");
  EXPECT_EQ(packages.find(1002)->second.name, "com.example.three");
}

TEST(PackagesListDataSourceTest, BothFiltersEmptyIncludesAll) {
  char buf[] =
      "com.test.one 1000 0 /data/user/0/com.test.one "
      "default:targetSdkVersion=10 none 0 10\n"
      "com.test.two 1001 0 /data/user/0/com.test.two "
      "default:targetSdkVersion=10 1065,3002 0 20\n";

  auto pipe = base::Pipe::Create();
  PERFETTO_CHECK(write(pipe.wr.get(), buf, sizeof(buf) - 1) == sizeof(buf) - 1);
  pipe.wr.reset();
  auto fs = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
  pipe.rd.release();

  std::unordered_multimap<uint64_t, Package> packages;
  std::set<std::string> name_filter{};
  std::vector<std::string> regex_filter{};

  ASSERT_FALSE(
      ParsePackagesListStream(packages, fs, name_filter, regex_filter));

  EXPECT_EQ(packages.size(), 2lu);
}

}  // namespace
}  // namespace perfetto
