use super::super::{parse_linker_invocation, LinkerFamily, ParsedLinkerInvocation};
use super::args;
use crate::core::NormalizedPath;
#[test]
fn basic_msvc_dll() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "/OUT:foo.dll", "a.obj", "b.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.family, LinkerFamily::MsvcLink);
assert_eq!(c.output_file, NormalizedPath::new("foo.dll"));
assert_eq!(c.input_files.len(), 2);
assert_eq!(c.input_files[0], NormalizedPath::new("a.obj"));
assert_eq!(c.input_files[1], NormalizedPath::new("b.obj"));
assert!(c.non_deterministic); }
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_with_deterministic() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "/DETERMINISTIC", "/OUT:foo.dll", "a.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert!(!c.non_deterministic); }
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_exe_cacheable() {
let result = parse_linker_invocation("link.exe", args(&["/OUT:foo.exe", "main.obj"]));
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.family, LinkerFamily::MsvcLink);
assert_eq!(c.output_file, NormalizedPath::new("foo.exe"));
assert_eq!(c.input_files, vec![NormalizedPath::new("main.obj")]);
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_exe_default_output_name() {
let result = parse_linker_invocation("link.exe", args(&["main.obj", "util.obj"]));
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.output_file, NormalizedPath::new("main.exe"));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_no_inputs() {
let result = parse_linker_invocation("link.exe", args(&["/DLL", "/OUT:foo.dll"]));
assert!(matches!(
result,
ParsedLinkerInvocation::NonCacheable { .. }
));
}
#[test]
fn msvc_dll_default_output_name() {
let result = parse_linker_invocation("link.exe", args(&["/DLL", "a.obj", "b.obj"]));
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.output_file, NormalizedPath::new("a.dll"));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_preserves_input_order() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "/OUT:foo.dll", "z.obj", "a.obj", "m.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.input_files[0], NormalizedPath::new("z.obj"));
assert_eq!(c.input_files[1], NormalizedPath::new("a.obj"));
assert_eq!(c.input_files[2], NormalizedPath::new("m.obj"));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_with_implib() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "/OUT:foo.dll", "/IMPLIB:foo.lib", "a.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert!(c
.cache_relevant_flags
.contains(&"/IMPLIB:foo.lib".to_string()));
assert_eq!(c.secondary_outputs.len(), 2);
assert_eq!(c.secondary_outputs[0], NormalizedPath::new("foo.lib"));
assert_eq!(c.secondary_outputs[1], NormalizedPath::new("foo.exp"));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_without_implib_no_secondary() {
let result = parse_linker_invocation("link.exe", args(&["/DLL", "/OUT:foo.dll", "a.obj"]));
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert!(c.secondary_outputs.is_empty());
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_implib_dash_syntax() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "-IMPLIB:mylib.lib", "/OUT:mylib.dll", "a.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.secondary_outputs.len(), 2);
assert_eq!(c.secondary_outputs[0], NormalizedPath::new("mylib.lib"));
assert_eq!(c.secondary_outputs[1], NormalizedPath::new("mylib.exp"));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_with_flags() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "/NOLOGO", "/MACHINE:X64", "/OUT:foo.dll", "a.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert!(c.cache_relevant_flags.contains(&"/NOLOGO".to_string()));
assert!(c.cache_relevant_flags.contains(&"/MACHINE:X64".to_string()));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_dll_dash_syntax() {
let result = parse_linker_invocation(
"link.exe",
args(&["-DLL", "-OUT:foo.dll", "-DETERMINISTIC", "a.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.output_file, NormalizedPath::new("foo.dll"));
assert!(!c.non_deterministic);
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_no_args() {
let result = parse_linker_invocation("link.exe", args(&[]));
assert!(matches!(
result,
ParsedLinkerInvocation::NonCacheable { .. }
));
}
#[test]
fn msvc_def_file_as_flag() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DLL", "/DEF:foo.def", "/OUT:foo.dll", "a.obj"]),
);
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert!(c.cache_relevant_flags.contains(&"/DEF:foo.def".to_string()));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_case_insensitive_dll_flag() {
let result = parse_linker_invocation("link.exe", args(&["/dll", "/out:foo.dll", "a.obj"]));
match result {
ParsedLinkerInvocation::Cacheable(c) => {
assert_eq!(c.output_file, NormalizedPath::new("foo.dll"));
}
other => panic!("expected cacheable, got: {other:?}"),
}
}
#[test]
fn msvc_declares_explicit_debug_side_outputs() {
let result = parse_linker_invocation(
"link.exe",
args(&[
"/OUT:foo.exe",
"/DEBUG",
"/PDB:symbols/foo.pdb",
"/ILK:state/foo.ilk",
"/MAP:maps/foo.map",
"/PDBSTRIPPED:symbols/foo-public.pdb",
"main.obj",
]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert_eq!(
c.secondary_outputs,
vec![
NormalizedPath::new("maps/foo.map"),
NormalizedPath::new("symbols/foo.pdb"),
NormalizedPath::new("symbols/foo-public.pdb"),
NormalizedPath::new("state/foo.ilk"),
]
);
}
#[test]
fn msvc_declares_implicit_debug_incremental_and_map_outputs() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DEBUG", "/MAP", "/OUT:bin/foo.exe", "main.obj"]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert_eq!(
c.secondary_outputs,
vec![
NormalizedPath::new("bin/foo.pdb"),
NormalizedPath::new("bin/foo.ilk"),
NormalizedPath::new("bin/foo.map"),
]
);
}
#[test]
fn msvc_debug_incremental_no_omits_implicit_ilk() {
let result = parse_linker_invocation(
"link.exe",
args(&["/DEBUG", "/INCREMENTAL:NO", "/OUT:foo.exe", "main.obj"]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert_eq!(c.secondary_outputs, vec![NormalizedPath::new("foo.pdb")]);
}
#[test]
fn msvc_ignored_pdb_and_ilk_options_are_not_declared() {
let result = parse_linker_invocation(
"link.exe",
args(&[
"/OUT:foo.exe",
"/PDB:foo.pdb",
"/ILK:foo.ilk",
"/INCREMENTAL:NO",
"main.obj",
]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert!(c.secondary_outputs.is_empty());
}
#[test]
fn msvc_debug_none_does_not_declare_debug_outputs() {
let result = parse_linker_invocation(
"link.exe",
args(&[
"/DEBUG:NONE",
"/PDB:foo.pdb",
"/ILK:foo.ilk",
"/OUT:foo.exe",
"main.obj",
]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert!(c.secondary_outputs.is_empty());
}
#[test]
fn msvc_declares_conditional_ltcg_profile_and_winmd_outputs() {
let result = parse_linker_invocation(
"link.exe",
args(&[
"/OUT:bin/app.exe",
"/LTCG:INCREMENTAL",
"/LTCGOUT:state/app.iobj",
"/FASTGENPROFILE",
"/PGD:profiles/app.pgd",
"/WINMD",
"/WINMDFILE:metadata/app",
"main.obj",
]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert_eq!(
c.secondary_outputs,
vec![
NormalizedPath::new("state/app.iobj"),
NormalizedPath::new("profiles/app.pgd"),
NormalizedPath::new("metadata/app.winmd"),
]
);
}
#[test]
fn msvc_declares_default_conditional_output_names() {
let result = parse_linker_invocation(
"link.exe",
args(&[
"/OUT:bin/app.exe",
"/LTCG:INCREMENTAL",
"/GENPROFILE",
"/WINMD",
"main.obj",
]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert_eq!(
c.secondary_outputs,
vec![
NormalizedPath::new("bin/app.iobj"),
NormalizedPath::new("bin/app.pgd"),
NormalizedPath::new("bin/app.winmd"),
]
);
}
#[test]
fn msvc_useprofile_hashes_explicit_pgd_input() {
let result = parse_linker_invocation(
"link.exe",
args(&[
"/OUT:bin/app.exe",
"/LTCG",
"/USEPROFILE:PGD=profiles/app",
"main.obj",
]),
);
let ParsedLinkerInvocation::Cacheable(c) = result else {
panic!("expected cacheable")
};
assert_eq!(
c.input_files,
vec![
NormalizedPath::new("main.obj"),
NormalizedPath::new("profiles/app.pgd"),
]
);
assert!(c.secondary_outputs.is_empty());
}
#[test]
fn msvc_useprofile_with_implicit_pgd_falls_back() {
let result = parse_linker_invocation(
"link.exe",
args(&["/OUT:bin/app.exe", "/LTCG", "/USEPROFILE", "main.obj"]),
);
assert!(matches!(
result,
ParsedLinkerInvocation::NonCacheable { .. }
));
}