SERVER-114770 re-enable clang tidy checks on 8.0 (#44824)

GitOrigin-RevId: 7ef389c4166b3d100799c7f96fd59165319b39a7
This commit is contained in:
Daniel Moody
2025-12-05 11:22:27 -06:00
committed by MongoDB Bot
parent 29e14c65c1
commit ac29497b08
47 changed files with 646 additions and 378 deletions

View File

@@ -8,6 +8,7 @@ src/third_party/wiredtiger/dist
bazel/auto_header/.auto_header
src/mongo/db/modules/atlas/.auto_header
src/mongo/db/modules/enterprise/.auto_header
.git
# Ignore node_modules due to the following error
# ERROR: in verify_node_modules_ignored:

View File

@@ -86,8 +86,10 @@ common --strategy=CcGenerateDwp=local
common --modify_execution_info=^(TestRunner|CppLink|CppArchive|SolibSymlink|ExtractDebuginfo|StripDebuginfo|CcGenerateIntermediateDwp|CcGenerateDwp)$=+no-remote-cache
# Global clang tidy flags to avoid invalidating analysis cache between clang-tidy / regular runs
common --@bazel_clang_tidy//:clang_tidy_excludes=".pb.cc,icu_init.cpp"
common --@bazel_clang_tidy//:clang_tidy_excludes=".pb.cc,icu_init.cpp,parser_gen.cpp"
common --@bazel_clang_tidy//:clang_tidy_additional_configs=//:clang_tidy_config_files
common --@bazel_clang_tidy//:clang_tidy_config=//:clang_tidy_config_strict
common --@bazel_clang_tidy//:clang_tidy_merge_tool=//bazel:merge_tidy_configs
common --@bazel_clang_tidy//:clang_tidy_executable=//:clang_tidy
common --@bazel_clang_tidy//:clang_tidy_additional_deps=//:toolchain_files
common --@bazel_clang_tidy//:clang_tidy_plugin_deps=//src/mongo/tools/mongo_tidy_checks:mongo_tidy_checks
@@ -419,6 +421,7 @@ common:clang-tidy --//bazel/config:compiler_type=clang
common:clang-tidy --keep_going
common:clang-tidy --aspects @bazel_clang_tidy//clang_tidy:clang_tidy.bzl%clang_tidy_aspect
common:clang-tidy --output_groups=report
common:clang-tidy --//bazel/config:use_diagnostic_latches=True
--config=mod-scanner
common:mod-scanner --config=dbg

View File

@@ -134,13 +134,12 @@ Checks: '-*,
-readability-string-compare,
-readability-uniqueptr-delete-release
'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*|@MONGO_BUILD_DIR@/.*)'
HeaderFilterRegex: 'mongo/[-A-Za-z0-9_/]*\.(h|hpp|ipp|inl|cstruct.h|defs|def.h)'
CheckOptions:
- key: bugprone-assert-side-effect.AssertMacros
value: assert
- key: mongo-header-bracket-check.mongoSourceDirs
value: 'src/mongo;@MONGO_BUILD_DIR@'
value: 'src/mongo;@MONGO_BRACKET_BUILD_DIR@'
- key: mongo-collection-sharding-runtime-check.exceptionDirs
value: 'src/mongo/db/s'
- key: bugprone-assert-side-effect.CheckFunctionCalls

5
.gitignore vendored
View File

@@ -290,6 +290,7 @@ buildozer
.compiledb
.bazelrc.xcode
.bazelrc.bazelisk
.bazelrc.wrapper_hook
*.bazel_info_for_ninja.txt
.ninja_last_command_line_targets.txt
bazel/coverity/analysis/BUILD.bazel
@@ -297,8 +298,8 @@ bazel/coverity/analysis/BUILD.bazel
.bazel_header_list_cache
.bazel_real
.mongo_checks_module_path
MODULE.bazel
MODULE.bazel.lock
**/MODULE.bazel.lock
!/MODULE.bazel.lock
.auto_header
# generated configs for external fixture suites

View File

@@ -448,3 +448,8 @@ sh_binary(
"BINARY_PATH": "$(location @scip-src//file)",
},
)
filegroup(
name = "clang_tidy_config_files",
srcs = [],
)

View File

@@ -15,12 +15,12 @@ http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_
http_archive(
name = "bazel_clang_tidy",
integrity = "sha256-A+TGGfuHdS7vWT20eyAEiA3u4YXFtuzsJDcpTEmDMS0=",
strip_prefix = "bazel_clang_tidy-10c4bf70a8946789e3932f30e29dfba2dfb78e67",
integrity = "sha256-Q5LQ/bAvLIu1kNhnkthNROFxToui376QRiPlYU9uhZU=",
strip_prefix = "bazel_clang_tidy-33c9013349b6178897598e67929201356b0ad5ea",
urls = [
# Implements retry by relisting each url multiple times to be used as a failover.
# TODO(SERVER-86719): Re-implement http_archive to allow sleeping between retries
"https://github.com/mongodb-forks/bazel_clang_tidy/archive/10c4bf70a8946789e3932f30e29dfba2dfb78e67.tar.gz",
"https://github.com/mongodb-forks/bazel_clang_tidy/archive/33c9013349b6178897598e67929201356b0ad5ea.tar.gz",
] * 5,
)

View File

@@ -1,3 +1,5 @@
load("@poetry//:dependencies.bzl", "dependency")
package(default_visibility = ["//visibility:public"])
# Expose script for external usage through bazel.
@@ -16,3 +18,18 @@ py_binary(
name = "bazelisk",
srcs = ["bazelisk.py"],
)
py_binary(
name = "merge_tidy_configs",
srcs = [
"merge_tidy_configs.py",
],
main = "merge_tidy_configs.py",
visibility = ["//visibility:public"],
deps = [
dependency(
"pyyaml",
group = "core",
),
],
)

187
bazel/merge_tidy_configs.py Normal file
View File

