Skip to content

Commit e419b3d

Browse files
committed
compiletest: improve robustness of LLVM version handling
1 parent 1e4f10b commit e419b3d

File tree

7 files changed

+140
-70
lines changed

7 files changed

+140
-70
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ dependencies = [
720720
"miropt-test-tools",
721721
"regex",
722722
"rustfix",
723+
"semver",
723724
"serde",
724725
"serde_json",
725726
"tracing",

src/tools/compiletest/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ build_helper = { path = "../build_helper" }
1818
tracing = "0.1"
1919
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
2020
regex = "1.0"
21+
semver = { version = "1.0.23", features = ["serde"] }
2122
serde = { version = "1.0", features = ["derive"] }
2223
serde_json = "1.0"
2324
rustfix = "0.8.1"

src/tools/compiletest/src/common.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::sync::OnceLock;
77
use std::{fmt, iter};
88

99
use build_helper::git::GitConfig;
10+
use semver::Version;
1011
use serde::de::{Deserialize, Deserializer, Error as _};
1112
use test::{ColorConfig, OutputFormat};
1213

@@ -298,7 +299,7 @@ pub struct Config {
298299
pub lldb_version: Option<u32>,
299300

300301
/// Version of LLVM
301-
pub llvm_version: Option<u32>,
302+
pub llvm_version: Option<Version>,
302303

303304
/// Is LLVM a system LLVM
304305
pub system_llvm: bool,

src/tools/compiletest/src/header.rs

+70-42
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::io::prelude::*;
66
use std::path::{Path, PathBuf};
77
use std::process::Command;
88

9+
use semver::Version;
910
use tracing::*;
1011

1112
use crate::common::{Config, Debugger, FailMode, Mode, PassMode};
@@ -1113,34 +1114,47 @@ fn parse_normalize_rule(header: &str) -> Option<(String, String)> {
11131114
Some((regex, replacement))
11141115
}
11151116

1116-
pub fn extract_llvm_version(version: &str) -> Option<u32> {
1117-
let pat = |c: char| !c.is_ascii_digit() && c != '.';
1118-
let version_without_suffix = match version.find(pat) {
1119-
Some(pos) => &version[..pos],
1117+
/// Given an llvm version string that looks like `1.2.3-rc1`, extract as semver. Note that this
1118+
/// accepts more than just strict `semver` syntax (as in `major.minor.patch`); this permits omitting
1119+
/// minor and patch version components so users can write e.g. `//@ min-llvm-version: 19` instead of
1120+
/// having to write `//@ min-llvm-version: 19.0.0`.
1121+
///
1122+
/// Currently panics if the input string is malformed, though we really should not use panic as an
1123+
/// error handling strategy.
1124+
///
1125+
/// FIXME(jieyouxu): improve error handling
1126+
pub fn extract_llvm_version(version: &str) -> Version {
1127+
// The version substring we're interested in usually looks like the `1.2.3`, without any of the
1128+
// fancy suffix like `-rc1` or `meow`.
1129+
let version = version.trim();
1130+
let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
1131+
let version_without_suffix = match version.split_once(uninterested) {
1132+
Some((prefix, _suffix)) => prefix,
11201133
None => version,
11211134
};
1122-
let components: Vec<u32> = version_without_suffix
1135+
1136+
let components: Vec<u64> = version_without_suffix
11231137
.split('.')
1124-
.map(|s| s.parse().expect("Malformed version component"))
1138+
.map(|s| s.parse().expect("llvm version component should consist of only digits"))
11251139
.collect();
1126-
let version = match *components {
1127-
[a] => a * 10_000,
1128-
[a, b] => a * 10_000 + b * 100,
1129-
[a, b, c] => a * 10_000 + b * 100 + c,
1130-
_ => panic!("Malformed version"),
1131-
};
1132-
Some(version)
1140+
1141+
match &components[..] {
1142+
[major] => Version::new(*major, 0, 0),
1143+
[major, minor] => Version::new(*major, *minor, 0),
1144+
[major, minor, patch] => Version::new(*major, *minor, *patch),
1145+
_ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
1146+
}
11331147
}
11341148

1135-
pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<u32> {
1149+
pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
11361150
let output = Command::new(binary_path).arg("--version").output().ok()?;
11371151
if !output.status.success() {
11381152
return None;
11391153
}
11401154
let version = String::from_utf8(output.stdout).ok()?;
11411155
for line in version.lines() {
11421156
if let Some(version) = line.split("LLVM version ").nth(1) {
1143-
return extract_llvm_version(version);
1157+
return Some(extract_llvm_version(version));
11441158
}
11451159
}
11461160
None
@@ -1247,15 +1261,17 @@ pub fn llvm_has_libzstd(config: &Config) -> bool {
12471261
false
12481262
}
12491263

1250-
/// Takes a directive of the form `"<version1> [- <version2>]"`,
1251-
/// returns the numeric representation of `<version1>` and `<version2>` as
1252-
/// tuple: `(<version1> as u32, <version2> as u32)`.
1264+
/// Takes a directive of the form `"<version1> [- <version2>]"`, returns the numeric representation
1265+
/// of `<version1>` and `<version2>` as tuple: `(<version1>, <version2>)`.
12531266
///
1254-
/// If the `<version2>` part is omitted, the second component of the tuple
1255-
/// is the same as `<version1>`.
1256-
fn extract_version_range<F>(line: &str, parse: F) -> Option<(u32, u32)>
1267+
/// If the `<version2>` part is omitted, the second component of the tuple is the same as
1268+
/// `<version1>`.
1269+
fn extract_version_range<'a, F, VersionTy: Clone>(
1270+
line: &'a str,
1271+
parse: F,
1272+
) -> Option<(VersionTy, VersionTy)>
12571273
where
1258-
F: Fn(&str) -> Option<u32>,
1274+
F: Fn(&'a str) -> Option<VersionTy>,
12591275
{
12601276
let mut splits = line.splitn(2, "- ").map(str::trim);
12611277
let min = splits.next().unwrap();
@@ -1273,7 +1289,7 @@ where
12731289
let max = match max {
12741290
Some("") => return None,
12751291
Some(max) => parse(max)?,
1276-
_ => min,
1292+
_ => min.clone(),
12771293
};
12781294

12791295
Some((min, max))
@@ -1489,43 +1505,55 @@ fn ignore_llvm(config: &Config, line: &str) -> IgnoreDecision {
14891505
};
14901506
}
14911507
}
1492-
if let Some(actual_version) = config.llvm_version {
1493-
if let Some(rest) = line.strip_prefix("min-llvm-version:").map(str::trim) {
1494-
let min_version = extract_llvm_version(rest).unwrap();
1495-
// Ignore if actual version is smaller the minimum required
1496-
// version
1497-
if actual_version < min_version {
1508+
if let Some(actual_version) = &config.llvm_version {
1509+
// Note that these `min` versions will check for not just major versions.
1510+
1511+
if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1512+
let min_version = extract_llvm_version(&version_string);
1513+
// Ignore if actual version is smaller than the minimum required version.
1514+
if *actual_version < min_version {
14981515
return IgnoreDecision::Ignore {
1499-
reason: format!("ignored when the LLVM version is older than {rest}"),
1516+
reason: format!(
1517+
"ignored when the LLVM version {actual_version} is older than {min_version}"
1518+
),
15001519
};
15011520
}
1502-
} else if let Some(rest) = line.strip_prefix("min-system-llvm-version:").map(str::trim) {
1503-
let min_version = extract_llvm_version(rest).unwrap();
1521+
} else if let Some(version_string) =
1522+
config.parse_name_value_directive(line, "min-system-llvm-version")
1523+
{
1524+
let min_version = extract_llvm_version(&version_string);
15041525
// Ignore if using system LLVM and actual version
15051526
// is smaller the minimum required version
1506-
if config.system_llvm && actual_version < min_version {
1527+
if config.system_llvm && *actual_version < min_version {
15071528
return IgnoreDecision::Ignore {
1508-
reason: format!("ignored when the system LLVM version is older than {rest}"),
1529+
reason: format!(
1530+
"ignored when the system LLVM version {actual_version} is older than {min_version}"
1531+
),
15091532
};
15101533
}
1511-
} else if let Some(rest) = line.strip_prefix("ignore-llvm-version:").map(str::trim) {
1534+
} else if let Some(version_range) =
1535+
config.parse_name_value_directive(line, "ignore-llvm-version")
1536+
{
15121537
// Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
15131538
let (v_min, v_max) =
1514-
extract_version_range(rest, extract_llvm_version).unwrap_or_else(|| {
1515-
panic!("couldn't parse version range: {:?}", rest);
1516-
});
1539+
extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1540+
.unwrap_or_else(|| {
1541+
panic!("couldn't parse version range: \"{version_range}\"");
1542+
});
15171543
if v_max < v_min {
1518-
panic!("Malformed LLVM version range: max < min")
1544+
panic!("malformed LLVM version range where {v_max} < {v_min}")
15191545
}
15201546
// Ignore if version lies inside of range.
1521-
if actual_version >= v_min && actual_version <= v_max {
1547+
if *actual_version >= v_min && *actual_version <= v_max {
15221548
if v_min == v_max {
15231549
return IgnoreDecision::Ignore {
1524-
reason: format!("ignored when the LLVM version is {rest}"),
1550+
reason: format!("ignored when the LLVM version is {actual_version}"),
15251551
};
15261552
} else {
15271553
return IgnoreDecision::Ignore {
1528-
reason: format!("ignored when the LLVM version is between {rest}"),
1554+
reason: format!(
1555+
"ignored when the LLVM version is between {v_min} and {v_max}"
1556+
),
15291557
};
15301558
}
15311559
}

src/tools/compiletest/src/header/tests.rs

+65-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use std::io::Read;
22
use std::path::Path;
33

4-
use super::iter_header;
4+
use semver::Version;
5+
6+
use super::{
7+
EarlyProps, HeadersCache, extract_llvm_version, extract_version_range, iter_header,
8+
parse_normalize_rule,
9+
};
510
use crate::common::{Config, Debugger, Mode};
6-
use crate::header::{EarlyProps, HeadersCache, parse_normalize_rule};
711

812
fn make_test_description<R: Read>(
913
config: &Config,
@@ -407,19 +411,67 @@ fn channel() {
407411
assert!(!check_ignore(&config, "//@ ignore-stable"));
408412
}
409413

414+
#[test]
415+
fn test_extract_llvm_version() {
416+
// Note: officially, semver *requires* that versions at the minimum have all three
417+
// `major.minor.patch` numbers, though for test-writer's convenience we allow omitting the minor
418+
// and patch numbers (which will be stubbed out as 0).
419+
assert_eq!(extract_llvm_version("0"), Version::new(0, 0, 0));
420+
assert_eq!(extract_llvm_version("0.0"), Version::new(0, 0, 0));
421+
assert_eq!(extract_llvm_version("0.0.0"), Version::new(0, 0, 0));
422+
assert_eq!(extract_llvm_version("1"), Version::new(1, 0, 0));
423+
assert_eq!(extract_llvm_version("1.2"), Version::new(1, 2, 0));
424+
assert_eq!(extract_llvm_version("1.2.3"), Version::new(1, 2, 3));
425+
assert_eq!(extract_llvm_version("4.5.6git"), Version::new(4, 5, 6));
426+
assert_eq!(extract_llvm_version("4.5.6-rc1"), Version::new(4, 5, 6));
427+
assert_eq!(extract_llvm_version("123.456.789-rc1"), Version::new(123, 456, 789));
428+
assert_eq!(extract_llvm_version("8.1.2-rust"), Version::new(8, 1, 2));
429+
assert_eq!(extract_llvm_version("9.0.1-rust-1.43.0-dev"), Version::new(9, 0, 1));
430+
assert_eq!(extract_llvm_version("9.3.1-rust-1.43.0-dev"), Version::new(9, 3, 1));
431+
assert_eq!(extract_llvm_version("10.0.0-rust"), Version::new(10, 0, 0));
432+
assert_eq!(extract_llvm_version("11.1.0"), Version::new(11, 1, 0));
433+
assert_eq!(extract_llvm_version("12.0.0libcxx"), Version::new(12, 0, 0));
434+
assert_eq!(extract_llvm_version("12.0.0-rc3"), Version::new(12, 0, 0));
435+
assert_eq!(extract_llvm_version("13.0.0git"), Version::new(13, 0, 0));
436+
}
437+
438+
#[test]
439+
#[should_panic]
440+
fn test_llvm_version_invalid_components() {
441+
extract_llvm_version("4.x.6");
442+
}
443+
444+
#[test]
445+
#[should_panic]
446+
fn test_llvm_version_invalid_prefix() {
447+
extract_llvm_version("meow4.5.6");
448+
}
449+
450+
#[test]
451+
#[should_panic]
452+
fn test_llvm_version_too_many_components() {
453+
extract_llvm_version("4.5.6.7");
454+
}
455+
410456
#[test]
411457
fn test_extract_version_range() {
412-
use super::{extract_llvm_version, extract_version_range};
413-
414-
assert_eq!(extract_version_range("1.2.3 - 4.5.6", extract_llvm_version), Some((10203, 40506)));
415-
assert_eq!(extract_version_range("0 - 4.5.6", extract_llvm_version), Some((0, 40506)));
416-
assert_eq!(extract_version_range("1.2.3 -", extract_llvm_version), None);
417-
assert_eq!(extract_version_range("1.2.3 - ", extract_llvm_version), None);
418-
assert_eq!(extract_version_range("- 4.5.6", extract_llvm_version), None);
419-
assert_eq!(extract_version_range("-", extract_llvm_version), None);
420-
assert_eq!(extract_version_range(" - 4.5.6", extract_llvm_version), None);
421-
assert_eq!(extract_version_range(" - 4.5.6", extract_llvm_version), None);
422-
assert_eq!(extract_version_range("0 -", extract_llvm_version), None);
458+
let wrapped_extract = |s: &str| Some(extract_llvm_version(s));
459+
460+
assert_eq!(
461+
extract_version_range("1.2.3 - 4.5.6", wrapped_extract),
462+
Some((Version::new(1, 2, 3), Version::new(4, 5, 6)))
463+
);
464+
assert_eq!(
465+
extract_version_range("0 - 4.5.6", wrapped_extract),
466+
Some((Version::new(0, 0, 0), Version::new(4, 5, 6)))
467+
);
468+
assert_eq!(extract_version_range("1.2.3 -", wrapped_extract), None);
469+
assert_eq!(extract_version_range("1.2.3 - ", wrapped_extract), None);
470+
assert_eq!(extract_version_range("- 4.5.6", wrapped_extract), None);
471+
assert_eq!(extract_version_range("-", wrapped_extract), None);
472+
assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None);
473+
assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None);
474+
assert_eq!(extract_version_range("0 -", wrapped_extract), None);
423475
}
424476

425477
#[test]

src/tools/compiletest/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
228228
Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
229229
};
230230
let llvm_version =
231-
matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version).or_else(
231+
matches.opt_str("llvm-version").as_deref().map(header::extract_llvm_version).or_else(
232232
|| header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
233233
);
234234

src/tools/compiletest/src/tests.rs

-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::ffi::OsString;
22

33
use crate::debuggers::{extract_gdb_version, extract_lldb_version};
4-
use crate::header::extract_llvm_version;
54
use crate::is_test;
65

76
#[test]
@@ -67,15 +66,3 @@ fn is_test_test() {
6766
assert!(!is_test(&OsString::from("#a_dog_gif")));
6867
assert!(!is_test(&OsString::from("~a_temp_file")));
6968
}
70-
71-
#[test]
72-
fn test_extract_llvm_version() {
73-
assert_eq!(extract_llvm_version("8.1.2-rust"), Some(80102));
74-
assert_eq!(extract_llvm_version("9.0.1-rust-1.43.0-dev"), Some(90001));
75-
assert_eq!(extract_llvm_version("9.3.1-rust-1.43.0-dev"), Some(90301));
76-
assert_eq!(extract_llvm_version("10.0.0-rust"), Some(100000));
77-
assert_eq!(extract_llvm_version("11.1.0"), Some(110100));
78-
assert_eq!(extract_llvm_version("12.0.0libcxx"), Some(120000));
79-
assert_eq!(extract_llvm_version("12.0.0-rc3"), Some(120000));
80-
assert_eq!(extract_llvm_version("13.0.0git"), Some(130000));
81-
}

0 commit comments

Comments
 (0)