vault 11.0.1

A parser for Company of Heroes replay files.
Documentation
//! `vault` library integration tests.

extern crate vault;

use std::{
    collections::HashSet,
    fs::{self, File},
    io::Read,
    thread,
};
use uuid::{uuid, Uuid};
use vault::{Command, CommandType, Faction, GameType, Replay, Team};

#[test]
fn parse_success() {
    let data = include_bytes!("../replays/USvDAK_v10612.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
    let unwrapped = replay.unwrap();
    assert_eq!(unwrapped.version(), 10612);
    assert_eq!(
        unwrapped
            .players()
            .iter()
            .map(|player| { player.name() })
            .collect::<Vec<&str>>(),
        vec!["madhax", "Quixalotl"]
    );
    assert_eq!(unwrapped.mod_uuid(), Uuid::nil());
    assert_eq!(unwrapped.game_type(), GameType::Multiplayer);
    assert_eq!(unwrapped.matchhistory_id(), Some(5569487));
}

#[test]
fn parse_failure() {
    let data = [1, 2, 3];
    let replay = Replay::from_bytes(&data);
    assert!(replay.is_err())
}

#[test]
fn parse_success_ai() {
    let data = include_bytes!("../replays/vs_ai.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
    let unwrapped = replay.unwrap();
    assert_eq!(unwrapped.version(), 21283);
    assert_eq!(
        unwrapped
            .players()
            .iter()
            .map(|player| { player.name() })
            .collect::<Vec<&str>>(),
        vec!["Janne252", "CPU - Standard"]
    );
    assert_eq!(
        unwrapped.mod_uuid(),
        uuid!("385d9810-96ba-4ece-9040-8281db65174e")
    );
    assert_eq!(unwrapped.game_type(), GameType::Skirmish);
    assert_eq!(unwrapped.matchhistory_id(), None);
}

#[test]
fn parse_weird_description() {
    let data = include_bytes!("../replays/weird_description.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
    let unwrapped = replay.unwrap();
    assert_eq!(unwrapped.map().localized_name_id(), "Twin Beaches ML");
    assert_eq!(unwrapped.map().localized_description_id(), "TB ML");
    assert_eq!(unwrapped.game_type(), GameType::Multiplayer);
    assert_eq!(unwrapped.matchhistory_id(), Some(11782009));
}

#[test]
fn parse_battlegroup() {
    let data = include_bytes!("../replays/USvDAK_v10612.rec");
    let replay = Replay::from_bytes(data).unwrap();
    assert_eq!(
        replay
            .players()
            .iter()
            .map(|player| { player.battlegroup() })
            .collect::<Vec<Option<u32>>>(),
        vec![Some(2072430), Some(196934)]
    );
}

#[test]
fn parse_automatch() {
    let data = include_bytes!("../replays/automatch.rec");
    let replay = Replay::from_bytes(data).unwrap();
    assert_eq!(replay.game_type(), GameType::Automatch);
    assert_eq!(replay.matchhistory_id(), Some(18837622));
}

#[test]
fn parse_custom() {
    let data = include_bytes!("../replays/custom.rec");
    let replay = Replay::from_bytes(data).unwrap();
    assert_eq!(replay.game_type(), GameType::Custom);
    assert_eq!(replay.matchhistory_id(), Some(18838931));
}

#[test]
fn parse_skirmish() {
    let data = include_bytes!("../replays/skirmish.rec");
    let replay = Replay::from_bytes(data).unwrap();
    assert_eq!(replay.game_type(), GameType::Skirmish);
    assert_eq!(replay.matchhistory_id(), None);
}

#[test]
fn parse_new_map_chunk() {
    let data = include_bytes!("../replays/one_seven_zero.rec");
    let replay = Replay::from_bytes(data).unwrap();
    assert_eq!(
        replay.map_filename(),
        "data:scenarios\\multiplayer\\desert_airfield_6p_mkii\\desert_airfield_6p_mkii"
    );
    assert_eq!(replay.map_localized_name_id(), "$11233954");
    assert_eq!(replay.map_localized_description_id(), "$11233955");
}

#[test]
fn parse_ai_takeover() {
    let data = include_bytes!("../replays/ai_takeover.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_zero_item_player() {
    let data = include_bytes!("../replays/zero_items.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_unusual_items_player() {
    let data = include_bytes!("../replays/unusual_items.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_unusual_options() {
    let data = include_bytes!("../replays/unusual_options.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_one_delimited_options() {
    let data = include_bytes!("../replays/one_delimited_options.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_unusual_cpu_items() {
    let data = include_bytes!("../replays/unusual_cpu_items.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_230_failure() {
    let data = include_bytes!("../replays/230.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_unusual_brit_faction() {
    let data = include_bytes!("../replays/unusual_brit_faction.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
    let unwrapped = replay.unwrap();
    assert_eq!(
        unwrapped
            .players()
            .iter()
            .map(|player| { player.faction() })
            .collect::<Vec<Faction>>(),
        vec![
            Faction::British,
            Faction::Americans,
            Faction::Wehrmacht,
            Faction::Wehrmacht,
            Faction::AfrikaKorps,
            Faction::Americans
        ]
    );
}

#[test]
fn parse_one_char_options() {
    let data = include_bytes!("../replays/one_char_options.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
}

#[test]
fn parse_unusual_team_id() {
    let data = include_bytes!("../replays/unusual_team_id.rec");
    let replay = Replay::from_bytes(data);
    assert!(replay.is_ok());
    let unwrapped = replay.unwrap();
    assert_eq!(
        unwrapped
            .players()
            .iter()
            .map(|player| { player.team() })
            .collect::<Vec<Team>>(),
        vec![
            Team::First,
            Team::Second,
            Team::First,
            Team::Second,
            Team::First,
            Team::Second,
            Team::First,
            Team::Second
        ]
    );
}

#[test]
#[cfg_attr(not(feature = "regression"), ignore)]
fn regression() {
    let paths = fs::read_dir("replays/regression").unwrap();
    let pathbufs: Vec<_> = paths
        .into_iter()
        .map(|path| path.unwrap().path())
        .filter(|path| path.is_file())
        .collect();
    let chunks = pathbufs.chunks(100);
    let mut results = Vec::new();

    for chunk in chunks {
        let handles: Vec<_> = chunk
            .iter()
            .map(|path| {
                let cloned_path = path.clone();
                thread::spawn(move || {
                    let mut file = File::open(cloned_path.clone()).unwrap();
                    let mut buffer = Vec::new();
                    file.read_to_end(&mut buffer).unwrap();

                    match Replay::from_bytes(&buffer) {
                        Ok(replay) => Ok(replay),
                        Err(_) => Err(format!("failed to parse {:?}", cloned_path)),
                    }
                })
            })
            .collect();

        let mut parse_results: Vec<_> = handles
            .into_iter()
            .map(|handle| handle.join().unwrap())
            .collect();
        results.append(&mut parse_results);
    }

    let errs: Vec<_> = results.iter().filter(|result| result.is_err()).collect();
    assert_eq!(errs.len(), 0);
}

#[test]
#[cfg_attr(not(feature = "missing"), ignore)]
fn missing_commands() {
    let paths = fs::read_dir("replays/regression").unwrap();
    let pathbufs: Vec<_> = paths
        .into_iter()
        .map(|path| path.unwrap().path())
        .filter(|path| path.is_file())
        .collect();
    let chunks = pathbufs.chunks(100);
    let mut results: Vec<Result<HashSet<CommandType>, String>> = Vec::new();

    for chunk in chunks {
        let handles: Vec<_> = chunk
            .iter()
            .map(|path| {
                let cloned_path = path.clone();
                thread::spawn(move || {
                    let mut file = File::open(cloned_path.clone()).unwrap();
                    let mut buffer = Vec::new();
                    file.read_to_end(&mut buffer).unwrap();

                    match Replay::from_bytes(&buffer) {
                        Ok(replay) => Ok(HashSet::from_iter(
                            replay
                                .players()
                                .iter()
                                .flat_map(|player| player.commands())
                                .filter_map(|command| {
                                    if let Command::Unknown(data) = command {
                                        if data.index() != 0 {
                                            Some(data.action_type())
                                        } else {
                                            None
                                        }
                                    } else {
                                        None
                                    }
                                }),
                        )),
                        Err(_) => Err(format!("failed to parse {:?}", cloned_path)),
                    }
                })
            })
            .collect();

        let mut parse_results: Vec<_> = handles
            .into_iter()
            .map(|handle| handle.join().unwrap())
            .collect();
        results.append(&mut parse_results);
    }

    let types = results
        .iter()
        .map(|result| result.clone().unwrap_or_default())
        .reduce(|mut acc, set| {
            acc.extend(set);
            acc
        });
    println!("{:?}", types.clone().unwrap());
    assert_eq!(types.unwrap().len(), 0);
}