@@ -0,0 +1,187 @@
"""
Merge clang-tidy config files:
- Baseline + zero or more additional config files.
- Checks: concatenated left→right (later entries can disable earlier via -pattern).
- CheckOptions: merged by key (later configs override).
- Other keys: deep-merge (dicts) or last-wins (scalars).
Requires: PyYAML
"""
from __future__ import annotations
import argparse
import pathlib
from typing import Any, Dict, List
import yaml
# --------------------------- YAML helpers ---------------------------
def load_yaml(file_path: str | pathlib.Path) -> Dict[str, Any]:
path = pathlib.Path(file_path)
if not path.exists():
raise SystemExit(f"Error: Config file '{file_path}' not found.")
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
def split_checks_to_list(value: Any) -> List[str]:
"""Normalize a Checks value (string or list) into a flat list of tokens."""
if value is None:
return []
parts: List[str] = []
if isinstance(value, str):
parts = [s.strip() for s in value.split(",")]
elif isinstance(value, list):
for item in value:
parts.extend([s.strip() for s in str(item).split(",")])
return [s for s in parts if s]
def merge_checks_into_config(
target_config: Dict[str, Any], incoming_config: Dict[str, Any]
) -> None:
"""Append incoming Checks onto target Checks (string-concatenated)."""
accumulated = split_checks_to_list(target_config.get("Checks"))
additions = split_checks_to_list(incoming_config.get("Checks"))
if additions:
target_config["Checks"] = ",".join(accumulated + additions)
def check_options_list_to_map(value: Any) -> Dict[str, Any]:
"""Transform CheckOptions list[{key,value}] into a dict[key]=value."""
out: Dict[str, Any] = {}
if isinstance(value, list):
for item in value:
if isinstance(item, dict) and "key" in item:
out[item["key"]] = item.get("value")
return out
def merge_check_options_into_config(
target_config: Dict[str, Any], incoming_config: Dict[str, Any]
) -> None:
"""
Merge CheckOptions so later configs override earlier by 'key'.
Stores back as list[{key,value}] sorted by key for determinism.
"""
base = check_options_list_to_map(target_config.get("CheckOptions"))
override = check_options_list_to_map(incoming_config.get("CheckOptions"))
if override:
base.update(override) # later wins
target_config["CheckOptions"] = [{"key": k, "value": v} for k, v in sorted(base.items())]
def deep_merge_dicts(base: Any, override: Any) -> Any:
"""Generic deep merge for everything except Checks/CheckOptions."""
if isinstance(base, dict) and isinstance(override, dict):
result = dict(base)
for key, o_val in override.items():
if key in ("Checks", "CheckOptions"):
# handled by specialized mergers
continue
b_val = result.get(key)
if isinstance(b_val, dict) and isinstance(o_val, dict):
result[key] = deep_merge_dicts(b_val, o_val)
else:
result[key] = o_val
return result
return override
# --------------------------- path helpers ---------------------------
def is_ancestor_directory(ancestor: pathlib.Path, descendant: pathlib.Path) -> bool:
"""
True if 'ancestor' is the same as or a parent of 'descendant'.
Resolution ensures symlinks and relative parts are normalized.
"""
try:
ancestor = ancestor.resolve()
descendant = descendant.resolve()
except FileNotFoundError:
# If either path doesn't exist yet, still resolve purely lexicaly
ancestor = ancestor.absolute()
descendant = descendant.absolute()
return ancestor == descendant or ancestor in descendant.parents
def filter_and_sort_config_paths(
config_paths: list[str | pathlib.Path], scope_directory: str | None
) -> list[pathlib.Path]:
"""
Keep only config files whose parent directory is an ancestor
of the provided scope directory.
Sort shallow → deep so deeper configs apply later and override earlier ones.
If scope_directory is None, keep paths in the order given.
"""
config_paths = [pathlib.Path(p) for p in config_paths]
if not scope_directory:
return config_paths
workspace_root = pathlib.Path.cwd().resolve()
scope_abs = (workspace_root / scope_directory).resolve()
selected: list[tuple[int, pathlib.Path]] = []
for cfg in config_paths:
parent_dir = cfg.parent
if is_ancestor_directory(parent_dir, scope_abs):
# Depth is number of path components from root
selected.append((len(parent_dir.parts), cfg.resolve()))
# Sort by depth ascending so root-most files merge first
selected.sort(key=lambda t: t[0])
return [cfg for _, cfg in selected]
# --------------------------- main ---------------------------
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--baseline", required=True, help="Baseline clang-tidy YAML.")
parser.add_argument(
"--config-file",
dest="config_files",
action="append",
default=[],
help="Additional clang-tidy config file(s). May be repeated.",
)
parser.add_argument(
"--scope-dir",
help="Repo-relative directory; only config files in its ancestor dirs are merged.",
)
parser.add_argument("--out", required=True, help="Output merged YAML path.")
args = parser.parse_args()
merged_config: Dict[str, Any] = load_yaml(args.baseline)
config_paths: List[pathlib.Path] = filter_and_sort_config_paths(
args.config_files, args.scope_dir
)
for config_path in config_paths:
incoming_config = load_yaml(config_path)
# clang-tidy special merges first:
merge_checks_into_config(merged_config, incoming_config)
merge_check_options_into_config(merged_config, incoming_config)
# then generic merge:
merged_config = deep_merge_dicts(merged_config, incoming_config)
merged_config["Checks"] = ",".join(split_checks_to_list(merged_config.get("Checks")))
output_path = pathlib.Path(args.out)
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
yaml.safe_dump(merged_config, f, sort_keys=True)
if __name__ == "__main__":
main()

View File

@@ -69,7 +69,7 @@ commit_queue_aliases:
variant_tags: []
task_tags: []
- variant: "^(amazon-linux2023-arm64-dynamic-compile|linux-x86-dynamic-compile-required)$"
task: "clang_tidy.*"
task: "^bazel_run_clang_tidy$"
variant_tags: []
task_tags: []
@@ -107,6 +107,7 @@ post:
- func: "attach task errors"
- func: "attach artifacts"
- func: "attach multiversion download links"
- func: "generate clang-tidy report"
- func: "save ec2 task artifacts"
- func: "attach wiki page"
- func: "upload docker compose"

View File

