// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "perf_option_parser.h"

#include "compat/string.h"
#include "compat/test.h"

namespace quipper {

TEST(PerfOptionParserTest, GoodRecord) {
  EXPECT_TRUE(ValidatePerfCommandLine({"perf", "record"}));
  EXPECT_TRUE(ValidatePerfCommandLine({"perf", "record", "-e", "cycles"}));
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "record", "-e", "-$;(*^:,.Non-sense!"}));  // let perf reject it.
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "record", "-a", "-e", "iTLB-misses", "-c", "1000003"}));
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "record", "-a", "-e", "cycles", "-g", "-c", "4000037"}));
  EXPECT_TRUE(ValidatePerfCommandLine({"perf", "record", "-a", "-e", "cycles",
                                       "-j", "any_call", "-c", "1000003"}));
}

TEST(PerfOptionParserTest, GoodStat) {
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "stat", "-a", "-e", "cpu/mem-loads/", "-e", "cpu/mem-stores/"}));
}

// Options that control the output format should only be specified by quipper.
TEST(PerfOptionParserTest, BadRecord_OutputOptions) {
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-e", "cycles", "-v"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "--verbose", "-e", "cycles"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-q", "-e", "cycles"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-e", "cycles", "--quiet"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-e", "cycles", "-m", "512"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "record", "-e", "cycles", "--mmap-pages", "512"}));
}

TEST(PerfOptionParserTest, BadRecord_BannedOptions) {
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-e", "cycles", "-D"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-e", "cycles", "-D", "10"}));
}

TEST(PerfOptionParserTest, GoodMemRecord) {
  EXPECT_TRUE(ValidatePerfCommandLine({"perf", "mem", "record"}));
  EXPECT_TRUE(
      ValidatePerfCommandLine({"perf", "mem", "record", "-e", "cycles"}));
  // let perf reject it.
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "mem", "record", "-e", "-$;(*^:,.Non-sense!"}));
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "mem", "record", "-a", "-e", "iTLB-misses", "-c", "1000003"}));
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "mem", "record", "-a", "-e", "cycles", "-g", "-c", "4000037"}));
  EXPECT_TRUE(
      ValidatePerfCommandLine({"perf", "mem", "record", "-a", "-e", "cycles",
                               "-j", "any_call", "-c", "1000003"}));

  // Check perf-mem options that come before "record".
  // See http://man7.org/linux/man-pages/man1/perf-mem.1.html
  EXPECT_TRUE(ValidatePerfCommandLine(
      {"perf", "mem", "-t", "load", "record", "-e", "-$;(*^:,.Non-sense!"}));
  EXPECT_TRUE(
      ValidatePerfCommandLine({"perf", "mem", "--type", "load,store", "record",
                               "-a", "-e", "iTLB-misses", "-c", "1000003"}));
  EXPECT_TRUE(
      ValidatePerfCommandLine({"perf", "mem", "-D", "-x", ":", "record", "-a",
                               "-e", "cycles", "-g", "-c", "4000037"}));
  EXPECT_TRUE(
      ValidatePerfCommandLine({"perf", "mem", "-C", "0,1", "record", "-a", "-e",
                               "cycles", "-j", "any_call", "-c", "1000003"}));
}

TEST(PerfOptionParserTest, BadMemRecord_OutputOptions) {
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "-t", "load,store", "record", "-e", "cycles", "-v"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "-t", "load", "record", "--verbose", "-e", "cycles"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "-D", "-x", ":", "record", "-q", "-e", "cycles"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "-C", "0,1", "record", "-e", "cycles", "--quiet"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "record", "-e", "cycles", "-m", "512"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "record", "-e", "cycles", "--mmap-pages", "512"}));

  // Try some bad perf-mem options.
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "-y", "-z", "record", "-e", "-$;(*^:,.Non-sense!"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "mem", "--blah", "record", "-a",
                                        "-e", "iTLB-misses", "-c", "1000003"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "mem", "--no-way", "record", "-a", "-e",
                               "cycles", "-g", "-c", "4000037"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "mem", "--danger", "record", "-a", "-e",
                               "cycles", "-j", "any_call", "-c", "1000003"}));
}

TEST(PerfOptionParserTest, BadMemRecord_BannedOptions) {
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "mem", "record", "-e", "cycles", "-D"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "record", "-e", "cycles", "-D", "10"}));
}

// Options that control the output format should only be specified by quipper.
TEST(PerfOptionParserTest, BadStat_OutputOptions) {
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "stat", "-e", "cycles", "-v"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "stat", "--verbose", "-e", "cycles"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "stat", "-q", "-e", "cycles"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "stat", "-e", "cycles", "--quiet"}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "stat", "-e", "cycles", "-x", "::"}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "stat", "-e", "cycles", "--field-separator", ","}));
}

TEST(PerfOptionParserTest, BadStat_BannedOptions) {
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "stat", "--pre", "rm -rf /"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "stat", "--post", "rm -rf /"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "stat", "-d"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "stat", "--log-fd", "4"}));
}

TEST(PerfOptionParserTest, DontAllowOtherPerfSubcommands) {
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "list"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "report"}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "trace"}));
}

// Unsafe command lines for either perf command.
TEST(PerfOptionParserTest, Ugly) {
  for (const string &subcmd : {"record", "stat", "mem"}) {
    EXPECT_FALSE(ValidatePerfCommandLine({"perf", subcmd, "rm", "-rf", "/"}));
    EXPECT_FALSE(
        ValidatePerfCommandLine({"perf", subcmd, "--", "rm", "-rf", "/"}));
    EXPECT_FALSE(ValidatePerfCommandLine(
        {"perf", subcmd, "-e", "cycles", "rm", "-rf", "/"}));
    EXPECT_FALSE(ValidatePerfCommandLine(
        {"perf", subcmd, "-e", "cycles", "-o", "/root/haha.perf.data"}));
  }
}

// Regression test for correct past-the-end iteration.
TEST(PerfOptionParserTest, ValueCommandAtEnd) {
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "record", "-c" /*missing value!*/}));
  EXPECT_FALSE(
      ValidatePerfCommandLine({"perf", "stat", "-e" /*missing value!*/}));
  EXPECT_FALSE(ValidatePerfCommandLine({"perf", "mem",
                                        "record"
                                        "-j" /*missing value!*/}));
  EXPECT_FALSE(ValidatePerfCommandLine(
      {"perf", "mem", "-t", "load", "record", "-e" /*missing value!*/}));
}

}  // namespace quipper