/*
 * Copyright (C) 2011 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 "base/flags.h"

#include <optional>

#include "android-base/properties.h"
#include "common_runtime_test.h"


namespace art {

// Tests may be run in parallel so this helper class ensures
// that we generate a unique test flag each time to avoid
// tests stepping on each other
class TestFlag {
 public:
  // Takes control of the tmp_file pointer.
  TestFlag(ScratchFile* tmp_file, FlagType flag_type) {
    tmp_file_.reset(tmp_file);

    std::string tmp_name = tmp_file_->GetFilename();
    size_t tmp_last_slash = tmp_name.rfind('/');
    tmp_name = tmp_name.substr(tmp_last_slash + 1);

    flag_name_ = "art.gtest." + tmp_name;
    system_prop_name_ = "dalvik.vm." + flag_name_;
    server_name_ = "persist.device_config.runtime_native." + flag_name_;
    cmd_line_name_ = flag_name_;
    std::replace(cmd_line_name_.begin(), cmd_line_name_.end(), '.', '-');

    flag_.reset(new Flag<int>(flag_name_, /*default_value=*/ 42, flag_type));
  }

  void AssertCmdlineValue(bool has_value, int expected) {
    ASSERT_EQ(flag_->from_command_line_.has_value(), has_value);
    if (has_value) {
      ASSERT_EQ(flag_->from_command_line_.value(), expected);
    }
  }

  void AssertSysPropValue(bool has_value, int expected) {
    ASSERT_EQ(flag_->from_system_property_.has_value(), has_value);
    if (has_value) {
      ASSERT_EQ(flag_->from_system_property_.value(), expected);
    }
  }

  void AssertServerSettingValue(bool has_value, int expected) {
    ASSERT_EQ(flag_->from_server_setting_.has_value(), has_value);
    if (has_value) {
      ASSERT_EQ(flag_->from_server_setting_.value(), expected);
    }
  }

  void AssertDefaultValue(int expected) {
    ASSERT_EQ(flag_->default_, expected);
  }

  int Value() {
    return (*flag_)();
  }

  std::string SystemProperty() const {
    return system_prop_name_;
  }

  std::string ServerSetting() const {
    return server_name_;
  }

  std::string CmdLineName() const {
    return cmd_line_name_;
  }

 private:
  std::unique_ptr<ScratchFile> tmp_file_;
  std::unique_ptr<Flag<int>> flag_;
  std::string flag_name_;
  std::string cmd_line_name_;
  std::string system_prop_name_;
  std::string server_name_;
};

class FlagsTests : public CommonRuntimeTest {
 protected:
  FlagsTests() {
    this->use_boot_image_ = true;  // Make the Runtime creation cheaper.
  }

  // We need to initialize the flag after the ScratchDir is created
  // but before we configure the runtime options (so that we can get
  // the right name for the config).
  //
  // So we do it in SetUpRuntimeOptions.
  virtual void SetUpRuntimeOptions(RuntimeOptions* options) {
    test_flag_.reset(new TestFlag(new ScratchFile(), FlagType::kDeviceConfig));
    CommonRuntimeTest::SetUpRuntimeOptions(options);
  }

  virtual void TearDown() {
    test_flag_ = nullptr;
    CommonRuntimeTest::TearDown();
  }

  std::unique_ptr<TestFlag> test_flag_;
};

class FlagsTestsWithCmdLineBase : public FlagsTests {
 public:
  explicit FlagsTestsWithCmdLineBase(FlagType type) : flag_type_(type) {
  }

 protected:
  virtual void TearDown() {
    android::base::SetProperty(test_flag_->SystemProperty(), "");
    android::base::SetProperty(test_flag_->ServerSetting(), "");
    FlagsTests::TearDown();
  }

  virtual void SetUpRuntimeOptions(RuntimeOptions* options) {
    test_flag_.reset(new TestFlag(new ScratchFile(), flag_type_));
    std::string option = "-X" + test_flag_->CmdLineName() + ":1";
    options->emplace_back(option.c_str(), nullptr);
  }

  FlagType flag_type_;
};

