Skip to content

Commit b8b5c37

Browse files
committed
[libc++] Add a utility for checking the output of commands
1 parent 3bf91ad commit b8b5c37

File tree

8 files changed

+231
-2
lines changed

8 files changed

+231
-2
lines changed

libcxx/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ add_custom_target(install-cxx-test-suite-prefix
4242
cxx
4343
cxx_experimental
4444
cxx-modules
45+
check_output
4546
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIBCXX_TESTING_INSTALL_PREFIX}"
4647
COMMAND "${CMAKE_COMMAND}"
4748
-DCMAKE_INSTALL_COMPONENT=cxx-headers

libcxx/test/configs/cmake-bridge.cfg.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ config.substitutions.append(('%{lib-dir}', '@LIBCXX_TESTING_INSTALL_PREFIX@/@LIB
3131
config.substitutions.append(('%{module-dir}', '@LIBCXX_TESTING_INSTALL_PREFIX@/@LIBCXX_INSTALL_MODULES_DIR@'))
3232
config.substitutions.append(('%{test-tools-dir}', '@LIBCXX_TEST_TOOLS_PATH@'))
3333
config.substitutions.append(('%{benchmark_flags}', '-I @LIBCXX_BINARY_DIR@/test/benchmarks/google-benchmark/include -L @LIBCXX_BINARY_DIR@/test/benchmarks/google-benchmark/lib -L @LIBCXX_BINARY_DIR@/test/benchmarks/google-benchmark/lib64 -l benchmark'))
34+
config.substitutions.append(('%{check-output}', os.path.join('@CMAKE_BINARY_DIR@', 'bin/check_output') + " %s"))
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===----------------------------------------------------------------------===//
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+
// This test is checking the LLVM IR
10+
// REQUIRES: clang
11+
12+
// RUN: %{cxx} %s %{compile_flags} -O3 -c -S -emit-llvm -o - | %{check-output}
13+
14+
#include <algorithm>
15+
16+
int* test1(int* first, int* last, int* out) {
17+
// CHECK: define dso_local void
18+
// CHECK-SAME: test
19+
// CHECK: tail call void @llvm.memmove
20+
return std::copy(first, last, out);
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
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+
// REQUIRES: has-clang-tidy
10+
11+
// RUN: %{clang-tidy} %s -header-filter=.* --checks='-*,libcpp-cpp-version-check' --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin -- %{compile_flags} -fno-modules 2>&1 | %{check-output}
12+
13+
#include <__config>
14+
15+
// CHECK: warning: _LIBCPP_STD_VER >= version should be used instead of _LIBCPP_STD_VER > prev_version
16+
#if _LIBCPP_STD_VER > 14
17+
#endif
18+
19+
// CHECK: warning: Use _LIBCPP_STD_VER instead of __cplusplus to constrain based on the C++ version
20+
#if __cplusplus >= 201103L
21+
#endif
22+
23+
// CHECK: warning: _LIBCPP_STD_VER >= 11 is always true. Did you mean '#ifndef _LIBCPP_CXX03_LANG'?
24+
#if _LIBCPP_STD_VER >= 11
25+
#endif
26+
27+
// CHECK: warning: Not a valid value for _LIBCPP_STD_VER. Use 14, 17, 20, 23, or 26
28+
#if _LIBCPP_STD_VER >= 12
29+
#endif
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//===----------------------------------------------------------------------===//
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+
// This test is checking the LLVM IR
10+
// REQUIRES: clang
11+
12+
// RUN: %{cxx} %s %{compile_flags} -Wno-missing-noreturn -O3 -c -S -emit-llvm -o - | %{check-output}
13+
14+
#include <utility>
15+
16+
void test() {
17+
// CHECK: define dso_local void
18+
// CHECK-SAME: test
19+
// CHECK-NEXT: unreachable
20+
// CHECK-NEXT: }
21+
std::unreachable();
22+
}

libcxx/test/tools/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ set(LIBCXX_TEST_TOOLS_PATH ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE)
33

44
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
55
message(STATUS "Clang-tidy tests are disabled due to non-clang based compiler.")
6-
return()
6+
else()
7+
add_subdirectory(clang_tidy_checks)
78
endif()
8-
add_subdirectory(clang_tidy_checks)
9+
10+
add_subdirectory(check_output)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
add_executable(check_output check_output.cpp)
3+
4+
# Link against libc++
5+
if (TARGET cxx_shared)
6+
target_link_libraries(check_output PRIVATE cxx_shared)
7+
elseif (TARGET cxx_static)
8+
target_link_libraries(check_output PRIVATE cxx_static)
9+
else()
10+
message(FATAL "Neither cxx_shared nor cxx_static are available to be linked against")
11+
endif()
12+
13+
# put the binary into <build>/bin
14+
set_target_properties(check_output PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
15+
16+
# set the language mode
17+
set_target_properties(check_output PROPERTIES
18+
CXX_STANDARD 23
19+
CXX_STANDARD_REQUIRED YES
20+
CXX_EXTENSIONS NO)
21+
22+
target_compile_options(check_output PRIVATE -Werror=missing-prototypes)
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//===----------------------------------------------------------------------===//
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 <algorithm>
10+
#include <fstream>
11+
#include <functional>
12+
#include <iostream>
13+
#include <iterator>
14+
#include <print>
15+
#include <ranges>
16+
#include <string>
17+
18+
using namespace std::string_view_literals;
19+
20+
enum class Result {
21+
success,
22+
mismatch,
23+
no_match_found,
24+
unknown_matcher,
25+
invalid_use,
26+
};
27+
28+
namespace co {
29+
namespace {
30+
[[noreturn]] void exit(Result result) { std::exit(static_cast<int>(result)); }
31+
32+
[[noreturn]] void print_failure(int line, std::string_view stdin_content, std::string_view matcher) {
33+
std::print("Failed to match: `{}`\nRemaining data:\n{}", matcher, stdin_content);
34+
co::exit(Result::mismatch);
35+
}
36+
37+
bool is_newline(char c) { return c == '\n'; }
38+
39+
bool isblank(char c) { return std::isblank(c); }
40+
41+
bool consume_front(std::string_view& sv, std::string_view start) {
42+
if (!sv.starts_with(start))
43+
return false;
44+
sv.remove_prefix(start.size());
45+
return true;
46+
}
47+
} // namespace
48+
} // namespace co
49+
50+
int main(int argc, char** argv) {
51+
if (argc != 2) {
52+
std::print(stderr, "check_output has to be used as `<command> | ./check_output %s`\n");
53+
co::exit(Result::invalid_use);
54+
}
55+
56+
std::string file_content_data = [&] {
57+
std::ifstream file(argv[1]);
58+
if (!file) {
59+
std::print(stderr, "Failed to open file: {}\n", argv[1]);
60+
co::exit(Result::invalid_use);
61+
}
62+
return std::string{std::istreambuf_iterator<char>{file}, {}};
63+
}();
64+
std::string_view file_content = file_content_data; // Don't copy the data around all the time
65+
66+
std::string stdin_content_data = [&] {
67+
std::cin >> std::noskipws;
68+
return std::string{std::istream_iterator<char>{std::cin}, {}};
69+
}();
70+
std::string_view stdin_content = stdin_content_data; // Don't copy the data around all the time
71+
72+
size_t match_count = 0;
73+
auto drop_blanks = std::views::drop_while(co::isblank);
74+
75+
while (!file_content.empty()) {
76+
auto marker = std::ranges::search(file_content, "// CHECK"sv);
77+
if (marker.empty()) {
78+
if (match_count == 0) {
79+
std::print(stderr, "No matcher found!\n");
80+
co::exit(Result::no_match_found);
81+
}
82+
co::exit(Result::success);
83+
}
84+
file_content.remove_prefix(marker.end() - file_content.begin());
85+
86+
const auto get_match = [&]() {
87+
return std::string_view(file_content.begin(), std::ranges::find(file_content, '\n'));
88+
};
89+
90+
if (co::consume_front(file_content, ":")) {
91+
auto match = get_match();
92+
auto found = std::ranges::search(
93+
stdin_content | std::views::drop_while(std::not_fn(co::is_newline)) | std::views::drop(1),
94+
match | drop_blanks);
95+
if (found.empty()) {
96+
co::print_failure(1, stdin_content, match);
97+
}
98+
++match_count;
99+
stdin_content.remove_prefix(found.end() - stdin_content.begin());
100+
} else if (co::consume_front(file_content, "-SAME:")) {
101+
auto match = get_match();
102+
auto haystack = std::string_view(stdin_content.begin(), std::ranges::find(stdin_content, '\n'));
103+
auto found = std::ranges::search(haystack, match | drop_blanks);
104+
if (found.empty()) {
105+
co::print_failure(1, stdin_content, match);
106+
}
107+
stdin_content.remove_prefix(found.end() - stdin_content.begin());
108+
} else if (co::consume_front(file_content, "-NEXT:")) {
109+
auto match = get_match();
110+
auto haystack = [&] {
111+
auto begin = std::ranges::find(stdin_content, '\n');
112+
if (begin == stdin_content.end()) {
113+
co::print_failure(1, stdin_content, match);
114+
}
115+
++begin;
116+
return std::string_view(begin, std::ranges::find(begin, stdin_content.end(), '\n'));
117+
}();
118+
auto found = std::ranges::search(haystack, match | drop_blanks);
119+
if (found.empty())
120+
co::print_failure(1, stdin_content, match);
121+
stdin_content.remove_prefix(found.end() - stdin_content.begin());
122+
} else {
123+
std::print(stderr,
124+
"Unkown matcher type {} found",
125+
std::string_view(file_content.begin(), std::ranges::find(file_content, ':')));
126+
co::exit(Result::unknown_matcher);
127+
}
128+
}
129+
130+
co::exit(Result::success);
131+
}

0 commit comments

Comments
 (0)