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 | #include "llvm/TargetParser/Host.h" |
17 | #include "llvm/TargetParser/Triple.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> invoc; |
42 | |
43 | void SetUp() override { |
44 | // Generate a unique test file name. |
45 | const testing::TestInfo *const testInfo = |
46 | testing::UnitTest::GetInstance()->current_test_info(); |
47 | inputFileName = std::string(testInfo->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 | args&: inputFileName, args&: ec, args: 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(result&: 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 | invoc = std::make_shared<CompilerInvocation>(); |
66 | |
67 | // Set-up default target triple and initialize LLVM Targets so that the |
68 | // target data layout can be passed to the frontend. |
69 | invoc->getTargetOpts().triple = |
70 | llvm::Triple::normalize(llvm::sys::getDefaultTargetTriple()); |
71 | llvm::InitializeAllTargets(); |
72 | llvm::InitializeAllTargetMCs(); |
73 | |
74 | compInst.setInvocation(std::move(invoc)); |
75 | compInst.getFrontendOpts().inputs.push_back( |
76 | FrontendInputFile(inputFilePath, Language::Fortran)); |
77 | } |
78 | |
79 | void TearDown() override { |
80 | // Clear the input file. |
81 | llvm::sys::fs::remove(path: inputFileName); |
82 | |
83 | // Clear the output files. |
84 | // Note that these tests use an output buffer (as opposed to an output |
85 | // file), hence there are no physical output files to delete and |
86 | // `EraseFiles` is set to `false`. Also, some actions (e.g. |
87 | // `ParseSyntaxOnly`) don't generated output. In such cases there's no |
88 | // output to clear and `ClearOutputFile` returns immediately. |
89 | compInst.clearOutputFiles(/*EraseFiles=*/false); |
90 | } |
91 | }; |
92 | |
93 | TEST_F(FrontendActionTest, TestInputOutput) { |
94 | // Populate the input file with the pre-defined input and flush it. |
95 | *(inputFileOs) << "End Program arithmetic" ; |
96 | inputFileOs.reset(); |
97 | |
98 | // Set-up the action kind. |
99 | compInst.getInvocation().getFrontendOpts().programAction = InputOutputTest; |
100 | |
101 | // Set-up the output stream. Using output buffer wrapped as an output |
102 | // stream, as opposed to an actual file (or a file descriptor). |
103 | llvm::SmallVector<char, 256> outputFileBuffer; |
104 | std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream( |
105 | new llvm::raw_svector_ostream(outputFileBuffer)); |
106 | compInst.setOutputStream(std::move(outputFileStream)); |
107 | |
108 | // Execute the action. |
109 | bool success = executeCompilerInvocation(&compInst); |
110 | |
111 | // Validate the expected output. |
112 | EXPECT_TRUE(success); |
113 | EXPECT_TRUE(!outputFileBuffer.empty()); |
114 | EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()) |
115 | .starts_with("End Program arithmetic" )); |
116 | } |
117 | |
118 | TEST_F(FrontendActionTest, PrintPreprocessedInput) { |
119 | // Populate the input file with the pre-defined input and flush it. |
120 | *(inputFileOs) << "#ifdef NEW\n" |
121 | << " Program A \n" |
122 | << "#else\n" |
123 | << " Program B\n" |
124 | << "#endif" ; |
125 | inputFileOs.reset(); |
126 | |
127 | // Set-up the action kind. |
128 | compInst.getInvocation().getFrontendOpts().programAction = |
129 | PrintPreprocessedInput; |
130 | compInst.getInvocation().getPreprocessorOpts().noReformat = true; |
131 | |
132 | // Set-up the output stream. We are using output buffer wrapped as an output |
133 | // stream, as opposed to an actual file (or a file descriptor). |
134 | llvm::SmallVector<char, 256> outputFileBuffer; |
135 | std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream( |
136 | new llvm::raw_svector_ostream(outputFileBuffer)); |
137 | compInst.setOutputStream(std::move(outputFileStream)); |
138 | |
139 | // Execute the action. |
140 | bool success = executeCompilerInvocation(&compInst); |
141 | |
142 | // Validate the expected output. |
143 | EXPECT_TRUE(success); |
144 | EXPECT_TRUE(!outputFileBuffer.empty()); |
145 | EXPECT_TRUE( |
146 | llvm::StringRef(outputFileBuffer.data()).starts_with("program b\n" )); |
147 | } |
148 | |
149 | TEST_F(FrontendActionTest, ParseSyntaxOnly) { |
150 | // Populate the input file with the pre-defined input and flush it. |
151 | *(inputFileOs) << "IF (A > 0.0) IF (B < 0.0) A = LOG (A)\n" |
152 | << "END" ; |
153 | inputFileOs.reset(); |
154 | |
155 | // Set-up the action kind. |
156 | compInst.getInvocation().getFrontendOpts().programAction = ParseSyntaxOnly; |
157 | |
158 | // Set-up the output stream for the semantic diagnostics. |
159 | llvm::SmallVector<char, 256> outputDiagBuffer; |
160 | std::unique_ptr<llvm::raw_pwrite_stream> outputStream( |
161 | new llvm::raw_svector_ostream(outputDiagBuffer)); |
162 | compInst.setSemaOutputStream(std::move(outputStream)); |
163 | |
164 | // Execute the action. |
165 | bool success = executeCompilerInvocation(&compInst); |
166 | |
167 | // Validate the expected output. |
168 | EXPECT_FALSE(success); |
169 | EXPECT_TRUE(!outputDiagBuffer.empty()); |
170 | EXPECT_TRUE( |
171 | llvm::StringRef(outputDiagBuffer.data()) |
172 | .contains( |
173 | ":1:14: error: IF statement is not allowed in IF statement\n" )); |
174 | } |
175 | |
176 | TEST_F(FrontendActionTest, EmitLLVM) { |
177 | // Populate the input file with the pre-defined input and flush it. |
178 | *(inputFileOs) << "end program" ; |
179 | inputFileOs.reset(); |
180 | |
181 | // Set-up the action kind. |
182 | compInst.getInvocation().getFrontendOpts().programAction = EmitLLVM; |
183 | |
184 | // Initialise LLVM backend |
185 | llvm::InitializeAllAsmPrinters(); |
186 | |
187 | // Set-up the output stream. We are using output buffer wrapped as an output |
188 | // stream, as opposed to an actual file (or a file descriptor). |
189 | llvm::SmallVector<char> outputFileBuffer; |
190 | std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream( |
191 | new llvm::raw_svector_ostream(outputFileBuffer)); |
192 | compInst.setOutputStream(std::move(outputFileStream)); |
193 | |
194 | // Execute the action. |
195 | bool success = executeCompilerInvocation(&compInst); |
196 | |
197 | // Validate the expected output. |
198 | EXPECT_TRUE(success); |
199 | EXPECT_TRUE(!outputFileBuffer.empty()); |
200 | |
201 | EXPECT_TRUE(llvm::StringRef(outputFileBuffer.begin(), outputFileBuffer.size()) |
202 | .contains("define void @_QQmain()" )); |
203 | } |
204 | |
205 | TEST_F(FrontendActionTest, EmitAsm) { |
206 | // Populate the input file with the pre-defined input and flush it. |
207 | *(inputFileOs) << "end program" ; |
208 | inputFileOs.reset(); |
209 | |
210 | // Set-up the action kind. |
211 | compInst.getInvocation().getFrontendOpts().programAction = EmitAssembly; |
212 | |
213 | // Initialise LLVM backend |
214 | llvm::InitializeAllAsmPrinters(); |
215 | |
216 | // Set-up the output stream. We are using output buffer wrapped as an output |
217 | // stream, as opposed to an actual file (or a file descriptor). |
218 | llvm::SmallVector<char, 256> outputFileBuffer; |
219 | std::unique_ptr<llvm::raw_pwrite_stream> outputFileStream( |
220 | new llvm::raw_svector_ostream(outputFileBuffer)); |
221 | compInst.setOutputStream(std::move(outputFileStream)); |
222 | |
223 | // Execute the action. |
224 | bool success = executeCompilerInvocation(&compInst); |
225 | |
226 | // Validate the expected output. |
227 | EXPECT_TRUE(success); |
228 | EXPECT_TRUE(!outputFileBuffer.empty()); |
229 | |
230 | EXPECT_TRUE(llvm::StringRef(outputFileBuffer.begin(), outputFileBuffer.size()) |
231 | .contains("_QQmain" )); |
232 | } |
233 | } // namespace |
234 | |