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/TargetSelect.h"
15 #include "llvm/Support/raw_ostream.h"
16 
17 #include "gtest/gtest.h"
18 
19 using namespace Fortran::frontend;
20 
21 namespace {
22 
23 class FrontendActionTest : public ::testing::Test {
24 protected:
25   // AllSources (which is used to manage files inside every compiler
26   // instance), works with paths. So we need a filename and a path for the
27   // input file.
28   // TODO: We could use `-` for inputFilePath_, but then we'd need a way to
29   // write to stdin that's then read by AllSources. Ideally, AllSources should
30   // be capable of reading from any stream.
31   std::string inputFileName_;
32   std::string inputFilePath_;
33   // The output stream for the input file. Use this to populate the input.
34   std::unique_ptr<llvm::raw_fd_ostream> inputFileOs_;
35 
36   std::error_code ec_;
37 
38   CompilerInstance compInst_;
39   std::shared_ptr<CompilerInvocation> invocation_;
40 
41   void SetUp() override {
42     // Generate a unique test file name.
43     const testing::TestInfo *const test_info =
44         testing::UnitTest::GetInstance()->current_test_info();
45     inputFileName_ = std::string(test_info->name()) + "_test-file.f90";
46 
47     // Create the input file stream. Note that this stream is populated
48     // separately in every test (i.e. the input is test specific).
49     inputFileOs_ = std::make_unique<llvm::raw_fd_ostream>(
50         inputFileName_, ec_, llvm::sys::fs::OF_None);
51     if (ec_)
52       FAIL() << "Failed to create the input file";
53 
54     // Get the path of the input file.
55     llvm::SmallString<256> cwd;
56     if (std::error_code ec_ = llvm::sys::fs::current_path(cwd))
57       FAIL() << "Failed to obtain the current working directory";
58     inputFilePath_ = cwd.c_str();
59     inputFilePath_ += "/" + inputFileName_;
60 
61     // Prepare the compiler (CompilerInvocation + CompilerInstance)
62     compInst_.CreateDiagnostics();
63     invocation_ = std::make_shared<CompilerInvocation>();
64 
65     compInst_.set_invocation(std::move(invocation_));
66     compInst_.frontendOpts().inputs.push_back(
67         FrontendInputFile(inputFilePath_, Language::Fortran));
68   }
69 
70   void TearDown() override {
71     // Clear the input file.
72     llvm::sys::fs::remove(inputFileName_);
73 
74     // Clear the output files.
75     // Note that these tests use an output buffer (as opposed to an output
76     // file), hence there are no physical output files to delete and
77     // `EraseFiles` is set to `false`. Also, some actions (e.g.
78     // `ParseSyntaxOnly`) don't generated output. In such cases there's no
79     // output to clear and `ClearOutputFile` returns immediately.
80     compInst_.ClearOutputFiles(/*EraseFiles=*/false);
81   }
82 };
83 
84 TEST_F(FrontendActionTest, TestInputOutput) {
85   // Populate the input file with the pre-defined input and flush it.
86   *(inputFileOs_) << "End Program arithmetic";
87   inputFileOs_.reset();
88 
89   // Set-up the action kind.
90   compInst_.invocation().frontendOpts().programAction = InputOutputTest;
91 
92   // Set-up the output stream. Using output buffer wrapped as an output
93   // stream, as opposed to an actual file (or a file descriptor).
94   llvm::SmallVector<char, 256> outputFileBuffer;
95   std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream(
96       new llvm::raw_svector_ostream(outputFileBuffer));
97   compInst_.set_outputStream(std::move(outputFileStream));
98 
99   // Execute the action.
100   bool success = ExecuteCompilerInvocation(&compInst_);
101 
102   // Validate the expected output.
103   EXPECT_TRUE(success);
104   EXPECT_TRUE(!outputFileBuffer.empty());
105   EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data())
106                   .startswith("End Program arithmetic"));
107 }
108 
109 TEST_F(FrontendActionTest, PrintPreprocessedInput) {
110   // Populate the input file with the pre-defined input and flush it.
111   *(inputFileOs_) << "#ifdef NEW\n"
112                   << "  Program A \n"
113                   << "#else\n"
114                   << "  Program B\n"
115                   << "#endif";
116   inputFileOs_.reset();
117 
118   // Set-up the action kind.
119   compInst_.invocation().frontendOpts().programAction = PrintPreprocessedInput;
120   compInst_.invocation().preprocessorOpts().noReformat = true;
121 
122   // Set-up the output stream. We are using output buffer wrapped as an output
123   // stream, as opposed to an actual file (or a file descriptor).
124   llvm::SmallVector<char, 256> outputFileBuffer;
125   std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream(
126       new llvm::raw_svector_ostream(outputFileBuffer));
127   compInst_.set_outputStream(std::move(outputFileStream));
128 
129   // Execute the action.
130   bool success = ExecuteCompilerInvocation(&compInst_);
131 
132   // Validate the expected output.
133   EXPECT_TRUE(success);
134   EXPECT_TRUE(!outputFileBuffer.empty());
135   EXPECT_TRUE(
136       llvm::StringRef(outputFileBuffer.data()).startswith("program b\n"));
137 }
138 
139 TEST_F(FrontendActionTest, ParseSyntaxOnly) {
140   // Populate the input file with the pre-defined input and flush it.
141   *(inputFileOs_) << "IF (A > 0.0) IF (B < 0.0) A = LOG (A)\n"
142                   << "END";
143   inputFileOs_.reset();
144 
145   // Set-up the action kind.
146   compInst_.invocation().frontendOpts().programAction = ParseSyntaxOnly;
147 
148   // Set-up the output stream for the semantic diagnostics.
149   llvm::SmallVector<char, 256> outputDiagBuffer;
150   std::unique_ptr<llvm::raw_pwrite_stream> outputStream(
151       new llvm::raw_svector_ostream(outputDiagBuffer));
152   compInst_.set_semaOutputStream(std::move(outputStream));
153 
154   // Execute the action.
155   bool success = ExecuteCompilerInvocation(&compInst_);
156 
157   // Validate the expected output.
158   EXPECT_FALSE(success);
159   EXPECT_TRUE(!outputDiagBuffer.empty());
160   EXPECT_TRUE(
161       llvm::StringRef(outputDiagBuffer.data())
162           .contains(
163               ":1:14: error: IF statement is not allowed in IF statement\n"));
164 }
165 
166 TEST_F(FrontendActionTest, EmitLLVM) {
167   // Populate the input file with the pre-defined input and flush it.
168   *(inputFileOs_) << "end program";
169   inputFileOs_.reset();
170 
171   // Set-up the action kind.
172   compInst_.invocation().frontendOpts().programAction = EmitLLVM;
173   compInst_.invocation().preprocessorOpts().noReformat = true;
174 
175   // Set-up the output stream. We are using output buffer wrapped as an output
176   // stream, as opposed to an actual file (or a file descriptor).
177   llvm::SmallVector<char> outputFileBuffer;
178   std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream(
179       new llvm::raw_svector_ostream(outputFileBuffer));
180   compInst_.set_outputStream(std::move(outputFileStream));
181 
182   // Execute the action.
183   bool success = ExecuteCompilerInvocation(&compInst_);
184 
185   // Validate the expected output.
186   EXPECT_TRUE(success);
187   EXPECT_TRUE(!outputFileBuffer.empty());
188 
189   EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data())
190                   .contains("define void @_QQmain()"));
191 }
192 
193 TEST_F(FrontendActionTest, EmitAsm) {
194   // Populate the input file with the pre-defined input and flush it.
195   *(inputFileOs_) << "end program";
196   inputFileOs_.reset();
197 
198   // Set-up the action kind.
199   compInst_.invocation().frontendOpts().programAction = EmitAssembly;
200   compInst_.invocation().preprocessorOpts().noReformat = true;
201 
202   // Initialise LLVM backend
203   llvm::InitializeAllTargets();
204   llvm::InitializeAllTargetMCs();
205   llvm::InitializeAllAsmPrinters();
206 
207   // Set-up the output stream. We are using output buffer wrapped as an output
208   // stream, as opposed to an actual file (or a file descriptor).
209   llvm::SmallVector<char, 256> outputFileBuffer;
210   std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream(
211       new llvm::raw_svector_ostream(outputFileBuffer));
212   compInst_.set_outputStream(std::move(outputFileStream));
213 
214   // Execute the action.
215   bool success = ExecuteCompilerInvocation(&compInst_);
216 
217   // Validate the expected output.
218   EXPECT_TRUE(success);
219   EXPECT_TRUE(!outputFileBuffer.empty());
220 
221   EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()).contains("_QQmain"));
222 }
223 } // namespace
224