From 24131358287a7ce68c7b9ceaefd1e701694567ae Mon Sep 17 00:00:00 2001 From: ykiko Date: Tue, 25 Nov 2025 00:26:17 +0800 Subject: [PATCH 01/17] introduce `cpptrace` --- CMakeLists.txt | 2 +- cmake/package.cmake | 10 +++++++++- xmake.lua | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd9552fbd..b45ecc7f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,5 +149,5 @@ if(CLICE_ENABLE_TEST) "${PROJECT_SOURCE_DIR}/bin/unit_tests.cc" ) target_include_directories(unit_tests PUBLIC "${PROJECT_SOURCE_DIR}") - target_link_libraries(unit_tests PRIVATE clice-core) + target_link_libraries(unit_tests PRIVATE clice-core cpptrace::cpptrace) endif() diff --git a/cmake/package.cmake b/cmake/package.cmake index 3df35f47e..dfd62e63d 100644 --- a/cmake/package.cmake +++ b/cmake/package.cmake @@ -117,6 +117,7 @@ FetchContent_Declare( GIT_TAG v4.4.2 ) set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE) +set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE) # flatbuffers FetchContent_Declare( @@ -128,7 +129,14 @@ set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE) set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers) +# cpptrace +FetchContent_Declare( + cpptrace + GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git + GIT_TAG v1.0.4 +) + +FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers cpptrace) if(WIN32) target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS) diff --git a/xmake.lua b/xmake.lua index 5dd12e3ba..89ca15111 100644 --- a/xmake.lua +++ b/xmake.lua @@ -41,7 +41,7 @@ end add_defines("TOML_EXCEPTIONS=0") add_requires("spdlog", {system = false, version = "1.15.3", configs = {header_only = false, std_format = true, noexcept = true}}) -add_requires(libuv_require, "toml++", "croaring", "flatbuffers") +add_requires(libuv_require, "toml++", "croaring", "flatbuffers", "cpptrace") add_requires("clice-llvm", {alias = "llvm"}) add_rules("mode.release", "mode.debug", "mode.releasedbg") @@ -145,6 +145,7 @@ target("unit_tests") add_files("bin/unit_tests.cc", "tests/unit/**.cpp") add_includedirs(".", {public = true}) + add_packages("cpptrace") add_deps("clice-core") add_tests("default") From 73a0d351342d29c1020a4b6d40e62270281fbe2b Mon Sep 17 00:00:00 2001 From: ykiko Date: Wed, 26 Nov 2025 23:08:03 +0800 Subject: [PATCH 02/17] fix macos build --- .github/workflows/cmake.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index cdab7a14f..e6c1f633f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -80,6 +80,7 @@ jobs: shell: bash run: | if [[ "${{ runner.os }}" == "macOS" ]]; then + export SDKROOT=$(xcrun --show-sdk-path) export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH" fi From 4a880253e1e76055912207b5bfb88a6e801bebdc Mon Sep 17 00:00:00 2001 From: ykiko Date: Thu, 27 Nov 2025 23:27:14 +0800 Subject: [PATCH 03/17] some update --- bin/unit_tests.cc | 3 +- include/Test/Runner.h | 64 ++++++++++++++++++++ include/Test/Test2.h | 69 +++++++++++++++++++++ tests/unit/Test/Runner.cpp | 121 +++++++++++++++++++++++++++++++++++++ tests/unit/Test/Test.cpp | 28 +++++++++ 5 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 include/Test/Runner.h create mode 100644 include/Test/Test2.h create mode 100644 tests/unit/Test/Runner.cpp create mode 100644 tests/unit/Test/Test.cpp diff --git a/bin/unit_tests.cc b/bin/unit_tests.cc index aee1d1a80..e4138133b 100644 --- a/bin/unit_tests.cc +++ b/bin/unit_tests.cc @@ -1,4 +1,5 @@ #include "Test/Test.h" +#include "Test/Test2.h" #include "Support/Logging.h" #include "Support/GlobPattern.h" @@ -198,5 +199,5 @@ int main(int argc, const char* argv[]) { } using namespace clice::testing; - return Runner::instance().run_tests(); + return Runner2::instance().run_tests(); } diff --git a/include/Test/Runner.h b/include/Test/Runner.h new file mode 100644 index 000000000..0e61d338e --- /dev/null +++ b/include/Test/Runner.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include "llvm/ADT/FunctionExtras.h" + +namespace clice::testing { + +enum class TestState { + Passed, + Skipped, + Failed, + Fatal, +}; + +struct skip_test {}; + +struct focus {}; + +template +struct test_tag {}; + +struct TestAttrs { + bool skiped = false; + bool focus = false; + + template + static TestAttrs parse(TestState (C::*)(test_tag)) { + TestAttrs attrs; + if constexpr((std::is_same_v || ...)) { + attrs.skiped = true; + } + + if constexpr((std::is_same_v || ...)) { + attrs.focus = true; + } + return attrs; + } +}; + +struct TestCase { + std::string name; + llvm::unique_function test; + TestAttrs attrs; +}; + +struct TestSuite { + std::string name; + std::vector (*cases)(); +}; + +class Runner2 { +public: + static Runner2& instance(); + + void add_suite(std::string_view suite, std::vector (*cases)()); + + int run_tests(); + +private: + std::vector suites; +}; + +} // namespace clice::testing diff --git a/include/Test/Test2.h b/include/Test/Test2.h new file mode 100644 index 000000000..16ac627b5 --- /dev/null +++ b/include/Test/Test2.h @@ -0,0 +1,69 @@ +#include +#include +#include +#include "Support/FixedString.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/FunctionExtras.h" +#include "Test.h" +#include "Runner.h" + +namespace clice::testing { + +template +struct TestSuiteDef { +private: + struct TestCaseDef { + std::string name; + TestState (Derived::*func)(); + }; + +public: + using Self = Derived; + + constexpr inline static auto& test_cases() { + static std::vector instance; + return instance; + } + + constexpr inline static auto suites() { + return std::move(test_cases()); + } + + template + inline static bool _register_suites = [] { + Runner2::instance().add_suite(TestName.data(), &suites); + return true; + }(); + + template + inline static bool _register_test_case = [] { + auto run_test = +[] -> TestState { + Derived test; + if constexpr(requires { test.setup(); }) { + test.setup(); + } + + auto state = (test.*Case)({}); + + if constexpr(requires { test.teardown(); }) { + test.teardown(); + } + + return state; + }; + + test_cases().emplace_back(CaseName.data(), run_test, TestAttrs::parse(Case)); + return true; + }(); +}; + +#define TEST_SUITE(name) struct name : TestSuiteDef<#name, name> + +#define TEST_CASE(name, ...) \ + void _register_##name() { \ + (void)_register_suites<>; \ + (void)_register_test_case<#name, &Self::test_##name>; \ + } \ + TestState test_##name(test_tag<__VA_ARGS__>) + +} // namespace clice::testing diff --git a/tests/unit/Test/Runner.cpp b/tests/unit/Test/Runner.cpp new file mode 100644 index 000000000..7d6b1f78c --- /dev/null +++ b/tests/unit/Test/Runner.cpp @@ -0,0 +1,121 @@ +#include +#include +#include "Test/Runner.h" + +namespace clice::testing { + +Runner2& Runner2::instance() { + static Runner2 runner; + return runner; +} + +void Runner2::add_suite(std::string_view name, std::vector (*cases)()) { + suites.emplace_back(std::string(name), cases); +} + +int Runner2::run_tests() { + constexpr static std::string_view GREEN = "\033[32m"; + constexpr static std::string_view YELLOW = "\033[33m"; + constexpr static std::string_view RED = "\033[31m"; + constexpr static std::string_view CLEAR = "\033[0m"; + + std::uint32_t total_tests_count = 0; + std::uint32_t total_suites_count = 0; + std::uint32_t total_failed_tests_count = 0; + std::chrono::milliseconds curr_test_duration; + std::chrono::milliseconds total_test_duration; + + struct Suite { + std::string name; + std::vector cases; + }; + + std::vector all_suites; + for(auto suite: suites) { + auto cases = suite.cases(); + total_suites_count += 1; + total_tests_count += cases.size(); + all_suites.emplace_back(suite.name, std::move(cases)); + } + + std::println("{}[----------] Global test environment set-up.{}", GREEN, CLEAR); + + bool focus_only_mode = false; + + for(auto& [suite_name, test_cases]: all_suites) { + for(auto& [test_name, test, attrs]: test_cases) { + if(!attrs.focus) { + focus_only_mode = true; + continue; + } + + std::println("{}[ RUN ] {}.{}{}", GREEN, suite_name, test_name, CLEAR); + + using namespace std::chrono; + auto begin = system_clock::now(); + auto state = test(); + auto end = system_clock::now(); + + bool curr_failed = state == TestState::Failed; + auto duration = duration_cast(end - begin); + std::println("{0}[ {1} ] {2}.{3} ({4} ms){5}", + curr_failed ? RED : GREEN, + curr_failed ? "FAILED" : " OK", + suite_name, + test_name, + duration.count(), + CLEAR); + + curr_test_duration += duration; + total_test_duration += duration; + if(curr_failed) { + total_failed_tests_count += 1; + } + } + } + + if(!focus_only_mode) { + for(auto& [suite_name, test_cases]: all_suites) { + for(auto& [test_name, test, attrs]: test_cases) { + if(attrs.skiped) { + std::println("{}[ SKIPPED ] {}.{}{}", YELLOW, suite_name, test_name, CLEAR); + continue; + } + + std::println("{}[ RUN ] {}.{}{}", GREEN, suite_name, test_name, CLEAR); + + using namespace std::chrono; + auto begin = system_clock::now(); + auto state = test(); + auto end = system_clock::now(); + + bool curr_failed = state == TestState::Failed; + auto duration = duration_cast(end - begin); + std::println("{0}[ {1} ] {2}.{3} ({4} ms){5}", + curr_failed ? RED : GREEN, + curr_failed ? "FAILED" : " OK", + suite_name, + test_name, + duration.count(), + CLEAR); + + curr_test_duration += duration; + total_test_duration += duration; + if(curr_failed) { + total_failed_tests_count += 1; + } + } + } + } + + std::println("{}[----------] Global test environment tear-down. {}", GREEN, CLEAR); + std::println("{}[==========] {} tests from {} test suites ran. ({} ms total){}", + GREEN, + total_tests_count, + total_suites_count, + total_test_duration.count(), + CLEAR); + return total_failed_tests_count != 0; +} + +} // namespace clice::testing diff --git a/tests/unit/Test/Test.cpp b/tests/unit/Test/Test.cpp new file mode 100644 index 000000000..faea946a3 --- /dev/null +++ b/tests/unit/Test/Test.cpp @@ -0,0 +1,28 @@ +#include "Test/Test2.h" + +namespace clice::testing { + +TEST_SUITE(MyTest) { + int x = 0; + + void setup() { + x += 1; + } + + void teardown() { + x -= 2; + } + + TEST_CASE(World, skip_test) { + x = 3; + /// + return TestState::Skipped; + }; + + TEST_CASE(Hello, focus) { + x = 4; + return TestState::Failed; + }; +}; + +} // namespace clice::testing From e8c57b0c1e8064da5cb726db7d91d7915da7e0c6 Mon Sep 17 00:00:00 2001 From: ykiko Date: Fri, 28 Nov 2025 22:32:32 +0800 Subject: [PATCH 04/17] some update --- .clang-format | 2 + bin/unit_tests.cc | 2 +- docs/semantic-tokens-example.cpp | 1 + include/Compiler/Compilation.h | 2 + include/Compiler/Diagnostic.h | 2 + include/Compiler/Tidy.h | 2 + include/Index/IncludeGraph.h | 2 + include/Test/Platform.h | 1 + include/Test/Runner.h | 23 +- include/Test/TExpr.h | 2 + include/Test/Test.h | 1 + include/Test/Test2.h | 75 +++- src/AST/Resolver.cpp | 8 +- src/Index/MergedIndex.cpp | 1 + src/Index/USRGeneration.cpp | 2 + src/Support/Doxygen.cpp | 2 + tests/unit/AST/Resolver.cpp | 749 +++++++++++++++---------------- tests/unit/Test/Runner.cpp | 62 +-- tests/unit/Test/Test.cpp | 28 -- 19 files changed, 494 insertions(+), 473 deletions(-) delete mode 100644 tests/unit/Test/Test.cpp diff --git a/.clang-format b/.clang-format index 814c1fb8c..af02ee7e7 100644 --- a/.clang-format +++ b/.clang-format @@ -93,6 +93,7 @@ SpacesInParensOptions: SpacesInSquareBrackets: false +WrapNamespaceBodyWithEmptyLines: Always # Order QualifierAlignment: Custom QualifierOrder: ["constexpr", "const", "inline", "static", "type"] @@ -101,3 +102,4 @@ SortUsingDeclarations: Never IncludeBlocks: Merge ForEachMacros: ["REFLECTABLE_RECORD"] +NamespaceMacros: [TEST_SUITE] diff --git a/bin/unit_tests.cc b/bin/unit_tests.cc index e4138133b..dd69a2029 100644 --- a/bin/unit_tests.cc +++ b/bin/unit_tests.cc @@ -199,5 +199,5 @@ int main(int argc, const char* argv[]) { } using namespace clice::testing; - return Runner2::instance().run_tests(); + return Runner2::instance().run_tests(test_filter); } diff --git a/docs/semantic-tokens-example.cpp b/docs/semantic-tokens-example.cpp index 8bb69c6a8..4df7a8147 100644 --- a/docs/semantic-tokens-example.cpp +++ b/docs/semantic-tokens-example.cpp @@ -58,4 +58,5 @@ struct Widget { template Widget(T) -> Widget; + } // namespace foo diff --git a/include/Compiler/Compilation.h b/include/Compiler/Compilation.h index 73e9ff79f..de567a13f 100644 --- a/include/Compiler/Compilation.h +++ b/include/Compiler/Compilation.h @@ -6,7 +6,9 @@ #include "Support/FileSystem.h" namespace clang { + class CodeCompleteConsumer; + } namespace clice { diff --git a/include/Compiler/Diagnostic.h b/include/Compiler/Diagnostic.h index 3cd90c0b3..e7892c40b 100644 --- a/include/Compiler/Diagnostic.h +++ b/include/Compiler/Diagnostic.h @@ -9,7 +9,9 @@ #include "clang/Basic/Diagnostic.h" namespace clang { + class DiagnosticConsumer; + } namespace clice { diff --git a/include/Compiler/Tidy.h b/include/Compiler/Tidy.h index 64acc7c4f..9d8d5d839 100644 --- a/include/Compiler/Tidy.h +++ b/include/Compiler/Tidy.h @@ -5,7 +5,9 @@ #include namespace clang { + class CompilerInstance; + } namespace clice::tidy { diff --git a/include/Index/IncludeGraph.h b/include/Index/IncludeGraph.h index 5cb5f1053..a45ced959 100644 --- a/include/Index/IncludeGraph.h +++ b/include/Index/IncludeGraph.h @@ -5,7 +5,9 @@ #include "llvm/ADT/DenseMap.h" namespace clice { + class CompilationUnit; + } namespace clice::index { diff --git a/include/Test/Platform.h b/include/Test/Platform.h index 0e489f304..6f99f94e6 100644 --- a/include/Test/Platform.h +++ b/include/Test/Platform.h @@ -48,4 +48,5 @@ constexpr inline bool CIEnvironment = true; #else constexpr inline bool CIEnvironment = false; #endif + } // namespace clice::testing diff --git a/include/Test/Runner.h b/include/Test/Runner.h index 0e61d338e..c450c0b49 100644 --- a/include/Test/Runner.h +++ b/include/Test/Runner.h @@ -2,6 +2,7 @@ #include #include +#include "llvm/ADT/StringRef.h" #include "llvm/ADT/FunctionExtras.h" namespace clice::testing { @@ -13,29 +14,9 @@ enum class TestState { Fatal, }; -struct skip_test {}; - -struct focus {}; - -template -struct test_tag {}; - struct TestAttrs { bool skiped = false; bool focus = false; - - template - static TestAttrs parse(TestState (C::*)(test_tag)) { - TestAttrs attrs; - if constexpr((std::is_same_v || ...)) { - attrs.skiped = true; - } - - if constexpr((std::is_same_v || ...)) { - attrs.focus = true; - } - return attrs; - } }; struct TestCase { @@ -55,7 +36,7 @@ class Runner2 { void add_suite(std::string_view suite, std::vector (*cases)()); - int run_tests(); + int run_tests(llvm::StringRef filter); private: std::vector suites; diff --git a/include/Test/TExpr.h b/include/Test/TExpr.h index 4c7ed9082..25fa26f27 100644 --- a/include/Test/TExpr.h +++ b/include/Test/TExpr.h @@ -39,6 +39,7 @@ decltype(auto) compute(const Expr& expr) { #define BINARY_PREDICATE(name, op) \ namespace clice::testing { \ + \ decltype(auto) name##_impl(auto&& lhs, auto&& rhs); \ \ template \ @@ -55,6 +56,7 @@ decltype(auto) compute(const Expr& expr) { \ template \ name(const LHS&, const RHS&) -> name; \ + \ } \ \ template \ diff --git a/include/Test/Test.h b/include/Test/Test.h index 3339e23d9..daafe56b5 100644 --- a/include/Test/Test.h +++ b/include/Test/Test.h @@ -9,6 +9,7 @@ #include "Support/FileSystem.h" #include "Support/FixedString.h" #include "llvm/ADT/FunctionExtras.h" +#include "Test2.h" namespace clice::testing { diff --git a/include/Test/Test2.h b/include/Test/Test2.h index 16ac627b5..320bd58d4 100644 --- a/include/Test/Test2.h +++ b/include/Test/Test2.h @@ -1,11 +1,14 @@ +#pragma once + +#include #include #include #include #include "Support/FixedString.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/FunctionExtras.h" -#include "Test.h" #include "Runner.h" +#include "cpptrace/cpptrace.hpp" namespace clice::testing { @@ -17,9 +20,23 @@ struct TestSuiteDef { TestState (Derived::*func)(); }; + TestState state = TestState::Passed; + public: using Self = Derived; + void failure() { + state = TestState::Failed; + } + + void pass() { + state = TestState::Passed; + } + + void skip() { + state = TestState::Skipped; + } + constexpr inline static auto& test_cases() { static std::vector instance; return instance; @@ -35,7 +52,7 @@ struct TestSuiteDef { return true; }(); - template + template inline static bool _register_test_case = [] { auto run_test = +[] -> TestState { Derived test; @@ -43,27 +60,71 @@ struct TestSuiteDef { test.setup(); } - auto state = (test.*Case)({}); + (test.*Case)(); if constexpr(requires { test.teardown(); }) { test.teardown(); } - return state; + return test.state; }; - test_cases().emplace_back(CaseName.data(), run_test, TestAttrs::parse(Case)); + test_cases().emplace_back(CaseName.data(), run_test, attrs); return true; }(); }; +inline void print_trace(cpptrace::stacktrace& trace, std::source_location location) { + auto& frames = trace.frames; + auto it = std::ranges::find_if(frames, [&](cpptrace::stacktrace_frame& frame) { + return frame.filename != location.file_name(); + }); + frames.erase(it, frames.end()); + trace.print(); +} + #define TEST_SUITE(name) struct name : TestSuiteDef<#name, name> #define TEST_CASE(name, ...) \ void _register_##name() { \ (void)_register_suites<>; \ - (void)_register_test_case<#name, &Self::test_##name>; \ + (void)_register_test_case<#name, &Self::test_##name __VA_OPT__(, ) __VA_ARGS__>; \ } \ - TestState test_##name(test_tag<__VA_ARGS__>) + void test_##name() + +#define EXPECT_TRUE(expr) \ + if(expr) { \ + auto trace = cpptrace::generate_trace(); \ + print_trace(trace, std::source_location::current()); \ + failure(); \ + } + +#define EXPECT_EQ(lhs, rhs) \ + if(lhs != rhs) { \ + auto trace = cpptrace::generate_trace(); \ + print_trace(trace, std::source_location::current()); \ + failure(); \ + } + +#define ASSERT_TRUE(expr) \ + if(!(expr)) { \ + auto trace = cpptrace::generate_trace(); \ + print_trace(trace, std::source_location::current()); \ + return failure(); \ + } + +#define ASSERT_FALSE(expr) \ + if((expr)) { \ + auto trace = cpptrace::generate_trace(); \ + print_trace(trace, std::source_location::current()); \ + return failure(); \ + } + +#define ASSERT_EQ(lhs, rhs) \ + if(lhs != rhs) { \ + auto trace = cpptrace::generate_trace(); \ + print_trace(trace, std::source_location::current()); \ + return failure(); \ + } } // namespace clice::testing diff --git a/src/AST/Resolver.cpp b/src/AST/Resolver.cpp index dae22fa75..ac01f3490 100644 --- a/src/AST/Resolver.cpp +++ b/src/AST/Resolver.cpp @@ -626,10 +626,14 @@ class PseudoInstantiator : public clang::TreeTransform { /// SomeAllocator -> SomeAllocator if(auto TST = Alloc->getAs()) { - llvm::SmallVector replaceArguments = {T}; + llvm::SmallVector replaceArguments = {T}; + llvm::SmallVector canonicalArguments; + for(auto& arg: replaceArguments) { + canonicalArguments.emplace_back(context.getCanonicalTemplateArgument(arg)); + } return context.getTemplateSpecializationType(TST->getTemplateName(), replaceArguments, - replaceArguments); + canonicalArguments); } } } diff --git a/src/Index/MergedIndex.cpp b/src/Index/MergedIndex.cpp index f4df581f6..063a20c5b 100644 --- a/src/Index/MergedIndex.cpp +++ b/src/Index/MergedIndex.cpp @@ -67,6 +67,7 @@ struct DenseMapInfo { lhs.target_symbol == rhs.target_symbol; } }; + } // namespace llvm namespace clice::index { diff --git a/src/Index/USRGeneration.cpp b/src/Index/USRGeneration.cpp index ef2336469..7d7453c7a 100644 --- a/src/Index/USRGeneration.cpp +++ b/src/Index/USRGeneration.cpp @@ -51,6 +51,7 @@ void AppendExprODRHash(const Expr* expr, llvm::raw_ostream& Out) { }; namespace { + class USRGenerator : public ConstDeclVisitor { SmallVectorImpl& Buf; llvm::raw_svector_ostream Out; @@ -139,6 +140,7 @@ class USRGenerator : public ConstDeclVisitor { /// the decl had no name. bool EmitDeclName(const NamedDecl* D); }; + } // end anonymous namespace //===----------------------------------------------------------------------===// diff --git a/src/Support/Doxygen.cpp b/src/Support/Doxygen.cpp index d7550524f..26b81e73c 100644 --- a/src/Support/Doxygen.cpp +++ b/src/Support/Doxygen.cpp @@ -4,6 +4,7 @@ #include "llvm/ADT/StringExtras.h" namespace clice { + void DoxygenInfo::add_block_command_comment(llvm::StringRef tag, llvm::StringRef content) { auto [it, _] = block_command_comments.try_emplace(tag); it->second.emplace_back(content.str()); @@ -246,4 +247,5 @@ std::pair strip_doxygen_info(llvm::StringRef raw_comme } return {di, os.str()}; } + } // namespace clice diff --git a/tests/unit/AST/Resolver.cpp b/tests/unit/AST/Resolver.cpp index 6c8146f25..2e1206d71 100644 --- a/tests/unit/AST/Resolver.cpp +++ b/tests/unit/AST/Resolver.cpp @@ -1,3 +1,4 @@ +#include "Test/Test2.h" #include "Test/Tester.h" #include "clang/AST/RecursiveASTVisitor.h" @@ -17,7 +18,7 @@ struct InputFinder : clang::RecursiveASTVisitor { bool TraverseDecl(clang::Decl* decl) { if(decl && (llvm::isa(decl) || unit.file_id(decl->getLocation()) == unit.interested_file())) { - return Base::TraverseDecl(decl); + Base::TraverseDecl(decl); } return true; @@ -36,441 +37,439 @@ struct InputFinder : clang::RecursiveASTVisitor { } }; -suite<"TemplateResolver"> suite = [] { - auto run = [](llvm::StringRef code) { - Tester tester; - tester.add_main("main.cpp", code); - tester.compile(); +TEST_SUITE(TemplateResolver) { - InputFinder finder(*tester.unit); - finder.TraverseAST(tester.unit->context()); +void run(llvm::StringRef code) { + Tester tester; + tester.add_main("main.cpp", code); + ASSERT_TRUE(tester.compile()); - auto input = tester.unit->resolver().resolve(finder.input); - auto target = finder.expect; + InputFinder finder(*tester.unit); + finder.TraverseAST(tester.unit->context()); - expect(that % (!input.isNull())); - expect(that % (!target.isNull())); - expect(that % (input.getCanonicalType() == target.getCanonicalType())); - }; + auto input = tester.unit->resolver().resolve(finder.input); + auto target = finder.expect; + ASSERT_FALSE(input.isNull() || target.isNull()); + EXPECT_EQ(input.getCanonicalType(), target.getCanonicalType()); +} - test("TypeParameterType") = [&] { - run(R"cpp( -template -struct A { - using type = T; -}; +TEST_CASE(TypeParameterType) { + run(R"code( + template + struct A { + using type = T; + }; -template -struct test { - using input = typename A::type; - using expect = X; -}; -)cpp"); - }; + template + struct test { + using input = typename A::type; + using expect = X; + }; + )code"); +} - test("SingleLevel") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(SingleLevel) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = type_list; -}; + template + struct A { + using type = type_list; + }; -template -struct test { - using input = typename A::type; - using expect = type_list; -}; -)cpp"); - }; - - test("SingleLevelNotDependent") = [&] { - run(R"cpp( -template -struct A { - using type = int; -}; + template + struct test { + using input = typename A::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(SingleLevelNotDependent) { + run(R"code( + template + struct A { + using type = int; + }; -template -struct test { - using input = typename A::type; - using expect = int; -}; -)cpp"); - }; + template + struct test { + using input = typename A::type; + using expect = int; + }; + )code"); +} - test("MultiLevel") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(MultiLevel) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = type_list; -}; + template + struct A { + using type = type_list; + }; -template -struct B { - using type = typename A::type; -}; + template + struct B { + using type = typename A::type; + }; -template -struct C { - using type = typename B::type; -}; + template + struct C { + using type = typename B::type; + }; -template -struct test { - using input = typename C::type; - using expect = type_list; -}; -)cpp"); - }; - - test("MultiLevelNotDependent") = [&] { - run(R"cpp( -template -struct A { - using type = int; -}; + template + struct test { + using input = typename C::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(MultiLevelNotDependent) { + run(R"code( + template + struct A { + using type = int; + }; -template -struct B { - using type = typename A::type; -}; + template + struct B { + using type = typename A::type; + }; -template -struct C { - using type = typename B::type; -}; + template + struct C { + using type = typename B::type; + }; -template -struct test { - using input = typename C::type; - using expect = int; -}; -)cpp"); - }; + template + struct test { + using input = typename C::type; + using expect = int; + }; + )code"); +} - test("ArgumentDependent") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(ArgumentDependent) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = T1; -}; + template + struct A { + using type = T1; + }; -template -struct B { - using type = type_list; -}; + template + struct B { + using type = type_list; + }; -template -struct test { - using input = typename B::type>::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename B::type>::type; + using expect = type_list; + }; + )code"); +} - test("AliasArgument") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(AliasArgument) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = T1; -}; + template + struct A { + using type = T1; + }; -template -struct B { - using base = A; - using type = type_list; -}; + template + struct B { + using base = A; + using type = type_list; + }; -template -struct test { - using input = typename B::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename B::type; + using expect = type_list; + }; + )code"); +} - test("AliasDependent") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(AliasDependent) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = type_list; -}; + template + struct A { + using type = type_list; + }; -template -struct B { - using base = A; - using type = typename base::type; -}; + template + struct B { + using base = A; + using type = typename base::type; + }; -template -struct test { - using input = typename B::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename B::type; + using expect = type_list; + }; + )code"); +} - test("AliasTemplate") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(AliasTemplate) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = type_list; -}; + template + struct A { + using type = type_list; + }; -template -struct B { - template - using type = typename A::type; -}; + template + struct B { + template + using type = typename A::type; + }; -template -struct test { - using input = typename B::template type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename B::template type; + using expect = type_list; + }; + )code"); +} - test("BaseDependent") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(BaseDependent) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = type_list; -}; + template + struct A { + using type = type_list; + }; -template -struct B : A {}; + template + struct B : A {}; -template -struct test { - using input = typename B::type; - using expect = type_list; -}; -)cpp"); - }; - - test("MultiNested") = [&] { - run(R"cpp( -template -struct type_list {}; - -template -struct A { - using self = A; - using type = type_list; -}; + template + struct test { + using input = typename B::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(MultiNested) { + run(R"code( + template + struct type_list {}; + + template + struct A { + using self = A; + using type = type_list; + }; -template -struct test { - using input = typename A::self::self::self::self::self::type; - using expect = type_list; -}; -)cpp"); - }; - - test("OuterDependentMemberClass") = [&] { - run(R"cpp( -template -struct type_list {}; - -template -struct A { - template - struct B { - template - struct C { - using type = type_list; + template + struct test { + using input = typename A::self::self::self::self::self::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(OuterDependentMemberClass) { + run(R"code( + template + struct type_list {}; + + template + struct A { + template + struct B { + template + struct C { + using type = type_list; + }; + }; }; - }; -}; -template -struct test { - using input = typename A::template B::template C::type; - using expect = type_list; -}; -)cpp"); - }; - - test("InnerDependentMemberClass") = [&] { - run(R"cpp( -template -struct type_list {}; - -template -struct test { - template - struct B { - using type = type_list; - }; - - using input = typename B<1, T>::type; - using expect = type_list; -}; -)cpp"); - }; - - test("InnerDependentPartialMemberClass") = [&] { - run(R"cpp( -template -struct type_list {}; - -template -struct test {}; - -template -struct test { - template - struct A { - using type = type_list; - }; - - using input = typename A<1, T>::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename A::template B::template C::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(InnerDependentMemberClass) { + run(R"code( + template + struct type_list {}; + + template + struct test { + template + struct B { + using type = type_list; + }; + + using input = typename B<1, T>::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(InnerDependentPartialMemberClass) { + run(R"code( + template + struct type_list {}; + + template + struct test {}; + + template + struct test { + template + struct A { + using type = type_list; + }; + + using input = typename A<1, T>::type; + using expect = type_list; + }; + )code"); +} - test("PartialSpecialization") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(PartialSpecialization) { + run(R"code( + template + struct type_list {}; -template -struct A {}; + template + struct A {}; -template -struct B {}; + template + struct B {}; -template typename HKT> -struct B> { - using type = type_list; -}; + template typename HKT> + struct B> { + using type = type_list; + }; -template -struct test { - using input = typename B>::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename B>::type; + using expect = type_list; + }; + )code"); +} - test("PartialDefaultArgument") = [&] { - run(R"cpp( -template -struct X {}; +TEST_CASE(PartialDefaultArgument) { + run(R"code( + template + struct X {}; -template -struct X { - using type = T; -}; + template + struct X { + using type = T; + }; -template -struct test { - using input = typename X::type; - using expect = T; -}; -)cpp"); - }; + template + struct test { + using input = typename X::type; + using expect = T; + }; + )code"); +} - test("DefaultArgument") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(DefaultArgument) { + run(R"code( + template + struct type_list {}; -template -struct A { - using type = type_list; -}; + template + struct A { + using type = type_list; + }; -template > -struct B { - using type = typename U2::type; -}; + template > + struct B { + using type = typename U2::type; + }; -template -struct test { - using input = typename B::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename B::type; + using expect = type_list; + }; + )code"); +} - test("PackExpansion") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(PackExpansion) { + run(R"code( + template + struct type_list {}; -template -struct X { - using type = type_list; -}; + template + struct X { + using type = type_list; + }; -template -struct test { - using input = typename X::type; - using expect = type_list; -}; -)cpp"); - }; + template + struct test { + using input = typename X::type; + using expect = type_list; + }; + )code"); +} - test("BasePackExpansion") = [&] { - run(R"cpp( -template -struct type_list {}; +TEST_CASE(BasePackExpansion) { + run(R"code( + template + struct type_list {}; -template -struct X { - using type = type_list; -}; + template + struct X { + using type = type_list; + }; -template -struct Y : X {}; + template + struct Y : X {}; -template -struct test { - using input = typename Y::type; - using expect = type_list; -}; -)cpp"); - }; - - /// FIXME: headers not found - /// - /// test("Standard") = [&] { - /// run(R"cpp( - /// #include - /// - /// template - /// struct test { - /// using input = typename std::vector::reference; - /// using expect = T&; - /// }; - /// )cpp"); - /// }; + template + struct test { + using input = typename Y::type; + using expect = type_list; + }; + )code"); +} + +TEST_CASE(Standard) { + run(R"code( + #include + + template + struct test { + using input = typename std::vector::reference; + using expect = T&; + }; + )code"); }; +}; // TEST_SUITE(TemplateResolver) + } // namespace } // namespace clice::testing diff --git a/tests/unit/Test/Runner.cpp b/tests/unit/Test/Runner.cpp index 7d6b1f78c..9b56370da 100644 --- a/tests/unit/Test/Runner.cpp +++ b/tests/unit/Test/Runner.cpp @@ -1,6 +1,7 @@ #include #include #include "Test/Runner.h" +#include "Support/GlobPattern.h" namespace clice::testing { @@ -13,7 +14,7 @@ void Runner2::add_suite(std::string_view name, std::vector (*cases)()) suites.emplace_back(std::string(name), cases); } -int Runner2::run_tests() { +int Runner2::run_tests(llvm::StringRef filter) { constexpr static std::string_view GREEN = "\033[32m"; constexpr static std::string_view YELLOW = "\033[33m"; constexpr static std::string_view RED = "\033[31m"; @@ -25,6 +26,13 @@ int Runner2::run_tests() { std::chrono::milliseconds curr_test_duration; std::chrono::milliseconds total_test_duration; + std::optional pattern; + if(!filter.empty()) { + if(auto result = GlobPattern::create(filter)) { + pattern.emplace(std::move(*result)); + } + } + struct Suite { std::string name; std::vector cases; @@ -40,12 +48,22 @@ int Runner2::run_tests() { std::println("{}[----------] Global test environment set-up.{}", GREEN, CLEAR); - bool focus_only_mode = false; - for(auto& [suite_name, test_cases]: all_suites) { + if(!filter.empty()) { + auto pos = filter.find_first_of('.'); + if(pos != std::string::npos && filter.substr(0, pos) != suite_name) { + continue; + } + } + for(auto& [test_name, test, attrs]: test_cases) { - if(!attrs.focus) { - focus_only_mode = true; + std::string display_name = std::format("{}.{}", suite_name, test_name); + if(pattern && !pattern->match(display_name)) { + continue; + } + + if(attrs.skiped) { + std::println("{}[ SKIPPED ] {}.{}{}", YELLOW, suite_name, test_name, CLEAR); continue; } @@ -74,40 +92,6 @@ int Runner2::run_tests() { } } - if(!focus_only_mode) { - for(auto& [suite_name, test_cases]: all_suites) { - for(auto& [test_name, test, attrs]: test_cases) { - if(attrs.skiped) { - std::println("{}[ SKIPPED ] {}.{}{}", YELLOW, suite_name, test_name, CLEAR); - continue; - } - - std::println("{}[ RUN ] {}.{}{}", GREEN, suite_name, test_name, CLEAR); - - using namespace std::chrono; - auto begin = system_clock::now(); - auto state = test(); - auto end = system_clock::now(); - - bool curr_failed = state == TestState::Failed; - auto duration = duration_cast(end - begin); - std::println("{0}[ {1} ] {2}.{3} ({4} ms){5}", - curr_failed ? RED : GREEN, - curr_failed ? "FAILED" : " OK", - suite_name, - test_name, - duration.count(), - CLEAR); - - curr_test_duration += duration; - total_test_duration += duration; - if(curr_failed) { - total_failed_tests_count += 1; - } - } - } - } - std::println("{}[----------] Global test environment tear-down. {}", GREEN, CLEAR); std::println("{}[==========] {} tests from {} test suites ran. ({} ms total){}", GREEN, diff --git a/tests/unit/Test/Test.cpp b/tests/unit/Test/Test.cpp deleted file mode 100644 index faea946a3..000000000 --- a/tests/unit/Test/Test.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Test/Test2.h" - -namespace clice::testing { - -TEST_SUITE(MyTest) { - int x = 0; - - void setup() { - x += 1; - } - - void teardown() { - x -= 2; - } - - TEST_CASE(World, skip_test) { - x = 3; - /// - return TestState::Skipped; - }; - - TEST_CASE(Hello, focus) { - x = 4; - return TestState::Failed; - }; -}; - -} // namespace clice::testing From cf2a96bb84a3f136d20945947859103eb757ab4b Mon Sep 17 00:00:00 2001 From: ykiko Date: Fri, 28 Nov 2025 22:55:21 +0800 Subject: [PATCH 05/17] some update --- include/Test/Test2.h | 20 +- tests/unit/AST/Resolver.cpp | 2 +- tests/unit/AST/Selection.cpp | 744 ++++++++++++++++++----------------- 3 files changed, 393 insertions(+), 373 deletions(-) diff --git a/include/Test/Test2.h b/include/Test/Test2.h index 320bd58d4..5f9750f39 100644 --- a/include/Test/Test2.h +++ b/include/Test/Test2.h @@ -83,7 +83,7 @@ inline void print_trace(cpptrace::stacktrace& trace, std::source_location locati trace.print(); } -#define TEST_SUITE(name) struct name : TestSuiteDef<#name, name> +#define TEST_SUITE(name) struct name##TEST : TestSuiteDef<#name, name##TEST> #define TEST_CASE(name, ...) \ void _register_##name() { \ @@ -127,4 +127,22 @@ inline void print_trace(cpptrace::stacktrace& trace, std::source_location locati return failure(); \ } +#ifndef ASSERT_NE +#define ASSERT_NE(lhs, rhs) \ + if((lhs) == (rhs)) { \ + auto trace = cpptrace::generate_trace(); \ + clice::testing::print_trace(trace, std::source_location::current()); \ + return failure(); \ + } +#endif + +#ifndef EXPECT_NE +#define EXPECT_NE(lhs, rhs) \ + if((lhs) == (rhs)) { \ + auto trace = cpptrace::generate_trace(); \ + clice::testing::print_trace(trace, std::source_location::current()); \ + failure(); \ + } +#endif + } // namespace clice::testing diff --git a/tests/unit/AST/Resolver.cpp b/tests/unit/AST/Resolver.cpp index 2e1206d71..341e05c8b 100644 --- a/tests/unit/AST/Resolver.cpp +++ b/tests/unit/AST/Resolver.cpp @@ -1,4 +1,4 @@ -#include "Test/Test2.h" +#include "Test/Test.h" #include "Test/Tester.h" #include "clang/AST/RecursiveASTVisitor.h" diff --git a/tests/unit/AST/Selection.cpp b/tests/unit/AST/Selection.cpp index 8385696bf..369f12655 100644 --- a/tests/unit/AST/Selection.cpp +++ b/tests/unit/AST/Selection.cpp @@ -1,3 +1,7 @@ +#include +#include +#include +#include "Test/Test.h" #include "Test/Tester.h" #include "AST/Selection.h" #include "clang/Lex/Lexer.h" @@ -212,450 +216,446 @@ std::optional toHalfOpenFileRange(const SourceManager& SM, } // namespace -suite<"SelectionTree"> selection = [] { - auto select_right = [](llvm::StringRef code, - auto&& callback, - std::source_location location = std::source_location::current()) { - Tester tester; - tester.add_main("main.cpp", code); - fatal / expect(that % tester.compile(), location); - /// expect(that % tester.unit->diagnostics().empty()); - - auto points = tester.nameless_points(); - expect(that % points.size() >= 1, location); - - LocalSourceRange selected_range; - selected_range.begin = points[0]; - selected_range.end = points.size() == 2 ? points[1] : points[0]; - auto tree = SelectionTree::create_right(*tester.unit, selected_range); - callback(tester, tree); - }; - - auto expect_select = [&](llvm::StringRef code, - const char* kind, - std::source_location location = std::source_location::current()) { - select_right( - code, - [&](Tester& tester, SelectionTree& tree) { - auto node = tree.common_ancestor(); - if(!kind) { - expect(that % !node, location); - } else { - expect(that % node, location); - auto range2 = toHalfOpenFileRange(tester.unit->context().getSourceManager(), - tester.unit->lang_options(), - node->source_range()); - LocalSourceRange range = { - tester.unit->file_offset(range2->getBegin()), - tester.unit->file_offset(range2->getEnd()), - }; - - /// llvm::outs() << tree << "\n"; - /// tree.print(llvm::outs(), *node, 2); - - expect(that % node->kind() == llvm::StringRef(kind), location); - expect(that % range == tester.range(), location); - } - }, - location); - }; - - test("Expressions") = [&] { - expect_select(R"( - struct AAA { struct BBB { static int ccc(); };}; - int x = @[AAA::BBB::c$c$c](); - )", - "DeclRefExpr"); - - expect_select(R"( - struct AAA { struct BBB { static int ccc(); };}; - int x = @[AAA::BBB::ccc($)]; - )", - "CallExpr"); +TEST_SUITE(SelectionTree) { - expect_select(R"( - struct S { - int foo() const; - int bar() { return @[f$oo](); } - }; - )", - "MemberExpr"); +template +void select_right(llvm::StringRef code, Callback&& callback) { + Tester tester; + tester.add_main("main.cpp", code); + ASSERT_TRUE(tester.compile()); + /// ASSERT_TRUE(tester.unit->diagnostics().empty()); - expect_select(R"(void foo() { @[$foo](); })", "DeclRefExpr"); - expect_select(R"(void foo() { @[f$oo](); })", "DeclRefExpr"); - expect_select(R"(void foo() { @[fo$o](); })", "DeclRefExpr"); + auto points = tester.nameless_points(); + ASSERT_FALSE(points.empty()); - expect_select(R"(void foo() { @[foo$] (); })", "DeclRefExpr"); - - expect_select(R"(void foo() { @[foo$()]; })", "CallExpr"); - expect_select(R"(void foo() { @[foo$()]; /*comment*/$})", "CallExpr"); - expect_select(R"(const int x = 1, y = 2; int array[ @[$x] ][10][y];)", "DeclRefExpr"); - expect_select(R"(const int x = 1, y = 2; int array[x][10][ @[$y] ];)", "DeclRefExpr"); - expect_select(R"(void func(int x) { int v_array[ @[$x] ][10]; })", "DeclRefExpr"); - expect_select(R"( - int a; - decltype(@[$a] + a) b; - )", - "DeclRefExpr"); - - expect_select(R"( - void func() { @[__$func__]; } - )", - "PredefinedExpr"); - }; - - test("Literals") = [&] { - expect_select(R"( - auto lambda = [](const char*){ return 0; }; - int x = lambda(@["y$"]); - )", - "StringLiteral"); + LocalSourceRange selected_range; + selected_range.begin = points[0]; + selected_range.end = points.size() == 2 ? points[1] : points[0]; + auto tree = SelectionTree::create_right(*tester.unit, selected_range); + std::forward(callback)(tester, tree); +} - expect_select(R"(int x = @[42]$;)", "IntegerLiteral"); - expect_select(R"(const int x = 1, y = 2; int array[x][ @[$10] ][y];)", "IntegerLiteral"); +void expect_select(llvm::StringRef code, const char* kind) { + select_right(code, [&](Tester& tester, SelectionTree& tree) { + auto node = tree.common_ancestor(); + if(!kind) { + ASSERT_FALSE(node); + return; + } - expect_select(R"( - struct Foo{}; - Foo operator""_ud(unsigned long long); - Foo x = @[$12_ud]; - )", - "UserDefinedLiteral"); - }; + ASSERT_TRUE(node); + auto range2 = toHalfOpenFileRange(tester.unit->context().getSourceManager(), + tester.unit->lang_options(), + node->source_range()); + ASSERT_TRUE(range2.has_value()); - test("ControlFlow") = [&] { - expect_select(R"( - void foo() { @[if (1$11) { return; } else {$ }]} } - )", - "IfStmt"); + LocalSourceRange range = { + tester.unit->file_offset(range2->getBegin()), + tester.unit->file_offset(range2->getEnd()), + }; - expect_select(R"(int bar; void foo() @[{ foo (); }]$)", "CompoundStmt"); + /// llvm::outs() << tree << "\n"; + /// tree.print(llvm::outs(), *node, 2); - /// FIXME: - /// expect_select(R"( - /// /*error-ok*/ - /// void func() @[{^])", - /// "CompoundStmt"); + ASSERT_EQ(node->kind(), llvm::StringRef(kind)); + ASSERT_EQ(range, tester.range()); + }); +} - expect_select(R"( - struct Str { - const char *begin(); - const char *end(); - }; - Str makeStr(const char*); - void loop() { - for (const char C : @[mak$eStr("foo"$)]) - ; - } - )", - "CallExpr"); - }; - - test("Declarations") = [&] { - /// FIXME: how to handle this? - /// expect_select(R"( - /// #define TARGET void foo() - /// @[TAR$GET{ return; }] - /// )", - /// "FunctionDecl"); - - expect_select(R"(@[$void foo$()];)", "FunctionDecl"); - expect_select(R"(@[void $foo()];)", "FunctionDecl"); - - expect_select(R"( - struct S { S(const char*); }; - @[S s $= "foo"]; - )", - "VarDecl"); +TEST_CASE(Expressions) { + expect_select(R"( + struct AAA { struct BBB { static int ccc(); };}; + int x = @[AAA::BBB::c$c$c](); + )", + "DeclRefExpr"); + + expect_select(R"( + struct AAA { struct BBB { static int ccc(); };}; + int x = @[AAA::BBB::ccc($)]; + )", + "CallExpr"); + + expect_select(R"( + struct S { + int foo() const; + int bar() { return @[f$oo](); } + }; + )", + "MemberExpr"); + + expect_select(R"(void foo() { @[$foo](); })", "DeclRefExpr"); + expect_select(R"(void foo() { @[f$oo](); })", "DeclRefExpr"); + expect_select(R"(void foo() { @[fo$o](); })", "DeclRefExpr"); + + expect_select(R"(void foo() { @[foo$] (); })", "DeclRefExpr"); + + expect_select(R"(void foo() { @[foo$()]; })", "CallExpr"); + expect_select(R"(void foo() { @[foo$()]; /*comment*/$})", "CallExpr"); + expect_select(R"(const int x = 1, y = 2; int array[ @[$x] ][10][y];)", "DeclRefExpr"); + expect_select(R"(const int x = 1, y = 2; int array[x][10][ @[$y] ];)", "DeclRefExpr"); + expect_select(R"(void func(int x) { int v_array[ @[$x] ][10]; })", "DeclRefExpr"); + expect_select(R"( + int a; + decltype(@[$a] + a) b; + )", + "DeclRefExpr"); - expect_select(R"( - struct S { S(const char*); }; - @[S $s = "foo"]; - )", - "VarDecl"); + expect_select(R"( + void func() { @[__$func__]; } + )", + "PredefinedExpr"); +} - expect_select(R"( - @[void (*$S)(int) = nullptr]; - )", - "VarDecl"); +TEST_CASE(Literals) { + expect_select(R"( + auto lambda = [](const char*){ return 0; }; + int x = lambda(@["y$"]); + )", + "StringLiteral"); + + expect_select(R"(int x = @[42]$;)", "IntegerLiteral"); + expect_select(R"(const int x = 1, y = 2; int array[x][ @[$10] ][y];)", "IntegerLiteral"); + + expect_select(R"( + struct Foo{}; + Foo operator""_ud(unsigned long long); + Foo x = @[$12_ud]; + )", + "UserDefinedLiteral"); +} - expect_select(R"(@[int $a], b;)", "VarDecl"); - expect_select(R"(@[int a, $b];)", "VarDecl"); - expect_select(R"(@[struct {int x;} $y];)", "VarDecl"); - expect_select(R"(struct foo { @[int has$h<:32:>]; };)", "FieldDecl"); - expect_select(R"(struct {@[int $x];} y;)", "FieldDecl"); +TEST_CASE(ControlFlow) { + expect_select(R"( + void foo() { @[if (1$11) { return; } else {$ }]} } + )", + "IfStmt"); + + expect_select(R"(int bar; void foo() @[{ foo (); }]$)", "CompoundStmt"); + + /// FIXME: + /// expect_select(R"( + /// /*error-ok*/ + /// void func() @[{^])", + /// "CompoundStmt"); + + expect_select(R"( + struct Str { + const char *begin(); + const char *end(); + }; + Str makeStr(const char*); + void loop() { + for (const char C : @[mak$eStr("foo"$)]) + ; + } + )", + "CallExpr"); +} - expect_select(R"( +TEST_CASE(Declarations) { + /// FIXME: how to handle this? + /// expect_select(R"( + /// #define TARGET void foo() + /// @[TAR$GET{ return; }] + /// )", + /// "FunctionDecl"); + + expect_select(R"(@[$void foo$()];)", "FunctionDecl"); + expect_select(R"(@[void $foo()];)", "FunctionDecl"); + + expect_select(R"( + struct S { S(const char*); }; + @[S s $= "foo"]; + )", + "VarDecl"); + + expect_select(R"( + struct S { S(const char*); }; + @[S $s = "foo"]; + )", + "VarDecl"); + + expect_select(R"( + @[void (*$S)(int) = nullptr]; + )", + "VarDecl"); + + expect_select(R"(@[int $a], b;)", "VarDecl"); + expect_select(R"(@[int a, $b];)", "VarDecl"); + expect_select(R"(@[struct {int x;} $y];)", "VarDecl"); + expect_select(R"(struct foo { @[int has$h<:32:>]; };)", "FieldDecl"); + expect_select(R"(struct {@[int $x];} y;)", "FieldDecl"); + + expect_select(R"( void test(int bar) { - auto l = [ $@[foo = bar] ] { }; - })", - "VarDecl"); - }; - - test("Types") = [&] { - expect_select(R"( - struct AAA { struct BBB { static int ccc(); };}; - int x = AAA::@[B$B$B]::ccc(); - )", - "RecordTypeLoc"); - expect_select(R"( - struct AAA { struct BBB { static int ccc(); };}; - int x = AAA::@[B$BB$]::ccc(); - )", - "RecordTypeLoc"); - expect_select(R"( - struct Foo {}; - struct Bar : private @[Fo$o] {}; - )", - "RecordTypeLoc"); - expect_select(R"( - struct Foo {}; - struct Bar : @[Fo$o] {}; - )", - "RecordTypeLoc"); - expect_select(R"(@[$void] (*S)(int) = nullptr;)", "BuiltinTypeLoc"); - /// expect_select(R"(@[void (*S)$(int)] = nullptr;)", "FunctionProtoTypeLoc"); - expect_select(R"(@[void ($*S)(int)] = nullptr;)", "PointerTypeLoc"); - /// expect_select(R"(@[void $(*S)(int)] = nullptr;)", "ParenTypeLoc"); - expect_select(R"(@[$void] foo();)", "BuiltinTypeLoc"); - expect_select(R"(@[void foo$()];)", "FunctionProtoTypeLoc"); - expect_select(R"(const int x = 1, y = 2; @[i$nt] array[x][10][y];)", "BuiltinTypeLoc"); - expect_select(R"(int (*getFunc(@[do$uble]))(int);)", "BuiltinTypeLoc"); - expect_select(R"(class X{}; @[int X::$*]y[10];)", "MemberPointerTypeLoc"); - expect_select(R"(const @[a$uto] x = 42;)", "AutoTypeLoc"); - /// expect_select(R"(@[decltype$(1)] b;)", "DecltypeTypeLoc"); - expect_select(R"(@[de$cltype(a$uto)] a = 1;)", "AutoTypeLoc"); - expect_select(R"( + auto l = [ $@[foo = bar] ] { }; + })", + "VarDecl"); +} + +TEST_CASE(Types) { + expect_select(R"( + struct AAA { struct BBB { static int ccc(); };}; + int x = AAA::@[B$B$B]::ccc(); + )", + "RecordTypeLoc"); + expect_select(R"( + struct AAA { struct BBB { static int ccc(); };}; + int x = AAA::@[B$BB$]::ccc(); + )", + "RecordTypeLoc"); + expect_select(R"( + struct Foo {}; + struct Bar : private @[Fo$o] {}; + )", + "RecordTypeLoc"); + expect_select(R"( + struct Foo {}; + struct Bar : @[Fo$o] {}; + )", + "RecordTypeLoc"); + expect_select(R"(@[$void] (*S)(int) = nullptr;)", "BuiltinTypeLoc"); + /// expect_select(R"(@[void (*S)$(int)] = nullptr;)", "FunctionProtoTypeLoc"); + expect_select(R"(@[void ($*S)(int)] = nullptr;)", "PointerTypeLoc"); + /// expect_select(R"(@[void $(*S)(int)] = nullptr;)", "ParenTypeLoc"); + expect_select(R"(@[$void] foo();)", "BuiltinTypeLoc"); + expect_select(R"(@[void foo$()];)", "FunctionProtoTypeLoc"); + expect_select(R"(const int x = 1, y = 2; @[i$nt] array[x][10][y];)", "BuiltinTypeLoc"); + expect_select(R"(int (*getFunc(@[do$uble]))(int);)", "BuiltinTypeLoc"); + expect_select(R"(class X{}; @[int X::$*]y[10];)", "MemberPointerTypeLoc"); + expect_select(R"(const @[a$uto] x = 42;)", "AutoTypeLoc"); + /// expect_select(R"(@[decltype$(1)] b;)", "DecltypeTypeLoc"); + expect_select(R"(@[de$cltype(a$uto)] a = 1;)", "AutoTypeLoc"); + expect_select(R"( typedef int Foo; enum Bar : @[Fo$o] {}; - )", - "TypedefTypeLoc"); - expect_select(R"( + )", + "TypedefTypeLoc"); + expect_select(R"( typedef int Foo; enum Bar : @[Fo$o]; - )", - "TypedefTypeLoc"); - }; + )", + "TypedefTypeLoc"); +} - test("CXXFeatures") = [&] { - expect_select(R"( +TEST_CASE(CXXFeatures) { + expect_select(R"( template int x = @[T::$U::]ccc(); )", - "NestedNameSpecifierLoc"); - expect_select(R"( + "NestedNameSpecifierLoc"); + expect_select(R"( struct Foo {}; struct Bar : @[v$ir$tual private Foo] {}; )", - "CXXBaseSpecifier"); - expect_select(R"( + "CXXBaseSpecifier"); + expect_select(R"( struct X { X(int); }; class Y { X x; Y() : @[$x(4)] {} }; )", - "CXXCtorInitializer"); - expect_select(R"(@[st$ruct {int x;}] y;)", "CXXRecordDecl"); - expect_select(R"(struct foo { @[op$erator int()]; };)", "CXXConversionDecl"); - expect_select(R"(struct foo { @[$~foo()]; };)", "CXXDestructorDecl"); - expect_select(R"(struct foo { @[~$foo()]; };)", "CXXDestructorDecl"); - expect_select(R"(struct foo { @[fo$o(){}] };)", "CXXConstructorDecl"); - expect_select(R"( + "CXXCtorInitializer"); + expect_select(R"(@[st$ruct {int x;}] y;)", "CXXRecordDecl"); + expect_select(R"(struct foo { @[op$erator int()]; };)", "CXXConversionDecl"); + expect_select(R"(struct foo { @[$~foo()]; };)", "CXXDestructorDecl"); + expect_select(R"(struct foo { @[~$foo()]; };)", "CXXDestructorDecl"); + expect_select(R"(struct foo { @[fo$o(){}] };)", "CXXConstructorDecl"); + expect_select(R"( struct S1 { void f(); }; struct S2 { S1 * operator->(); }; void test(S2 s2) { s2@[-$>]f(); } )", - "DeclRefExpr"); // Test for overloaded operator-> - }; + "DeclRefExpr"); // Test for overloaded operator-> +} - test("UsingEnum") = [&] { - expect_select(R"( +TEST_CASE(UsingEnum) { + expect_select(R"( namespace ns { enum class A {}; }; using enum ns::@[$A]; )", - "EnumTypeLoc"); - expect_select(R"( + "EnumTypeLoc"); + expect_select(R"( namespace ns { enum class A {}; using B = A; }; using enum ns::@[$B]; )", - "TypedefTypeLoc"); - expect_select(R"( + "TypedefTypeLoc"); + expect_select(R"( namespace ns { enum class A {}; }; using enum @[$ns::]A; )", - "NestedNameSpecifierLoc"); - expect_select(R"( + "NestedNameSpecifierLoc"); + expect_select(R"( namespace ns { enum class A {}; }; @[using $enum ns::A]; )", - "UsingEnumDecl"); - expect_select(R"( + "UsingEnumDecl"); + expect_select(R"( namespace ns { enum class A {}; }; @[$using enum ns::A]; )", - "UsingEnumDecl"); - }; - - test("Templates") = [&] { - expect_select(R"(template void foo(@[T*$...]x);)", "PackExpansionTypeLoc"); - expect_select(R"(template void foo(@[$T]*...x);)", - "TemplateTypeParmTypeLoc"); - expect_select(R"(template void foo() { @[$T] t; })", - "TemplateTypeParmTypeLoc"); - expect_select(R"( + "UsingEnumDecl"); +} + +TEST_CASE(Templates) { + expect_select(R"(template void foo(@[T*$...]x);)", "PackExpansionTypeLoc"); + expect_select(R"(template void foo(@[$T]*...x);)", "TemplateTypeParmTypeLoc"); + expect_select(R"(template void foo() { @[$T] t; })", "TemplateTypeParmTypeLoc"); + expect_select(R"( template struct Foo {}; template <@[template class /*cursor here*/$U]> struct Foo*> {}; )", - "TemplateTemplateParmDecl"); - expect_select(R"(template struct foo { ~foo<@[$T]>(){} };)", - "TemplateTypeParmTypeLoc"); - expect_select(R"( + "TemplateTemplateParmDecl"); + expect_select(R"(template struct foo { ~foo<@[$T]>(){} };)", + "TemplateTypeParmTypeLoc"); + expect_select(R"( template class Vector {}; template