use super::report::{ar_capabilities_as_json, cxx_capabilities_as_json};
use super::*;
use crate::language_standard::{CStandard, CxxStandard};
#[test]
fn parses_clang_first_line() {
let id = parse_cxx_version_output(
"clang version 17.0.6\nTarget: x86_64-unknown-linux-gnu\nThread model: posix\n",
);
assert_eq!(id.kind, CompilerKind::Clang);
let v = id.version.expect("version parsed");
assert_eq!(v.major, 17);
assert_eq!(v.minor, Some(0));
assert_eq!(v.patch, Some(6));
assert_eq!(id.target.as_deref(), Some("x86_64-unknown-linux-gnu"));
}
#[test]
fn parses_apple_clang() {
let id = parse_cxx_version_output(
"Apple clang version 14.0.3 (clang-1403.0.22.14.1)\nTarget: arm64-apple-darwin22.5.0\nThread model: posix\n",
);
assert_eq!(id.kind, CompilerKind::AppleClang);
let v = id.version.unwrap();
assert_eq!((v.major, v.minor, v.patch), (14, Some(0), Some(3)));
}
#[test]
fn parses_gcc_with_distro_prefix() {
let id = parse_cxx_version_output(
"g++ (Ubuntu 11.4.0-1ubuntu1) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\n",
);
assert_eq!(id.kind, CompilerKind::Gcc);
let v = id.version.unwrap();
assert_eq!((v.major, v.minor, v.patch), (11, Some(4), Some(0)));
}
#[test]
fn parses_msvc_first_line() {
let id = parse_cxx_version_output(
"Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64\n",
);
assert_eq!(id.kind, CompilerKind::Msvc);
let v = id.version.unwrap();
assert_eq!(v.major, 19);
}
#[test]
fn unknown_when_unrecognized() {
let id = parse_cxx_version_output("My funky compiler 0.0\n");
assert_eq!(id.kind, CompilerKind::Unknown);
assert!(id.version.is_none());
}
#[test]
fn empty_output_is_unknown() {
let id = parse_cxx_version_output("");
assert_eq!(id.kind, CompilerKind::Unknown);
assert!(id.raw_version_line.is_empty());
}
#[test]
fn parses_gnu_ar() {
let id = parse_ar_version_output(
"GNU ar (GNU Binutils for Debian) 2.40\nCopyright (C) 2023 Free Software Foundation, Inc.\n",
);
assert_eq!(id.kind, ArchiverKind::Ar);
let v = id.version.unwrap();
assert_eq!(v.major, 2);
}
#[test]
fn parses_llvm_ar_version() {
let id = parse_ar_version_output(
"LLVM (http://llvm.org/):\n LLVM version 17.0.6\n Optimized build.\n",
);
assert_eq!(id.kind, ArchiverKind::LlvmAr);
let v = id.version.unwrap();
assert_eq!(v.major, 17);
}
#[test]
fn detects_lib_exe_as_unsupported() {
let id = parse_ar_version_output(
"Microsoft (R) Library Manager Version 14.39.33523.0\nCopyright (C) Microsoft Corporation.\n",
);
assert_eq!(id.kind, ArchiverKind::Lib);
}
#[test]
fn unknown_archiver_classification() {
let id = parse_ar_version_output("just-some-archiver 0.1\n");
assert_eq!(id.kind, ArchiverKind::Unknown);
assert!(id.version.is_none());
}
#[test]
fn clang_capabilities_include_gcc_style_and_depfile() {
let id = CompilerIdentity {
kind: CompilerKind::Clang,
version: CompilerVersion::parse("17.0.6"),
target: None,
raw_version_line: "clang version 17.0.6".into(),
};
let caps = derive_cxx_capabilities(&id);
assert!(caps.gcc_style_flags.supported);
assert!(caps.depfile_mmd_mf.supported);
}
#[test]
fn gcc_standard_capabilities_are_version_gated_per_standard() {
let gcc = |v: &str| CompilerIdentity {
kind: CompilerKind::Gcc,
version: CompilerVersion::parse(v),
target: None,
raw_version_line: format!("g++ {v}"),
};
assert!(cxx_standard_capability(&gcc("4.8.5"), CxxStandard::Cxx11).supported);
assert!(!cxx_standard_capability(&gcc("4.8.5"), CxxStandard::Cxx17).supported);
assert!(cxx_standard_capability(&gcc("10.1.0"), CxxStandard::Cxx20).supported);
assert!(!cxx_standard_capability(&gcc("9.4.0"), CxxStandard::Cxx20).supported);
assert!(cxx_standard_capability(&gcc("11.4.0"), CxxStandard::Cxx23).supported);
assert!(!cxx_standard_capability(&gcc("10.5.0"), CxxStandard::Cxx23).supported);
assert!(c_standard_capability(&gcc("8.1.0"), CStandard::C17).supported);
assert!(!c_standard_capability(&gcc("7.5.0"), CStandard::C17).supported);
assert!(c_standard_capability(&gcc("14.1.0"), CStandard::C23).supported);
assert!(!c_standard_capability(&gcc("13.2.0"), CStandard::C23).supported);
let mut unknown_version = gcc("10.1.0");
unknown_version.version = None;
let cap = cxx_standard_capability(&unknown_version, CxxStandard::Cxx23);
assert!(cap.supported);
assert_eq!(cap.source, CapabilitySource::AssumedDefault);
}
#[test]
fn clang_and_apple_clang_standard_gates_follow_the_audited_table() {
let make = |kind: CompilerKind, v: &str| CompilerIdentity {
kind,
version: CompilerVersion::parse(v),
target: None,
raw_version_line: format!("clang version {v}"),
};
assert!(
cxx_standard_capability(&make(CompilerKind::Clang, "10.0.0"), CxxStandard::Cxx20).supported
);
assert!(
!cxx_standard_capability(&make(CompilerKind::Clang, "9.0.1"), CxxStandard::Cxx20).supported
);
assert!(
cxx_standard_capability(&make(CompilerKind::Clang, "17.0.6"), CxxStandard::Cxx23).supported
);
assert!(
!cxx_standard_capability(&make(CompilerKind::Clang, "16.0.0"), CxxStandard::Cxx23)
.supported
);
assert!(c_standard_capability(&make(CompilerKind::Clang, "18.1.0"), CStandard::C23).supported);
assert!(!c_standard_capability(&make(CompilerKind::Clang, "17.0.6"), CStandard::C23).supported);
assert!(
cxx_standard_capability(
&make(CompilerKind::AppleClang, "12.0.0"),
CxxStandard::Cxx20
)
.supported
);
assert!(
!cxx_standard_capability(
&make(CompilerKind::AppleClang, "11.0.3"),
CxxStandard::Cxx20
)
.supported
);
assert!(
cxx_standard_capability(
&make(CompilerKind::AppleClang, "16.0.0"),
CxxStandard::Cxx23
)
.supported
);
assert!(
c_standard_capability(&make(CompilerKind::AppleClang, "17.0.0"), CStandard::C23).supported
);
}
#[test]
fn cxx26_gates_follow_the_audited_table_and_have_no_msvc_flag() {
let make = |kind: CompilerKind, v: &str| CompilerIdentity {
kind,
version: CompilerVersion::parse(v),
target: None,
raw_version_line: format!("compiler {v}"),
};
assert!(
cxx_standard_capability(&make(CompilerKind::Gcc, "14.1.0"), CxxStandard::Cxx26).supported
);
assert!(
!cxx_standard_capability(&make(CompilerKind::Gcc, "13.2.0"), CxxStandard::Cxx26).supported
);
assert!(
cxx_standard_capability(&make(CompilerKind::Clang, "17.0.6"), CxxStandard::Cxx26).supported
);
assert!(
!cxx_standard_capability(&make(CompilerKind::Clang, "16.0.0"), CxxStandard::Cxx26)
.supported
);
assert!(
cxx_standard_capability(
&make(CompilerKind::AppleClang, "16.0.0"),
CxxStandard::Cxx26
)
.supported
);
assert!(
!cxx_standard_capability(
&make(CompilerKind::AppleClang, "15.0.0"),
CxxStandard::Cxx26
)
.supported
);
for kind in [CompilerKind::Msvc, CompilerKind::ClangCl] {
let cap = cxx_standard_capability(&make(kind, "19.39.0"), CxxStandard::Cxx26);
assert!(!cap.supported, "{kind}");
assert_eq!(cap.source, CapabilitySource::Unsupported);
}
}
#[test]
fn msvc_capabilities_reject_gcc_style() {
let id = CompilerIdentity {
kind: CompilerKind::Msvc,
version: CompilerVersion::parse("19.39.0"),
target: None,
raw_version_line: "Microsoft Optimizing Compiler".into(),
};
let caps = derive_cxx_capabilities(&id);
assert!(!caps.gcc_style_flags.supported);
assert_eq!(caps.gcc_style_flags.source, CapabilitySource::Unsupported);
assert!(caps.msvc_style_flags.supported);
}
#[test]
fn unknown_compiler_capabilities_are_conservative() {
let id = CompilerIdentity::unknown("strange compiler");
let caps = derive_cxx_capabilities(&id);
assert!(!caps.gcc_style_flags.supported);
assert_eq!(
caps.gcc_style_flags.source,
CapabilitySource::AssumedDefault
);
assert!(!caps.depfile_mmd_mf.supported);
}
#[test]
fn ar_capabilities_recognize_gnu_ar() {
let id = ArchiverIdentity {
kind: ArchiverKind::Ar,
version: CompilerVersion::parse("2.40"),
raw_version_line: "GNU ar".into(),
};
let caps = derive_ar_capabilities(&id);
assert!(caps.ar_crs.supported);
assert!(caps.static_library_output.supported);
}
#[test]
fn msvc_lib_archives_without_ar_crs() {
let id = ArchiverIdentity {
kind: ArchiverKind::Lib,
version: None,
raw_version_line: "Microsoft Library Manager".into(),
};
let caps = derive_ar_capabilities(&id);
assert!(!caps.ar_crs.supported);
assert_eq!(caps.ar_crs.source, CapabilitySource::Unsupported);
assert!(caps.static_library_output.supported);
}
#[test]
fn validate_accepts_msvc_cxx() {
let id = CompilerIdentity {
kind: CompilerKind::Msvc,
version: None,
target: None,
raw_version_line: "MSVC".into(),
};
let caps = derive_cxx_capabilities(&id);
assert!(caps.msvc_style_flags.supported);
assert!(validate_cxx_for_backend("cl.exe", &id, &caps).is_ok());
}
fn msvc_identity(version: &str) -> CompilerIdentity {
CompilerIdentity {
kind: CompilerKind::Msvc,
version: CompilerVersion::parse(version),
target: None,
raw_version_line: format!("Microsoft Optimizing Compiler {version}"),
}
}
#[test]
fn msvc_std_capabilities_are_version_gated() {
let modern = msvc_identity("19.39.33523");
let cap = cxx_standard_capability(&modern, CxxStandard::Cxx17);
assert!(cap.supported);
assert_eq!(cap.source, CapabilitySource::Version);
assert!(c_standard_capability(&modern, CStandard::C11).supported);
assert!(cxx_standard_capability(&modern, CxxStandard::Cxx20).supported);
let mid = msvc_identity("19.20.0");
assert!(cxx_standard_capability(&mid, CxxStandard::Cxx17).supported);
let cap = c_standard_capability(&mid, CStandard::C11);
assert!(!cap.supported);
assert_eq!(cap.source, CapabilitySource::Version);
assert!(!cxx_standard_capability(&mid, CxxStandard::Cxx20).supported);
let old = msvc_identity("19.00.24210");
assert!(!cxx_standard_capability(&old, CxxStandard::Cxx17).supported);
assert!(!c_standard_capability(&old, CStandard::C11).supported);
}
#[test]
fn msvc_has_no_stable_flag_for_old_or_new_standards() {
let cl = msvc_identity("19.39.33523");
for standard in [
CxxStandard::Cxx98,
CxxStandard::Cxx11,
CxxStandard::Cxx23,
CxxStandard::Cxx26,
] {
let cap = cxx_standard_capability(&cl, standard);
assert!(!cap.supported, "{standard} must be unsupported on cl");
assert_eq!(cap.source, CapabilitySource::Unsupported);
}
for standard in [CStandard::C89, CStandard::C99, CStandard::C23] {
let cap = c_standard_capability(&cl, standard);
assert!(!cap.supported, "{standard} must be unsupported on cl");
assert_eq!(cap.source, CapabilitySource::Unsupported);
}
assert!(c_standard_capability(&cl, CStandard::C17).supported);
}
#[test]
fn msvc_unparsed_version_assumes_modern_support() {
let id = CompilerIdentity {
kind: CompilerKind::Msvc,
version: None,
target: None,
raw_version_line: "Microsoft Optimizing Compiler".into(),
};
let cap = cxx_standard_capability(&id, CxxStandard::Cxx17);
assert!(cap.supported);
assert_eq!(cap.source, CapabilitySource::AssumedDefault);
let cap = c_standard_capability(&id, CStandard::C11);
assert!(cap.supported);
assert_eq!(cap.source, CapabilitySource::AssumedDefault);
}
#[test]
fn gnu_c_standard_11_is_unconditional() {
for id in [
CompilerIdentity {
kind: CompilerKind::Gcc,
version: CompilerVersion::parse("4.8.5"),
target: None,
raw_version_line: "g++ 4.8.5".into(),
},
CompilerIdentity {
kind: CompilerKind::Clang,
version: CompilerVersion::parse("3.4"),
target: None,
raw_version_line: "clang version 3.4".into(),
},
] {
assert!(c_standard_capability(&id, CStandard::C11).supported);
}
}
#[test]
fn external_include_dirs_capability_is_version_gated_for_msvc() {
let modern = derive_cxx_capabilities(&msvc_identity("19.29.30133"));
assert!(modern.external_include_dirs.supported);
assert_eq!(
modern.external_include_dirs.source,
CapabilitySource::Version
);
let old = derive_cxx_capabilities(&msvc_identity("19.28.29915"));
assert!(!old.external_include_dirs.supported);
assert_eq!(old.external_include_dirs.source, CapabilitySource::Version);
}
#[test]
fn external_include_dirs_capability_is_unconditional_for_gnu_style() {
for kind in [
CompilerKind::Gcc,
CompilerKind::Clang,
CompilerKind::AppleClang,
] {
let id = CompilerIdentity {
kind,
version: CompilerVersion::parse("4.8.5"),
target: None,
raw_version_line: "old compiler 4.8.5".into(),
};
assert!(derive_cxx_capabilities(&id).external_include_dirs.supported);
}
let unknown = derive_cxx_capabilities(&CompilerIdentity::unknown("strange compiler"));
assert!(!unknown.external_include_dirs.supported);
assert_eq!(
unknown.external_include_dirs.source,
CapabilitySource::AssumedDefault
);
}
#[test]
fn external_include_dirs_capability_is_version_gated_for_clang_cl() {
let make = |version: &str| CompilerIdentity {
kind: CompilerKind::ClangCl,
version: CompilerVersion::parse(version),
target: None,
raw_version_line: format!("clang version {version}"),
};
assert!(
derive_cxx_capabilities(&make("13.0.0"))
.external_include_dirs
.supported
);
assert!(
!derive_cxx_capabilities(&make("12.0.1"))
.external_include_dirs
.supported
);
}
#[test]
fn validate_standards_rejects_msvc_too_old_for_std_flags() {
let old = msvc_identity("19.00.24210");
let err = validate_cxx_standards(
"cl.exe",
&old,
&std::collections::BTreeSet::from([CxxStandard::Cxx17]),
)
.unwrap_err();
assert!(matches!(
err,
ToolDetectionError::CxxLacksStandard {
standard: CxxStandard::Cxx17,
..
}
));
let err = validate_c_standards(
"cl.exe",
&old,
&std::collections::BTreeSet::from([CStandard::C11]),
)
.unwrap_err();
assert!(matches!(
err,
ToolDetectionError::CLacksStandard {
standard: CStandard::C11,
..
}
));
let modern = msvc_identity("19.39.33523");
let err = validate_cxx_standards(
"cl.exe",
&modern,
&std::collections::BTreeSet::from([CxxStandard::Cxx11, CxxStandard::Cxx20]),
)
.unwrap_err();
match err {
ToolDetectionError::CxxLacksStandard {
standard, detail, ..
} => {
assert_eq!(standard, CxxStandard::Cxx11);
assert!(detail.contains("no stable flag"), "detail: {detail}");
}
other => panic!("expected CxxLacksStandard, got {other:?}"),
}
}
#[test]
fn clang_cl_has_msvc_dialect_with_clang_diagnostics() {
let id = CompilerIdentity {
kind: CompilerKind::ClangCl,
version: CompilerVersion::parse("17.0.6"),
target: None,
raw_version_line: "clang version 17.0.6".into(),
};
assert!(id.kind.speaks_msvc_dialect());
assert!(id.kind.is_clang_like());
assert!(!id.kind.supports_gcc_style_command_line());
let caps = derive_cxx_capabilities(&id);
assert!(caps.msvc_style_flags.supported);
assert!(!caps.gcc_style_flags.supported);
assert!(!caps.depfile_mmd_mf.supported);
assert!(cxx_standard_capability(&id, CxxStandard::Cxx17).supported);
assert!(c_standard_capability(&id, CStandard::C11).supported);
assert!(cxx_standard_capability(&id, CxxStandard::Cxx20).supported);
assert!(!cxx_standard_capability(&id, CxxStandard::Cxx23).supported);
let old = CompilerIdentity {
kind: CompilerKind::ClangCl,
version: CompilerVersion::parse("12.0.0"),
target: None,
raw_version_line: "clang version 12.0.0".into(),
};
assert!(!c_standard_capability(&old, CStandard::C11).supported);
assert!(!cxx_standard_capability(&old, CxxStandard::Cxx20).supported);
assert!(validate_cxx_for_backend("clang-cl", &id, &caps).is_ok());
assert!(validate_cc_for_backend("clang-cl", &id, &caps).is_ok());
}
#[test]
fn validate_accepts_modern_and_unversioned_msvc_c() {
for id in [
msvc_identity("19.39.33523"),
CompilerIdentity {
kind: CompilerKind::Msvc,
version: None,
target: None,
raw_version_line: "MSVC".into(),
},
] {
let caps = derive_cxx_capabilities(&id);
assert!(validate_cc_for_backend("cl.exe", &id, &caps).is_ok());
}
}
#[test]
fn validate_rejects_unknown_cxx() {
let id = CompilerIdentity::unknown("???");
let caps = derive_cxx_capabilities(&id);
let err = validate_cxx_for_backend("custom-cxx", &id, &caps).unwrap_err();
assert!(matches!(
err,
ToolDetectionError::UnknownCxxRequiresGccStyle { .. }
));
}
#[test]
fn validate_accepts_clang() {
let id = CompilerIdentity {
kind: CompilerKind::Clang,
version: CompilerVersion::parse("17.0.6"),
target: None,
raw_version_line: "clang version 17.0.6".into(),
};
let caps = derive_cxx_capabilities(&id);
assert!(validate_cxx_for_backend("clang++", &id, &caps).is_ok());
}
#[test]
fn validate_standards_rejects_gcc_too_old_for_cxx17() {
let id = CompilerIdentity {
kind: CompilerKind::Gcc,
version: CompilerVersion::parse("4.8.5"),
target: None,
raw_version_line: "g++ 4.8".into(),
};
let err = validate_cxx_standards(
"g++",
&id,
&std::collections::BTreeSet::from([CxxStandard::Cxx17]),
)
.unwrap_err();
match err {
ToolDetectionError::CxxLacksStandard {
standard, detail, ..
} => {
assert_eq!(standard, CxxStandard::Cxx17);
assert!(detail.contains("version predates"), "detail: {detail}");
}
other => panic!("expected CxxLacksStandard, got {other:?}"),
}
}
#[test]
fn backend_validation_is_standard_agnostic() {
let id = CompilerIdentity {
kind: CompilerKind::Clang,
version: CompilerVersion::parse("17.0.6"),
target: None,
raw_version_line: "clang version 17.0.6".into(),
};
let caps = derive_cxx_capabilities(&id);
assert!(validate_cc_for_backend("cc", &id, &caps).is_ok());
assert!(validate_cxx_for_backend("cc", &id, &caps).is_ok());
assert!(validate_cxx_standards("cc", &id, &std::collections::BTreeSet::new()).is_ok());
assert!(validate_c_standards("cc", &id, &std::collections::BTreeSet::new()).is_ok());
}
#[test]
fn validate_cc_accepts_msvc() {
let id = CompilerIdentity {
kind: CompilerKind::Msvc,
version: None,
target: None,
raw_version_line: "MSVC".into(),
};
let caps = derive_cxx_capabilities(&id);
assert!(validate_cc_for_backend("cl.exe", &id, &caps).is_ok());
}
#[test]
fn validate_cc_rejects_unknown_compiler_without_gcc_style() {
let id = CompilerIdentity::unknown("???");
let caps = derive_cxx_capabilities(&id);
let err = validate_cc_for_backend("custom-cc", &id, &caps).unwrap_err();
assert!(matches!(
err,
ToolDetectionError::UnknownCRequiresGccStyle { .. }
));
}
#[test]
fn validate_cc_rejects_gcc_without_depfile_support() {
let id = CompilerIdentity {
kind: CompilerKind::Gcc,
version: CompilerVersion::parse("9.4.0"),
target: None,
raw_version_line: "gcc 9.4".into(),
};
let mut caps = derive_cxx_capabilities(&id);
caps.depfile_mmd_mf = Capability {
supported: false,
source: CapabilitySource::Unsupported,
};
let err = validate_cc_for_backend("cc", &id, &caps).unwrap_err();
assert!(matches!(err, ToolDetectionError::CLacksDepfile { .. }));
}
#[test]
fn validate_accepts_msvc_archiver() {
let id = ArchiverIdentity {
kind: ArchiverKind::Lib,
version: None,
raw_version_line: "Microsoft Library Manager".into(),
};
let caps = derive_ar_capabilities(&id);
assert!(validate_ar_for_backend("lib.exe", &id, &caps).is_ok());
}
#[test]
fn version_display_truncates_unset_components() {
let v = CompilerVersion::parse("11").unwrap();
assert_eq!(v.to_display_string(), "11");
let v = CompilerVersion::parse("11.4").unwrap();
assert_eq!(v.to_display_string(), "11.4");
let v = CompilerVersion::parse("11.4.0").unwrap();
assert_eq!(v.to_display_string(), "11.4.0");
}
fn pretty(value: &serde_json::Value) -> String {
serde_json::to_string_pretty(value).unwrap()
}
fn cxx_identity_and_capabilities_json(version_output: &str) -> String {
let id = parse_cxx_version_output(version_output);
let caps = derive_cxx_capabilities(&id);
pretty(&serde_json::json!({
"identity": id.as_json(),
"capabilities": cxx_capabilities_as_json(&caps),
}))
}
fn ar_identity_and_capabilities_json(version_output: &str) -> String {
let id = parse_ar_version_output(version_output);
let caps = derive_ar_capabilities(&id);
pretty(&serde_json::json!({
"identity": id.as_json(),
"capabilities": ar_capabilities_as_json(&caps),
}))
}
#[test]
fn snapshot_clang_identity_and_capabilities() {
let actual = cxx_identity_and_capabilities_json(
"clang version 17.0.6\nTarget: x86_64-unknown-linux-gnu\nThread model: posix\n",
);
let expected = r#"{
"identity": {
"kind": "clang",
"version": "17.0.6",
"target": "x86_64-unknown-linux-gnu",
"raw_version_line": "clang version 17.0.6"
},
"capabilities": {
"depfile_mmd_mf": {
"supported": true,
"source": "version"
},
"external_include_dirs": {
"supported": true,
"source": "version"
},
"gcc_style_flags": {
"supported": true,
"source": "version"
},
"msvc_style_flags": {
"supported": false,
"source": "assumed-default"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_apple_clang_identity_and_capabilities() {
let actual = cxx_identity_and_capabilities_json(
"Apple clang version 14.0.3 (clang-1403.0.22.14.1)\nTarget: arm64-apple-darwin22.5.0\nThread model: posix\n",
);
let expected = r#"{
"identity": {
"kind": "apple-clang",
"version": "14.0.3",
"target": "arm64-apple-darwin22.5.0",
"raw_version_line": "Apple clang version 14.0.3 (clang-1403.0.22.14.1)"
},
"capabilities": {
"depfile_mmd_mf": {
"supported": true,
"source": "version"
},
"external_include_dirs": {
"supported": true,
"source": "version"
},
"gcc_style_flags": {
"supported": true,
"source": "version"
},
"msvc_style_flags": {
"supported": false,
"source": "assumed-default"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_gcc_identity_and_capabilities() {
let actual = cxx_identity_and_capabilities_json(
"g++ (Ubuntu 11.4.0-1ubuntu1) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\n",
);
let expected = r#"{
"identity": {
"kind": "gcc",
"version": "11.4.0",
"raw_version_line": "g++ (Ubuntu 11.4.0-1ubuntu1) 11.4.0"
},
"capabilities": {
"depfile_mmd_mf": {
"supported": true,
"source": "version"
},
"external_include_dirs": {
"supported": true,
"source": "version"
},
"gcc_style_flags": {
"supported": true,
"source": "version"
},
"msvc_style_flags": {
"supported": false,
"source": "assumed-default"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_msvc_identity_and_capabilities() {
let actual = cxx_identity_and_capabilities_json(
"Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64\n",
);
let expected = r#"{
"identity": {
"kind": "msvc",
"version": "19.39.33523",
"raw_version_line": "Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64"
},
"capabilities": {
"depfile_mmd_mf": {
"supported": false,
"source": "unsupported"
},
"external_include_dirs": {
"supported": true,
"source": "version"
},
"gcc_style_flags": {
"supported": false,
"source": "unsupported"
},
"msvc_style_flags": {
"supported": true,
"source": "version"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_unknown_compiler_capabilities_are_conservative() {
let actual = cxx_identity_and_capabilities_json("My funky compiler 0.0\n");
let expected = r#"{
"identity": {
"kind": "unknown",
"raw_version_line": "My funky compiler 0.0"
},
"capabilities": {
"depfile_mmd_mf": {
"supported": false,
"source": "assumed-default"
},
"external_include_dirs": {
"supported": false,
"source": "assumed-default"
},
"gcc_style_flags": {
"supported": false,
"source": "assumed-default"
},
"msvc_style_flags": {
"supported": false,
"source": "assumed-default"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_gnu_ar_identity_and_capabilities() {
let actual = ar_identity_and_capabilities_json(
"GNU ar (GNU Binutils for Debian) 2.40\nCopyright (C) 2023 Free Software Foundation, Inc.\n",
);
let expected = r#"{
"identity": {
"kind": "ar",
"version": "2.40",
"raw_version_line": "GNU ar (GNU Binutils for Debian) 2.40"
},
"capabilities": {
"ar_crs": {
"supported": true,
"source": "version"
},
"static_library_output": {
"supported": true,
"source": "version"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_msvc_lib_archiver_produces_static_library_without_ar_crs() {
let actual = ar_identity_and_capabilities_json(
"Microsoft (R) Library Manager Version 14.39.33523.0\nCopyright (C) Microsoft Corporation.\n",
);
let expected = r#"{
"identity": {
"kind": "lib",
"version": "14.39.33523",
"raw_version_line": "Microsoft (R) Library Manager Version 14.39.33523.0"
},
"capabilities": {
"ar_crs": {
"supported": false,
"source": "unsupported"
},
"static_library_output": {
"supported": true,
"source": "version"
}
}
}"#;
assert_eq!(actual, expected);
}
#[test]
fn snapshot_full_detection_report_for_clang_plus_gnu_ar() {
let cxx_id =
parse_cxx_version_output("clang version 17.0.6\nTarget: x86_64-unknown-linux-gnu\n");
let cxx_caps = derive_cxx_capabilities(&cxx_id);
let ar_id = parse_ar_version_output("GNU ar (GNU Binutils) 2.40\n");
let ar_caps = derive_ar_capabilities(&ar_id);
let report = ToolchainDetectionReport {
cxx: ToolDetection {
path: camino::Utf8PathBuf::from("/opt/llvm/bin/clang++"),
identity: cxx_id,
capabilities: cxx_caps,
},
cc: None,
ar: ToolDetection {
path: camino::Utf8PathBuf::from("/usr/bin/ar"),
identity: ar_id,
capabilities: ar_caps,
},
};
let actual = pretty(&report.as_json());
let expected = r#"{
"cxx": {
"path": "/opt/llvm/bin/clang++",
"identity": {
"kind": "clang",
"version": "17.0.6",
"target": "x86_64-unknown-linux-gnu",
"raw_version_line": "clang version 17.0.6"
},
"capabilities": {
"depfile_mmd_mf": {
"supported": true,
"source": "version"
},
"external_include_dirs": {
"supported": true,
"source": "version"
},
"gcc_style_flags": {
"supported": true,
"source": "version"
},
"msvc_style_flags": {
"supported": false,
"source": "assumed-default"
}
}
},
"ar": {
"path": "/usr/bin/ar",
"identity": {
"kind": "ar",
"version": "2.40",
"raw_version_line": "GNU ar (GNU Binutils) 2.40"
},
"capabilities": {
"ar_crs": {
"supported": true,
"source": "version"
},
"static_library_output": {
"supported": true,
"source": "version"
}
}
}
}"#;
assert_eq!(actual, expected);
}
struct CxxBannerCase {
banner: &'static str,
kind: CompilerKind,
version: Option<(u32, Option<u32>, Option<u32>)>,
target: Option<&'static str>,
}
const CXX_BANNER_CORPUS: &[CxxBannerCase] = &[
CxxBannerCase {
banner: "g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\n",
kind: CompilerKind::Gcc,
version: Some((11, Some(4), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0\n",
kind: CompilerKind::Gcc,
version: Some((13, Some(3), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (Debian 12.2.0-14) 12.2.0\n",
kind: CompilerKind::Gcc,
version: Some((12, Some(2), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (GCC) 14.2.1 20240910\n",
kind: CompilerKind::Gcc,
version: Some((14, Some(2), Some(1))),
target: None,
},
CxxBannerCase {
banner: "c++ (GCC) 8.5.0 20210514 (Red Hat 8.5.0-26)\n",
kind: CompilerKind::Gcc,
version: Some((8, Some(5), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (Alpine 13.2.1_git20231014) 13.2.1 20231014\n",
kind: CompilerKind::Gcc,
version: Some((13, Some(2), Some(1))),
target: None,
},
CxxBannerCase {
banner: "g++ (Homebrew GCC 14.2.0_1) 14.2.0\n",
kind: CompilerKind::Gcc,
version: Some((14, Some(2), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++.exe (Rev3, Built by MSYS2 project) 13.2.0\n",
kind: CompilerKind::Gcc,
version: Some((13, Some(2), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++-14 (Homebrew GCC 14.2.0) 14.2.0\n",
kind: CompilerKind::Gcc,
version: Some((14, Some(2), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)\n",
kind: CompilerKind::Gcc,
version: Some((7, Some(3), Some(1))),
target: None,
},
CxxBannerCase {
banner: "g++ (SUSE Linux) 7.5.0\n",
kind: CompilerKind::Gcc,
version: Some((7, Some(5), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (MinGW-W64 i686-ucrt-posix-dwarf, built by Brecht Sanders) 13.2.0\n",
kind: CompilerKind::Gcc,
version: Some((13, Some(2), Some(0))),
target: None,
},
CxxBannerCase {
banner: "g++ (GCC) 15.1.0\n",
kind: CompilerKind::Gcc,
version: Some((15, Some(1), Some(0))),
target: None,
},
CxxBannerCase {
banner: "aarch64-linux-gnu-g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\n",
kind: CompilerKind::Gcc,
version: Some((11, Some(4), Some(0))),
target: None,
},
CxxBannerCase {
banner: "clang version 17.0.6 (Fedora 17.0.6-2.fc39)\nTarget: x86_64-redhat-linux-gnu\nThread model: posix\n",
kind: CompilerKind::Clang,
version: Some((17, Some(0), Some(6))),
target: Some("x86_64-redhat-linux-gnu"),
},
CxxBannerCase {
banner: "Ubuntu clang version 18.1.3 (1ubuntu1)\nTarget: x86_64-pc-linux-gnu\n",
kind: CompilerKind::Clang,
version: Some((18, Some(1), Some(3))),
target: Some("x86_64-pc-linux-gnu"),
},
CxxBannerCase {
banner: "Debian clang version 16.0.6 (15~deb12u1)\n",
kind: CompilerKind::Clang,
version: Some((16, Some(0), Some(6))),
target: None,
},
CxxBannerCase {
banner: "clang version 3.9.1 (tags/RELEASE_391/final)\n",
kind: CompilerKind::Clang,
version: Some((3, Some(9), Some(1))),
target: None,
},
CxxBannerCase {
banner: "clang version 10.0.0-4ubuntu1 \nTarget: x86_64-pc-linux-gnu\n",
kind: CompilerKind::Clang,
version: Some((10, Some(0), Some(0))),
target: Some("x86_64-pc-linux-gnu"),
},
CxxBannerCase {
banner: "clang version 20.1.0git (https://github.com/llvm/llvm-project 4a0aff96d7e9695be96b1429e1d1f9e305766b0a)\n",
kind: CompilerKind::Clang,
version: Some((20, Some(1), Some(0))),
target: None,
},
CxxBannerCase {
banner: "FreeBSD clang version 18.1.6 (https://github.com/llvm/llvm-project.git llvmorg-18.1.6-0-g1118c2e05e67)\nTarget: x86_64-unknown-freebsd14.1\n",
kind: CompilerKind::Clang,
version: Some((18, Some(1), Some(6))),
target: Some("x86_64-unknown-freebsd14.1"),
},
CxxBannerCase {
banner: "Homebrew clang version 19.1.7\nTarget: arm64-apple-darwin24.3.0\n",
kind: CompilerKind::Clang,
version: Some((19, Some(1), Some(7))),
target: Some("arm64-apple-darwin24.3.0"),
},
CxxBannerCase {
banner: "OpenBSD clang version 16.0.6\nTarget: amd64-unknown-openbsd7.5\n",
kind: CompilerKind::Clang,
version: Some((16, Some(0), Some(6))),
target: Some("amd64-unknown-openbsd7.5"),
},
CxxBannerCase {
banner: "clang version 19.1.0-rc2\n",
kind: CompilerKind::Clang,
version: Some((19, Some(1), Some(0))),
target: None,
},
CxxBannerCase {
banner: "clang version 18.1.8\nTarget: x86_64-pc-windows-msvc\nThread model: posix\n",
kind: CompilerKind::Clang,
version: Some((18, Some(1), Some(8))),
target: Some("x86_64-pc-windows-msvc"),
},
CxxBannerCase {
banner: "Apple clang version 16.0.0 (clang-1600.0.26.4)\nTarget: arm64-apple-darwin24.0.0\nThread model: posix\n",
kind: CompilerKind::AppleClang,
version: Some((16, Some(0), Some(0))),
target: Some("arm64-apple-darwin24.0.0"),
},
CxxBannerCase {
banner: "Apple clang version 11.0.3 (clang-1103.0.32.62)\n",
kind: CompilerKind::AppleClang,
version: Some((11, Some(0), Some(3))),
target: None,
},
CxxBannerCase {
banner: "Apple LLVM version 10.0.1 (clang-1001.0.46.4)\nTarget: x86_64-apple-darwin18.5.0\n",
kind: CompilerKind::AppleClang,
version: Some((10, Some(0), Some(1))),
target: Some("x86_64-apple-darwin18.5.0"),
},
CxxBannerCase {
banner: "Apple LLVM version 9.1.0 (clang-902.0.39.1)\n",
kind: CompilerKind::AppleClang,
version: Some((9, Some(1), Some(0))),
target: None,
},
CxxBannerCase {
banner: "Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30153 for x64\nCopyright (C) Microsoft Corporation. All rights reserved.\n",
kind: CompilerKind::Msvc,
version: Some((19, Some(29), Some(30153))),
target: None,
},
CxxBannerCase {
banner: "Microsoft (R) C/C++ Optimizing Compiler Version 19.40.33811 for ARM64\n",
kind: CompilerKind::Msvc,
version: Some((19, Some(40), Some(33811))),
target: None,
},
CxxBannerCase {
banner: "Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x86\n",
kind: CompilerKind::Msvc,
version: Some((19, Some(0), Some(24210))),
target: None,
},
CxxBannerCase {
banner: "Microsoft (R) C/C++-Optimierungscompiler Version 19.29.30133 f\u{fc}r x64\n",
kind: CompilerKind::Msvc,
version: Some((19, Some(29), Some(30133))),
target: None,
},
CxxBannerCase {
banner: "Intel(R) oneAPI DPC++/C++ Compiler 2025.0.0 (2025.0.0.20241008)\n",
kind: CompilerKind::Unknown,
version: None,
target: None,
},
CxxBannerCase {
banner: "nvc++ 24.7-0 64-bit target on x86-64 Linux -tp haswell\n",
kind: CompilerKind::Unknown,
version: None,
target: None,
},
CxxBannerCase {
banner: "tcc version 0.9.27 (x86_64 Linux)\n",
kind: CompilerKind::Unknown,
version: None,
target: None,
},
CxxBannerCase {
banner: "cc: error: no input files\n",
kind: CompilerKind::Unknown,
version: None,
target: None,
},
];
#[test]
fn cxx_banner_corpus() {
for case in CXX_BANNER_CORPUS {
let id = parse_cxx_version_output(case.banner);
assert_eq!(
id.kind, case.kind,
"kind mismatch for banner {:?}",
case.banner
);
let got = id.version.as_ref().map(|v| (v.major, v.minor, v.patch));
assert_eq!(
got, case.version,
"version mismatch for banner {:?}",
case.banner
);
assert_eq!(
id.target.as_deref(),
case.target,
"target mismatch for banner {:?}",
case.banner
);
}
}
struct ArBannerCase {
banner: &'static str,
kind: ArchiverKind,
version: Option<(u32, Option<u32>, Option<u32>)>,
}
const AR_BANNER_CORPUS: &[ArBannerCase] = &[
ArBannerCase {
banner: "GNU ar (GNU Binutils for Ubuntu) 2.42\n",
kind: ArchiverKind::Ar,
version: Some((2, Some(42), None)),
},
ArBannerCase {
banner: "GNU ar (GNU Binutils for Debian) 2.40\n",
kind: ArchiverKind::Ar,
version: Some((2, Some(40), None)),
},
ArBannerCase {
banner: "LLVM (http://llvm.org/):\n LLVM version 17.0.6\n Optimized build.\n",
kind: ArchiverKind::LlvmAr,
version: Some((17, Some(0), Some(6))),
},
ArBannerCase {
banner: "Microsoft (R) Library Manager Version 14.40.33811.0\nCopyright (C) Microsoft Corporation. All rights reserved.\n",
kind: ArchiverKind::Lib,
version: Some((14, Some(40), Some(33811))),
},
ArBannerCase {
banner: "not an archiver\n",
kind: ArchiverKind::Unknown,
version: None,
},
];
#[test]
fn ar_banner_corpus() {
for case in AR_BANNER_CORPUS {
let id = parse_ar_version_output(case.banner);
assert_eq!(
id.kind, case.kind,
"kind mismatch for banner {:?}",
case.banner
);
let got = id.version.as_ref().map(|v| (v.major, v.minor, v.patch));
assert_eq!(
got, case.version,
"version mismatch for banner {:?}",
case.banner
);
}
}
#[test]
fn parse_with_suffix_strips_trailing_garbage_per_component() {
for (raw, expect) in [
("20.1.0git", (20, Some(1), Some(0))),
("10.0.0-4ubuntu1", (10, Some(0), Some(0))),
("19.1.0-rc2", (19, Some(1), Some(0))),
("14.2.1", (14, Some(2), Some(1))),
("2.42", (2, Some(42), None)),
] {
let v = CompilerVersion::parse_with_suffix(raw).expect(raw);
assert_eq!((v.major, v.minor, v.patch), expect, "{raw}");
assert_eq!(v.raw, raw);
}
assert!(CompilerVersion::parse_with_suffix("garbage").is_none());
assert!(CompilerVersion::parse_with_suffix("v14.2").is_none());
}