1 /** 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #ifndef ES2PANDA_TEST_UTILS_AST_VERIFIER_TEST_H 17 #define ES2PANDA_TEST_UTILS_AST_VERIFIER_TEST_H 18 19 #include "ast_verifier/ASTVerifier.h" 20 #include "panda_executable_path_getter.h" 21 22 #include <gtest/gtest.h> 23 24 namespace ir_alias = ark::es2panda::ir; 25 namespace verifier_alias = ark::es2panda::compiler::ast_verifier; 26 27 class LSPAPITests; 28 29 namespace test::utils { 30 31 // CC-OFFNXT(G.PRE.02) macro to improve readability 32 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) 33 #define CONTEXT(...) if (ContextHolder<ContextFromStringExtractor> h {this, __VA_ARGS__}; true) 34 35 // CC-OFFNXT(G.PRE.02) macro to improve readability 36 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) 37 #define CONTEXT_FROM_FILE(...) if (ContextHolder<ContextFromFileExtractor> h {this, __VA_ARGS__}; true) 38 39 // NOLINTNEXTLINE(fuchsia-multiple-inheritance) 40 class AstVerifierTest : public testing::Test, public verifier_alias::InvariantsRegistry { 41 public: 42 AstVerifierTest(); 43 NO_COPY_SEMANTIC(AstVerifierTest); 44 NO_MOVE_SEMANTIC(AstVerifierTest); 45 ~AstVerifierTest() override; 46 Allocator()47 ark::ArenaAllocator *Allocator() const 48 { 49 return allocator_; 50 } 51 GetChecker()52 auto *GetChecker() 53 { 54 return reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx_)->checker->AsETSChecker(); 55 } 56 GetAst()57 auto *GetAst() 58 { 59 return reinterpret_cast<ir_alias::AstNode *>(impl_->ProgramAst(ctx_, impl_->ContextProgram(ctx_))); 60 } 61 GetCfg()62 auto *GetCfg() 63 { 64 return reinterpret_cast<ark::es2panda::parser::Program *>(impl_->ContextProgram(ctx_))->GetCFG(); 65 } 66 GetImpl()67 auto *GetImpl() 68 { 69 return impl_; 70 } 71 GetContext()72 auto *GetContext() 73 { 74 return ctx_; 75 } 76 AstNodeForEach(void (* func)(es2panda_AstNode *,void *),void * arg)77 void AstNodeForEach(void (*func)(es2panda_AstNode *, void *), void *arg) 78 { 79 impl_->AstNodeForEach(reinterpret_cast<es2panda_AstNode *>(GetAst()), func, arg); 80 } 81 ContextErrorMessage()82 auto ContextErrorMessage() 83 { 84 return std::string(impl_->ContextErrorMessage(ctx_)); 85 } 86 87 struct ContextFromStringExtractor { 88 ContextFromStringExtractor(AstVerifierTest *fixture, char const *source, char const *fileName = "dummy.ets") 89 { 90 ASSERT(fixture->ctx_ == nullptr); 91 fixture->ctx_ = fixture->impl_->CreateContextFromString(fixture->cfg_, source, fileName); 92 } 93 }; 94 95 struct ContextFromFileExtractor { ContextFromFileExtractorContextFromFileExtractor96 ContextFromFileExtractor(AstVerifierTest *fixture, char const *fileName) 97 { 98 ASSERT(fixture->ctx_ == nullptr); 99 fixture->ctx_ = fixture->impl_->CreateContextFromFile(fixture->cfg_, fileName); 100 } 101 }; 102 103 template <typename ContextExtractor> 104 class ContextHolder : private ContextExtractor { 105 public: 106 NO_COPY_SEMANTIC(ContextHolder); 107 NO_MOVE_SEMANTIC(ContextHolder); 108 109 template <typename... ExtractorArgs> ContextHolder(AstVerifierTest * fixture,es2panda_ContextState state,ExtractorArgs &&...args)110 ContextHolder(AstVerifierTest *fixture, es2panda_ContextState state, ExtractorArgs &&...args) 111 : ContextHolder(fixture, state, state, std::forward<ExtractorArgs>(args)...) 112 { 113 } 114 115 template <typename... ExtractorArgs> ContextHolder(AstVerifierTest * fixture,es2panda_ContextState target,es2panda_ContextState expected,ExtractorArgs &&...args)116 ContextHolder(AstVerifierTest *fixture, es2panda_ContextState target, es2panda_ContextState expected, 117 ExtractorArgs &&...args) 118 : ContextExtractor(fixture, std::forward<ExtractorArgs>(args)...), fixture_ {fixture} 119 { 120 ASSERT(target != ES2PANDA_STATE_ERROR); 121 fixture_->impl_->ProceedToState(fixture_->ctx_, target); 122 EXPECT_EQ(fixture_->impl_->ContextState(fixture_->ctx_), expected) 123 << fixture_->impl_->ContextErrorMessage(fixture_->ctx_); 124 ; 125 } 126 ~ContextHolder()127 ~ContextHolder() 128 { 129 ASSERT(fixture_->ctx_ != nullptr); 130 fixture_->impl_->DestroyContext(fixture_->ctx_); 131 fixture_->ctx_ = nullptr; 132 } 133 134 private: 135 AstVerifierTest *fixture_ {}; 136 }; 137 138 class ExpectVerifierMessage { 139 public: 140 ExpectVerifierMessage() = default; 141 // NOLINTNEXTLINE(*-explicit-constructor) ExpectVerifierMessage(std::initializer_list<std::string> l)142 explicit ExpectVerifierMessage(std::initializer_list<std::string> l) : l_(l) {} 143 CheckMessages(const verifier_alias::Messages & messages)144 bool CheckMessages(const verifier_alias::Messages &messages) 145 { 146 if (messages.size() != l_.size()) { 147 std::cerr << "Expected " << l_.size() << " messages, got " << messages.size() << std::endl; 148 if (!messages.empty()) { 149 std::cerr << "Got messages:" << std::endl; 150 } 151 for (const auto &msg : messages) { 152 std::cerr << " " << msg.ToString() << std::endl; 153 } 154 return false; 155 } 156 auto res = true; 157 // NOLINTNEXTLINE(modernize-loop-convert) 158 for (size_t i = 0; i < messages.size(); i++) { 159 if (messages[i].ToString().find(l_.begin()[i]) == std::string::npos) { 160 std::cerr << "Expected '" << l_.begin()[i] << "', got '" << messages[i].ToString() << "'" 161 << std::endl; 162 res = false; 163 } 164 } 165 return res; 166 } 167 168 private: 169 std::initializer_list<std::string> l_; 170 }; 171 172 template <typename Invariant> 173 [[nodiscard]] auto Verify(ExpectVerifierMessage expected = {}) 174 { 175 ASSERT(ctx_ != nullptr); 176 auto *inv = Get<Invariant>(); 177 inv->Init(); 178 179 std::function<void(const ir_alias::AstNode *)> aux {}; 180 aux = [inv, &aux](const ir_alias::AstNode *child) -> void { 181 // Required invariants need to be manually called in tests: 182 std::apply([child](auto &...requiredInv) { (((void)(requiredInv)(child)), ...); }, inv->GetRequired()); 183 const auto [_, action] = (*inv)(child); 184 if (action == verifier_alias::CheckAction::SKIP_SUBTREE) { 185 return; 186 } 187 child->Iterate(aux); 188 }; 189 aux(GetAst()); 190 return expected.CheckMessages(inv->ViewMessages()); 191 } 192 193 template <typename Invariant> 194 [[nodiscard]] auto VerifyNode(const ir_alias::AstNode *ast, ExpectVerifierMessage expected = {}) 195 { 196 auto *inv = Get<Invariant>(); 197 inv->Init(); 198 // Required invariants need to be manually called in tests: 199 std::apply([ast](auto &...requiredInv) { (((void)(requiredInv)(ast)), ...); }, inv->GetRequired()); 200 (void)(*inv)(ast); 201 return expected.CheckMessages(inv->ViewMessages()); 202 } 203 204 private: 205 es2panda_Impl const *impl_ {}; 206 es2panda_Config *cfg_ {}; 207 es2panda_Context *ctx_ {}; 208 ark::ArenaAllocator *allocator_ {}; 209 210 friend class ::LSPAPITests; 211 }; 212 213 } // namespace test::utils 214 215 #endif // ES2PANDA_TEST_UTILS_AST_VERIFIER_TEST_H 216