Skip to content

Commit 4f40e7a

Browse files
committed
feat userver: add checks for compilation error messages
commit_hash:bed8161755c3e2cbe0fd1a3449e777ba8cc1fe79
1 parent 75cd325 commit 4f40e7a

File tree

6 files changed

+246
-2
lines changed

6 files changed

+246
-2
lines changed

.mapping.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4292,6 +4292,7 @@
42924292
"scripts/build_and_install_all.sh":"taxi/uservices/userver/scripts/build_and_install_all.sh",
42934293
"scripts/chaotic/requirements.txt":"taxi/uservices/userver/scripts/chaotic/requirements.txt",
42944294
"scripts/clickhouse/ubuntu-install-clickhouse.sh":"taxi/uservices/userver/scripts/clickhouse/ubuntu-install-clickhouse.sh",
4295+
"scripts/compile_fail_test.py":"taxi/uservices/userver/scripts/compile_fail_test.py",
42954296
"scripts/create-service-test-helper":"taxi/uservices/userver/scripts/create-service-test-helper",
42964297
"scripts/debian-rules":"taxi/uservices/userver/scripts/debian-rules",
42974298
"scripts/docker/Readme.md":"taxi/uservices/userver/scripts/docker/Readme.md",
@@ -5617,6 +5618,7 @@
56175618
"universal/src/utils/mock_now.cpp":"taxi/uservices/userver/universal/src/utils/mock_now.cpp",
56185619
"universal/src/utils/mock_now_test.cpp":"taxi/uservices/userver/universal/src/utils/mock_now_test.cpp",
56195620
"universal/src/utils/move_only_function_test.cpp":"taxi/uservices/userver/universal/src/utils/move_only_function_test.cpp",
5621+
"universal/src/utils/not_null_compilefailtest.cpp":"taxi/uservices/userver/universal/src/utils/not_null_compilefailtest.cpp",
56205622
"universal/src/utils/not_null_test.cpp":"taxi/uservices/userver/universal/src/utils/not_null_test.cpp",
56215623
"universal/src/utils/numeric_cast_test.cpp":"taxi/uservices/userver/universal/src/utils/numeric_cast_test.cpp",
56225624
"universal/src/utils/optional_ref_test.cpp":"taxi/uservices/userver/universal/src/utils/optional_ref_test.cpp",
@@ -5629,6 +5631,7 @@
56295631
"universal/src/utils/regex_test.cpp":"taxi/uservices/userver/universal/src/utils/regex_test.cpp",
56305632
"universal/src/utils/resources.cpp":"taxi/uservices/userver/universal/src/utils/resources.cpp",
56315633
"universal/src/utils/scope_guard_test.cpp":"taxi/uservices/userver/universal/src/utils/scope_guard_test.cpp",
5634+
"universal/src/utils/shared_readable_ptr_compilefailtest.cpp":"taxi/uservices/userver/universal/src/utils/shared_readable_ptr_compilefailtest.cpp",
56325635
"universal/src/utils/shared_readable_ptr_test.cpp":"taxi/uservices/userver/universal/src/utils/shared_readable_ptr_test.cpp",
56335636
"universal/src/utils/small_string_benchmark.cpp":"taxi/uservices/userver/universal/src/utils/small_string_benchmark.cpp",
56345637
"universal/src/utils/small_string_test.cpp":"taxi/uservices/userver/universal/src/utils/small_string_test.cpp",

