1 //===- unittests/Frontend/FrontendActionTest.cpp FrontendAction tests-----===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "flang/Frontend/CompilerInstance.h" 10 #include "flang/Frontend/CompilerInvocation.h" 11 #include "flang/Frontend/FrontendOptions.h" 12 #include "flang/FrontendTool/Utils.h" 13 #include "llvm/Support/FileSystem.h" 14 #include "llvm/Support/raw_ostream.h" 15 16 #include "gtest/gtest.h" 17 18 using namespace Fortran::frontend; 19 20 namespace { 21 22 class FrontendActionTest : public ::testing::Test { 23 protected: 24 // AllSources (which is used to manage files inside every compiler 25 // instance), works with paths. So we need a filename and a path for the 26 // input file. 27 // TODO: We could use `-` for inputFilePath_, but then we'd need a way to 28 // write to stdin that's then read by AllSources. Ideally, AllSources should 29 // be capable of reading from any stream. 30 std::string inputFileName_; 31 std::string inputFilePath_; 32 // The output stream for the input file. Use this to populate the input. 33 std::unique_ptr<llvm::raw_fd_ostream> inputFileOs_; 34 35 std::error_code ec_; 36 37 CompilerInstance compInst_; 38 std::shared_ptr<CompilerInvocation> invocation_; 39 40 void SetUp() override { 41 // Generate a unique test file name. 42 const testing::TestInfo *const test_info = 43 testing::UnitTest::GetInstance()->current_test_info(); 44 inputFileName_ = std::string(test_info->name()) + "_test-file.f90"; 45 46 // Create the input file stream. Note that this stream is populated 47 // separately in every test (i.e. the input is test specific). 48 inputFileOs_ = std::make_unique<llvm::raw_fd_ostream>( 49 inputFileName_, ec_, llvm::sys::fs::OF_None); 50 if (ec_) 51 FAIL() << "Failed to create the input file"; 52 53 // Get the path of the input file. 54 llvm::SmallString<256> cwd; 55 if (std::error_code ec_ = llvm::sys::fs::current_path(cwd)) 56 FAIL() << "Failed to obtain the current working directory"; 57 inputFilePath_ = cwd.c_str(); 58 inputFilePath_ += "/" + inputFileName_; 59 60 // Prepare the compiler (CompilerInvocation + CompilerInstance) 61 compInst_.CreateDiagnostics(); 62 invocation_ = std::make_shared<CompilerInvocation>(); 63 64 compInst_.set_invocation(std::move(invocation_)); 65 compInst_.frontendOpts().inputs.push_back( 66 FrontendInputFile(inputFilePath_, Language::Fortran)); 67 } 68 69 void TearDown() override { 70 // Clear the input file. 71 llvm::sys::fs::remove(inputFileName_); 72 73 // Clear the output files. 74 // Note that these tests use an output buffer (as opposed to an output 75 // file), hence there are no physical output files to delete and 76 // `EraseFiles` is set to `false`. Also, some actions (e.g. 77 // `ParseSyntaxOnly`) don't generated output. In such cases there's no 78 // output to clear and `ClearOutputFile` returns immediately. 79 compInst_.ClearOutputFiles(/*EraseFiles=*/false); 80 } 81 }; 82 83 TEST_F(FrontendActionTest, TestInputOutput) { 84 // Populate the input file with the pre-defined input and flush it. 85 *(inputFileOs_) << "End Program arithmetic"; 86 inputFileOs_.reset(); 87 88 // Set-up the action kind. 89 compInst_.invocation().frontendOpts().programAction = InputOutputTest; 90 91 // Set-up the output stream. Using output buffer wrapped as an output 92 // stream, as opposed to an actual file (or a file descriptor). 93 llvm::SmallVector<char, 256> outputFileBuffer; 94 std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream( 95 new llvm::raw_svector_ostream(outputFileBuffer)); 96 compInst_.set_outputStream(std::move(outputFileStream)); 97 98 // Execute the action. 99 bool success = ExecuteCompilerInvocation(&compInst_); 100 101 // Validate the expected output. 102 EXPECT_TRUE(success); 103 EXPECT_TRUE(!outputFileBuffer.empty()); 104 EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()) 105 .startswith("End Program arithmetic")); 106 } 107 108 TEST_F(FrontendActionTest, PrintPreprocessedInput) { 109 // Populate the input file with the pre-defined input and flush it. 110 *(inputFileOs_) << "#ifdef NEW\n" 111 << " Program A \n" 112 << "#else\n" 113 << " Program B\n" 114 << "#endif"; 115 inputFileOs_.reset(); 116 117 // Set-up the action kind. 118 compInst_.invocation().frontendOpts().programAction = PrintPreprocessedInput; 119 compInst_.invocation().preprocessorOpts().noReformat = true; 120 121 // Set-up the output stream. We are using output buffer wrapped as an output 122 // stream, as opposed to an actual file (or a file descriptor). 123 llvm::SmallVector<char, 256> outputFileBuffer; 124 std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream( 125 new llvm::raw_svector_ostream(outputFileBuffer)); 126 compInst_.set_outputStream(std::move(outputFileStream)); 127 128 // Execute the action. 129 bool success = ExecuteCompilerInvocation(&compInst_); 130 131 // Validate the expected output. 132 EXPECT_TRUE(success); 133 EXPECT_TRUE(!outputFileBuffer.empty()); 134 EXPECT_TRUE( 135 llvm::StringRef(outputFileBuffer.data()).startswith("program b\n")); 136 } 137 138 TEST_F(FrontendActionTest, ParseSyntaxOnly) { 139 // Populate the input file with the pre-defined input and flush it. 140 *(inputFileOs_) << "IF (A > 0.0) IF (B < 0.0) A = LOG (A)\n" 141 << "END"; 142 inputFileOs_.reset(); 143 144 // Set-up the action kind. 145 compInst_.invocation().frontendOpts().programAction = ParseSyntaxOnly; 146 147 // Set-up the output stream for the semantic diagnostics. 148 llvm::SmallVector<char, 256> outputDiagBuffer; 149 std::unique_ptr<llvm::raw_pwrite_stream> outputStream( 150 new llvm::raw_svector_ostream(outputDiagBuffer)); 151 compInst_.set_semaOutputStream(std::move(outputStream)); 152 153 // Execute the action. 154 bool success = ExecuteCompilerInvocation(&compInst_); 155 156 // Validate the expected output. 157 EXPECT_FALSE(success); 158 EXPECT_TRUE(!outputDiagBuffer.empty()); 159 EXPECT_TRUE( 160 llvm::StringRef(outputDiagBuffer.data()) 161 .contains( 162 ":1:14: error: IF statement is not allowed in IF statement\n")); 163 } 164 } // namespace 165