/* Copyright 2018 The TensorFlow 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.
==============================================================================*/

// Test for the platform_strings.h header file.

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

#include <string>
#include <vector>

#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/init_main.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/platform_strings.h"

// Embed the platform strings in this binary.
TF_PLATFORM_STRINGS()

// A vector of strings.
typedef std::vector<std::string> string_vec;

// Append to *found the strings within the named file with the platform_strings
// magic prefix, and return true; or return false on error.

// Print the platform strings embedded in the binary file_name and return 0,
// on on error return 2.
static int PrintStrings(const std::string file_name) {
  int rc = 0;
  string_vec str;
  if (!tensorflow::GetPlatformStrings(file_name, &str)) {
    for (int i = 0; i != str.size(); i++) {
      printf("%s\n", str[i].c_str());
    }
  } else {
    perror(file_name.c_str());
    rc = 2;
  }
  return rc;
}

// Return whether str[] conatins a string with prefix "macro_name="; if so,
// set *pvalue to the suffix.
static bool GetValue(const string_vec &str, const std::string &macro_name,
                     std::string *pvalue) {
  std::string nam_eq = macro_name + "=";
  int i = 0;
  while (i != str.size() && !absl::StartsWith(str[i], nam_eq)) {
    i++;
  }
  bool found = (i != str.size());
  if (found) {
    *pvalue = str[i].substr(nam_eq.size());
  }
  return found;
}

// If macro_name[] is not equal to value[], check that str[] contains the
// string "macro_name=value".  Otherwise, check that str[] does not contain any
// string starting with macro_name=".
static void CheckStr(const string_vec &str, const std::string &macro_name,
                     const std::string &value) {
  std::string value_from_str;
  if (GetValue(str, macro_name, &value_from_str)) {
    if (value != value_from_str) {
      // Output everything found, to aid debugging.
      LOG(ERROR) << "===== value=" << value
                 << "  value_from_str=" << value_from_str;
      for (int i = 0; i != str.size(); i++) {
        LOG(ERROR) << "% " << str[i];
      }
      LOG(ERROR) << "=====";
    }
    CHECK_EQ(value, value_from_str) << " " << macro_name << ": bad value";
  } else {
    // If the string is not found, we expect value to be macro_name.
    if (value != macro_name) {
      // Output everything found, to aid debugging.
      LOG(ERROR) << "===== value=" << value << "  macro_name=" << macro_name;
      for (int i = 0; i != str.size(); i++) {
        LOG(ERROR) << "% " << str[i];
      }
      LOG(ERROR) << "=====";
    }
    CHECK_EQ(value, macro_name) << " " << macro_name << ": not found in binary";
  }
}

// Helper for AS_STR(), below, to perform macro expansion.
#define AS_STR_1_(x) #x

// Yield x after macro expansion as a nul-terminated constant string.
#define AS_STR(x) AS_STR_1_(x)

// Run the test, and return 0 on success, 2 otherwise.
static int RunTest(const std::string &binary_name) {
  int rc = 0;
  string_vec str;

  if (!tensorflow::GetPlatformStrings(binary_name, &str)) {
    CheckStr(str, "__linux__", AS_STR(__linux__));
    CheckStr(str, "_WIN32", AS_STR(_WIN32));
    CheckStr(str, "__APPLE__", AS_STR(__APPLE__));
    CheckStr(str, "__x86_64__", AS_STR(__x86_64__));
    CheckStr(str, "__aarch64__", AS_STR(__aarch64__));
    CheckStr(str, "__powerpc64__", AS_STR(__powerpc64__));
    CheckStr(str, "TF_PLAT_STR_VERSION", TF_PLAT_STR_VERSION_);
  } else {
    perror(binary_name.c_str());
    rc = 2;
  }

  return rc;
}

int main(int argc, char *argv[]) {
  tensorflow::Env *env = tensorflow::Env::Default();
  static const char usage[] = "usage: platform_strings_test [file...]";
  int rc = 0;
  tensorflow::port::InitMain(usage, &argc, &argv);
  if (argc == 1) {
    printf("rc=%d\n", PrintStrings(env->GetExecutablePath()));
    rc = RunTest(env->GetExecutablePath());
  } else {
    for (int argn = 1; argn != argc; argn++) {
      rc |= PrintStrings(argv[argn]);
    }
  }
  return rc;
}
