use std::io::Cursor;
use super::*;
use crate::{field_flags::BuildEventArgsFieldFlags, string_table::StringTable};
fn encode_7bit(value: i32) -> Vec<u8> {
let mut v = value as u32;
let mut buf = Vec::new();
loop {
let mut byte = (v & 0x7F) as u8;
v >>= 7;
if v != 0 {
byte |= 0x80;
}
buf.push(byte);
if v == 0 {
break;
}
}
buf
}
fn build_stream(flags: BuildEventArgsFieldFlags, payloads: &[Vec<u8>]) -> Vec<u8> {
let mut data = encode_7bit(flags.bits() as i32);
for p in payloads {
data.extend_from_slice(p);
}
data
}
#[test]
fn no_flags_set_produces_defaults() {
let strings = StringTable::new();
let data = build_stream(BuildEventArgsFieldFlags::NONE, &[]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.flags, BuildEventArgsFieldFlags::NONE);
assert!(fields.message.is_none());
assert!(fields.build_event_context.is_none());
assert_eq!(fields.thread_id, 0);
assert!(fields.help_keyword.is_none());
assert!(fields.sender_name.is_none());
assert!(fields.timestamp.is_none());
assert!(fields.extended.is_none());
assert!(fields.subcategory.is_none());
assert!(fields.code.is_none());
assert!(fields.file.is_none());
assert!(fields.project_file.is_none());
assert_eq!(fields.line_number, 0);
assert_eq!(fields.column_number, 0);
assert_eq!(fields.end_line_number, 0);
assert_eq!(fields.end_column_number, 0);
assert!(fields.arguments.is_none());
assert_eq!(fields.importance, 0);
}
#[test]
fn message_flag_reads_dedup_string() {
let mut strings = StringTable::new();
let idx = strings.add("hello world".to_string());
let data = build_stream(BuildEventArgsFieldFlags::MESSAGE, &[encode_7bit(idx)]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.message.as_deref(), Some("hello world"));
}
#[test]
fn null_message_index() {
let strings = StringTable::new();
let data = build_stream(
BuildEventArgsFieldFlags::MESSAGE,
&[encode_7bit(0)], );
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert!(fields.message.is_none());
}
#[test]
fn empty_message_index() {
let strings = StringTable::new();
let data = build_stream(
BuildEventArgsFieldFlags::MESSAGE,
&[encode_7bit(1)], );
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.message.as_deref(), Some(""));
}
#[test]
fn build_event_context_flag() {
let strings = StringTable::new();
let mut ctx_bytes = Vec::new();
for i in 1..=7 {
ctx_bytes.extend_from_slice(&encode_7bit(i));
}
let data = build_stream(BuildEventArgsFieldFlags::BUILD_EVENT_CONTEXT, &[ctx_bytes]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
let ctx = fields.build_event_context.unwrap();
assert_eq!(ctx.node_id, 1);
assert_eq!(ctx.evaluation_id, 7);
}
#[test]
fn thread_id_flag() {
let strings = StringTable::new();
let data = build_stream(BuildEventArgsFieldFlags::THREAD_ID, &[encode_7bit(42)]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.thread_id, 42);
}
#[test]
fn help_keyword_flag() {
let mut strings = StringTable::new();
let idx = strings.add("MSB1234".to_string());
let data = build_stream(BuildEventArgsFieldFlags::HELP_KEYWORD, &[encode_7bit(idx)]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.help_keyword.as_deref(), Some("MSB1234"));
}
#[test]
fn sender_name_flag() {
let mut strings = StringTable::new();
let idx = strings.add("MSBuild".to_string());
let data = build_stream(BuildEventArgsFieldFlags::SENDER_NAME, &[encode_7bit(idx)]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.sender_name.as_deref(), Some("MSBuild"));
}
#[test]
fn timestamp_flag() {
let strings = StringTable::new();
let ticks: i64 = 637_500_000_000_000_000;
let mut payload = Vec::new();
payload.extend_from_slice(&ticks.to_le_bytes());
payload.extend_from_slice(&encode_7bit(1)); let data = build_stream(BuildEventArgsFieldFlags::TIMESTAMP, &[payload]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
let ts = fields.timestamp.unwrap();
assert_eq!(ts.ticks, ticks);
assert_eq!(ts.kind, 1);
}
#[test]
fn line_and_column_flags() {
let strings = StringTable::new();
let flags = BuildEventArgsFieldFlags::LINE_NUMBER
| BuildEventArgsFieldFlags::COLUMN_NUMBER
| BuildEventArgsFieldFlags::END_LINE_NUMBER
| BuildEventArgsFieldFlags::END_COLUMN_NUMBER;
let data = build_stream(
flags,
&[
encode_7bit(10),
encode_7bit(20),
encode_7bit(30),
encode_7bit(40),
],
);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.line_number, 10);
assert_eq!(fields.column_number, 20);
assert_eq!(fields.end_line_number, 30);
assert_eq!(fields.end_column_number, 40);
}
#[test]
fn arguments_flag() {
let mut strings = StringTable::new();
let idx_a = strings.add("arg1".to_string()); let idx_b = strings.add("arg2".to_string());
let mut payload = encode_7bit(2); payload.extend_from_slice(&encode_7bit(idx_a));
payload.extend_from_slice(&encode_7bit(idx_b));
let data = build_stream(BuildEventArgsFieldFlags::ARGUMENTS, &[payload]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
let args = fields.arguments.unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0].as_deref(), Some("arg1"));
assert_eq!(args[1].as_deref(), Some("arg2"));
}
#[test]
fn importance_flag_v18() {
let strings = StringTable::new();
let data = build_stream(
BuildEventArgsFieldFlags::IMPORTANCE,
&[encode_7bit(2)], );
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.importance, 2);
}
#[test]
fn importance_unconditional_v12() {
let strings = StringTable::new();
let data = build_stream(
BuildEventArgsFieldFlags::NONE,
&[encode_7bit(1)], );
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 12, true).unwrap();
assert_eq!(fields.importance, 1);
}
#[test]
fn importance_not_read_when_not_requested_v12() {
let strings = StringTable::new();
let data = build_stream(BuildEventArgsFieldFlags::NONE, &[]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 12, false).unwrap();
assert_eq!(fields.importance, 0);
}
#[test]
fn file_and_project_file_flags() {
let mut strings = StringTable::new();
let f_idx = strings.add("foo.cs".to_string()); let p_idx = strings.add("bar.csproj".to_string());
let data = build_stream(
BuildEventArgsFieldFlags::FILE | BuildEventArgsFieldFlags::PROJECT_FILE,
&[encode_7bit(f_idx), encode_7bit(p_idx)],
);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.file.as_deref(), Some("foo.cs"));
assert_eq!(fields.project_file.as_deref(), Some("bar.csproj"));
}
#[test]
fn subcategory_and_code_flags() {
let mut strings = StringTable::new();
let sub_idx = strings.add("compiler".to_string()); let code_idx = strings.add("CS0001".to_string());
let data = build_stream(
BuildEventArgsFieldFlags::SUBCATEGORY | BuildEventArgsFieldFlags::CODE,
&[encode_7bit(sub_idx), encode_7bit(code_idx)],
);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.subcategory.as_deref(), Some("compiler"));
assert_eq!(fields.code.as_deref(), Some("CS0001"));
}
#[test]
fn multiple_flags_combined() {
let mut strings = StringTable::new();
let msg_idx = strings.add("Build succeeded".to_string()); let sender_idx = strings.add("MSBuild".to_string());
let flags = BuildEventArgsFieldFlags::MESSAGE | BuildEventArgsFieldFlags::SENDER_NAME;
let data = build_stream(flags, &[encode_7bit(msg_idx), encode_7bit(sender_idx)]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
assert_eq!(fields.message.as_deref(), Some("Build succeeded"));
assert_eq!(fields.sender_name.as_deref(), Some("MSBuild"));
}
#[test]
fn extended_fields() {
let mut strings = StringTable::new();
let type_idx = strings.add("CustomType".to_string()); let data_idx = strings.add("{\"key\":1}".to_string());
let mut ext_payload = encode_7bit(type_idx);
ext_payload.extend_from_slice(&encode_7bit(0)); ext_payload.extend_from_slice(&encode_7bit(data_idx));
let data = build_stream(BuildEventArgsFieldFlags::EXTENDED, &[ext_payload]);
let mut cursor = Cursor::new(data);
let fields = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap();
let ext = fields.extended.unwrap();
assert_eq!(ext.extended_type.as_deref(), Some("CustomType"));
assert_eq!(ext.extended_metadata_index, 0);
assert_eq!(ext.extended_data.as_deref(), Some("{\"key\":1}"));
}
#[test]
fn rejects_negative_arguments_count() {
let strings = StringTable::new();
let mut data = encode_7bit(BuildEventArgsFieldFlags::ARGUMENTS.bits() as i32);
data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF, 0x0F]); let mut cursor = Cursor::new(data);
let err = read_build_event_args_fields(&mut cursor, &strings, 18, false).unwrap_err();
assert!(
matches!(err, crate::error::MuninError::InvalidFormat(_)),
"expected InvalidFormat, got {err:?}"
);
}