@@ -631,16 +631,33 @@ functions:
content_type: text/plain
display_name: Pip Requirements
"run clang tidy": &run_clang_tidy
- command: subprocess.exec
params:
binary: bash
add_expansions_to_env: true
args:
"generate clang-tidy report sh": &generate_clang_tidy_report_sh
command: subprocess.exec
display_name: "generate clang-tidy report"
type: test
params:
binary: bash
args:
- "src/evergreen/run_python_script.sh"
- "buildscripts/clang_tidy.py"
- "--split-jobs=4"
- "--split=${split}"
- "evergreen/generate_clang_tidy_report.py"
"enable run_for_clang_tidy expansions update":
&enable_run_for_clang_tidy_expansions_update
command: expansions.update
display_name: "enable bazel test report creation"
params:
updates:
- key: run_for_clang_tidy
value: "true"
"enable run_for_clang_tidy expansions":
- *f_expansions_write
- *enable_run_for_clang_tidy_expansions_update
- *f_expansions_write
"generate clang-tidy report":
- *f_expansions_write
- *generate_clang_tidy_report_sh
"upload clang tidy results": &upload_clang_tidy_results
- command: s3.put

View File

@@ -648,6 +648,32 @@ tasks:
--keep_going
--build_tag_filters=${bazel_filters_for_cache_hydration}
- name: run_bazel_clang_tidy
tags:
[
"assigned_to_jira_team_devprod_build",
"development_critical_single_variant",
"requires_large_host",
"clang_tidy",
]
exec_timeout_secs: 3600 # 1 hr timeout for the task overall, commit queue has a more specific timeout
depends_on:
- name: version_expansions_gen
variant: generate-tasks-for-version
commands:
- func: "do bazel setup"
- func: "enable run_for_clang_tidy expansions"
- func: "bazel run"
vars:
target: >-
mongo-tidy-test
- func: "bazel compile"
vars:
# Note that we only run clang-tidy on the Mongo-authored targets, not on third-party code.
targets: //src/mongo/...
bazel_args: --config=clang-tidy --mongo_toolchain_version=${clang_tidy_toolchain|v5} --keep_going
- func: "generate clang-tidy report"
- name: stitch_support_create_lib
tags:
[

View File

@@ -0,0 +1,63 @@
import os
import re
import sys
from buildscripts.simple_report import make_report, put_report, try_combine_reports
from buildscripts.util.read_config import read_config_file
expansions = read_config_file("../expansions.yml")
clang_tidy = expansions.get("run_for_clang_tidy", None)
if clang_tidy:
failures = []
for root, _, files in os.walk("bazel-bin"):
for name in files:
if name.endswith(".clang-tidy.status") and "mongo_tidy_checks/tests/" not in root:
with open(os.path.join(root, name)) as f:
if f.read().strip() == "1":
tokens = name.split(".")
log_file = os.path.join(root, ".".join(tokens[:-1]) + ".log")
with open(log_file) as log:
content = log.read()
# exmaple
# bazel-bin/src/mongo/shell/bazel_clang_tidy_src/mongo/shell/mongo_main.cpp.mongo_main_with_debug.clang-tidy.status
# source_file is:
# mongo_main.cpp
# target is mongo_main_with_debug
# target dir is src/mongo/shell
filename = os.path.basename(log_file)
parts = filename.split(".")
if len(parts) < 5:
raise ValueError(f"Unexpected status file format: {filename}")
source_file = ".".join(parts[:2])
target_name = parts[2]
target_dir = re.search(
r"bazel-bin/(.*)/bazel_clang_tidy_src/", log_file
).group(1)
target = f"//{target_dir}:{target_name}"
content += f"Run 'bazel build --config=clang-tidy --keep_going {target}' to reproduce this error"
failures.append(
[
os.path.join(
re.sub("^.*/bazel_clang_tidy_src/", "src/", root, 1),
source_file,
),
content,
]
)
with open("bazel-invocation.txt", "w") as f:
f.write("bazel build --config=clang-tidy //src/mongo/...")
if failures:
for failure in failures:
report = make_report(failure[0], failure[1], 1)
try_combine_reports(report)
put_report(report)
sys.exit(1)
else:
report = make_report("bazel build --config=clang-tidy //src/mongo/...", "", 0)
try_combine_reports(report)
put_report(report)

View File

@@ -83,7 +83,7 @@ StringData CollectionTruncateMarkers::toString(
boost::optional<CollectionTruncateMarkers::Marker>
CollectionTruncateMarkers::peekOldestMarkerIfNeeded(OperationContext* opCtx) const {
stdx::lock_guard<std::mutex> lk(_markersMutex);
stdx::lock_guard<Latch> lk(_markersMutex);
if (!_hasExcessMarkers(opCtx)) {
return {};
@@ -93,7 +93,7 @@ CollectionTruncateMarkers::peekOldestMarkerIfNeeded(OperationContext* opCtx) con
}
void CollectionTruncateMarkers::popOldestMarker() {
stdx::lock_guard<std::mutex> lk(_markersMutex);
stdx::lock_guard<Latch> lk(_markersMutex);
_markers.pop_front();
}
@@ -116,7 +116,7 @@ void CollectionTruncateMarkers::createNewMarkerIfNeeded(const RecordId& lastReco
// Try to lock the mutex, if we fail to lock then someone else is either already creating a new
// marker or popping the oldest one. In the latter case, we let the next insert trigger the new
// marker's creation.
stdx::unique_lock<std::mutex> lk(_markersMutex, stdx::try_to_lock);
stdx::unique_lock<Latch> lk(_markersMutex, stdx::try_to_lock);
if (!lk) {
logFailedLockAcquisition("_markersMutex");
return;
@@ -187,7 +187,7 @@ void CollectionTruncateMarkers::setMinBytesPerMarker(int64_t size) {
}
void CollectionTruncateMarkers::initialSamplingFinished() {
stdx::lock_guard<stdx::mutex> lk(_markersMutex);
stdx::lock_guard<Latch> lk(_markersMutex);
LOGV2_DEBUG(10167200, 2, "Initial sampling finished marked true.");
_initialSamplingFinished = true;
}
@@ -557,7 +557,7 @@ void CollectionTruncateMarkersWithPartialExpiration::createPartialMarkerIfNecess
// creating a new marker or popping the oldest one. In the latter case, we let the next check
// trigger the new partial marker's creation.
stdx::unique_lock<std::mutex> lk(_markersMutex, stdx::try_to_lock);
stdx::unique_lock<Latch> lk(_markersMutex, stdx::try_to_lock);
if (!lk) {
logFailedLockAcquisition("_markersMutex");
return;

View File

@@ -270,7 +270,7 @@ public:
static constexpr uint64_t kRandomSamplesPerMarker = 10;
size_t numMarkers() const {
stdx::lock_guard<std::mutex> lk(_markersMutex);
stdx::lock_guard<Latch> lk(_markersMutex);
return _markers.size();
}
@@ -311,7 +311,8 @@ private:
// Protects against concurrent access to the deque of collection markers and the
// _initialSamplingFinished variable.
mutable std::mutex _markersMutex;
mutable Mutex _markersMutex = MONGO_MAKE_LATCH("CollectionTruncateMarkers::_markersMutex");
std::deque<Marker> _markers; // front = oldest, back = newest.
// Whether or not the initial set of markers has finished being sampled.
@@ -349,7 +350,7 @@ protected:
* markers will be created.
*/
bool isEmpty() const {
stdx::lock_guard<std::mutex> lk(_markersMutex);
stdx::lock_guard<Latch> lk(_markersMutex);
return _markers.size() == 0 && _currentBytes.load() == 0 && _currentRecords.load() == 0;
}

View File

@@ -239,7 +239,7 @@ void OplogCapMaintainerThread::run() {
boost::optional<ScopedAdmissionPriority<ExecutionAdmissionContext>> admissionPriority;
{
stdx::lock_guard<stdx::mutex> lk(_opCtxMutex);
stdx::lock_guard<Latch> lk(_opCtxMutex);
// Initialize the thread's opCtx.
_uniqueCtx.emplace(tc->makeOperationContext());
@@ -251,7 +251,7 @@ void OplogCapMaintainerThread::run() {
}
ON_BLOCK_EXIT([&] {
stdx::lock_guard<stdx::mutex> lk(_opCtxMutex);
stdx::lock_guard<Latch> lk(_opCtxMutex);
admissionPriority.reset();
_uniqueCtx.reset();
});
@@ -260,7 +260,7 @@ void OplogCapMaintainerThread::run() {
if (gOplogSamplingAsyncEnabled) {
try {
{
stdx::unique_lock<stdx::mutex> lk(_stateMutex);
stdx::unique_lock<Latch> lk(_stateMutex);
if (_shuttingDown) {
return;
}
@@ -308,7 +308,7 @@ void OplogCapMaintainerThread::run() {
// We need this check since the first check to _shuttingDown is guarded by
// gOplogSamplingAsyncEnabled and we will never check this value if async is disabled.
{
stdx::unique_lock<stdx::mutex> lk(_stateMutex);
stdx::unique_lock<Latch> lk(_stateMutex);
if (_shuttingDown) {
return;
}
@@ -344,7 +344,7 @@ void OplogCapMaintainerThread::shutdown(const Status& reason) {
LOGV2_INFO(7474902, "Shutting down oplog cap maintainer thread", "reason"_attr = reason);
{
stdx::lock_guard<stdx::mutex> lk(_opCtxMutex);
stdx::lock_guard<Latch> lk(_opCtxMutex);
if (_uniqueCtx) {
stdx::lock_guard<Client> lk(*_uniqueCtx->get()->getClient());
_uniqueCtx->get()->markKilled(reason.code());
@@ -352,7 +352,7 @@ void OplogCapMaintainerThread::shutdown(const Status& reason) {
}
{
stdx::lock_guard<stdx::mutex> lk(_stateMutex);
stdx::lock_guard<Latch> lk(_stateMutex);
_shuttingDown = true;
_shutdownReason = reason;
}

View File

@@ -34,6 +34,7 @@
#include "mongo/db/auth/cluster_auth_mode.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/platform/mutex.h"
#include "mongo/util/background.h"
namespace mongo {
@@ -74,12 +75,12 @@ private:
bool _deleteExcessDocuments(OperationContext* opCtx);
// Serializes setting/resetting _uniqueCtx and marking _uniqueCtx killed.
mutable stdx::mutex _opCtxMutex;
mutable Mutex _opCtxMutex = MONGO_MAKE_LATCH("OplogCapMaintainerThread::_opCtxMutex");
// Saves a reference to the cap maintainer thread's operation context.
boost::optional<ServiceContext::UniqueOperationContext> _uniqueCtx;
mutable stdx::mutex _stateMutex;
mutable Mutex _stateMutex = MONGO_MAKE_LATCH("OplogCapMaintainerThread::_stateMutex");
bool _shuttingDown = false;
Status _shutdownReason = Status::OK();

View File

@@ -31,9 +31,9 @@
#include <cstdint>
#include <cstring>
#include <third_party/wiredtiger/wiredtiger.h>
#include <type_traits>
#include <utility>
#include <wiredtiger.h>
#include "mongo/base/data_type_endian.h"
#include "mongo/base/data_view.h"

View File

@@ -84,6 +84,7 @@ cc_library(
"-Wno-noexcept-type",
"-Wno-redundant-move",
"-Wno-unused-parameter",
"-Wno-deprecated-enum-enum-conversion",
"-Woverloaded-virtual",
"-Wwrite-strings",
"-Wsuggest-override",
@@ -153,6 +154,7 @@ cc_shared_library(
"-Wl,-O3",
"-Wl,--gc-sections",
"-Wl,-rpath,\\$ORIGIN/../lib", # Ensure that the shared library can find its dependencies.
"-Wl,-rpath-link,external/mongo_toolchain_v4/stow/llvm-v4/lib",
],
deps = [
":mongo_tidy_checks_static",

View File

@@ -30,12 +30,35 @@
#include "MongoCollectionShardingRuntimeCheck.h"
#include <clang-tidy/utils/OptionsUtils.h>
#include <filesystem>
namespace mongo::tidy {
using namespace clang;
using namespace clang::ast_matchers;
namespace {
std::string normalizeToCwdRelative(llvm::StringRef rawPath) {
namespace fs = std::filesystem;
fs::path p(rawPath.str());
p = p.lexically_normal();
if (p.is_absolute()) {
std::error_code ec;
auto rel = fs::relative(p, fs::current_path(), ec);
if (!ec) {
p = std::move(rel);
}
}
// Force forward slashes for consistent StringRef::startswith comparisons.
return p.generic_string();
}
} // namespace
MongoCollectionShardingRuntimeCheck::MongoCollectionShardingRuntimeCheck(
StringRef Name, clang::tidy::ClangTidyContext* Context)
: ClangTidyCheck(Name, Context),
@@ -74,16 +97,25 @@ void MongoCollectionShardingRuntimeCheck::check(const MatchFinder::MatchResult&
return;
}
std::string suffix = "_test.cpp";
// Check if FilePath ends with the suffix "_test.cpp"
if (FilePath.size() > suffix.size() &&
FilePath.rfind(suffix) == FilePath.size() - suffix.size()) {
// Normalize to a path relative to the current working directory (Bazel execroot),
// so we don't depend on absolute remote paths.
const std::string normPath = normalizeToCwdRelative(FilePath);
if (normPath.empty()) {
return;
}
const std::string suffix = "_test.cpp";
// Skip tests.
if (normPath.size() > suffix.size() &&
normPath.rfind(suffix) == normPath.size() - suffix.size()) {
return;
}
// If the file path is in an exception directory, skip the check.
for (const auto& dir : this->exceptionDirs) {
if (FilePath.startswith(dir)) {
llvm::StringRef dirRef(dir);
if (llvm::StringRef(normPath).startswith(dirRef)) {
return;
}
}

View File

@@ -30,11 +30,13 @@
#include "MongoHeaderBracketCheck.h"
#include <filesystem>
#include <optional>
#include <clang-tidy/utils/OptionsUtils.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Lex/PPCallbacks.h>
#include <clang/Lex/Preprocessor.h>
#include <filesystem>
#include <llvm/ADT/StringMap.h>
#include <llvm/ADT/StringSet.h>
@@ -42,12 +44,52 @@ namespace mongo::tidy {
namespace {
bool startsWithAny(llvm::StringRef fullString, std::vector<std::string> const& startings) {
return std::any_of(startings.cbegin(), startings.cend(), [&](llvm::StringRef starting) {
return fullString.startswith(starting);
static std::optional<std::string> canonicalFromSrc(const std::filesystem::path& full) { // NOLINT
std::filesystem::path rel = full.lexically_normal();
// If absolute, try to make it relative to CWD (best effort; OK if it fails)
if (rel.is_absolute()) {
std::error_code ec;
auto tmp = std::filesystem::relative(rel, std::filesystem::current_path(), ec);
if (!ec)
rel = std::move(tmp);
}
bool seenSrc = false;
std::filesystem::path out;
for (const auto& comp : rel) {
// Note: on Windows, comp is a path; compare as string ignoring slashes.
if (!seenSrc) {
if (comp == "src")
seenSrc = true;
continue;
}
out /= comp;
}
if (!seenSrc || out.empty())
return std::nullopt;
return out.generic_string(); // forward slashes
}
bool containsAny(llvm::StringRef fullString, const std::vector<std::string>& patterns) {
namespace fs = std::filesystem;
const auto cwd = fs::current_path();
return std::any_of(patterns.begin(), patterns.end(), [&](const std::string& pattern) {
fs::path p(pattern);
// Ensure a trailing separator so we only match directory names.
auto patRel = (p / "").generic_string();
if (fullString.contains(patRel))
return true;
auto absolute = (cwd / p / "").generic_string();
return fullString.contains(absolute);
});
}
class MongoIncludeBracketsPPCallbacks : public clang::PPCallbacks {
public:
explicit MongoIncludeBracketsPPCallbacks(MongoHeaderBracketCheck& Check,
@@ -73,6 +115,10 @@ public:
(std::filesystem::path(SearchPath.str()) / std::filesystem::path(RelativePath.str()))
.lexically_normal();
// If the computed path doesnt actually exist on disk, ignore it as it would not be used.
if (!std::filesystem::exists(header_path)) {
return;
}
// This represents the full path from the project root to the
// source file that is including the include that is currently being processed.
std::filesystem::path origin_source_path(HashLoc.printToString(SM));
@@ -83,12 +129,12 @@ public:
// if this is a mongo source file including a real file
if (!llvm::StringRef(origin_source_path).startswith("<built-in>:") &&
startsWithAny(llvm::StringRef(origin_source_path.lexically_normal()),
Check.mongoSourceDirs)) {
containsAny(llvm::StringRef(origin_source_path.lexically_normal()),
Check.mongoSourceDirs)) {
// Check that the a third party header which is included from a mongo source file
// is used angle brackets.
if (!IsAngled && !startsWithAny(llvm::StringRef(header_path), Check.mongoSourceDirs)) {
if (!IsAngled && !containsAny(llvm::StringRef(header_path), Check.mongoSourceDirs)) {
std::string Replacement = (llvm::Twine("<") + FileName + ">").str();
Check.diag(FilenameRange.getBegin(),
@@ -99,9 +145,9 @@ public:
// Check that the a third party header which is in our vendored tree is not including
// the third_party in the include path.
if (!startsWithAny(llvm::StringRef(header_path), Check.mongoSourceDirs) &&
llvm::StringRef(header_path).startswith("src/third_party") &&
FileName.startswith("third_party")) {
if (!containsAny(llvm::StringRef(header_path), Check.mongoSourceDirs) &&
llvm::StringRef(header_path).contains("src/third_party/") &&
FileName.contains("third_party/")) {
Check.diag(FilenameRange.getBegin(),
"third_party include '%0' should not start with 'third_party/'. The "
"included file should be useable in either context of system or "
@@ -111,12 +157,17 @@ public:
// Check that the a mongo header which is included from a mongo source file
// is used double quotes.
else if (IsAngled &&
startsWithAny(llvm::StringRef(header_path), Check.mongoSourceDirs)) {
std::string Replacement = (llvm::Twine("\"") + FileName + "\"").str();
Check.diag(FilenameRange.getBegin(), "mongo include '%0' should use double quotes")
<< FileName
<< clang::FixItHint::CreateReplacement(FilenameRange.getAsRange(), Replacement);
else if (IsAngled && containsAny(llvm::StringRef(header_path), Check.mongoSourceDirs)) {
// TODO(SERVER-95253): Explicitly handle third party headers inside of the
// enterprise module directory.
if (!FileName.contains("bsoncxx/") && !FileName.contains("mongocxx/")) {
std::string Replacement = (llvm::Twine("\"") + FileName + "\"").str();
Check.diag(FilenameRange.getBegin(),
"mongo include '%0' should use double quotes")
<< FileName
<< clang::FixItHint::CreateReplacement(FilenameRange.getAsRange(),
Replacement);
}
}
}
}

View File

@@ -34,7 +34,7 @@
namespace mongo::tidy {
/**
Overrides the default PPCallback class to primarly override
Overrides the default PPCallback class to primarily override
the InclusionDirective call which is called for each include included. This
allows the chance to evaluate specifically the include and determine whether
it is considered a "mongo" include or not and if it is using the appropriate include style.
@@ -46,7 +46,12 @@ public:
void registerPPCallbacks(const clang::SourceManager& SM,
clang::Preprocessor* PP,
clang::Preprocessor* ModuleExpanderPP) override;
// used to store option `mongoSourceDirs`; supports both absolute and relative paths
std::vector<std::string> mongoSourceDirs;
// The path component that denotes the repo's root source directory (default: "src").
// Used to compute canonical include paths "from src/".
std::string srcRootComponent;
};
} // namespace mongo::tidy

View File

@@ -1,15 +1,19 @@
load("//bazel:mongo_src_rules.bzl", "mongo_cc_binary")
load("//bazel:mongo_src_rules.bzl", "mongo_cc_binary", "mongo_cc_library")
package(default_visibility = ["//visibility:public"])
exports_files(glob([
"*.cpp",
"*.h",
"*.inl",
"*.hpp",
"*.py",
"*.in",
]))
exports_files(
glob([
"*.h",
"*.cpp",
"*.tidy_config",
]),
)
filegroup(
name = "tidy_check_headers",
srcs = glob(["*.h"]),
)
filegroup(
name = "tests_global_hdrs",
@@ -20,61 +24,82 @@ filegroup(
]),
)
mongo_cc_binary(
name = "MongoTidyCheck_test",
srcs = ["MongoTidyCheckTestMain.cpp"] + [
# This list represents the test source files, which should contain a single issue which will be flagged
# by a clang tidy check. The issue should be isolated in as minimal way as possible.
"test_MongoHeaderBracketCheck.cpp",
"test_MongoHeaderBracketCheck.h",
"test_MongoVolatileCheck.cpp",
"test_MongoUninterruptibleLockGuardCheck.cpp",
"test_MongoUninterruptibleLockGuardCheckForOpCtxMember.cpp",
"test_MongoCctypeCheck.cpp",
"test_MongoCctypeCheck.h",
"test_MongoConfigHeaderCheck.cpp",
"test_MongoCxx20BannedIncludesCheck.cpp",
"test_MongoCxx20StdChronoCheck.cpp",
"test_MongoStdOptionalCheck.cpp",
"test_MongoTraceCheck.cpp",
"test_MongoStdAtomicCheck.cpp",
"test_MongoAssertCheck.cpp",
"test_MongoFCVConstantCheck.cpp",
"test_MongoUnstructuredLogCheck.cpp",
"test_MongoCollectionShardingRuntimeCheck.cpp",
"test_MongoMacroDefinitionLeaksCheck.cpp",
"test_MongoMacroDefinitionLeaksCheck.h",
"test_MongoRandCheck.cpp",
"test_MongoPolyFillCheck.cpp",
"test_MongoNoUniqueAddressCheck.cpp",
"test_MongoStringDataConstRefCheck1.cpp",
"test_MongoStringDataConstRefCheck2.cpp",
"test_MongoStringDataConstRefCheck3.cpp",
"test_MongoMutexCheck.cpp",
],
# These test files will purposefully be error prone, so we can disable warnings any warnings we expect
# to see.
copts = [
"-Wno-unused-variable",
"-Wno-return-type",
"-Wno-deprecated-volatile",
],
includes = ["."],
skip_global_deps = [
"allocator",
"libunwind",
],
tags = [
"mongo-tidy-tests",
],
target_compatible_with = select({
"@platforms//os:windows": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [
"//src/third_party/s2",
],
)
tests = [
# This list represents the test source files, which should contain a single issue which will be flagged
# by a clang tidy check. The issue should be isolated in as minimal way as possible.
"test_MongoHeaderBracketCheck",
"test_MongoVolatileCheck",
"test_MongoUninterruptibleLockGuardCheck",
"test_MongoUninterruptibleLockGuardCheckForOpCtxMember",
"test_MongoCctypeCheck",
"test_MongoConfigHeaderCheck",
"test_MongoCxx20BannedIncludesCheck",
"test_MongoCxx20StdChronoCheck",
"test_MongoStdOptionalCheck",
"test_MongoTraceCheck",
"test_MongoStdAtomicCheck",
"test_MongoAssertCheck",
"test_MongoFCVConstantCheck",
"test_MongoUnstructuredLogCheck",
"test_MongoCollectionShardingRuntimeCheck",
"test_MongoMacroDefinitionLeaksCheck",
"test_MongoRandCheck",
"test_MongoPolyFillCheck",
"test_MongoNoUniqueAddressCheck",
"test_MongoStringDataConstRefCheck1",
"test_MongoStringDataConstRefCheck2",
"test_MongoStringDataConstRefCheck3",
"test_MongoMutexCheck",
]
[
mongo_cc_library(
name = test,
srcs = [
test + ".cpp",
"tidy_check_headers",
],
# These test files will purposefully be error prone, so we can disable warnings any warnings we expect
# to see.
copts = [
"-Wno-unused-but-set-parameter",
"-Wno-unused-variable",
"-Wno-return-type",
"-Isrc/mongo/tools/mongo_tidy_checks/tests",
"-Isrc/mongo", # for mongo header path test
] + select({
"//bazel/config:compiler_type_gcc": ["-Wno-volatile"],
"//bazel/config:compiler_type_clang": ["-Wno-deprecated-volatile"],
"//conditions:default": [],
}),
skip_global_deps = [
"allocator",
"libunwind",
],
tags = [
"mongo-tidy-tests",
],
target_compatible_with = select({
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],
}) + select({
"//bazel/config:linkstatic_disabled": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [
"//src/third_party/s2",
],
)
for test in tests
]
[
filegroup(
name = test + "_tidy_config",
srcs = [test + ".tidy_config"],
)
for test in tests
]
py_binary(
name = "MongoTidyCheck_unittest",

View File

@@ -1,32 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
int main(int argc, char* argv[]) {
return 0;
}

View File

@@ -1,46 +1,45 @@
import argparse
import os
import subprocess
import sys
import tempfile
import textwrap
import unittest
class MongoTidyTests(unittest.TestCase):
TIDY_BIN = None
TIDY_MODULE = None
COMPILE_COMMANDS_FILES = []
def write_config(self, config_str: str):
self.config_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
self.config_file.write(config_str)
self.config_file.close()
self.cmd += [f'--clang-tidy-cfg={self.config_file.name}']
return self.config_file.name
def run_clang_tidy(self):
p = subprocess.run(self.cmd, capture_output=True, text=True)
cmd = [
"bazel",
"build",
"--config=clang-tidy",
"--skip_archive=False",
"--build_tag_filters=mongo-tidy-tests",
"--@bazel_clang_tidy//:clang_tidy_config=//src/mongo/tools/mongo_tidy_checks/tests:"
+ self._testMethodName
+ "_tidy_config",
"//src/mongo/tools/mongo_tidy_checks/tests:" + self._testMethodName + "_with_debug",
]
p = subprocess.run(
cmd, cwd=os.environ.get("BUILD_WORKSPACE_DIRECTORY"), capture_output=True, text=True
)
if isinstance(self.expected_output, list):
passed = all([expected_output in p.stdout for expected_output in self.expected_output])
print_expected_output = "\n".join(self.expected_output)
else:
passed = self.expected_output is not None and self.expected_output in p.stdout
print_expected_output = self.expected_output
with open(self.config_file.name) as f:
msg = '\n'.join([
'>' * 80,
msg = "\n".join(
[
">" * 80,
f"Mongo Tidy Unittest {self._testMethodName}: {'PASSED' if passed else 'FAILED'}",
"",
"Command:",
' '.join(self.cmd),
"",
"With config:",
f.read(),
" ".join(cmd),
"",
f"Exit code was: {p.returncode}",
"",
f"Output expected in stdout: {self.expected_output}",
"Output expected in stdout: ",
f"{print_expected_output}",
"",
"stdout was:",
p.stdout,
@@ -48,49 +47,19 @@ class MongoTidyTests(unittest.TestCase):
"stderr was:",
p.stderr,
"",
'<' * 80,
])
if passed:
sys.stderr.write(msg)
else:
print(msg)
self.fail()
with open(f'{os.path.splitext(self.compile_db)[0]}.results', 'w') as results:
results.write(msg)
def setUp(self):
self.config_file = None
self.expected_output = None
for compiledb in self.COMPILE_COMMANDS_FILES:
if compiledb.endswith("/" + self._testMethodName + "/compile_commands.json"):
self.compile_db = compiledb
if self.compile_db:
self.cmd = [
sys.executable,
'buildscripts/clang_tidy.py',
'--disable-reporting',
f'--check-module={self.TIDY_MODULE}',
f'--output-dir={os.path.join(os.path.dirname(self.compile_db), self._testMethodName + "_out")}',
f'--compile-commands={self.compile_db}',
"<" * 80,
]
else:
raise (f"ERROR: did not findh matching compiledb for {self._testMethodName}")
)
def tearDown(self):
if self.config_file:
self.config_file.close()
os.unlink(self.config_file.name)
if passed:
if os.environ.get("CI"):
print(msg)
else:
sys.stderr.write(msg)
self.fail()
def test_MongoHeaderBracketCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-header-bracket-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"error: non-mongo include 'cctype' should use angle brackets",
"error: mongo include 'test_MongoHeaderBracketCheck.h' should use double quotes",
@@ -101,12 +70,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoUninterruptibleLockGuardCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-uninterruptible-lock-guard-check'
WarningsAsErrors: '*'
"""))
self.expected_output = (
"Potentially incorrect use of UninterruptibleLockGuard, "
"the programming model inside MongoDB requires that all operations be interruptible. "
@@ -117,12 +80,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoUninterruptibleLockGuardCheckForOpCtxMember(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-uninterruptible-lock-guard-check'
WarningsAsErrors: '*'
"""))
self.expected_output = (
"Potentially incorrect use of "
"OperationContext::uninterruptibleLocksRequested_DO_NOT_USE, this is a legacy "
@@ -136,13 +93,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoCctypeCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-cctype-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'
"""))
self.expected_output = [
"Use of prohibited \"cctype\" header, use \"mongo/util/ctype.h\"",
"Use of prohibited <ctype.h> header, use \"mongo/util/ctype.h\"",
@@ -152,13 +102,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoCxx20BannedIncludesCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-cxx20-banned-includes-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'
"""))
self.expected_output = [
"Use of prohibited <syncstream> header.",
"Use of prohibited <ranges> header.",
@@ -170,11 +113,7 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoCxx20StdChronoCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-cxx20-std-chrono-check'
WarningsAsErrors: '*'
"""))
prohibited_types = ["day", "day", "month", "year", "month_day", "month", "day", "day"]
self.expected_output = [
f"Illegal use of prohibited type 'std::chrono::{t}'." for t in prohibited_types
@@ -183,12 +122,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoStdOptionalCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-std-optional-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Use of std::optional, use boost::optional instead. [mongo-std-optional-check,-warnings-as-errors]\nvoid f(std::optional<std::string> parameterDeclTest) {",
"Use of std::optional, use boost::optional instead. [mongo-std-optional-check,-warnings-as-errors]\n std::optional<std::string> variableDeclTest;",
@@ -203,12 +136,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoVolatileCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-volatile-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Illegal use of the volatile storage keyword, use AtomicWord instead from \"mongo/platform/atomic_word.h\" [mongo-volatile-check,-warnings-as-errors]\nvolatile int varVolatileTest;",
"Illegal use of the volatile storage keyword, use AtomicWord instead from \"mongo/platform/atomic_word.h\" [mongo-volatile-check,-warnings-as-errors]\n volatile int fieldVolatileTest;",
@@ -219,12 +146,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoTraceCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-trace-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Illegal use of prohibited tracing support, this is only for local development use and should not be committed. [mongo-trace-check,-warnings-as-errors]\n TracerProvider::initialize();",
"Illegal use of prohibited tracing support, this is only for local development use and should not be committed. [mongo-trace-check,-warnings-as-errors]\n TracerProvider provider = TracerProvider::get();",
@@ -234,12 +155,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoStdAtomicCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-std-atomic-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Illegal use of prohibited std::atomic<T>, use AtomicWord<T> or other types from \"mongo/platform/atomic_word.h\" [mongo-std-atomic-check,-warnings-as-errors]\nstd::atomic<int> atomic_var;",
"Illegal use of prohibited std::atomic<T>, use AtomicWord<T> or other types from \"mongo/platform/atomic_word.h\" [mongo-std-atomic-check,-warnings-as-errors]\n std::atomic<int> field_decl;",
@@ -249,12 +164,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoMutexCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-mutex-check,mongo-std-atomic-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Illegal use of prohibited stdx::mutex, use mongo::Mutex from mongo/platform/mutex.h instead. [mongo-mutex-check,-warnings-as-errors]\nstdx::mutex stdxmutex_vardecl;",
"Illegal use of prohibited stdx::mutex, use mongo::Mutex from mongo/platform/mutex.h instead. [mongo-mutex-check,-warnings-as-errors]\nstd::mutex stdmutex_vardecl;",
@@ -266,12 +175,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoAssertCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-assert-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"error: Illegal use of the bare assert function, use a function from assert_util.h instead",
]
@@ -280,12 +183,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoFCVConstantCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-fcv-constant-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"error: Illegal use of FCV constant in FCV comparison check functions. FCV gating should be done through feature flags instead.",
]
@@ -294,12 +191,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoUnstructuredLogCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-unstructured-log-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"error: Illegal use of unstructured logging, this is only for local development use and should not be committed [mongo-unstructured-log-check,-warnings-as-errors]\n logd();",
"error: Illegal use of unstructured logging, this is only for local development use and should not be committed [mongo-unstructured-log-check,-warnings-as-errors]\n doUnstructuredLogImpl();",
@@ -309,13 +200,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoConfigHeaderCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-config-header-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'
"""))
self.expected_output = [
"error: MONGO_CONFIG define used without prior inclusion of config.h [mongo-config-header-check,-warnings-as-errors]\n#define MONGO_CONFIG_TEST1 1",
"error: MONGO_CONFIG define used without prior inclusion of config.h [mongo-config-header-check,-warnings-as-errors]\n#ifdef MONGO_CONFIG_TEST1",
@@ -327,15 +211,6 @@ class MongoTidyTests(unittest.TestCase):
def test_MongoCollectionShardingRuntimeCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-collection-sharding-runtime-check'
WarningsAsErrors: '*'
CheckOptions:
- key: mongo-collection-sharding-runtime-check.exceptionDirs
value: 'src/mongo/db/s'
"""))
self.expected_output = [
"error: Illegal use of CollectionShardingRuntime outside of mongo/db/s/; use CollectionShardingState instead; see src/mongo/db/s/collection_sharding_state.h for details. [mongo-collection-sharding-runtime-check,-warnings-as-errors]\n CollectionShardingRuntime csr(5, \"Test\");",
"error: Illegal use of CollectionShardingRuntime outside of mongo/db/s/; use CollectionShardingState instead; see src/mongo/db/s/collection_sharding_state.h for details. [mongo-collection-sharding-runtime-check,-warnings-as-errors]\n int result = CollectionShardingRuntime::functionTest(7, \"Test\");",
@@ -344,12 +219,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoMacroDefinitionLeaksCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-macro-definition-leaks-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'
"""))
self.expected_output = [
"Missing #undef 'MONGO_LOGV2_DEFAULT_COMPONENT'",
@@ -358,12 +227,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoNoUniqueAddressCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-no-unique-address-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'
"""))
self.expected_output = [
"Illegal use of [[no_unique_address]]",
@@ -372,11 +235,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoPolyFillCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-polyfill-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"error: Illegal use of banned name from std::/boost:: for std::mutex, use mongo::stdx:: variant instead",
@@ -389,11 +247,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoRandCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-rand-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"error: Use of rand or srand, use <random> or PseudoRandom instead. [mongo-rand-check,-warnings-as-errors]\n srand(time(0));",
@@ -403,11 +256,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoStringDataConstRefCheck1(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-stringdata-const-ref-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Prefer passing StringData by value.",
@@ -416,11 +264,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoStringDataConstRefCheck2(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-stringdata-const-ref-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"Prefer passing StringData by value.",
@@ -429,11 +272,6 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoStringDataConstRefCheck3(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-stringdata-const-ref-check'
WarningsAsErrors: '*'
"""))
self.expected_output = [
"",
@@ -441,34 +279,5 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--clang-tidy-path', default='/opt/mongodbtoolchain/v4/bin/clang-tidy',
help="Path to clang-tidy binary.")
parser.add_argument('--mongo-tidy-module', default='bazel-bin/install/lib/libmongo_tidy_checks.so',
help="Path to mongo tidy check library.")
parser.add_argument(
'--test-compiledbs', action='append', default=[],
help="Used multiple times. Each use adds a test compilation database to use. " +
"The compilation database name must match the unittest method name.")
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
MongoTidyTests.TIDY_BIN = args.clang_tidy_path
MongoTidyTests.TIDY_MODULE = args.mongo_tidy_module
MongoTidyTests.COMPILE_COMMANDS_FILES = args.test_compiledbs
# We need to validate the toolchain can support the load operation for our module.
cmd = [MongoTidyTests.TIDY_BIN, '-load', MongoTidyTests.TIDY_MODULE, '--list-checks']
p = subprocess.run(cmd, capture_output=True)
if p.returncode != 0:
print(f"Could not validate toolchain was able to load module {cmd}.")
sys.exit(1)
# Workaround to allow use to use argparse on top of unittest module.
sys.argv[1:] = args.unittest_args
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-assert-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,3 @@
Checks: '-*,mongo-cctype-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'

View File

@@ -0,0 +1,5 @@
Checks: '-*,mongo-collection-sharding-runtime-check'
WarningsAsErrors: '*'
CheckOptions:
- key: mongo-collection-sharding-runtime-check.exceptionDirs
value: 'src/mongo/db/s'

View File

@@ -0,0 +1,3 @@
Checks: '-*,mongo-config-header-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'

View File

@@ -0,0 +1,3 @@
Checks: '-*,mongo-cxx20-banned-includes-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-cxx20-std-chrono-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-fcv-constant-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-header-bracket-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,3 @@
Checks: '-*,mongo-macro-definition-leaks-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-mutex-check,mongo-std-atomic-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,3 @@
Checks: '-*,mongo-no-unique-address-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-polyfill-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-rand-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-std-atomic-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-std-optional-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-stringdata-const-ref-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-stringdata-const-ref-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-stringdata-const-ref-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-trace-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-uninterruptible-lock-guard-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-uninterruptible-lock-guard-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-unstructured-log-check'
WarningsAsErrors: '*'

View File

@@ -0,0 +1,2 @@
Checks: '-*,mongo-volatile-check'
WarningsAsErrors: '*'