llvm-project/flang/unittests/Frontend/FrontendActionTest.cpp
peter klausler 3338ef93b0 [flang] Produce proper "preprocessor output" for -E option
Rename the current -E option to "-E -Xflang -fno-reformat".

Add a new Parsing::EmitPreprocessedSource() routine to convert the
cooked character stream output of the prescanner back to something
more closely resembling output from a traditional preprocessor;
call this new routine when -E appears.

The new -E output is suitable for use as fixed form Fortran source to
compilation by (one hopes) any Fortran compiler.  If the original
top-level source file had been free form source, the output will be
suitable for use as free form source as well; otherwise there may be
diagnostics about missing spaces if they were indeed absent in the
original fixed form source.

Unless the -P option appears, #line directives are interspersed
with the output (but be advised, f18 will ignore these if presented
with them in a later compilation).

An effort has been made to preserve original alphabetic character case
and source indentation.

Add -P and -fno-reformat to the new drivers.

Tweak test options to avoid confusion with prior -E output; use
-fno-reformat where needed, but prefer to keep -E, sometimes
in concert with -P, on most, updating expected results accordingly.

Differential Revision: https://reviews.llvm.org/D106727
2021-07-30 15:13:56 -07:00

165 lines
6.1 KiB
C++

//===- unittests/Frontend/FrontendActionTest.cpp FrontendAction tests-----===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "flang/Frontend/CompilerInstance.h"
#include "flang/Frontend/CompilerInvocation.h"
#include "flang/Frontend/FrontendOptions.h"
#include "flang/FrontendTool/Utils.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
using namespace Fortran::frontend;
namespace {
class FrontendActionTest : public ::testing::Test {
protected:
// AllSources (which is used to manage files inside every compiler
// instance), works with paths. So we need a filename and a path for the
// input file.
// TODO: We could use `-` for inputFilePath_, but then we'd need a way to
// write to stdin that's then read by AllSources. Ideally, AllSources should
// be capable of reading from any stream.
std::string inputFileName_;
std::string inputFilePath_;
// The output stream for the input file. Use this to populate the input.
std::unique_ptr<llvm::raw_fd_ostream> inputFileOs_;
std::error_code ec_;
CompilerInstance compInst_;
std::shared_ptr<CompilerInvocation> invocation_;
void SetUp() override {
// Generate a unique test file name.
const testing::TestInfo *const test_info =
testing::UnitTest::GetInstance()->current_test_info();
inputFileName_ = std::string(test_info->name()) + "_test-file.f90";
// Create the input file stream. Note that this stream is populated
// separately in every test (i.e. the input is test specific).
inputFileOs_ = std::make_unique<llvm::raw_fd_ostream>(
inputFileName_, ec_, llvm::sys::fs::OF_None);
if (ec_)
FAIL() << "Failed to create the input file";
// Get the path of the input file.
llvm::SmallString<256> cwd;
if (std::error_code ec_ = llvm::sys::fs::current_path(cwd))
FAIL() << "Failed to obtain the current working directory";
inputFilePath_ = cwd.c_str();
inputFilePath_ += "/" + inputFileName_;
// Prepare the compiler (CompilerInvocation + CompilerInstance)
compInst_.CreateDiagnostics();
invocation_ = std::make_shared<CompilerInvocation>();
compInst_.set_invocation(std::move(invocation_));
compInst_.frontendOpts().inputs_.push_back(
FrontendInputFile(inputFilePath_, Language::Fortran));
}
void TearDown() override {
// Clear the input file.
llvm::sys::fs::remove(inputFileName_);
// Clear the output files.
// Note that these tests use an output buffer (as opposed to an output
// file), hence there are no physical output files to delete and
// `EraseFiles` is set to `false`. Also, some actions (e.g.
// `ParseSyntaxOnly`) don't generated output. In such cases there's no
// output to clear and `ClearOutputFile` returns immediately.
compInst_.ClearOutputFiles(/*EraseFiles=*/false);
}
};
TEST_F(FrontendActionTest, TestInputOutput) {
// Populate the input file with the pre-defined input and flush it.
*(inputFileOs_) << "End Program arithmetic";
inputFileOs_.reset();
// Set-up the action kind.
compInst_.invocation().frontendOpts().programAction_ = InputOutputTest;
// Set-up the output stream. Using output buffer wrapped as an output
// stream, as opposed to an actual file (or a file descriptor).
llvm::SmallVector<char, 256> outputFileBuffer;
std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream(
new llvm::raw_svector_ostream(outputFileBuffer));
compInst_.set_outputStream(std::move(outputFileStream));
// Execute the action.
bool success = ExecuteCompilerInvocation(&compInst_);
// Validate the expected output.
EXPECT_TRUE(success);
EXPECT_TRUE(!outputFileBuffer.empty());
EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data())
.startswith("End Program arithmetic"));
}
TEST_F(FrontendActionTest, PrintPreprocessedInput) {
// Populate the input file with the pre-defined input and flush it.
*(inputFileOs_) << "#ifdef NEW\n"
<< " Program A \n"
<< "#else\n"
<< " Program B\n"
<< "#endif";
inputFileOs_.reset();
// Set-up the action kind.
compInst_.invocation().frontendOpts().programAction_ = PrintPreprocessedInput;
compInst_.invocation().preprocessorOpts().noReformat = true;
// Set-up the output stream. We are using output buffer wrapped as an output
// stream, as opposed to an actual file (or a file descriptor).
llvm::SmallVector<char, 256> outputFileBuffer;
std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream(
new llvm::raw_svector_ostream(outputFileBuffer));
compInst_.set_outputStream(std::move(outputFileStream));
// Execute the action.
bool success = ExecuteCompilerInvocation(&compInst_);
// Validate the expected output.
EXPECT_TRUE(success);
EXPECT_TRUE(!outputFileBuffer.empty());
EXPECT_TRUE(
llvm::StringRef(outputFileBuffer.data()).startswith("program b\n"));
}
TEST_F(FrontendActionTest, ParseSyntaxOnly) {
// Populate the input file with the pre-defined input and flush it.
*(inputFileOs_) << "IF (A > 0.0) IF (B < 0.0) A = LOG (A)\n"
<< "END";
inputFileOs_.reset();
// Set-up the action kind.
compInst_.invocation().frontendOpts().programAction_ = ParseSyntaxOnly;
// Set-up the output stream for the semantic diagnostics.
llvm::SmallVector<char, 256> outputDiagBuffer;
std::unique_ptr<llvm::raw_pwrite_stream> outputStream(
new llvm::raw_svector_ostream(outputDiagBuffer));
compInst_.set_semaOutputStream(std::move(outputStream));
// Execute the action.
bool success = ExecuteCompilerInvocation(&compInst_);
// Validate the expected output.
EXPECT_FALSE(success);
EXPECT_TRUE(!outputDiagBuffer.empty());
EXPECT_TRUE(
llvm::StringRef(outputDiagBuffer.data())
.contains(
":1:14: error: IF statement is not allowed in IF statement\n"));
}
} // namespace