Skip to content

Commit 8b92efb

Browse files
committed
compiletest: improve robustness of LLVM version handling
1 parent a9d1762 commit 8b92efb

File tree

5 files changed

+143
-65
lines changed

5 files changed

+143
-65
lines changed

src/tools/compiletest/src/common.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,25 @@ pub enum Sanitizer {
167167
Hwaddress,
168168
}
169169

170+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
171+
pub struct LlvmVersion {
172+
pub major: u32,
173+
pub minor: u32,
174+
pub patch: u32,
175+
}
176+
177+
impl LlvmVersion {
178+
pub fn new(major: u32, minor: u32, patch: u32) -> LlvmVersion {
179+
Self { major, minor, patch }
180+
}
181+
}
182+
183+
impl fmt::Display for LlvmVersion {
184+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
185+
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
186+
}
187+
}
188+
170189
/// Configuration for compiletest
171190
#[derive(Debug, Default, Clone)]
172191
pub struct Config {
@@ -298,7 +317,7 @@ pub struct Config {
298317
pub lldb_version: Option<u32>,
299318

300319
/// Version of LLVM
301-
pub llvm_version: Option<u32>,
320+
pub llvm_version: Option<LlvmVersion>,
302321

303322
/// Is LLVM a system LLVM
304323
pub system_llvm: bool,

src/tools/compiletest/src/header.rs

+59-37
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::process::Command;
88

99
use tracing::*;
1010

11-
use crate::common::{Config, Debugger, FailMode, Mode, PassMode};
11+
use crate::common::{Config, Debugger, FailMode, LlvmVersion, Mode, PassMode};
1212
use crate::debuggers::{extract_cdb_version, extract_gdb_version};
1313
use crate::header::auxiliary::{AuxProps, parse_and_update_aux};
1414
use crate::header::cfg::{MatchOutcome, parse_cfg_name_directive};
@@ -1113,34 +1113,45 @@ fn parse_normalize_rule(header: &str) -> Option<(String, String)> {
11131113
Some((regex, replacement))
11141114
}
11151115

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],
1116+
/// Given an llvm version string that looks like `1.2.3-rc1`, extract key version info into a
1117+
/// [`LlvmVersion`].
1118+
///
1119+
/// Currently panics if the input string is malformed, though we really should not use panic as an
1120+
/// error handling strategy.
1121+
///
1122+
/// FIXME(jieyouxu): improve error handling
1123+
pub fn extract_llvm_version(version: &str) -> LlvmVersion {
1124+
// The version substring we're interested in usually looks like the `1.2.3`, without any of the
1125+
// fancy suffix like `-rc1` or `meow`.
1126+
let version = version.trim();
1127+
let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
1128+
let version_without_suffix = match version.split_once(uninterested) {
1129+
Some((prefix, _suffix)) => prefix,
11201130
None => version,
11211131
};
1132+
11221133
let components: Vec<u32> = version_without_suffix
11231134
.split('.')
1124-
.map(|s| s.parse().expect("Malformed version component"))
1135+
.map(|s| s.parse().expect("llvm version component should consist of only digits"))
11251136
.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)
1137+
1138+
match &components[..] {
1139+
[major] => LlvmVersion::new(*major, 0, 0),
1140+
[major, minor] => LlvmVersion::new(*major, *minor, 0),
1141+
[major, minor, patch] => LlvmVersion::new(*major, *minor, *patch),
1142+
_ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
1143+
}
11331144
}
11341145