class FlagsTestsWithCmdLine : public FlagsTestsWithCmdLineBase {
 public:
  FlagsTestsWithCmdLine() : FlagsTestsWithCmdLineBase(FlagType::kDeviceConfig) {
  }
};

class FlagsTestsCmdLineOnly : public FlagsTestsWithCmdLineBase {
 public:
  FlagsTestsCmdLineOnly() : FlagsTestsWithCmdLineBase(FlagType::kCmdlineOnly) {
  }
};

// Validate that when no flag is set, the default is taken and none of the other
// locations are populated
TEST_F(FlagsTests, ValidateDefaultValue) {
  FlagBase::ReloadAllFlags("test");

  test_flag_->AssertCmdlineValue(false, 1);
  test_flag_->AssertSysPropValue(false, 2);
  test_flag_->AssertServerSettingValue(false, 3);
  test_flag_->AssertDefaultValue(42);

  ASSERT_EQ(test_flag_->Value(), 42);
}

// Validate that the server side config is picked when it is set.
TEST_F(FlagsTestsWithCmdLine, FlagsTestsGetValueServerSetting) {
  // On older releases (e.g. nougat) the system properties have very strict
  // limitations (e.g. for length) and setting the properties will fail.
  // On modern platforms this should not be the case, so condition the test
  // based on the success of setting the properties.
  if (!android::base::SetProperty(test_flag_->SystemProperty(), "2")) {
    LOG(ERROR) << "Release does not support property setting, skipping test: "
        << test_flag_->SystemProperty();
    return;
  }

  if (!android::base::SetProperty(test_flag_->ServerSetting(), "3")) {
    LOG(ERROR) << "Release does not support property setting, skipping test: "
        << test_flag_->ServerSetting();
    return;
  }

  FlagBase::ReloadAllFlags("test");

  test_flag_->AssertCmdlineValue(true, 1);
  test_flag_->AssertSysPropValue(true, 2);
  test_flag_->AssertServerSettingValue(true, 3);
  test_flag_->AssertDefaultValue(42);

  ASSERT_EQ(test_flag_->Value(), 3);
}

// Validate that the system property value is picked when the server one is not set.
TEST_F(FlagsTestsWithCmdLine, FlagsTestsGetValueSysProperty) {
  if (!android::base::SetProperty(test_flag_->SystemProperty(), "2")) {
    LOG(ERROR) << "Release does not support property setting, skipping test: "
        << test_flag_->SystemProperty();
    return;
  }

  FlagBase::ReloadAllFlags("test");

  test_flag_->AssertCmdlineValue(true, 1);
  test_flag_->AssertSysPropValue(true, 2);
  test_flag_->AssertServerSettingValue(false, 3);
  test_flag_->AssertDefaultValue(42);

  ASSERT_EQ(test_flag_->Value(), 2);
}

// Validate that the cmdline value is picked when no properties are set.
TEST_F(FlagsTestsWithCmdLine, FlagsTestsGetValueCmdline) {
  FlagBase::ReloadAllFlags("test");

  test_flag_->AssertCmdlineValue(true, 1);
  test_flag_->AssertSysPropValue(false, 2);
  test_flag_->AssertServerSettingValue(false, 3);
  test_flag_->AssertDefaultValue(42);

  ASSERT_EQ(test_flag_->Value(), 1);
}

// Validate that cmdline only flags don't read system properties.
TEST_F(FlagsTestsCmdLineOnly, CmdlineOnlyFlags) {
  if (!android::base::SetProperty(test_flag_->SystemProperty(), "2")) {
    LOG(ERROR) << "Release does not support property setting, skipping test: "
        << test_flag_->SystemProperty();
    return;
  }

  if (!android::base::SetProperty(test_flag_->ServerSetting(), "3")) {
    LOG(ERROR) << "Release does not support property setting, skipping test: "
        << test_flag_->ServerSetting();
    return;
  }

  FlagBase::ReloadAllFlags("test");

  test_flag_->AssertCmdlineValue(true, 1);
  test_flag_->AssertSysPropValue(false, 2);
  test_flag_->AssertServerSettingValue(false, 3);
  test_flag_->AssertDefaultValue(42);

  ASSERT_EQ(test_flag_->Value(), 1);
}

}  // namespace art
