use zccache::compiler::response_file::parse_response_file_content;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|x| x.to_string()).collect()
}
#[test]
fn parse_unterminated_double_quote() {
let result = parse_response_file_content("\"hello");
assert_eq!(result, s(&["hello"]));
}
#[test]
fn parse_unterminated_single_quote() {
let result = parse_response_file_content("'hello");
assert_eq!(result, s(&["hello"]));
}
#[test]
fn parse_unterminated_double_quote_after_args() {
let result = parse_response_file_content("-O2 \"unterminated");
assert_eq!(result, s(&["-O2", "unterminated"]));
}
#[test]
fn parse_unterminated_single_quote_after_args() {
let result = parse_response_file_content("-Wall 'never closed");
assert_eq!(result, s(&["-Wall", "never closed"]));
}
#[test]
fn parse_empty_double_quotes() {
let result = parse_response_file_content("\"\"");
assert_eq!(result, s(&[""]));
}
#[test]
fn parse_empty_single_quotes() {
let result = parse_response_file_content("''");
assert_eq!(result, s(&[""]));
}
#[test]
fn parse_adjacent_empty_quotes() {
let result = parse_response_file_content("\"\"''");
assert_eq!(result, s(&[""]));
}
#[test]
fn parse_mixed_quotes_in_single_arg() {
let result = parse_response_file_content("foo\"bar\"baz");
assert_eq!(result, s(&["foobarbaz"]));
}
#[test]
fn parse_single_quote_inside_double() {
let result = parse_response_file_content("\"it's a test\"");
assert_eq!(result, s(&["it's a test"]));
}
#[test]
fn parse_double_quote_inside_single() {
let result = parse_response_file_content("'say \"hi\"'");
assert_eq!(result, s(&["say \"hi\""]));
}
#[test]
fn parse_quoted_then_unquoted() {
let result = parse_response_file_content("\"foo\"bar");
assert_eq!(result, s(&["foobar"]));
}
#[test]
fn parse_unquoted_then_quoted() {
let result = parse_response_file_content("foo\"bar\"");
assert_eq!(result, s(&["foobar"]));
}
#[test]
fn parse_adjacent_double_quoted_segments() {
let result = parse_response_file_content("\"abc\"\"def\"");
assert_eq!(result, s(&["abcdef"]));
}
#[test]
fn parse_alternating_quote_types() {
let result = parse_response_file_content("\"a\"'b'\"c\"");
assert_eq!(result, s(&["abc"]));
}
#[test]
fn parse_nested_looking_quotes() {
let result = parse_response_file_content("\"a'b'c\"");
assert_eq!(result, s(&["a'b'c"]));
}
#[test]
fn parse_multiple_empty_quoted_args() {
let result = parse_response_file_content("\"\" '' \"\"");
assert_eq!(result, s(&["", "", ""]));
}
#[test]
fn parse_quoted_whitespace_preserved() {
let result = parse_response_file_content("\" spaces \" '\ttab\t'");
assert_eq!(result, s(&[" spaces ", "\ttab\t"]));
}
#[test]
fn parse_lone_double_quote() {
let result = parse_response_file_content("\"");
assert_eq!(result, s(&[""]));
}
#[test]
fn parse_lone_single_quote() {
let result = parse_response_file_content("'");
assert_eq!(result, s(&[""]));
}
#[test]
fn parse_backslash_at_eof_in_double_quotes() {
let result = parse_response_file_content("\"foo\\");
assert_eq!(result, s(&["foo"]));
}
#[test]
fn parse_backslash_before_closing_double_quote() {
let result = parse_response_file_content("\"foo\\\"");
assert_eq!(result, s(&["foo\""]));
}
#[test]
fn parse_double_backslash_before_close() {
let result = parse_response_file_content("\"foo\\\\\"");
assert_eq!(result, s(&["foo\\"]));
}
#[test]
fn parse_triple_backslash_before_close() {
let result = parse_response_file_content("\"foo\\\\\\\"");
assert_eq!(result, s(&["foo\\\""]));
}
#[test]
fn parse_even_backslashes_before_close() {
let result = parse_response_file_content("\"a\\\\\\\\\"");
assert_eq!(result, s(&["a\\\\"]));
}
#[test]
fn parse_unknown_escape_passthrough() {
let result = parse_response_file_content("\"\\x\\y\\z\"");
assert_eq!(result, s(&["xyz"]));
}
#[test]
fn parse_escape_n_in_double_quotes() {
let result = parse_response_file_content("\"line1\\nline2\"");
assert_eq!(result, s(&["line1\nline2"]));
}
#[test]
fn parse_escape_t_is_literal_t() {
let result = parse_response_file_content("\"\\t\"");
assert_eq!(result, s(&["t"]));
}
#[test]
fn parse_backslash_in_single_quotes_literal() {
let result = parse_response_file_content("'\\n\\t\\\\'");
assert_eq!(result, s(&["\\n\\t\\\\"]));
}
#[test]
fn parse_backslash_unquoted_literal() {
let result = parse_response_file_content("C:\\Users\\path");
assert_eq!(result, s(&["C:\\Users\\path"]));
}
#[test]
fn parse_backslash_at_eof_unquoted() {
let result = parse_response_file_content("path\\");
assert_eq!(result, s(&["path\\"]));
}
#[test]
fn parse_chained_escapes() {
let result = parse_response_file_content("\"\\\\\\n\\\"\"");
assert_eq!(result, s(&["\\\n\""]));
}
#[test]
fn parse_only_spaces() {
let result = parse_response_file_content(" ");
assert!(result.is_empty());
}
#[test]
fn parse_only_tabs() {
let result = parse_response_file_content("\t\t\t");
assert!(result.is_empty());
}
#[test]
fn parse_only_newlines() {
let result = parse_response_file_content("\n\n\n");
assert!(result.is_empty());
}
#[test]
fn parse_only_carriage_returns() {
let result = parse_response_file_content("\r\r\r");
assert!(result.is_empty());
}
#[test]
fn parse_crlf_between_args() {
let result = parse_response_file_content("-O2\r\n-Wall\r\n-c\r\n");
assert_eq!(result, s(&["-O2", "-Wall", "-c"]));
}
#[test]
fn parse_mixed_lf_crlf() {
let result = parse_response_file_content("-a\n-b\r\n-c\r-d");
assert_eq!(result, s(&["-a", "-b", "-c", "-d"]));
}
#[test]
fn parse_form_feed_vertical_tab_not_whitespace() {
let result = parse_response_file_content("a\x0Cb\x0Bc");
assert_eq!(result, s(&["a\x0Cb\x0Bc"]));
}
#[test]
fn parse_leading_trailing_whitespace_single_arg() {
let result = parse_response_file_content(" \t\n -O2 \t\n ");
assert_eq!(result, s(&["-O2"]));
}
#[test]
fn parse_massive_whitespace_between_args() {
let ws = " ".repeat(1000);
let content = format!("-a{ws}-b{ws}-c");
let result = parse_response_file_content(&content);
assert_eq!(result, s(&["-a", "-b", "-c"]));
}
#[test]
fn parse_no_whitespace_single_arg() {
let result = parse_response_file_content("-DFOO=BAR");
assert_eq!(result, s(&["-DFOO=BAR"]));
}
#[test]
fn parse_utf8_bom_at_start() {
let content = "\u{FEFF}-O2 -Wall";
let result = parse_response_file_content(content);
assert_eq!(result.len(), 2);
assert!(result[0].starts_with('\u{FEFF}'));
assert_eq!(result[0], "\u{FEFF}-O2");
assert_eq!(result[1], "-Wall");
}
#[test]
fn parse_unicode_multibyte() {
let result = parse_response_file_content("-DMSG=\"\u{00E9}l\u{00E8}ve\" -I/\u{00FC}ber/path");
assert_eq!(
result,
s(&["-DMSG=\u{00E9}l\u{00E8}ve", "-I/\u{00FC}ber/path"])
);
}
#[test]
fn parse_emoji() {
let result = parse_response_file_content("-DEMOJI=\"\u{1F600}\" file\u{1F4C4}.cpp");
assert_eq!(result, s(&["-DEMOJI=\u{1F600}", "file\u{1F4C4}.cpp"]));
}
#[test]
fn parse_null_byte() {
let result = parse_response_file_content("-a\0b -c");
assert_eq!(result.len(), 2);
assert_eq!(result[0], "-a\0b");
assert_eq!(result[1], "-c");
}
#[test]
fn parse_at_signs_literal() {
let result = parse_response_file_content("@file1 @@double -D@symbol");
assert_eq!(result, s(&["@file1", "@@double", "-D@symbol"]));
}
#[test]
fn parse_shell_syntax_literal() {
let result = parse_response_file_content("-o output | rm -rf ; echo pwned");
assert_eq!(
result,
s(&["-o", "output", "|", "rm", "-rf", ";", "echo", "pwned"])
);
}
#[test]
fn parse_double_dash_args() {
let result = parse_response_file_content("--target=x86_64 -- foo.cpp");
assert_eq!(result, s(&["--target=x86_64", "--", "foo.cpp"]));
}
#[test]
fn parse_single_dash() {
let result = parse_response_file_content("- -c foo.c");
assert_eq!(result, s(&["-", "-c", "foo.c"]));
}
#[test]
fn parse_equals_in_args() {
let result =
parse_response_file_content("-DFOO=BAR -DBAZ=\"q=1\" -std=c++17 --sysroot=/usr/local");
assert_eq!(
result,
s(&[
"-DFOO=BAR",
"-DBAZ=q=1",
"-std=c++17",
"--sysroot=/usr/local"
])
);
}
#[test]
fn parse_very_long_single_arg() {
let long_arg = format!("-D{}", "A".repeat(10_000));
let result = parse_response_file_content(&long_arg);
assert_eq!(result.len(), 1);
assert_eq!(result[0].len(), 10_002); }
#[test]
fn parse_many_small_args() {
let content: String = (0..10_000)
.map(|i| format!("-D_{i}"))
.collect::<Vec<_>>()
.join(" ");
let result = parse_response_file_content(&content);
assert_eq!(result.len(), 10_000);
assert_eq!(result[0], "-D_0");
assert_eq!(result[9999], "-D_9999");
}
#[test]
fn parse_realistic_windows_response_file() {
let content = "'-IC:\\Program Files\\LLVM\\include'\n\
-IC:\\Users\\dev\\project\\src\n\
-DWIN32\n\
-D_WINDOWS\n\
-DCMAKE_INTDIR=\"Debug\"\n\
-D_DEBUG\n\
-std:c++17\n\
-Wall\n\
-Wextra\n\
-O0\n\
-g\n\
-c\n\
'C:\\Users\\dev\\project\\src\\main.cpp'\n\
-o 'C:\\Users\\dev\\project\\build\\main.obj'\n";
let result = parse_response_file_content(content);
assert_eq!(result[0], r"-IC:\Program Files\LLVM\include");
assert_eq!(result[4], "-DCMAKE_INTDIR=Debug");
assert_eq!(result[12], r"C:\Users\dev\project\src\main.cpp");
assert_eq!(result[14], r"C:\Users\dev\project\build\main.obj");
}
#[test]
fn parse_double_quotes_eat_backslashes() {
let result = parse_response_file_content("\"C:\\foo\\bar\"");
assert_eq!(result, s(&["C:foobar"]));
}
#[test]
fn parse_doubled_backslashes_in_double_quotes() {
let result = parse_response_file_content("\"C:\\\\foo\\\\bar\"");
assert_eq!(result, s(&["C:\\foo\\bar"]));
}
#[test]
fn parse_unquoted_windows_path_with_space_splits() {
let result = parse_response_file_content(r"-IC:\Program Files\LLVM\include");
assert_eq!(result, s(&[r"-IC:\Program", r"Files\LLVM\include"]));
}
#[test]
fn parse_realistic_linux_response_file() {
let content = "-I/usr/include\n\
-I/home/user/project/include\n\
-isystem /usr/include/c++/12\n\
-DVERSION=\"1.2.3\"\n\
-D'GREETING=hello world'\n\
-std=c++20\n\
-fPIC\n\
-Wall -Wextra -Werror\n\
-O2 -g\n\
-c /home/user/project/src/main.cpp\n\
-o /home/user/project/build/main.o\n";
let result = parse_response_file_content(content);
assert!(result.contains(&"-isystem".to_string()));
assert!(result.contains(&"/usr/include/c++/12".to_string()));
assert!(result.contains(&"-DVERSION=1.2.3".to_string()));
assert!(result.contains(&"-DGREETING=hello world".to_string()));
}
#[test]
fn parse_backslash_quote_unquoted_context_trap() {
let result = parse_response_file_content(r#"-DBUILD_TYPE=\"Release\""#);
assert_eq!(result, s(&["-DBUILD_TYPE=\\Release\""]));
}
#[test]
fn parse_literal_newline_in_double_quotes() {
let result = parse_response_file_content("\"line1\nline2\"");
assert_eq!(result, s(&["line1\nline2"]));
}
#[test]
fn parse_literal_newline_in_single_quotes() {
let result = parse_response_file_content("'line1\nline2'");
assert_eq!(result, s(&["line1\nline2"]));
}
#[test]
fn parse_four_double_quotes() {
let result = parse_response_file_content("\"\"\"\"");
assert_eq!(result, s(&[""]));
}
#[test]
fn parse_complex_mixed_quoting() {
let result = parse_response_file_content("-DFOO='bar'\"baz\"qux");
assert_eq!(result, s(&["-DFOO=barbazqux"]));
}
#[test]
fn parse_space_in_quote_then_unquoted() {
let result = parse_response_file_content("\"a b\"c");
assert_eq!(result, s(&["a bc"]));
}
#[test]
fn parse_unquoted_then_quoted_with_space() {
let result = parse_response_file_content("c\"a b\"");
assert_eq!(result, s(&["ca b"]));
}
#[test]
fn parse_three_quoted_segments() {
let result = parse_response_file_content("'A '\"B \"'C'");
assert_eq!(result, s(&["A B C"]));
}
#[test]
fn parse_escaped_quote_then_unquoted() {
let result = parse_response_file_content("\"a\\\"b\" c");
assert_eq!(result, s(&["a\"b", "c"]));
}