scripts/compile_fail_test.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/usr/bin/python3
2+
3+
from collections.abc import Callable
4+
from collections.abc import Iterable
5+
import dataclasses
6+
import json
7+
import re
8+
import subprocess
9+
import sys
10+
11+
12+
def make_compile_commands(path: str) -> list[dict]:
13+
output = subprocess.check_output(
14+
['ya', 'dump', 'compile-commands'],
15+
cwd=path,
16+
encoding='utf-8',
17+
stderr=subprocess.DEVNULL,
18+
)
19+
return json.loads(output)
20+
21+
22+
def read_compile_commands() -> list[dict]:
23+
with open('compile_commands.json') as ifile:
24+
return json.load(ifile)
25+
26+
27+
def compile_command_yamake(cc_rule: dict) -> str:
28+
cmd = cc_rule['command']
29+
30+
# clang++ ... -> ya tool c++ ...
31+
cmd = 'ya tool c++ ' + cmd.split(' ', 1)[1]
32+
33+
# remove '-o xxx.cpp.o'
34+
cmd = re.sub(' -o [^ ]* ', ' -o /dev/null ', cmd)
35+
36+
cmd = re.sub(' -fcolor', ' -fno-color', cmd)
37+
38+
return cmd
39+
40+
41+
def compile_command_cmake(cc_rule: dict) -> str:
42+
cmd = cc_rule['command']
43+
44+
# remove '-o xxx.cpp.o'
45+
cmd = re.sub(' -o [^ ]* ', ' -o /dev/null ', cmd)
46+
47+
cmd = re.sub(' -fcolor', ' -fno-color', cmd)
48+
49+
return cmd
50+
51+
52+
@dataclasses.dataclass
53+
class Message:
54+
line: int
55+
text: str
56+
57+
58+
FAIL_RE = re.compile(r'.*FAIL\(([^)]*)\).*')
59+
FAIL_NEXT_RE = re.compile(r'.*FAILNEXTLINE\(([^)]*)\).*')
60+
61+
62+
def read_file_messages(filename: str) -> list[Message]:
63+
result = []
64+
65+
with open(filename) as ifile:
66+
for linenum, line in enumerate(ifile):
67+
match = FAIL_RE.match(line)
68+
if match:
69+
result.append(Message(line=linenum + 1, text=match.group(1)))
70+
71+
match = FAIL_NEXT_RE.match(line)
72+
if match:
73+
result.append(Message(line=linenum + 2, text=match.group(1)))
74+
75+
return result
76+
77+
78+
def find_if(collection: Iterable, pred: Callable):
79+
for item in collection:
80+
if pred(item):
81+
return item
82+
return None
83+
84+
85+
class CheckFailure(Exception):
86+
pass
87+
88+
89+
def handle_rule(cc_rule: dict) -> None:
90+
cpp = cc_rule['file']
91+
92+
cmd = compile_command_cmake(cc_rule)
93+
stderr, errors = compile_for_errors(cmd)
94+
95+
asserts = []
96+
97+
expected_msgs = read_file_messages(cpp)
98+
for msg in expected_msgs:
99+
# search for FAIL(...) line
100+
if not find_if(errors, lambda x: x.file == cpp and x.line == msg.line):
101+
asserts.append(f'Expected to get a compilation error/note at {cpp}:{msg.line}, but failed.')
102+
continue
103+
104+
# search for FAIL(...) message
105+
if not find_if(errors, lambda x: msg.text in x.text):
106+
asserts.append(f'Expected to get a compilation error with text "{msg.text}", but failed.')
107+
108+
if not asserts:
109+
return
110+
111+
for line in asserts:
112+
print('error: ', line)
113+
114+
print('\nexpected errors:')
115+
for msg in expected_msgs:
116+
print(f' {cpp}:{msg.line}: {msg.text}')
117+
118+
print('\nactual compiler output:')
119+
print(stderr)
120+
121+
raise CheckFailure()
122+
123+
124+
@dataclasses.dataclass
125+
class CompilationMessage:
126+
file: str
127+
line: int
128+
level: str
129+
text: str
130+
131+
orig_text: str
132+
133+
134+
def compile_for_errors(cmd: str) -> tuple[str, list[CompilationMessage]]:
135+
proc = subprocess.run(
136+
cmd,
137+
shell=True,
138+
encoding='utf-8',
139+
capture_output=True,
140+
)
141+
142+
result = []
143+
stderr = proc.stderr
144+
for line in stderr.splitlines():
145+
if not line.startswith('/'):
146+
continue
147+
148+
parts = line.split(':', 4)
149+
150+
try:
151+
linenum = int(parts[1])
152+
except ValueError:
153+
continue
154+
155+
if len(parts) >= 5:
156+
level = parts[3].strip()
157+
text = parts[4].strip()
158+
else:
159+
level = '<none>'
160+
text = parts[3].strip()
161+
162+
result.append(
163+
CompilationMessage(
164+
file=parts[0],
165+
line=linenum,
166+
level=level,
167+
text=text,
168+
orig_text=line,
169+
)
170+
)
171+
return stderr, result
172+
173+
174+
def matches_prefix(rule, src_prefix):
175+
file = rule['file']
176+
if not file.startswith(src_prefix):
177+
return False
178+
179+
return file.endswith('_compilefailtest.cpp')
180+
181+
182+
def main() -> None:
183+
src_prefix = sys.argv[1]
184+
cc = read_compile_commands()
185+
186+
status = 0
187+
for rule in cc:
188+
if not matches_prefix(rule, src_prefix):
189+
continue
190+
191+
try:
192+
handle_rule(rule)
193+
except CheckFailure:
194+
status = 1
195+
return status
196+
197+
198+
sys.exit(main())

