text: // Copyright 2017 The Bazel Authors. All rights reserved.
  //
  // 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 <cstdlib>
  #include <fstream>
  #include <iostream>
  #include <memory>
  #include <vector>
  
  #include "src/main/cpp/util/strings.h"
  #include "src/tools/launcher/util/data_parser.h"
  #include "gtest/gtest.h"
  #include "src/tools/launcher/util/launcher_util.h"
  
  namespace bazel {
  namespace launcher {
  
  using std::getenv;
  using std::ios;
  using std::make_unique;
  using std::ofstream;
  using std::pair;
  using std::string;
  using std::unique_ptr;
  using std::vector;
  
  class LaunchDataParserTest : public ::testing::Test {
   protected:
    LaunchDataParserTest() {}
  
    virtual ~LaunchDataParserTest() {}
  
    void SetUp() override {
      char* tmpdir = getenv("TEST_TMPDIR");
      if (tmpdir != NULL) {
        test_tmpdir = string(tmpdir);
      } else {
        tmpdir = getenv("TEMP");
        ASSERT_FALSE(tmpdir == NULL);
        test_tmpdir = string(tmpdir);
      }
    }
  
    void TearDown() override {}
  
    static void WriteBinaryFileWithList(const string& binary_file,
                                        const vector<string>& launch_info) {
      ofstream binary_file_stream(binary_file, ios::out | ios::binary);
  
      int64_t data_size = 0;
      for (auto const& entry : launch_info) {
        binary_file_stream << entry;
        binary_file_stream << '\0';
        data_size += entry.length() + 1;
      }
  
      binary_file_stream.write(reinterpret_cast<char*>(&data_size),
                               sizeof(data_size));
    }
  
    static void WriteBinaryFileWithMap(
        const string& binary_file,
        const vector<pair<string, string>>& launch_info) {
      ofstream binary_file_stream(binary_file, ios::out | ios::binary);
  
      int64_t data_size = 0;
      for (auto const& entry : launch_info) {
        binary_file_stream << entry.first;
        binary_file_stream.put('=');
        binary_file_stream << entry.second;
        binary_file_stream.put('\0');
        data_size += entry.first.length() + entry.second.length() + 2;
      }
  
      binary_file_stream.write(reinterpret_cast<char*>(&data_size),
                               sizeof(data_size));
    }
  
    static bool ParseBinaryFile(
        const string& binary_file,
        LaunchDataParser::LaunchInfo* parsed_launch_info) {
      if (LaunchDataParser::GetLaunchInfo(
          blaze_util::CstringToWstring(binary_file), parsed_launch_info)) {
        return true;
      }
      exit(-1);
    }
  
    string GetLaunchInfo(const string& key) const {
      auto item = parsed_launch_info->find(key);
      if (item == parsed_launch_info->end()) {
        return "Cannot find key: " + key;
      }
      return blaze_util::WstringToString(item->second);
    }
  
    string test_tmpdir;
    unique_ptr<LaunchDataParser::LaunchInfo> parsed_launch_info;
  };
  
  TEST_F(LaunchDataParserTest, GetLaunchInfoTest) {
    vector<pair<string, string>> launch_info = {
        {"binary_type", "Bash"},
        {"workspace_name", "__main__"},
        {"bash_bin_path", "C:\\foo\\bar\\bash.exe"},
        {"bash_main_file", "./bazel-bin/foo/bar/bin.sh"},
        {"empty_value_key", ""},
    };
  
    string binary_file = test_tmpdir + "/binary_file";
    WriteBinaryFileWithMap(binary_file, launch_info);
  
    parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>();
    ASSERT_TRUE(ParseBinaryFile(binary_file, parsed_launch_info.get()));
  
    for (auto const& entry : launch_info) {
      ASSERT_EQ(entry.second, GetLaunchInfo(entry.first));
    }
    ASSERT_EQ(GetLaunchInfo("no_such_key"), "Cannot find key: no_such_key");
  }
  
  TEST_F(LaunchDataParserTest, EmptyLaunchInfoTest) {
    string binary_file = test_tmpdir + "/empty_binary_file";
    WriteBinaryFileWithMap(binary_file, {});
  
    parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>();
    // ASSERT_DEATH requires TEMP environment variable to be set.
    // Otherwise, it will try to write to C:/Windows, then fails.
    // A workaround in Bazel is to use --action_env to set TEMP.
    ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()),
                 "LAUNCHER ERROR: No data appended, cannot launch anything!");
  }
  
  TEST_F(LaunchDataParserTest, DuplicatedLaunchInfoTest) {
    string binary_file = test_tmpdir + "/duplicated_binary_file";
    WriteBinaryFileWithMap(binary_file, {
                                            {"foo", "bar1"},
                                            {"foo", "bar2"},
                                        });
  
    parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>();
    // ASSERT_DEATH requires TEMP environment variable to be set.
    // Otherwise, it will try to write to C:/Windows, then fails.
    // A workaround in Bazel is to use --action_env to set TEMP.
    ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()),
                 "LAUNCHER ERROR: Duplicated launch info key: foo");
  }
  
  TEST_F(LaunchDataParserTest, EmptyKeyLaunchInfoTest) {
    string binary_file = test_tmpdir + "/empty_key_binary_file";
    WriteBinaryFileWithMap(binary_file, {
                                            {"foo", "bar"},
                                            {"", "bar2"},
                                        });
  
    parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>();
    // ASSERT_DEATH requires TEMP environment variable to be set.
    // Otherwise, it will try to write to C:/Windows, then fails.
    // A workaround in Bazel is to use --action_env to set TEMP.
    ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()),
                 "LAUNCHER ERROR: Key is empty string in line: =bar2");
  }
  
  TEST_F(LaunchDataParserTest, NoEqualSignLaunchInfoTest) {
    string binary_file = test_tmpdir + "/no_equal_binary_file";
    WriteBinaryFileWithList(binary_file, {
                                             "foo1=bar1",
                                             "foo2bar2",
                                         });
  
    parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>();
    // ASSERT_DEATH requires TEMP environment variable to be set.
    // Otherwise, it will try to write to C:/Windows, then fails.
    // A workaround in Bazel is to use --action_env to set TEMP.
    ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()),
                 "LAUNCHER ERROR: Cannot find equal symbol in line: foo2bar2");
  }
  
  }  // namespace launcher
  }  // namespace bazel
  