Skip to content

Commit 3a0ea27

Browse files
committed
MVP of tidy watcher
1 parent b0889cb commit 3a0ea27

File tree

7 files changed

+148
-1
lines changed

7 files changed

+148
-1
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -5337,6 +5337,7 @@ dependencies = [
53375337
"cargo_metadata",
53385338
"ignore",
53395339
"lazy_static",
5340+
"md-5",
53405341
"miropt-test-tools",
53415342
"regex",
53425343
"semver",

src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/
5757
RUN ./build-clang.sh
5858
ENV CC=clang CXX=clang++
5959

60+
# tidy-ticket-perf-commit
6061
# rustc-perf version from 2023-05-30
61-
# Should also be changed in the opt-dist tool for other environments.
6262
ENV PERF_COMMIT 8b2ac3042e1ff2c0074455a0a3618adef97156b1
63+
# tidy-ticket-perf-commit
6364
RUN curl -LS -o perf.zip https://ci-mirrors.rust-lang.org/rustc/rustc-perf-$PERF_COMMIT.zip && \
6465
unzip perf.zip && \
6566
mv rustc-perf-$PERF_COMMIT rustc-perf && \

src/tools/tidy/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ walkdir = "2"
1414
ignore = "0.4.18"
1515
semver = "1.0"
1616
termcolor = "1.1.3"
17+
md-5 = "0.10"
1718

1819
[[bin]]
1920
name = "rust-tidy"

src/tools/tidy/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ pub mod ui_tests;
7272
pub mod unit_tests;
7373
pub mod unstable_book;
7474
pub mod walk;
75+
pub mod watcher;
7576
pub mod x_version;

src/tools/tidy/src/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ fn main() {
137137

138138
check!(x_version, &root_path, &cargo);
139139

140+
check!(watcher, &root_path);
141+
140142
let collected = {
141143
drain_handles(&mut handles);
142144

src/tools/tidy/src/watcher.rs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! Checks that text between tags unchanged, emitting warning otherwise,
2+
//! allowing asserting that code in different places over codebase is in sync.
3+
//!
4+
//! This works via hashing text between tags and saving hash in tidy.
5+
//!
6+
//! Usage:
7+
//!
8+
//! some.rs:
9+
//! // tidy-ticket-foo
10+
//! const FOO: usize = 42;
11+
//! // tidy-ticket-foo
12+
//!
13+
//! some.sh:
14+
//! # tidy-ticket-foo
15+
//! export FOO=42
16+
//! # tidy-ticket-foo
17+
use md5::{Digest, Md5};
18+
use std::fs;
19+
use std::path::Path;
20+
21+
#[cfg(test)]
22+
mod tests;
23+
24+
/// Return hash for source text between 2 tag occurrence,
25+
/// ignoring lines where tag written
26+
///
27+
/// Expecting:
28+
/// tag is not multiline
29+
/// source always have at least 2 occurrence of tag (>2 ignored)
30+
fn span_hash(source: &str, tag: &str, bad: &mut bool) -> Result<String, ()> {
31+
let start_idx = match source.find(tag) {
32+
Some(idx) => idx,
33+
None => return Err(tidy_error!(bad, "tag {} should exist in provided text", tag)),
34+
};
35+
let end_idx = {
36+
let end = match source[start_idx + tag.len()..].find(tag) {
37+
// index from source start
38+
Some(idx) => start_idx + tag.len() + idx,
39+
None => return Err(tidy_error!(bad, "tag end {} should exist in provided text", tag)),
40+
};
41+
// second line with tag can contain some other text before tag, ignore it
42+
// by finding position of previous line ending
43+
//
44+
// FIXME: what if line ending is \r\n? In that case \r will be hashed too
45+
let offset = source[start_idx..end].rfind('\n').unwrap();
46+
start_idx + offset
47+
};
48+
49+
let mut hasher = Md5::new();
50+
51+
source[start_idx..end_idx]
52+
.lines()
53+
// skip first line with tag
54+
.skip(1)
55+
// hash next lines, ignoring end trailing whitespaces
56+
.for_each(|line| {
57+
let trimmed = line.trim_end();
58+
hasher.update(trimmed);
59+
});
60+
Ok(format!("{:x}", hasher.finalize()))
61+
}
62+
63+
fn check_entry(entry: &ListEntry<'_>, bad: &mut bool, root_path: &Path) {
64+
let file = fs::read_to_string(root_path.join(Path::new(entry.0))).unwrap();
65+
let actual_hash = span_hash(&file, entry.2, bad).unwrap();
66+
if actual_hash != entry.1 {
67+
// Write tidy error description for wather only once.
68+
// Will not work if there was previous errors of other types.
69+
if *bad == false {
70+
tidy_error!(
71+
bad,
72+
"Mismatched hashes for tidy watcher found.\n\
73+
Check src/tools/tidy/src/watcher.rs, find tag/hash in TIDY_WATCH_LIST list \
74+
and verify that sources for provided group of tags in sync. If they in sync, update hash."
75+
)
76+
}
77+
tidy_error!(
78+
bad,
79+
"hash for tag `{}` in path `{}` mismatch:\n actual: `{}`, expected: `{}`\n",
80+
entry.2,
81+
entry.0,
82+
actual_hash,
83+
entry.1
84+
);
85+
}
86+
}
87+
88+
/// (path, hash, tag)
89+
type ListEntry<'a> = (&'a str, &'a str, &'a str);
90+
91+
/// List of tags to watch, along with paths and hashes
92+
#[rustfmt::skip]
93+
const TIDY_WATCH_LIST: &[ListEntry<'_>] = &[
94+
("src/tools/opt-dist/src/environment/windows.rs", "dcad53f163a2775164b5d2faaa70b653", "tidy-ticket-perf-commit"),
95+
("src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile", "76c8d9783e38e25a461355f82fcd7955", "tidy-ticket-perf-commit"),
96+
];
97+
98+
pub fn check(root_path: &Path, bad: &mut bool) {
99+
for entry in TIDY_WATCH_LIST {
100+
check_entry(entry, bad, root_path);
101+
}
102+
}

src/tools/tidy/src/watcher/tests.rs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use super::*;
2+
3+
#[test]
4+
fn test_span_hash_one_line() {
5+
let source = "some text\ntidy-tag\ncheckme=42\ntidy-tag\n";
6+
let tag = "tidy-tag";
7+
assert_eq!("42258eba764c3f94a24de379e5715dc8", span_hash(source, tag, &mut true).unwrap());
8+
}
9+
10+
#[test]
11+
fn test_span_hash_multiple_lines() {
12+
let source = "some text\ntidy-tag\ncheckme=42\nother line\ntidy-tag\n";
13+
let tag = "tidy-tag";
14+
assert_eq!("49cb23dc2032ceea671ca48092750a1c", span_hash(source, tag, &mut true).unwrap());
15+
}
16+
17+
#[test]
18+
fn test_span_hash_has_some_text_in_line_with_tag() {
19+
let source = "some text\ntidy-tag ignore me\ncheckme=42\nother line\ntidy-tag\n";
20+
let tag = "tidy-tag";
21+
assert_eq!("49cb23dc2032ceea671ca48092750a1c", span_hash(source, tag, &mut true).unwrap());
22+
}
23+
24+
#[test]
25+
fn test_span_hash_has_some_text_in_line_before_second_tag() {
26+
let source = r#"
27+
RUN ./build-clang.sh
28+
ENV CC=clang CXX=clang++
29+
# tidy-ticket-perf-commit
30+
# rustc-perf version from 2023-05-30
31+
ENV PERF_COMMIT 8b2ac3042e1ff2c0074455a0a3618adef97156b1
32+
# tidy-ticket-perf-commit
33+
RUN curl -LS -o perf.zip https://github.com/rust-lang/rustc-perf/archive/$PERF_COMMIT.zip && \
34+
unzip perf.zip && \
35+
mv rustc-perf-$PERF_COMMIT rustc-perf && \
36+
rm perf.zip"#;
37+
let tag = "tidy-ticket-perf-commit";
38+
assert_eq!("76c8d9783e38e25a461355f82fcd7955", span_hash(source, tag, &mut true).unwrap());
39+
}

0 commit comments

Comments
 (0)