1135-
pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<u32> {
1146+
pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<LlvmVersion> {
11361147
let output = Command::new(binary_path).arg("--version").output().ok()?;
11371148
if !output.status.success() {
11381149
return None;
11391150
}
11401151
let version = String::from_utf8(output.stdout).ok()?;
11411152
for line in version.lines() {
11421153
if let Some(version) = line.split("LLVM version ").nth(1) {
1143-
return extract_llvm_version(version);
1154+
return Some(extract_llvm_version(version));
11441155
}
11451156
}
11461157
None
@@ -1247,15 +1258,14 @@ pub fn llvm_has_libzstd(config: &Config) -> bool {
12471258
false
12481259
}
12491260

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)`.
1261+
/// Takes a directive of the form `"<version1> [- <version2>]"`, returns the numeric representation
1262+
/// of `<version1>` and `<version2>` as tuple: `(<version1>, <version2>)`.
12531263
///
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)>
1264+
/// If the `<version2>` part is omitted, the second component of the tuple is the same as
1265+
/// `<version1>`.
1266+
fn extract_version_range<F, VersionTy: Copy>(line: &str, parse: F) -> Option<(VersionTy, VersionTy)>
12571267
where
1258-
F: Fn(&str) -> Option<u32>,
1268+
F: Fn(&str) -> Option<VersionTy>,
12591269
{
12601270
let mut splits = line.splitn(2, "- ").map(str::trim);
12611271
let min = splits.next().unwrap();
@@ -1490,42 +1500,54 @@ fn ignore_llvm(config: &Config, line: &str) -> IgnoreDecision {
14901500
}
14911501
}
14921502
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
1503+
// Note that these `min` versions will check for not just major versions.
1504+
1505+
if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1506+
let min_version = extract_llvm_version(&version_string);
1507+
// Ignore if actual version is smaller than the minimum required version.
14971508
if actual_version < min_version {
14981509
return IgnoreDecision::Ignore {
1499-
reason: format!("ignored when the LLVM version is older than {rest}"),
1510+
reason: format!(
1511+
"ignored when the LLVM version {actual_version} is older than {min_version}"
1512+
),
15001513
};
15011514
}
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();
1515+
} else if let Some(version_string) =
1516+
config.parse_name_value_directive(line, "min-system-llvm-version")
1517+
{
1518+
let min_version = extract_llvm_version(&version_string);
15041519
// Ignore if using system LLVM and actual version
15051520
// is smaller the minimum required version
15061521
if config.system_llvm && actual_version < min_version {
15071522
return IgnoreDecision::Ignore {
1508-
reason: format!("ignored when the system LLVM version is older than {rest}"),
1523+
reason: format!(
1524+
"ignored when the system LLVM version {actual_version} is older than {min_version}"
1525+
),
15091526
};
15101527
}
1511-
} else if let Some(rest) = line.strip_prefix("ignore-llvm-version:").map(str::trim) {
1528+
} else if let Some(version_range) =
1529+
config.parse_name_value_directive(line, "ignore-llvm-version")
1530+
{
15121531
// Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
15131532
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-
});
1533+
extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1534+
.unwrap_or_else(|| {
1535+
panic!("couldn't parse version range: \"{version_range}\"");
1536+
});
15171537
if v_max < v_min {
1518-
panic!("Malformed LLVM version range: max < min")
1538+
panic!("malformed LLVM version range where {v_max} < {v_min}")
15191539
}
15201540
// Ignore if version lies inside of range.
15211541
if actual_version >= v_min && actual_version <= v_max {
15221542
if v_min == v_max {
15231543
return IgnoreDecision::Ignore {
1524-
reason: format!("ignored when the LLVM version is {rest}"),
1544+
reason: format!("ignored when the LLVM version is {actual_version}"),
15251545
};
15261546
} else {
15271547
return IgnoreDecision::Ignore {
1528-
reason: format!("ignored when the LLVM version is between {rest}"),
1548+
reason: format!(
1549+
"ignored when the LLVM version is between {v_min} and {v_max}"
1550+
),
15291551
};
15301552
}
15311553
}

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

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

4-
use super::iter_header;
4+
use super::{
5+
EarlyProps, HeadersCache, LlvmVersion, extract_llvm_version, extract_version_range,
6+
iter_header, parse_normalize_rule,
7+
};
58
use crate::common::{Config, Debugger, Mode};
6-
use crate::header::{EarlyProps, HeadersCache, parse_normalize_rule};
79

810
fn make_test_description<R: Read>(
911
config: &Config,
@@ -407,19 +409,67 @@ fn channel() {
407409
assert!(!check_ignore(&config, "//@ ignore-stable"));
408410
}
409411

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

425475
#[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)