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