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