universal/CMakeLists.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp ${CMAKE_CURR
1616
)
1717

1818
file(GLOB_RECURSE UNIT_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*_test.cpp)
19+
file(GLOB_RECURSE COMPILEFAIL_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*_compilefailtest.cpp)
1920
file(GLOB_RECURSE BENCH_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*_benchmark.cpp)
2021
file(GLOB_RECURSE INTERNAL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/internal/*.cpp
2122
${CMAKE_CURRENT_SOURCE_DIR}/internal/*.hpp
2223
)
23-
list(REMOVE_ITEM SOURCES ${UNIT_TEST_SOURCES} ${BENCH_SOURCES} ${INTERNAL_SOURCES})
24+
list(REMOVE_ITEM SOURCES ${UNIT_TEST_SOURCES} ${COMPILEFAIL_TEST_SOURCES} ${BENCH_SOURCES} ${INTERNAL_SOURCES})
2425

2526
set(CMAKE_THREAD_PREFER_PTHREAD ON)
2627
set(THREADS_PREFER_PTHREAD_FLAG ON)
@@ -388,6 +389,18 @@ if(USERVER_BUILD_TESTS)
388389
endif()
389390

390391
add_google_benchmark_tests(${PROJECT_NAME}-benchmark)
392+
393+
add_library(${PROJECT_NAME}-compile-fail EXCLUDE_FROM_ALL ${COMPILEFAIL_TEST_SOURCES})
394+
target_link_libraries(
395+
${PROJECT_NAME}-compile-fail
396+
PUBLIC ${PROJECT_NAME}
397+
)
398+
add_test(
399+
NAME ${PROJECT_NAME}-compile-err-msg-test
400+
COMMAND
401+
python3 ${USERVER_ROOT_DIR}/scripts/compile_fail_test.py ${CMAKE_CURRENT_SOURCE_DIR}
402+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
403+
)
391404
endif()
392405

393406
_userver_directory_install(

universal/include/userver/utils/not_null.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace utils {
2020
template <typename T>
2121
class NotNull {
2222
static_assert(!std::is_reference_v<T>, "NotNull does not work with references");
23-
static_assert(!std::is_const_v<T>);
23+
static_assert(!std::is_const_v<T>, "NotNull does not work with const T");
2424

2525
public:
2626
constexpr explicit NotNull() = delete;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <userver/utils/not_null.hpp>
2+
3+
USERVER_NAMESPACE_BEGIN
4+
5+
void f() {
6+
// FAILNEXTLINE(NotNull does not work with references)
7+
utils::NotNull<int&> ref;
8+
9+
// FAILNEXTLINE(NotNull does not work with const T)
10+
utils::NotNull<const int> const_int;
11+
}
12+
13+
void g() {
14+
// FAILNEXTLINE(NotNull does not work with references)
15+
utils::NotNull<int&> ref;
16+
}
17+
18+
USERVER_NAMESPACE_END
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <userver/utils/shared_readable_ptr.hpp>
2+
3+
USERVER_NAMESPACE_BEGIN
4+
5+
utils::SharedReadablePtr<int> GetSharedPtr() { return std::make_shared<const int>(42); }
6+
7+
void test_dereference_rvalue() {
8+
// FAILNEXTLINE(keep the pointer before using, please)
9+
[[maybe_unused]] int guard = *GetSharedPtr();
10+
}
11+
12+
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)