//===- Tester.cpp ---------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines the Tester class used in the MLIR Reduce tool.
//
// A Tester object is passed as an argument to the reduction passes and it is
// used to run the interestingness testing script on the different generated
// reduced variants of the test case.
//
//===----------------------------------------------------------------------===//

#include "mlir/Reducer/Tester.h"
#include "mlir/IR/Verifier.h"
#include "llvm/Support/ToolOutputFile.h"

using namespace mlir;

Tester::Tester(StringRef scriptName, ArrayRef<std::string> scriptArgs)
    : testScript(scriptName), testScriptArgs(scriptArgs) {}

std::pair<Tester::Interestingness, size_t>
Tester::isInteresting(ModuleOp module) const {
  // The reduced module should always be vaild, or we may end up retaining the
  // error message by an invalid case. Besides, an invalid module may not be
  // able to print properly.
  if (failed(verify(module)))
    return std::make_pair(Interestingness::False, /*size=*/0);

  SmallString<128> filepath;
  int fd;

  // Print module to temporary file.
  std::error_code ec =
      llvm::sys::fs::createTemporaryFile("mlir-reduce", "mlir", fd, filepath);

  if (ec)
    llvm::report_fatal_error("Error making unique filename: " + ec.message());

  llvm::ToolOutputFile out(filepath, fd);
  module.print(out.os());
  out.os().close();

  if (out.os().has_error())
    llvm::report_fatal_error("Error emitting the IR to file '" + filepath);

  size_t size = out.os().tell();
  return std::make_pair(isInteresting(filepath), size);
}

/// Runs the interestingness testing script on a MLIR test case file. Returns
/// true if the interesting behavior is present in the test case or false
/// otherwise.
Tester::Interestingness Tester::isInteresting(StringRef testCase) const {
  std::vector<StringRef> testerArgs;
  testerArgs.push_back(testCase);

  for (const std::string &arg : testScriptArgs)
    testerArgs.push_back(arg);

  testerArgs.push_back(testCase);

  std::string errMsg;
  int result = llvm::sys::ExecuteAndWait(
      testScript, testerArgs, /*Env=*/None, /*Redirects=*/None,
      /*SecondsToWait=*/0, /*MemoryLimit=*/0, &errMsg);

  if (result < 0)
    llvm::report_fatal_error("Error running interestingness test: " + errMsg,
                             false);

  if (!result)
    return Interestingness::False;

  return Interestingness::True;
}
