mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-19 11:06:17 +00:00
27d23b6135
* test(io-metrics): assert lib.rs record helper emissions The 29 assertion-less record_* smoke tests in io-metrics/src/lib.rs called their helpers and checked nothing; because METRICS_ENABLED defaults to false they did not even reach the emission bodies. Replace them with six DebuggingRecorder tests that enable the gate, pin every metric name the helpers own, and pin the derived values, branch selection and label mapping (rustfs/backlog#1836). * test(tooling): anchor assertless-census delegation tokens to name segments
124 lines
4.5 KiB
Python
Executable File
124 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright 2024 RustFS Team
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Census of assertion-less tests (rustfs/backlog#1836 PR3).
|
|
|
|
Flags `#[test]` / `#[tokio::test]` functions whose bodies contain no
|
|
verification signal: no assert!/assert_eq!/assert_ne!/panic! macro, no
|
|
`.expect(`/`.unwrap(`, no `?` operator, no `#[should_panic]`, and no
|
|
`insta` snapshot / proptest / matches! usage. Such a test is green no
|
|
matter what the code under test does.
|
|
|
|
This is a heuristic REVIEW QUEUE, not a lint: a hit still needs human
|
|
reading before it is fixed or deleted, because assertions may live in a
|
|
called helper. Known false-positive classes are excluded up front:
|
|
|
|
- `#[test_case(...)]`-driven functions (the values are the assertion's
|
|
parameters; the assert lives in the shared body — still scanned, but a
|
|
body that asserts is not flagged anyway; the exclusion covers wrappers
|
|
that only delegate to a suite runner).
|
|
- Functions whose body calls a helper *named* like a shared check or suite
|
|
runner: an `assert_`/`verify_`/`check_`/`expect_`/`ensure_`/`run_` prefix,
|
|
or a `_case`/`_cases`/`_harness`/`_roundtrip` suffix. The name must carry
|
|
the token as its own leading or trailing segment — matching it anywhere
|
|
inside the identifier hid whole test bodies behind an unrelated domain
|
|
call such as `record_get_object_bitrot_verify_duration(..)`.
|
|
- Functions whose body only defines an unused inner `fn _name(..)`: that is
|
|
the compile-time shape check (exhaustive match, signature pin), where the
|
|
type system is the assertion.
|
|
|
|
Usage:
|
|
scripts/find_assertless_tests.py [path ...] # default: crates rustfs/src
|
|
|
|
Exit code is always 0; the output is the queue.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
VERIFY_SIGNALS = re.compile(
|
|
r"assert!|assert_eq!|assert_ne!|debug_assert|panic!\(|\.expect\(|\.unwrap\(|"
|
|
r"unreachable!|matches!\(|insta::|proptest!|\.await\?|\)\?|\?;|should_panic"
|
|
)
|
|
DELEGATION = re.compile(
|
|
r"\b(?:assert|verify|check|expect|ensure|run)_[a-z0-9_]*\s*\(|"
|
|
r"\b[a-z0-9_]+_(?:case|cases|harness|roundtrip|round_trip)\s*\("
|
|
)
|
|
COMPILE_TIME_CHECK = re.compile(r"\bfn\s+_[a-zA-Z0-9_]*\s*(?:<[^>]*>)?\s*\(")
|
|
TEST_ATTR = re.compile(r"#\[(?:tokio::)?test[\](]")
|
|
TEST_CASE_ATTR = re.compile(r"#\[test_case")
|
|
FN_LINE = re.compile(r"^\s*(?:pub\s+)?(?:async\s+)?fn\s+([a-zA-Z0-9_]+)")
|
|
|
|
|
|
def scan_file(path: Path):
|
|
try:
|
|
lines = path.read_text(encoding="utf-8").split("\n")
|
|
except (UnicodeDecodeError, OSError):
|
|
return
|
|
i = 0
|
|
while i < len(lines):
|
|
if not TEST_ATTR.search(lines[i]):
|
|
i += 1
|
|
continue
|
|
# collect the whole attribute block (may include #[serial], #[test_case], ...)
|
|
attrs = []
|
|
j = i
|
|
while j < len(lines) and (lines[j].strip().startswith("#[") or lines[j].strip().startswith("//")):
|
|
attrs.append(lines[j])
|
|
j += 1
|
|
if j >= len(lines):
|
|
break
|
|
m = FN_LINE.match(lines[j])
|
|
if not m:
|
|
i = j + 1
|
|
continue
|
|
name = m.group(1)
|
|
if any(TEST_CASE_ATTR.search(a) for a in attrs):
|
|
i = j + 1
|
|
continue
|
|
# brace-match the body
|
|
depth = 0
|
|
begun = False
|
|
body = []
|
|
k = j
|
|
while k < len(lines):
|
|
for ch in lines[k]:
|
|
if ch == "{":
|
|
depth += 1
|
|
begun = True
|
|
elif ch == "}":
|
|
depth -= 1
|
|
body.append(lines[k])
|
|
if begun and depth <= 0:
|
|
break
|
|
k += 1
|
|
text = "\n".join(body)
|
|
if not VERIFY_SIGNALS.search(text) and not DELEGATION.search(text) and not COMPILE_TIME_CHECK.search(text):
|
|
print(f"{path}:{j + 1}: {name}")
|
|
i = k + 1
|
|
|
|
|
|
def main():
|
|
roots = [Path(p) for p in (sys.argv[1:] or ["crates", "rustfs/src"])]
|
|
for root in roots:
|
|
for path in sorted(root.rglob("*.rs")):
|
|
if "target" in path.parts:
|
|
continue
|
|
scan_file(path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|