<commit_before>// src/cli.rs
// Argument parser and CLI (Command Line Interface)
// for BreakFast

use clap::{App, Arg, SubCommand};

pub fn build_cli() -> App<'static, 'static> {
    App::new("breakfastrs")
        .version("0.1")
        .about("BreakFast is a toolkit for detecting chromosomal rearrangements based on whole genome sequencing data.")
        .usage("\tbreakfast detect <bam_file> <genome> <out_prefix> [-a N] [-f N] [-q N] [-O OR] [--discard-duplicates=METHOD]\n\
        \tbreakfast detect specific [-A] <bam_file> <donors> <acceptors> <genome> <out_prefix>\n\
        \tbreakfast filter <sv_file> [-r P-S-A]... [--blacklist=PATH]\n\
        \tbreakfast annotate <sv_file> <bed_file>\n\
        \tbreakfast blacklist [--freq-above=FREQ] <sv_files>...\n\
        \tbreakfast visualize <sv_file>\n\
        \tbreakfast tabulate rearranged genes <sv_files>...\n\
        \tbreakfast tabulate fusions <sv_files>...\n\
        \tbreakfast statistics <sv_files>...\n\
        \tbreakfast filter by region <sv_file> <region>\n\
        \tbreakfast filter by distance <min_distance> <sv_file>\n\
        \tbreakfast align junction <reads>")

        .arg(Arg::with_name("anchor-len")
            .short("a")
            .long("anchor-len")
            .takes_value(true)
            .value_name("N")
            .display_order(1)
            .help("Anchor length for split read analysis. When zero, split reads are not used [default: 0]"))

        .arg(Arg::with_name("max-frag-len")
            .short("f")
            .long("max-frag-len")
            .takes_value(true)
            .value_name("N")
            .display_order(2)
            .help("Maximum fragment length [default: 5000]"))

        .arg(Arg::with_name("min-mapq")
            .short("q")
            .long("min-mapq")
            .takes_value(true)
            .value_name("N")
            .display_order(3)
            .help("Minimum mapping quality to consider [default: 15]"))

        .arg(Arg::with_name("orientation")
            .short("O")
            .long("orientation")
            .takes_value(true)
            .value_name("OR")
            .display_order(4)
            .help("Read pair orientation produced by sequencer. Either 'fr' (converging), 'rf' (diverging) or 'ff' [default: fr]"))

        .arg(Arg::with_name("all-reads")
            .short("A")
            .long("all-reads")
            .display_order(5)
            .help("Use all reads for rearrangement detection, not just unaligned reads."))

        .arg(Arg::with_name("discard-duplicates")
            .long("discard-duplicates")
            .takes_value(true)
            .value_name("Method")
            .display_order(6)
            .help("Method to use when discarding duplicate reads.'both-ends' considers a read pair (or unaligned read) to be a duplicate of another if the positions"))

        .arg(Arg::with_name("blacklist")
            .long("blacklist")
            .takes_value(true)
            .value_name("list")
            .display_order(7)
            .help("Path to a file containing blacklisted regions."))

        .arg(Arg::with_name("min-reads")
            .short("r")
            .long("min-reads")
            .takes_value(true)
            .value_name("P-S-A")
            .display_order(8)
            .help("Minimum number of spanning reads required to accept a breakpoint. Specified in the format P-S-A, where P=paired, S=split, A=either. For example, -r 1-2-0 would require at least one mate pair and two split reads of evidence [default: 0-0-0]."))

        .arg(Arg::with_name("freq-above")
            .long("freq-above")
            .takes_value(true)
            .value_name("FREQ")
            .display_order(9)
            .help("Minimum frequency at which a variant must be present among the control samples to be considered a false positive [default: 0]."))

        .subcommand(SubCommand::with_name("detect")
            .usage("\tbreakfast detect <bam_file> <genome> <out_prefix> [-a N] [-f N] [-q N] [-O OR] [--discard-duplicates=METHOD]")
            .display_order(1)
            .arg(Arg::with_name("bam_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("genome")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("out_prefix")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("anchor-len")
                .short("a")
                .long("anchor-len")
                .takes_value(true)
                .value_name("N")
                .display_order(1)
                .help("Anchor length for split read analysis. When zero, split reads are not used [default: 0]"))
            .arg(Arg::with_name("max-frag-len")
                .short("f")
                .long("max-frag-len")
                .takes_value(true)
                .value_name("N")
                .display_order(2)
                .help("Maximum fragment length [default: 5000]"))
            .arg(Arg::with_name("min-mapq")
                .short("q")
                .long("min-mapq")
                .takes_value(true)
                .value_name("N")
                .display_order(3)
                .help("Minimum mapping quality to consider [default: 15]"))
            .arg(Arg::with_name("orientation")
                .short("O")
                .long("orientation")
                .takes_value(true)
                .value_name("OR")
                .display_order(4)
                .help("Read pair orientation produced by sequencer. Either 'fr' (converging), 'rf' (diverging) or 'ff' [default: fr]"))
            .arg(Arg::with_name("discard-duplicates")
                .long("discard-duplicates")
                .takes_value(true)
                .value_name("Method")
                .display_order(6)
                .help("Method to use when discarding duplicate reads.'both-ends' considers a read pair (or unaligned read) to be a duplicate of another if the positions")))

        .subcommand(SubCommand::with_name("detect-specific")
            .usage("breakfast detect specific [-A] <bam_file> <donors> <acceptors> <genome> <out_prefix>")
            .display_order(2)
            .arg(Arg::with_name("all-reads")
                .short("A")
                .long("all-reads")
                .display_order(5)
                .help("Use all reads for rearrangement detection, not just unaligned reads."))
            .arg(Arg::with_name("bam_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("donors")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("acceptors")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("genome")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("out_prefix")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("filter")
            .usage("breakfast filter <sv_file> [-r P-S-A]... [--blacklist=PATH]")
            .display_order(3)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("min-reads")
                .short("r")
                .long("min-reads")
                .takes_value(true)
                .value_name("P-S-A")
                .display_order(8)
                .help("Minimum number of spanning reads required to accept a breakpoint. Specified in the format P-S-A, where P=paired, S=split, A=either. For example, -r 1-2-0 would require at least one mate pair and two split reads of evidence [default: 0-0-0]."))
            .arg(Arg::with_name("blacklist")
                .long("blacklist")
                .takes_value(true)
                .value_name("list")
                .display_order(7)
                .help("Path to a file containing blacklisted regions.")))

        .subcommand(SubCommand::with_name("annotate")
            .usage("breakfast annotate <sv_file> <bed_file>")
            .display_order(4)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("bed_file")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("blacklist")
            .usage("breakfast blacklist [--freq-above=FREQ] <sv_files>...")
            .display_order(5)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("freq-above")
                .long("freq-above")
                .takes_value(true)
                .value_name("FREQ")
                .display_order(9)
                .help("Minimum frequency at which a variant must be present among the control samples to be considered a false positive [default: 0].")))

        .subcommand(SubCommand::with_name("visualize")
            .usage("breakfast visualize <sv_file>")
            .display_order(6)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("tabulate-rearranged-genes")
            .usage("breakfast tabulate rearranged genes <sv_files>...")
            .display_order(7)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("tabulate-fusions")
            .usage("breakfast tabulate fusions <sv_files>...")
            .display_order(8)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("statistics")
            .usage("breakfast statistics <sv_files>..")
            .display_order(9)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("filter-by-region")
            .usage("breakfast filter by region <sv_file> <region>")
            .display_order(10)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("region")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("filter-by-distance")
            .usage("breakfast filter by distance <min_distance> <sv_file>")
            .display_order(11)
            .arg(Arg::with_name("min_distance")
                .takes_value(true)
                .required(true))
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("align-junction")
            .usage("breakfast align junction <reads>")
            .display_order(12)
            .arg(Arg::with_name("reads")
                .takes_value(true)
                .required(true)))
}
<commit_msg>global options restrctured<commit_after>// src/cli.rs
// Argument parser and CLI (Command Line Interface)
// for BreakFast

use clap::{App, Arg, SubCommand};

pub fn build_cli() -> App<'static, 'static> {
    App::new("breakfastrs")
        .version("0.1")
        .about("BreakFast is a toolkit for detecting chromosomal rearrangements based on whole genome sequencing data.")
        .usage("\tbreakfast detect <bam_file> <genome> <out_prefix> [-a N] [-f N] [-q N] [-O OR] [--discard-duplicates=METHOD]\n\
        \tbreakfast detect specific [-A] <bam_file> <donors> <acceptors> <genome> <out_prefix>\n\
        \tbreakfast filter <sv_file> [-r P-S-A]... [--blacklist=PATH]\n\
        \tbreakfast annotate <sv_file> <bed_file>\n\
        \tbreakfast blacklist [--freq-above=FREQ] <sv_files>...\n\
        \tbreakfast visualize <sv_file>\n\
        \tbreakfast tabulate rearranged genes <sv_files>...\n\
        \tbreakfast tabulate fusions <sv_files>...\n\
        \tbreakfast statistics <sv_files>...\n\
        \tbreakfast filter by region <sv_file> <region>\n\
        \tbreakfast filter by distance <min_distance> <sv_file>\n\
        \tbreakfast align junction <reads>")

        .arg(Arg::with_name("anchor-len")
            .short("a")
            .long("anchor-len")
            .takes_value(true)
            .value_name("N")
            .display_order(1)
            .help("Anchor length for split read analysis. When zero, split reads are not used [default: 0]"))

        .arg(Arg::with_name("max-frag-len")
            .short("f")
            .long("max-frag-len")
            .takes_value(true)
            .value_name("N")
            .display_order(2)
            .help("Maximum fragment length [default: 5000]"))

        .arg(Arg::with_name("min-mapq")
            .short("q")
            .long("min-mapq")
            .takes_value(true)
            .value_name("N")
            .display_order(3)
            .help("Minimum mapping quality to consider [default: 15]"))

        .arg(Arg::with_name("orientation")
            .short("O")
            .long("orientation")
            .takes_value(true)
            .value_name("OR")
            .display_order(4)
            .help("Read pair orientation produced by sequencer. Either 'fr' (converging), 'rf' (diverging) or 'ff' [default: fr]"))

        .arg(Arg::with_name("all-reads")
            .short("A")
            .long("all-reads")
            .display_order(5)
            .help("Use all reads for rearrangement detection, not just unaligned reads."))

        .arg(Arg::with_name("discard-duplicates")
            .long("discard-duplicates")
            .takes_value(true)
            .value_name("Method")
            .display_order(6)
            .help("Method to use when discarding duplicate reads.'both-ends' considers a read pair (or unaligned read) to be a duplicate of another if the positions"))

        .arg(Arg::with_name("blacklist")
            .long("blacklist")
            .takes_value(true)
            .value_name("list")
            .display_order(7)
            .help("Path to a file containing blacklisted regions."))

        .arg(Arg::with_name("min-reads")
            .short("r")
            .long("min-reads")
            .takes_value(true)
            .value_name("P-S-A")
            .display_order(8)
            .help("Minimum number of spanning reads required to accept a breakpoint. Specified in the format P-S-A, where P=paired, S=split, A=either. For example, -r 1-2-0 would require at least one mate pair and two split reads of evidence [default: 0-0-0]."))

        .arg(Arg::with_name("freq-above")
            .long("freq-above")
            .takes_value(true)
            .value_name("FREQ")
            .display_order(9)
            .help("Minimum frequency at which a variant must be present among the control samples to be considered a false positive [default: 0]."))

        .subcommand(SubCommand::with_name("detect")
            .usage("\tbreakfast detect <bam_file> <genome> <out_prefix> [-a N] [-f N] [-q N] [-O OR] [--discard-duplicates=METHOD]")
            .display_order(1)
            .arg(Arg::with_name("bam_file")
                .index(1)
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("genome")
                .index(2)
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("out_prefix")
                .index(3)
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("anchor-len")
                .short("a")
                .long("anchor-len")
                .takes_value(true)
                .value_name("N")
                .display_order(1)
                .help("Anchor length for split read analysis. When zero, split reads are not used [default: 0]"))
            .arg(Arg::with_name("max-frag-len")
                .short("f")
                .long("max-frag-len")
                .takes_value(true)
                .value_name("N")
                .display_order(2)
                .help("Maximum fragment length [default: 5000]"))
            .arg(Arg::with_name("min-mapq")
                .short("q")
                .long("min-mapq")
                .takes_value(true)
                .value_name("N")
                .display_order(3)
                .help("Minimum mapping quality to consider [default: 15]"))
            .arg(Arg::with_name("orientation")
                .short("O")
                .long("orientation")
                .takes_value(true)
                .value_name("OR")
                .display_order(4)
                .help("Read pair orientation produced by sequencer. Either 'fr' (converging), 'rf' (diverging) or 'ff' [default: fr]"))
            .arg(Arg::with_name("discard-duplicates")
                .long("discard-duplicates")
                .takes_value(true)
                .value_name("Method")
                .display_order(6)
                .help("Method to use when discarding duplicate reads.'both-ends' considers a read pair (or unaligned read) to be a duplicate of another if the positions")))

        .subcommand(SubCommand::with_name("detectspecific")
            .usage("breakfast detect specific [-A] <bam_file> <donors> <acceptors> <genome> <out_prefix>")
            .display_order(2)
            .arg(Arg::with_name("all-reads")
                .short("A")
                .long("all-reads")
                .display_order(5)
                .help("Use all reads for rearrangement detection, not just unaligned reads."))
            .arg(Arg::with_name("bam_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("donors")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("acceptors")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("genome")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("out_prefix")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("filter")
            .usage("breakfast filter <sv_file> [-r P-S-A]... [--blacklist=PATH]")
            .display_order(3)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("min-reads")
                .short("r")
                .long("min-reads")
                .takes_value(true)
                .value_name("P-S-A")
                .display_order(8)
                .help("Minimum number of spanning reads required to accept a breakpoint. Specified in the format P-S-A, where P=paired, S=split, A=either. For example, -r 1-2-0 would require at least one mate pair and two split reads of evidence [default: 0-0-0]."))
            .arg(Arg::with_name("blacklist")
                .long("blacklist")
                .takes_value(true)
                .value_name("list")
                .display_order(7)
                .help("Path to a file containing blacklisted regions.")))

        .subcommand(SubCommand::with_name("annotate")
            .usage("breakfast annotate <sv_file> <bed_file>")
            .display_order(4)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("bed_file")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("blacklist")
            .usage("breakfast blacklist [--freq-above=FREQ] <sv_files>...")
            .display_order(5)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("freq-above")
                .long("freq-above")
                .takes_value(true)
                .value_name("FREQ")
                .display_order(9)
                .help("Minimum frequency at which a variant must be present among the control samples to be considered a false positive [default: 0].")))

        .subcommand(SubCommand::with_name("visualize")
            .usage("breakfast visualize <sv_file>")
            .display_order(6)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("tabulate-rearranged-genes")
            .usage("breakfast tabulate rearranged genes <sv_files>...")
            .display_order(7)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("tabulate-fusions")
            .usage("breakfast tabulate fusions <sv_files>...")
            .display_order(8)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("statistics")
            .usage("breakfast statistics <sv_files>..")
            .display_order(9)
            .arg(Arg::with_name("sv_files")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("filter-by-region")
            .usage("breakfast filter by region <sv_file> <region>")
            .display_order(10)
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true))
            .arg(Arg::with_name("region")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("filter-by-distance")
            .usage("breakfast filter by distance <min_distance> <sv_file>")
            .display_order(11)
            .arg(Arg::with_name("min_distance")
                .takes_value(true)
                .required(true))
            .arg(Arg::with_name("sv_file")
                .required(true)
                .takes_value(true)))

        .subcommand(SubCommand::with_name("align-junction")
            .usage("breakfast align junction <reads>")
            .display_order(12)
            .arg(Arg::with_name("reads")
                .takes_value(true)
                .required(true)))
}
<|endoftext|>


<commit_before>extern crate iso8601;
extern crate chrono;
extern crate nom;

use chrono::{FixedOffset,LocalResult,TimeZone};

use nom::IResult::*;

use iso8601::{Date,Time,DateTime};
use iso8601::easy::*;

#[test]
fn easy_parse_date() {
    assert_eq!(Done(&[][..], Date{ year: 2015, month: 6, day: 26 }), date(b"2015-06-26"));
    assert_eq!(Done(&[][..], Date{ year: -333, month: 7, day: 11 }), date(b"-0333-07-11"));

    assert!(date(b"201").is_incomplete());
    assert!(date(b"2015p00p00").is_err());
    assert!(date(b"pppp").is_err());
}

#[test]
fn easy_parse_time() {
    assert_eq!(Done(&[][..], Time{ hour: 16, minute: 43, second: 16, tz_offset: 0}), time(b"16:43:16"));
    assert_eq!(Done(&[][..], Time{ hour: 16, minute: 43, second:  0, tz_offset: 0}), time(b"16:43"));

    assert!(time(b"20:").is_incomplete());
    assert!(time(b"20p42p16").is_err());
    assert!(time(b"pppp").is_err());
}

#[test]
fn easy_parse_datetime_correct() {
    fn make_datetime((year, month, day, hour, minute, second, tz_offset): (i32, u32, u32, u32, u32, u32, i32)) -> DateTime {
        DateTime {
            date: Date{ year: year, month: month, day: day },
            time: Time{ hour: hour, minute: minute, second: second, tz_offset: tz_offset*3600 },
        }
    }

    let test_datetimes = vec![
        ("2007-08-31T16:47+00:00",     (2007,  08,  31,  16,  47,  0,   0)),
        ("2007-12-24T18:21Z",          (2007,  12,  24,  18,  21,  0,   0)),
        ("2008-02-01T09:00:22+05",     (2008,  02,  01,  9,   0,   22,  5)),
        ("2009-01-01T12:00:00+01:00",  (2009,  1,   1,   12,  0,   0,   1)),
        ("2009-06-30T18:30:00+02:00",  (2009,  06,  30,  18,  30,  0,   2)),
        ("2015-06-29T23:07+02:00",     (2015,  06,  29,  23,  07,  0,   2)),
        ("2015-06-26T16:43:16",        (2015,  06,  26,  16,  43, 16,   0)),
    ];

    for (iso_string, data) in test_datetimes {
        assert_eq!(Done(&[][..], make_datetime(data)), datetime(iso_string.as_bytes()));
    }
}

#[test]
fn easy_parse_datetime_error() {
    let test_datetimes = vec![
        "ppp",
        "dumd-di-duTmd:iu:m"
    ];

    for iso_string in test_datetimes {
        let res = datetime(iso_string.as_bytes());
        assert!(res.is_err() || res.is_incomplete());
    }
}

#[test]
fn easy_allows_notallowed() {
    assert_eq!(Done(&[][..], Time{ hour: 30, minute: 90, second: 90, tz_offset: 0}), time(b"30:90:90"));
    assert_eq!(Done(&[][..], Date{ year: 0, month: 20, day: 40}), date(b"0000-20-40"));
}

#[test]
fn easy_test_chrono_conversion() {
    let t = datetime(b"0000-20-40T30:90:90Z");
    if let Done(_, dt) = t {
        assert_eq!(LocalResult::None, dt.to_chrono());
    }

    let t = datetime(b"2007-08-31T16:47+00:00");
    if let Done(_, dt) = t {
        let fixed_dt = FixedOffset::east(0).ymd(2007,08,31).and_hms(16,47,0);
        assert_eq!(fixed_dt, dt.to_chrono().unwrap());

    let t = datetime(b"2007-08-31T16:47-09:00");
    if let Done(_, dt) = t {
        let fixed_dt = FixedOffset::east(-9*3600).ymd(2007,08,31).and_hms(16,47,0);
        assert_eq!(fixed_dt, dt.to_chrono().unwrap());
    }   }
}
<commit_msg>Don't nest tests<commit_after>extern crate iso8601;
extern crate chrono;
extern crate nom;

use chrono::{FixedOffset,LocalResult,TimeZone};

use nom::IResult::*;

use iso8601::{Date,Time,DateTime};
use iso8601::easy::*;

#[test]
fn easy_parse_date() {
    assert_eq!(Done(&[][..], Date{ year: 2015, month: 6, day: 26 }), date(b"2015-06-26"));
    assert_eq!(Done(&[][..], Date{ year: -333, month: 7, day: 11 }), date(b"-0333-07-11"));

    assert!(date(b"201").is_incomplete());
    assert!(date(b"2015p00p00").is_err());
    assert!(date(b"pppp").is_err());
}

#[test]
fn easy_parse_time() {
    assert_eq!(Done(&[][..], Time{ hour: 16, minute: 43, second: 16, tz_offset: 0}), time(b"16:43:16"));
    assert_eq!(Done(&[][..], Time{ hour: 16, minute: 43, second:  0, tz_offset: 0}), time(b"16:43"));

    assert!(time(b"20:").is_incomplete());
    assert!(time(b"20p42p16").is_err());
    assert!(time(b"pppp").is_err());
}

#[test]
fn easy_parse_datetime_correct() {
    fn make_datetime((year, month, day, hour, minute, second, tz_offset): (i32, u32, u32, u32, u32, u32, i32)) -> DateTime {
        DateTime {
            date: Date{ year: year, month: month, day: day },
            time: Time{ hour: hour, minute: minute, second: second, tz_offset: tz_offset*3600 },
        }
    }

    let test_datetimes = vec![
        ("2007-08-31T16:47+00:00",     (2007,  08,  31,  16,  47,  0,   0)),
        ("2007-12-24T18:21Z",          (2007,  12,  24,  18,  21,  0,   0)),
        ("2008-02-01T09:00:22+05",     (2008,  02,  01,  9,   0,   22,  5)),
        ("2009-01-01T12:00:00+01:00",  (2009,  1,   1,   12,  0,   0,   1)),
        ("2009-06-30T18:30:00+02:00",  (2009,  06,  30,  18,  30,  0,   2)),
        ("2015-06-29T23:07+02:00",     (2015,  06,  29,  23,  07,  0,   2)),
        ("2015-06-26T16:43:16",        (2015,  06,  26,  16,  43, 16,   0)),
    ];

    for (iso_string, data) in test_datetimes {
        assert_eq!(Done(&[][..], make_datetime(data)), datetime(iso_string.as_bytes()));
    }
}

#[test]
fn easy_parse_datetime_error() {
    let test_datetimes = vec![
        "ppp",
        "dumd-di-duTmd:iu:m"
    ];

    for iso_string in test_datetimes {
        let res = datetime(iso_string.as_bytes());
        assert!(res.is_err() || res.is_incomplete());
    }
}

#[test]
fn easy_allows_notallowed() {
    assert_eq!(Done(&[][..], Time{ hour: 30, minute: 90, second: 90, tz_offset: 0}), time(b"30:90:90"));
    assert_eq!(Done(&[][..], Date{ year: 0, month: 20, day: 40}), date(b"0000-20-40"));
}

#[test]
fn easy_test_chrono_conversion() {
    let t = datetime(b"0000-20-40T30:90:90Z");
    if let Done(_, dt) = t {
        assert_eq!(LocalResult::None, dt.to_chrono());
    }

    let t = datetime(b"2007-08-31T16:47+00:00");
    if let Done(_, dt) = t {
        let fixed_dt = FixedOffset::east(0).ymd(2007,08,31).and_hms(16,47,0);
        assert_eq!(fixed_dt, dt.to_chrono().unwrap());
    }

    let t = datetime(b"2007-08-31T16:47-09:00");
    if let Done(_, dt) = t {
        let fixed_dt = FixedOffset::east(-9*3600).ymd(2007,08,31).and_hms(16,47,0);
        assert_eq!(fixed_dt, dt.to_chrono().unwrap());
    }
}
<|endoftext|>


<commit_before>extern crate env_logger;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate collect_mac;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate futures;
extern crate futures_cpupool;
extern crate gluon;
extern crate pulldown_cmark;
extern crate tensile;
extern crate tokio;
extern crate walkdir;

use gluon::base::ast::{Expr, Pattern, SpannedExpr};
use gluon::base::filename_to_module;
use gluon::base::symbol::Symbol;
use gluon::base::types::{ArcType, Type};

use gluon::vm::api::de::De;
use gluon::vm::api::{Getable, Hole, OpaqueValue, OwnedFunction, VmType};

use gluon::{new_vm, Compiler, RootedThread, Thread};

use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};

use futures::{future, stream, Future, Stream};
use tokio::runtime::current_thread::Runtime;

#[derive(Debug, Fail)]
enum Error {
    #[fail(display = "{}", _0)]
    Error(failure::Error),
    #[fail(display = "{}", _0)]
    Io(io::Error),
    #[fail(display = "{}", _0)]
    Gluon(gluon::Error),
    #[fail(display = "{}", _0)]
    Message(String),
}

impl From<String> for Error {
    fn from(d: String) -> Error {
        Error::Message(d)
    }
}

impl<'a> From<&'a str> for Error {
    fn from(d: &'a str) -> Error {
        Error::Message(d.to_string())
    }
}

impl From<failure::Error> for Error {
    fn from(d: failure::Error) -> Error {
        Error::Error(d)
    }
}

impl From<io::Error> for Error {
    fn from(d: io::Error) -> Error {
        Error::Io(d)
    }
}

impl From<gluon::Error> for Error {
    fn from(d: gluon::Error) -> Error {
        Error::Gluon(d)
    }
}

fn main() {
    if let Err(err) = main_() {
        assert!(false, "{}", err);
    }
}

fn test_files(path: &str) -> Result<Vec<PathBuf>, Error> {
    let paths: Vec<_> = walkdir::WalkDir::new(path)
        .into_iter()
        .filter_map(|f| {
            f.ok().and_then(|f| {
                let path = f.path();
                if path.extension().and_then(|e| e.to_str()) == Some("glu") {
                    Some(path.to_owned())
                } else {
                    None
                }
            })
        })
        .collect();
    assert!(!paths.is_empty(), "Expected test files");
    Ok(paths)
}

macro_rules! define_test_type {
    ($name:ident) => {
        impl VmType for $name {
            type Type = $name;
            fn make_type(vm: &Thread) -> ArcType {
                let typ = concat!("std.test.", stringify!($name));
                Type::app(
                    (*vm.global_env().get_env().find_type_info(typ).unwrap())
                        .clone()
                        .into_type(),
                    collect![Type::unit()],
                )
            }
        }
    };
}

type TestFn = OwnedFunction<fn(()) -> OpaqueValue<RootedThread, Test>>;

#[derive(Deserialize)]
enum TestCase {
    Test { name: String, test: TestFn },
    Group { name: String, tests: Vec<TestCase> },
}

define_test_type! { TestCase }

struct Test;

define_test_type! { Test }

struct GluonTestable<F>(F);

impl<F> tensile::Testable for GluonTestable<F>
where
    F: Future<Item = (), Error = Error> + Send + Sync + 'static,
{
    type Error = Error;

    fn test(self) -> tensile::TestFuture<Self::Error> {
        Box::new(self.0)
    }
}

impl TestCase {
    fn into_tensile_test(self) -> tensile::Test<Error> {
        match self {
            TestCase::Test { name, test } => {
                let child_thread = test.vm().new_thread().unwrap();
                let test = TestFn::from_value(&child_thread, test.get_variant());
                let mut test = ::std::panic::AssertUnwindSafe(test);
                tensile::test(name, move || {
                    let future = test
                        .call_async(())
                        .and_then(|test| {
                            future::result(test.vm().get_global("std.test.run")).and_then(
                                |action| {
                                    let mut action: OwnedFunction<
                                        fn(OpaqueValue<RootedThread, Test>) -> (),
                                    > = action;
                                    action.call_async(test)
                                },
                            )
                        })
                        .map_err(gluon::Error::from)
                        .map_err(Error::from);
                    GluonTestable(::std::panic::AssertUnwindSafe(future))
                })
            }
            TestCase::Group { name, tests } => tensile::Test::Group {
                name,
                tests: tests.into_iter().map(TestCase::into_tensile_test).collect(),
            },
        }
    }
}

fn make_test<'t>(vm: &'t Thread, name: &str, filename: &Path) -> Result<TestCase, Error> {
    let mut compiler = Compiler::new();

    let mut file = File::open(&filename)?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;
    let (De(test), _) = compiler.run_expr(&vm, &name, &text)?;
    Ok(test)
}

fn run_file<'t>(
    vm: &'t Thread,
    name: &str,
    filename: &Path,
) -> Result<(OpaqueValue<&'t Thread, Hole>, ArcType), Error> {
    let mut compiler = Compiler::new();

    let mut file = File::open(&filename)?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;
    Ok(compiler.run_expr::<OpaqueValue<&Thread, Hole>>(&vm, &name, &text)?)
}

fn gather_doc_tests(expr: &SpannedExpr<Symbol>) -> Vec<(String, String)> {
    use gluon::base::ast::{walk_expr, Visitor};

    fn make_test(comment: &str) -> String {
        let mut parser = pulldown_cmark::Parser::new(comment);

        let mut source = String::new();
        loop {
            let content = match parser.next() {
                Some(pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock(code))) => code,
                None => break,
                _ => continue,
            };
            source.push_str(&content);
            loop {
                match parser.next() {
                    Some(pulldown_cmark::Event::End(pulldown_cmark::Tag::CodeBlock(content))) => {
                        source.push_str(&content);
                        break;
                    }
                    Some(pulldown_cmark::Event::Text(content)) => {
                        source.push_str(&content);
                    }
                    None => break,
                    _ => continue,
                }
            }
        }
        source
    }

    struct DocVisitor(Vec<(String, String)>);
    impl<'a> Visitor<'a> for DocVisitor {
        type Ident = Symbol;

        fn visit_expr(&mut self, expr: &SpannedExpr<Symbol>) {
            match expr.value {
                Expr::LetBindings(ref binds, _) => {
                    for bind in binds {
                        if let Some(ref comment) = bind.metadata.comment {
                            let mut source = make_test(&comment.content);
                            if !source.is_empty() {
                                let name = match bind.name.value {
                                    Pattern::Ident(ref id) => id.name.declared_name(),
                                    _ => "Unknown",
                                };
                                self.0.push((format!("{}", name), String::from(source)));
                            }
                        }
                    }
                }
                Expr::TypeBindings(ref binds, _) => {
                    for bind in binds {
                        if let Some(ref comment) = bind.metadata.comment {
                            let mut source = make_test(&comment.content);
                            if !source.is_empty() {
                                self.0.push((
                                    format!("{}", bind.name.value.declared_name()),
                                    String::from(source),
                                ));
                            }
                        }
                    }
                }
                _ => (),
            }
            walk_expr(self, expr);
        }
    }
    let mut visitor = DocVisitor(Vec::new());

    visitor.visit_expr(expr);

    visitor.0
}

fn run_doc_tests<'t>(
    vm: &'t Thread,
    name: &str,
    filename: &Path,
) -> Result<Vec<tensile::Test<Error>>, Error> {
    let mut compiler = Compiler::new();

    let mut file = File::open(&filename)?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;

    let (expr, _, _) = compiler.extract_metadata(&vm, &name, &text)?;

    let tests = gather_doc_tests(&expr);
    Ok(tests
        .into_iter()
        .map(move |(test_name, test_source)| {
            let vm = vm.new_thread().unwrap();
            tensile::test(test_name.clone(), move || {
                Compiler::new().run_expr::<OpaqueValue<&Thread, Hole>>(
                    &vm,
                    &test_name,
                    &test_source,
                )?;
                Ok(())
            })
        })
        .collect())
}

fn main_() -> Result<(), Error> {
    let _ = ::env_logger::try_init();
    let args: Vec<_> = ::std::env::args().collect();
    let filter = if args.len() > 1 && args.last().unwrap() != "main" {
        args.last()
    } else {
        None
    };

    let file_filter = filter.as_ref().map_or(false, |f| f.starts_with("@"));
    let filter = filter.as_ref().map(|f| f.trim_left_matches('@'));

    let vm = new_vm();
    Compiler::new().load_file(&vm, "std/test.glu")?;

    let iter = test_files("tests/pass")?.into_iter();

    let pool = futures_cpupool::CpuPool::new(1);
    let mut runtime = tokio::runtime::Runtime::new()?;
    let pass_tests_future = stream::futures_ordered(
        iter.filter_map(|filename| {
            let name = filename_to_module(filename.to_str().unwrap_or("<unknown>"));

            match filter {
                Some(ref filter) if file_filter && !name.contains(&filter[..]) => None,
                _ => Some((filename, name)),
            }
        })
        .map(|(filename, name)| {
            let vm = vm.new_thread().unwrap();

            let name2 = name.clone();
            pool.spawn_fn(move || make_test(&vm, &name, &filename))
                .then(|result| -> Result<_, Error> {
                    Ok(match result {
                        Ok(test) => test.into_tensile_test(),
                        Err(err) => {
                            let err = ::std::panic::AssertUnwindSafe(err);
                            tensile::test(name2, || Err(err.0))
                        }
                    })
                })
        }),
    )
    .collect();
    let pass_tests = runtime.block_on(pass_tests_future)?;

    let iter = test_files("tests/fail")?
        .into_iter()
        .filter(|filename| !filename.to_string_lossy().contains("deps"));

    let fail_tests = iter
        .filter_map(|filename| {
            let name = filename_to_module(filename.to_str().unwrap_or("<unknown>"));

            match filter {
                Some(ref filter) if file_filter && !name.contains(&filter[..]) => None,
                _ => Some((filename, name)),
            }
        })
        .map(|(filename, name)| {
            let vm = vm.new_thread().unwrap();

            tensile::test(name.clone(), move || -> Result<(), Error> {
                match run_file(&vm, &name, &filename) {
                    Ok(err) => Err(format!(
                        "Expected test '{}' to fail\n{:?}",
                        filename.to_str().unwrap(),
                        err.0,
                    )
                    .into()),
                    Err(_) => Ok(()),
                }
            })
        })
        .collect();

    let doc_tests = test_files("std")?
        .into_iter()
        .filter_map(|filename| {
            let name = filename_to_module(filename.to_str().unwrap_or("<unknown>"));

            match filter {
                Some(ref filter) if file_filter && !name.contains(&filter[..]) => None,
                _ => Some((filename, name)),
            }
        })
        .map(|(filename, name)| {
            let vm = vm.new_thread().unwrap();
            match run_doc_tests(&vm, &name, &filename) {
                Ok(tests) => tensile::group(name.clone(), tests),
                Err(err) => {
                    let err = ::std::panic::AssertUnwindSafe(err);
                    tensile::test(name.clone(), || Err(err.0))
                }
            }
        })
        .collect();

    let mut runtime = Runtime::new()?;
    runtime.block_on(future::lazy(|| {
        tensile::console_runner(
            tensile::group(
                "main",
                vec![
                    tensile::group("pass", pass_tests),
                    tensile::group("fail", fail_tests),
                    tensile::group("doc", doc_tests),
                ],
            ),
            &tensile::Options::default().filter(filter.map_or("", |s| &s[..])),
        )
    }))?;
    Ok(())
}
<commit_msg>Compile and run gluon tests in parallel<commit_after>extern crate env_logger;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate collect_mac;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate futures;
extern crate futures_cpupool;
extern crate gluon;
extern crate pulldown_cmark;
extern crate tensile;
extern crate tokio;
extern crate walkdir;

use gluon::base::ast::{Expr, Pattern, SpannedExpr};
use gluon::base::filename_to_module;
use gluon::base::symbol::Symbol;
use gluon::base::types::{ArcType, Type};

use gluon::vm::api::de::De;
use gluon::vm::api::{Getable, Hole, OpaqueValue, OwnedFunction, VmType};

use gluon::{new_vm, Compiler, RootedThread, Thread};

use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};

use futures::{future, stream, Future, Stream};
use tokio::runtime::current_thread::Runtime;

#[derive(Debug, Fail)]
enum Error {
    #[fail(display = "{}", _0)]
    Error(failure::Error),
    #[fail(display = "{}", _0)]
    Io(io::Error),
    #[fail(display = "{}", _0)]
    Gluon(gluon::Error),
    #[fail(display = "{}", _0)]
    Message(String),
}

impl From<String> for Error {
    fn from(d: String) -> Error {
        Error::Message(d)
    }
}

impl<'a> From<&'a str> for Error {
    fn from(d: &'a str) -> Error {
        Error::Message(d.to_string())
    }
}

impl From<failure::Error> for Error {
    fn from(d: failure::Error) -> Error {
        Error::Error(d)
    }
}

impl From<io::Error> for Error {
    fn from(d: io::Error) -> Error {
        Error::Io(d)
    }
}

impl From<gluon::Error> for Error {
    fn from(d: gluon::Error) -> Error {
        Error::Gluon(d)
    }
}

fn main() {
    if let Err(err) = main_() {
        assert!(false, "{}", err);
    }
}

fn test_files(path: &str) -> Result<Vec<PathBuf>, Error> {
    let paths: Vec<_> = walkdir::WalkDir::new(path)
        .into_iter()
        .filter_map(|f| {
            f.ok().and_then(|f| {
                let path = f.path();
                if path.extension().and_then(|e| e.to_str()) == Some("glu") {
                    Some(path.to_owned())
                } else {
                    None
                }
            })
        })
        .collect();
    assert!(!paths.is_empty(), "Expected test files");
    Ok(paths)
}

macro_rules! define_test_type {
    ($name:ident) => {
        impl VmType for $name {
            type Type = $name;
            fn make_type(vm: &Thread) -> ArcType {
                let typ = concat!("std.test.", stringify!($name));
                Type::app(
                    (*vm.global_env().get_env().find_type_info(typ).unwrap())
                        .clone()
                        .into_type(),
                    collect![Type::unit()],
                )
            }
        }
    };
}

type TestFn = OwnedFunction<fn(()) -> OpaqueValue<RootedThread, Test>>;

#[derive(Deserialize)]
enum TestCase {
    Test { name: String, test: TestFn },
    Group { name: String, tests: Vec<TestCase> },
}

define_test_type! { TestCase }

struct Test;

define_test_type! { Test }

struct GluonTestable<F>(F);

impl<F> tensile::Testable for GluonTestable<F>
where
    F: Future<Item = (), Error = Error> + Send + Sync + 'static,
{
    type Error = Error;

    fn test(self) -> tensile::TestFuture<Self::Error> {
        Box::new(self.0)
    }
}

impl TestCase {
    fn into_tensile_test(self) -> tensile::Test<Error> {
        match self {
            TestCase::Test { name, test } => {
                let child_thread = test.vm().new_thread().unwrap();
                let test = TestFn::from_value(&child_thread, test.get_variant());
                let mut test = ::std::panic::AssertUnwindSafe(test);
                tensile::test(name, move || {
                    let future = test
                        .call_async(())
                        .and_then(|test| {
                            future::result(test.vm().get_global("std.test.run")).and_then(
                                |action| {
                                    let mut action: OwnedFunction<
                                        fn(OpaqueValue<RootedThread, Test>) -> (),
                                    > = action;
                                    action.call_async(test)
                                },
                            )
                        })
                        .map_err(gluon::Error::from)
                        .map_err(Error::from);
                    GluonTestable(::std::panic::AssertUnwindSafe(future))
                })
            }
            TestCase::Group { name, tests } => tensile::Test::Group {
                name,
                tests: tests.into_iter().map(TestCase::into_tensile_test).collect(),
            },
        }
    }
}

fn make_test<'t>(vm: &'t Thread, name: &str, filename: &Path) -> Result<TestCase, Error> {
    let mut compiler = Compiler::new();

    let mut file = File::open(&filename)?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;
    let (De(test), _) = compiler.run_expr(&vm, &name, &text)?;
    Ok(test)
}

fn run_file<'t>(
    vm: &'t Thread,
    name: &str,
    filename: &Path,
) -> Result<(OpaqueValue<&'t Thread, Hole>, ArcType), Error> {
    let mut compiler = Compiler::new();

    let mut file = File::open(&filename)?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;
    Ok(compiler.run_expr::<OpaqueValue<&Thread, Hole>>(&vm, &name, &text)?)
}

fn gather_doc_tests(expr: &SpannedExpr<Symbol>) -> Vec<(String, String)> {
    use gluon::base::ast::{walk_expr, Visitor};

    fn make_test(comment: &str) -> String {
        let mut parser = pulldown_cmark::Parser::new(comment);

        let mut source = String::new();
        loop {
            let content = match parser.next() {
                Some(pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock(code))) => code,
                None => break,
                _ => continue,
            };
            source.push_str(&content);
            loop {
                match parser.next() {
                    Some(pulldown_cmark::Event::End(pulldown_cmark::Tag::CodeBlock(content))) => {
                        source.push_str(&content);
                        break;
                    }
                    Some(pulldown_cmark::Event::Text(content)) => {
                        source.push_str(&content);
                    }
                    None => break,
                    _ => continue,
                }
            }
        }
        source
    }

    struct DocVisitor(Vec<(String, String)>);
    impl<'a> Visitor<'a> for DocVisitor {
        type Ident = Symbol;

        fn visit_expr(&mut self, expr: &SpannedExpr<Symbol>) {
            match expr.value {
                Expr::LetBindings(ref binds, _) => {
                    for bind in binds {
                        if let Some(ref comment) = bind.metadata.comment {
                            let mut source = make_test(&comment.content);
                            if !source.is_empty() {
                                let name = match bind.name.value {
                                    Pattern::Ident(ref id) => id.name.declared_name(),
                                    _ => "Unknown",
                                };
                                self.0.push((format!("{}", name), String::from(source)));
                            }
                        }
                    }
                }
                Expr::TypeBindings(ref binds, _) => {
                    for bind in binds {
                        if let Some(ref comment) = bind.metadata.comment {
                            let mut source = make_test(&comment.content);
                            if !source.is_empty() {
                                self.0.push((
                                    format!("{}", bind.name.value.declared_name()),
                                    String::from(source),
                                ));
                            }
                        }
                    }
                }
                _ => (),
            }
            walk_expr(self, expr);
        }
    }
    let mut visitor = DocVisitor(Vec::new());

    visitor.visit_expr(expr);

    visitor.0
}

fn run_doc_tests<'t>(
    vm: &'t Thread,
    name: &str,
    filename: &Path,
) -> Result<Vec<tensile::Test<Error>>, Error> {
    let mut compiler = Compiler::new();

    let mut file = File::open(&filename)?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;

    let (expr, _, _) = compiler.extract_metadata(&vm, &name, &text)?;

    let tests = gather_doc_tests(&expr);
    Ok(tests
        .into_iter()
        .map(move |(test_name, test_source)| {
            let vm = vm.new_thread().unwrap();
            tensile::test(test_name.clone(), move || {
                Compiler::new().run_expr::<OpaqueValue<&Thread, Hole>>(
                    &vm,
                    &test_name,
                    &test_source,
                )?;
                Ok(())
            })
        })
        .collect())
}

fn main_() -> Result<(), Error> {
    let _ = ::env_logger::try_init();
    let args: Vec<_> = ::std::env::args().collect();
    let filter = if args.len() > 1 && args.last().unwrap() != "main" {
        args.last()
    } else {
        None
    };

    let file_filter = filter.as_ref().map_or(false, |f| f.starts_with("@"));
    let filter = filter.as_ref().map(|f| f.trim_left_matches('@'));

    let vm = new_vm();
    Compiler::new().load_file(&vm, "std/test.glu")?;

    let iter = test_files("tests/pass")?.into_iter();

    let pool = futures_cpupool::CpuPool::new_num_cpus();
    let mut runtime = tokio::runtime::Runtime::new()?;
    let pass_tests_future = stream::futures_ordered(
        iter.filter_map(|filename| {
            let name = filename_to_module(filename.to_str().unwrap_or("<unknown>"));

            match filter {
                Some(ref filter) if file_filter && !name.contains(&filter[..]) => None,
                _ => Some((filename, name)),
            }
        })
        .map(|(filename, name)| {
            let vm = vm.new_thread().unwrap();

            let name2 = name.clone();
            pool.spawn_fn(move || make_test(&vm, &name, &filename))
                .then(|result| -> Result<_, Error> {
                    Ok(match result {
                        Ok(test) => test.into_tensile_test(),
                        Err(err) => {
                            let err = ::std::panic::AssertUnwindSafe(err);
                            tensile::test(name2, || Err(err.0))
                        }
                    })
                })
        }),
    )
    .collect();
    let pass_tests = runtime.block_on(pass_tests_future)?;

    let iter = test_files("tests/fail")?
        .into_iter()
        .filter(|filename| !filename.to_string_lossy().contains("deps"));

    let fail_tests = iter
        .filter_map(|filename| {
            let name = filename_to_module(filename.to_str().unwrap_or("<unknown>"));

            match filter {
                Some(ref filter) if file_filter && !name.contains(&filter[..]) => None,
                _ => Some((filename, name)),
            }
        })
        .map(|(filename, name)| {
            let vm = vm.new_thread().unwrap();

            tensile::test(name.clone(), move || -> Result<(), Error> {
                match run_file(&vm, &name, &filename) {
                    Ok(err) => Err(format!(
                        "Expected test '{}' to fail\n{:?}",
                        filename.to_str().unwrap(),
                        err.0,
                    )
                    .into()),
                    Err(_) => Ok(()),
                }
            })
        })
        .collect();

    let doc_tests = test_files("std")?
        .into_iter()
        .filter_map(|filename| {
            let name = filename_to_module(filename.to_str().unwrap_or("<unknown>"));

            match filter {
                Some(ref filter) if file_filter && !name.contains(&filter[..]) => None,
                _ => Some((filename, name)),
            }
        })
        .map(|(filename, name)| {
            let vm = vm.new_thread().unwrap();
            match run_doc_tests(&vm, &name, &filename) {
                Ok(tests) => tensile::group(name.clone(), tests),
                Err(err) => {
                    let err = ::std::panic::AssertUnwindSafe(err);
                    tensile::test(name.clone(), || Err(err.0))
                }
            }
        })
        .collect();

    let mut runtime = Runtime::new()?;
    runtime.block_on(future::lazy(|| {
        tensile::console_runner(
            tensile::group(
                "main",
                vec![
                    tensile::group("pass", pass_tests),
                    tensile::group("fail", fail_tests),
                    tensile::group("doc", doc_tests),
                ],
            ),
            &tensile::Options::default().filter(filter.map_or("", |s| &s[..])),
        )
    }))?;
    Ok(())
}
<|endoftext|>


<commit_before>//! The integration suite for inspecting sessions.

extern crate libc;
extern crate rand;
extern crate regex;
extern crate yaml_rust;

mod helpers;

mod open {
    use rand::{random};
    use std::fs::File;
    use std::fs;
    use std::path::PathBuf;
    use std::io::prelude::*;
    use std::env::home_dir;
    use std::str;
    use helpers::*;
    use yaml_rust::YamlLoader;

    fn project_name(contents: &[u8]) -> String {
        let string_content = str::from_utf8(contents).unwrap();
        let yaml = YamlLoader::load_from_str(string_content).unwrap();

        match yaml[0]["name"].as_str() {
            Some(x) => x.to_string(),
            None    => format!("muxed_int_test_{}", random::<u16>())
        }
    }

    fn setup(contents: &[u8]) -> (String, PathBuf) {
        let project_name = project_name(&contents);
        let project_file = format!("/tmp/muxed_{}/{}.yml", random::<u16>(), project_name);
        let project_path = PathBuf::from(&project_file);

        let muxed_path = project_path.parent().unwrap();
        if !muxed_path.exists() { println!("{:?}", fs::create_dir(muxed_path)) };

        let mut buffer = File::create(&project_path).unwrap();
        let _ = buffer.write(contents);

        (project_name, project_path.clone())
    }

    fn cleanup(project_name: &String, config_path: &PathBuf) -> () {
        let _ = fs::remove_file(config_path);
        let _ = fs::remove_dir(config_path.parent().unwrap());
        kill_session(&project_name);
    }

    fn test_with_contents(contents: &[u8]) -> TmuxSession {
        let (project_name, config_path) = setup(contents);
        open_muxed(&project_name, config_path.parent().unwrap());

        let completed = PathBuf::from(format!("/tmp/{}.complete", project_name));
        let exec = format!("touch '{}'", completed.display());

        send_keys(&project_name, &exec);
        wait_on(&completed);

        let session = TmuxSession::from_string(&list_windows(&project_name.to_string()));
        cleanup(&project_name, &config_path);
        session
    }

    #[test]
    fn opens_3_windows_from_array() {
        let contents = b"---
windows: ['ls', 'vi', 'git']
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 3)
    }

    #[test]
    fn opens_2_windows() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['ls', 'vi']
  - stuff: ''
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 2)
    }

    #[test]
    fn opens_3_windows_with_integer_names() {
        let contents = b"---
windows: [1, 'ls', 3]
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 3)
    }

    #[test]
    fn single_window_has_2_panes() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['ls', 'vi']
";
        let session = test_with_contents(contents);
        let num = session.windows.get("editor").unwrap().panes.as_usize().unwrap();
        assert_eq!(num, 2)
    }

    #[test]
    fn multiple_windows_with_panes() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['ls', 'vi']
  - tests:
      layout: 'main-vertical'
      panes: ['ls', 'vi', 'ls']
";
        let session = test_with_contents(contents);
        let num = session.windows.get("editor").unwrap().panes.as_usize().unwrap();
        let num1 = session.windows.get("tests").unwrap().panes.as_usize().unwrap();
        assert_eq!(num, 2);
        assert_eq!(num1, 3)
    }

    #[test]
    fn window_with_empty_command_is_valid() {
        let contents = b"---
windows:
  - editor:
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 1)
    }

    #[test]
    fn panes_with_empty_commands_are_valid() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes:
        -
        -
";
        let session = test_with_contents(contents);
        let num = session.windows.get("editor").unwrap().panes.as_usize().unwrap();
        assert_eq!(num, 2)
    }

    #[test]
    fn expect_to_open_in_directory_containing_spaces() {
        let dir = PathBuf::from("/tmp/Directory With Spaces/");
        if !dir.exists() { println!("{:?}", fs::create_dir(&dir)) };
        let contents = b"---
root: /tmp/Directory With Spaces/
windows:
  - editor: ''
";
        let session = test_with_contents(contents);
        let pane_current_path = session.windows.get("editor").unwrap().pane_current_path.as_str().unwrap();
        let _ = fs::remove_dir(dir);
        // Use contains because OSX on travis ci symlinks /tmp/ to /private/tmp/
        // resulting in `pane_current_path` being `/private/tmp/Direct…`
        assert!(pane_current_path.contains("/tmp/Directory With Spaces"));
    }

    #[test]
    fn expect_home_var_to_open_in_home_dir() {
        let contents = b"---
root: '$HOME'
windows:
  - editor: ''
";
        let session = test_with_contents(contents);
        let pane_current_path = session.windows.get("editor").unwrap().pane_current_path.as_str().unwrap();
        assert_eq!(pane_current_path, home_dir().unwrap().to_str().unwrap());
    }

    #[test]
    fn expect_tilde_slash_to_open_in_home_dir() {
        let contents = b"---
root: ~/
windows:
  - editor: ''
";
        let session = test_with_contents(contents);
        let pane_current_path = session.windows.get("editor").unwrap().pane_current_path.as_str().unwrap();
        assert_eq!(pane_current_path, home_dir().unwrap().to_str().unwrap());
    }

    #[test]
    fn expect_focus_on_the_first_window() {
        let contents = b"---
windows: ['ssh', 'git']
";
        let session = test_with_contents(contents);
        let window_active = session.window_active.as_str().unwrap();
        assert_eq!(window_active, "ssh")
    }

    #[test]
    fn expect_pre_to_create_file() {
        let file = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre: touch {}
windows: ['ssh', 'git']
", file.display());
        let _ = test_with_contents(contents.as_bytes());
        assert!(file.exists());
        let _ = fs::remove_file(file);
    }

    #[test]
    fn expect_pre_to_create_two_files() {
        let file1 = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let file2 = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre:
  - touch {}
  - touch {}
windows: ['ssh', 'git']
", file1.display(), file2.display());
        let _ = test_with_contents(contents.as_bytes());
        assert!(file1.exists());
        assert!(file2.exists());
        let _ = fs::remove_file(file1);
        let _ = fs::remove_file(file2);
    }

    #[test]
    fn expect_pre_window_to_be_called_for_each_window() {
        let file = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre_window: echo 'pre_window' >> {}
windows: ['ssh', 'git']
", file.display());
        let _ = test_with_contents(contents.as_bytes());
        let mut f = File::open(&file).unwrap();
        let mut s = String::new();
        let _ = f.read_to_string(&mut s);
        assert_eq!(s.lines().count(), 2);
        let _ = fs::remove_file(&file);
    }

    #[test]
    fn expect_pre_window_to_be_called_twice_for_each_window() {
        let file = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre_window:
 - echo 'pre_window' >> {}
 - echo 'pre_window' >> {}
windows: ['ssh', 'git']
", file.display(), file.display());
        let _ = test_with_contents(contents.as_bytes());
        let mut f = File::open(&file).unwrap();
        let mut s = String::new();
        let _ = f.read_to_string(&mut s);
        assert_eq!(s.lines().count(), 4);
        let _ = fs::remove_file(&file);
    }

//    #[test]
//    fn expect_session_name_brians_session() {
//        let contents = b"---
//name: 'Brians Session'
//windows: ['ssh', 'git']
//";
//        let session = test_with_contents(contents);
//        let name = session.name.as_str().unwrap();
//        assert_eq!(name, "Brians Session")
//    }

// This test should exist but we currently don't do anything to list panes.
//    #[test]
//    fn expect_focus_on_the_top_pane() {
//        let contents = b"---
//windows:
//  - ssh:
//    layout: main-horizontal
//    panes:
//      - ''
//      - ''
//  - git: ''
//";
//        let session = test_with_contents(contents);
//        assert_eq!(session.pane_active, "ssh.0")
//    }
}
<commit_msg>test(open.rs): Add assertion for new session name<commit_after>//! The integration suite for inspecting sessions.

extern crate libc;
extern crate rand;
extern crate regex;
extern crate yaml_rust;

mod helpers;

mod open {
    use rand::{random};
    use std::fs::File;
    use std::fs;
    use std::path::PathBuf;
    use std::io::prelude::*;
    use std::env::home_dir;
    use std::str;
    use helpers::*;
    use yaml_rust::YamlLoader;

    fn project_name(contents: &[u8]) -> String {
        let string_content = str::from_utf8(contents).unwrap();
        let yaml = YamlLoader::load_from_str(string_content).unwrap();

        match yaml[0]["name"].as_str() {
            Some(x) => x.to_string(),
            None    => format!("muxed_int_test_{}", random::<u16>())
        }
    }

    fn setup(contents: &[u8]) -> (String, PathBuf) {
        let project_name = project_name(&contents);
        let project_file = format!("/tmp/muxed_{}/{}.yml", random::<u16>(), project_name);
        let project_path = PathBuf::from(&project_file);

        let muxed_path = project_path.parent().unwrap();
        if !muxed_path.exists() { println!("{:?}", fs::create_dir(muxed_path)) };

        let mut buffer = File::create(&project_path).unwrap();
        let _ = buffer.write(contents);

        (project_name, project_path.clone())
    }

    fn cleanup(project_name: &String, config_path: &PathBuf) -> () {
        let _ = fs::remove_file(config_path);
        let _ = fs::remove_dir(config_path.parent().unwrap());
        kill_session(&project_name);
    }

    fn test_with_contents(contents: &[u8]) -> TmuxSession {
        let (project_name, config_path) = setup(contents);
        open_muxed(&project_name, config_path.parent().unwrap());

        let completed = PathBuf::from(format!("/tmp/{}.complete", project_name));
        let exec = format!("touch '{}'", completed.display());

        send_keys(&project_name, &exec);
        wait_on(&completed);

        let session = TmuxSession::from_string(&list_windows(&project_name.to_string()));
        cleanup(&project_name, &config_path);
        session
    }

    #[test]
    fn opens_3_windows_from_array() {
        let contents = b"---
windows: ['ls', 'vi', 'git']
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 3)
    }

    #[test]
    fn opens_2_windows() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['ls', 'vi']
  - stuff: ''
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 2)
    }

    #[test]
    fn opens_3_windows_with_integer_names() {
        let contents = b"---
windows: [1, 'ls', 3]
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 3)
    }

    #[test]
    fn single_window_has_2_panes() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['ls', 'vi']
";
        let session = test_with_contents(contents);
        let num = session.windows.get("editor").unwrap().panes.as_usize().unwrap();
        assert_eq!(num, 2)
    }

    #[test]
    fn multiple_windows_with_panes() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['ls', 'vi']
  - tests:
      layout: 'main-vertical'
      panes: ['ls', 'vi', 'ls']
";
        let session = test_with_contents(contents);
        let num = session.windows.get("editor").unwrap().panes.as_usize().unwrap();
        let num1 = session.windows.get("tests").unwrap().panes.as_usize().unwrap();
        assert_eq!(num, 2);
        assert_eq!(num1, 3)
    }

    #[test]
    fn window_with_empty_command_is_valid() {
        let contents = b"---
windows:
  - editor:
";
        let session = test_with_contents(contents);
        assert_eq!(session.num_of_windows, 1)
    }

    #[test]
    fn panes_with_empty_commands_are_valid() {
        let contents = b"---
windows:
  - editor:
      layout: 'main-vertical'
      panes:
        -
        -
";
        let session = test_with_contents(contents);
        let num = session.windows.get("editor").unwrap().panes.as_usize().unwrap();
        assert_eq!(num, 2)
    }

    #[test]
    fn expect_to_open_in_directory_containing_spaces() {
        let dir = PathBuf::from("/tmp/Directory With Spaces/");
        if !dir.exists() { println!("{:?}", fs::create_dir(&dir)) };
        let contents = b"---
root: /tmp/Directory With Spaces/
windows:
  - editor: ''
";
        let session = test_with_contents(contents);
        let pane_current_path = session.windows.get("editor").unwrap().pane_current_path.as_str().unwrap();
        let _ = fs::remove_dir(dir);
        // Use contains because OSX on travis ci symlinks /tmp/ to /private/tmp/
        // resulting in `pane_current_path` being `/private/tmp/Direct…`
        assert!(pane_current_path.contains("/tmp/Directory With Spaces"));
    }

    #[test]
    fn expect_home_var_to_open_in_home_dir() {
        let contents = b"---
root: '$HOME'
windows:
  - editor: ''
";
        let session = test_with_contents(contents);
        let pane_current_path = session.windows.get("editor").unwrap().pane_current_path.as_str().unwrap();
        assert_eq!(pane_current_path, home_dir().unwrap().to_str().unwrap());
    }

    #[test]
    fn expect_tilde_slash_to_open_in_home_dir() {
        let contents = b"---
root: ~/
windows:
  - editor: ''
";
        let session = test_with_contents(contents);
        let pane_current_path = session.windows.get("editor").unwrap().pane_current_path.as_str().unwrap();
        assert_eq!(pane_current_path, home_dir().unwrap().to_str().unwrap());
    }

    #[test]
    fn expect_focus_on_the_first_window() {
        let contents = b"---
windows: ['ssh', 'git']
";
        let session = test_with_contents(contents);
        let window_active = session.window_active.as_str().unwrap();
        assert_eq!(window_active, "ssh")
    }

    #[test]
    fn expect_pre_to_create_file() {
        let file = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre: touch {}
windows: ['ssh', 'git']
", file.display());
        let _ = test_with_contents(contents.as_bytes());
        assert!(file.exists());
        let _ = fs::remove_file(file);
    }

    #[test]
    fn expect_pre_to_create_two_files() {
        let file1 = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let file2 = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre:
  - touch {}
  - touch {}
windows: ['ssh', 'git']
", file1.display(), file2.display());
        let _ = test_with_contents(contents.as_bytes());
        assert!(file1.exists());
        assert!(file2.exists());
        let _ = fs::remove_file(file1);
        let _ = fs::remove_file(file2);
    }

    #[test]
    fn expect_pre_window_to_be_called_for_each_window() {
        let file = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre_window: echo 'pre_window' >> {}
windows: ['ssh', 'git']
", file.display());
        let _ = test_with_contents(contents.as_bytes());
        let mut f = File::open(&file).unwrap();
        let mut s = String::new();
        let _ = f.read_to_string(&mut s);
        assert_eq!(s.lines().count(), 2);
        let _ = fs::remove_file(&file);
    }

    #[test]
    fn expect_pre_window_to_be_called_twice_for_each_window() {
        let file = PathBuf::from(format!("/tmp/{}", random::<u16>()));
        let contents = format!("---
pre_window:
 - echo 'pre_window' >> {}
 - echo 'pre_window' >> {}
windows: ['ssh', 'git']
", file.display(), file.display());
        let _ = test_with_contents(contents.as_bytes());
        let mut f = File::open(&file).unwrap();
        let mut s = String::new();
        let _ = f.read_to_string(&mut s);
        assert_eq!(s.lines().count(), 4);
        let _ = fs::remove_file(&file);
    }

    #[test]
    fn expect_session_name_brians_session() {
        let contents = b"---
name: 'Brians Session'
windows: ['ssh', 'git']
";
        let session = test_with_contents(contents);
        let name = session.name.as_str().unwrap();
        assert_eq!(name, "Brians Session")
    }

// This test should exist but we currently don't do anything to list panes.
//    #[test]
//    fn expect_focus_on_the_top_pane() {
//        let contents = b"---
//windows:
//  - ssh:
//    layout: main-horizontal
//    panes:
//      - ''
//      - ''
//  - git: ''
//";
//        let session = test_with_contents(contents);
//        assert_eq!(session.pane_active, "ssh.0")
//    }
}
<|endoftext|>


<commit_before>use error::*;

use buffer::Buffered;
use std::io::SeekFrom;
use std::any::Any;
use std::sync::Arc;

use common::*;

use stream::Stream;
use data::packet::Packet;

#[derive(Clone, Debug)]
pub enum Event {
    NewPacket(Packet),
    NewStream(Stream),
    MoreDataNeeded(usize),
}

pub trait Demuxer : Send {
    fn read_headers(&mut self, buf: &Box<Buffered>, info: &mut GlobalInfo) -> Result<SeekFrom>;
    fn read_event(&mut self, buf: &Box<Buffered>) -> Result<(SeekFrom, Event)>;
}

#[derive(Clone, Debug, PartialEq)]
pub struct Descr {
    pub name: &'static str,
    pub demuxer: &'static str,
    pub description: &'static str,
    pub extensions: &'static [&'static str],
    pub mime: &'static [&'static str],
}

pub trait Descriptor {
    fn create(&self) -> Box<Demuxer>;
    fn describe<'a>(&'a self) -> &'a Descr;

    fn probe(&self, data: &[u8]) -> u8;
}

pub struct Context {
    demuxer: Box<Demuxer>,
    reader: Box<Buffered>,
    pub info: GlobalInfo,
    pub user_private: Option<Arc<Any + Send + Sync>>,
}

impl Context {
    pub fn new<R: Buffered + 'static>(demuxer: Box<Demuxer>, reader: Box<R>) -> Self {
        Context {
            demuxer: demuxer,
            reader: reader,
            info: GlobalInfo {
                duration: None,
                timebase: None,
                streams: Vec::with_capacity(2),
            },
            user_private: None,
        }
    }

    fn read_headers_internal(&mut self) -> Result<()> {
        let ref mut demux = self.demuxer;
        let res = demux.read_headers(&self.reader, &mut self.info);
        match res {
            Err(e) => Err(e),
            Ok(seek) => {
                //TODO: handle seeking here
                let res = self.reader.seek(seek);
                println!("stream now at index: {:?}", res);
                Ok(())
            }
        }
    }

    pub fn read_headers(&mut self) -> Result<()> {
        loop {
            // TODO: wrap fill_buf() with a check for Eof
            self.reader.fill_buf()?;
            match self.read_headers_internal() {
                Err(e) => match e {
                    Error::MoreDataNeeded(needed) => {
                        self.reader.grow(needed);
                    },
                    _ => return Err(e)
                },
                Ok(_) => return Ok(()),
            }
        }
    }

    fn read_event_internal(&mut self) -> Result<Event> {
        let ref mut demux = self.demuxer;

        let res = demux.read_event(&self.reader);
        match res {
            Err(e) => Err(e),
            Ok((seek, mut event)) => {
                //TODO: handle seeking here
                let _ = self.reader.seek(seek)?;
                if let Event::NewStream(ref st) = event {
                    self.info.streams.push(st.clone());
                }
                if let Event::MoreDataNeeded(size) = event {
                    return Err(Error::MoreDataNeeded(size));
                }
                if let Event::NewPacket(ref mut pkt) = event {
                    if pkt.t.timebase.is_none() {
                        if let Some(ref st) = self.info.streams.iter().find(|s| {
                            s.index as isize == pkt.stream_index
                        }) {
                            pkt.t.timebase = Some(st.timebase);
                        }
                    }
                }
                Ok(event)
            }
        }
    }

    pub fn read_event(&mut self) -> Result<Event> {
        use std::io::ErrorKind as Eio;
        // TODO: guard against infiniloops and maybe factor the loop.
        loop {
            match self.read_event_internal() {
                Err(e) => match e {
                    Error::MoreDataNeeded(needed) => {
                        let len = self.reader.data().len();
                        self.reader.grow(needed);
                        self.reader.fill_buf()?;
                        if self.reader.data().len() <= len {
                            return Err(Error::Io(Eio::UnexpectedEof.into()));
                        }
                    },
                    _ => return Err(e)
                },
                Ok(ev) => return Ok(ev),
            }
        }
    }
}

pub const PROBE_DATA: usize = 4 * 1024;
pub const PROBE_SCORE_EXTENSION: u8 = 50;

// TODO:
// IntoIterator<Item = &'static Descriptor> is confusing

pub trait Probe {
    fn probe(&self, data: &[u8]) -> Option<&'static Descriptor>;
}

impl<'a> Probe for [&'static Descriptor] {
    fn probe(&self, data: &[u8]) -> Option<&'static Descriptor> {
        let mut max = u8::min_value();
        let mut candidate: Option<&'static Descriptor> = None;
        for desc in self {
            let score = desc.probe(data);

            if score > max {
                max = score;
                candidate = Some(*desc);
            }
        }

        if max > PROBE_SCORE_EXTENSION {
            candidate
        } else {
            None
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::SeekFrom;
    use data::packet::Packet;

    struct DummyDes {
        d: Descr,
    }

    struct DummyDemuxer {}

    impl Demuxer for DummyDemuxer {
        fn read_headers(&mut self, buf: &Box<Buffered>, _info: &mut GlobalInfo) -> Result<SeekFrom> {
            let len = buf.data().len();
            if 9 > len {
                let needed = 9 - len;
                Err(Error::MoreDataNeeded(needed))
            } else {
                Ok(SeekFrom::Current(9))
            }
        }
        fn read_event(&mut self, buf: &Box<Buffered>) -> Result<(SeekFrom, Event)> {
            let size = 2;
            let len = buf.data().len();
            if size > len {
                Err(Error::MoreDataNeeded(size - len))
            } else {
                println!("{:?}", buf.data());
                match &buf.data()[..2] {
                    b"p1" => {
                        Ok((SeekFrom::Current(3), Event::NewPacket(Packet::new())))
                    },
                    b"e1" => {
                        Ok((SeekFrom::Current(3), Event::MoreDataNeeded(0)))
                    }
                    _ => Err(Error::InvalidData.into())
                }
            }
        }
    }

    impl Descriptor for DummyDes {
        fn create(&self) -> Box<Demuxer> {
            Box::new(DummyDemuxer {})
        }
        fn describe<'a>(&'a self) -> &'a Descr {
            &self.d
        }
        fn probe(&self, data: &[u8]) -> u8 {
            match data {
                b"dummy" => 100,
                _ => 0,
            }
        }
    }

    const DUMMY_DES: &Descriptor = &DummyDes {
        d: Descr {
            name: "dummy",
            demuxer: "dummy",
            description: "Dummy dem",
            extensions: &["dm", "dum"],
            mime: &["application/dummy"],
        },
    };

    #[test]
    fn probe() {
        let demuxers: &[&'static Descriptor] = &[DUMMY_DES];

        demuxers.probe(b"dummy").unwrap();
    }

    use std::io::Cursor;
    use buffer::*;

    #[test]
    fn read_headers() {
        let buf = b"dummy header";
        let r = AccReader::with_capacity(4, Cursor::new(buf));
        let d = DUMMY_DES.create();
        let mut c = Context::new(d, Box::new(r));

        c.read_headers().unwrap();
    }

    #[test]
    fn read_event() {
        let buf = b"dummy header p1 e1 p1 ";

        let r = AccReader::with_capacity(4, Cursor::new(buf));
        let d = DUMMY_DES.create();
        let mut c = Context::new(d, Box::new(r));

        c.read_headers().unwrap();

        println!("{:?}", c.read_event());
        println!("{:?}", c.read_event());
        println!("{:?}", c.read_event());
        println!("{:?}", c.read_event());
    }
}
<commit_msg>add an Eof event to the demuxer<commit_after>use error::*;

use buffer::Buffered;
use std::io::SeekFrom;
use std::any::Any;
use std::sync::Arc;

use common::*;

use stream::Stream;
use data::packet::Packet;

#[derive(Clone, Debug)]
pub enum Event {
    NewPacket(Packet),
    NewStream(Stream),
    MoreDataNeeded(usize),
    Eof,
}

pub trait Demuxer : Send {
    fn read_headers(&mut self, buf: &Box<Buffered>, info: &mut GlobalInfo) -> Result<SeekFrom>;
    fn read_event(&mut self, buf: &Box<Buffered>) -> Result<(SeekFrom, Event)>;
}

#[derive(Clone, Debug, PartialEq)]
pub struct Descr {
    pub name: &'static str,
    pub demuxer: &'static str,
    pub description: &'static str,
    pub extensions: &'static [&'static str],
    pub mime: &'static [&'static str],
}

pub trait Descriptor {
    fn create(&self) -> Box<Demuxer>;
    fn describe<'a>(&'a self) -> &'a Descr;

    fn probe(&self, data: &[u8]) -> u8;
}

pub struct Context {
    demuxer: Box<Demuxer>,
    reader: Box<Buffered>,
    pub info: GlobalInfo,
    pub user_private: Option<Arc<Any + Send + Sync>>,
}

impl Context {
    pub fn new<R: Buffered + 'static>(demuxer: Box<Demuxer>, reader: Box<R>) -> Self {
        Context {
            demuxer: demuxer,
            reader: reader,
            info: GlobalInfo {
                duration: None,
                timebase: None,
                streams: Vec::with_capacity(2),
            },
            user_private: None,
        }
    }

    fn read_headers_internal(&mut self) -> Result<()> {
        let ref mut demux = self.demuxer;
        let res = demux.read_headers(&self.reader, &mut self.info);
        match res {
            Err(e) => Err(e),
            Ok(seek) => {
                //TODO: handle seeking here
                let res = self.reader.seek(seek);
                println!("stream now at index: {:?}", res);
                Ok(())
            }
        }
    }

    pub fn read_headers(&mut self) -> Result<()> {
        loop {
            // TODO: wrap fill_buf() with a check for Eof
            self.reader.fill_buf()?;
            match self.read_headers_internal() {
                Err(e) => match e {
                    Error::MoreDataNeeded(needed) => {
                        self.reader.grow(needed);
                    },
                    _ => return Err(e)
                },
                Ok(_) => return Ok(()),
            }
        }
    }

    fn read_event_internal(&mut self) -> Result<Event> {
        let ref mut demux = self.demuxer;

        let res = demux.read_event(&self.reader);
        match res {
            Err(e) => Err(e),
            Ok((seek, mut event)) => {
                //TODO: handle seeking here
                let _ = self.reader.seek(seek)?;
                if let Event::NewStream(ref st) = event {
                    self.info.streams.push(st.clone());
                }
                if let Event::MoreDataNeeded(size) = event {
                    return Err(Error::MoreDataNeeded(size));
                }
                if let Event::NewPacket(ref mut pkt) = event {
                    if pkt.t.timebase.is_none() {
                        if let Some(ref st) = self.info.streams.iter().find(|s| {
                            s.index as isize == pkt.stream_index
                        }) {
                            pkt.t.timebase = Some(st.timebase);
                        }
                    }
                }
                Ok(event)
            }
        }
    }

    pub fn read_event(&mut self) -> Result<Event> {
        use std::io::ErrorKind as Eio;
        // TODO: guard against infiniloops and maybe factor the loop.
        loop {
            match self.read_event_internal() {
                Err(e) => match e {
                    Error::MoreDataNeeded(needed) => {
                        let len = self.reader.data().len();
                        self.reader.grow(needed);
                        self.reader.fill_buf()?;
                        if self.reader.data().len() <= len {
                            return Err(Error::Io(Eio::UnexpectedEof.into()));
                        }
                    },
                    _ => return Err(e)
                },
                Ok(ev) => return Ok(ev),
            }
        }
    }
}

pub const PROBE_DATA: usize = 4 * 1024;
pub const PROBE_SCORE_EXTENSION: u8 = 50;

// TODO:
// IntoIterator<Item = &'static Descriptor> is confusing

pub trait Probe {
    fn probe(&self, data: &[u8]) -> Option<&'static Descriptor>;
}

impl<'a> Probe for [&'static Descriptor] {
    fn probe(&self, data: &[u8]) -> Option<&'static Descriptor> {
        let mut max = u8::min_value();
        let mut candidate: Option<&'static Descriptor> = None;
        for desc in self {
            let score = desc.probe(data);

            if score > max {
                max = score;
                candidate = Some(*desc);
            }
        }

        if max > PROBE_SCORE_EXTENSION {
            candidate
        } else {
            None
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::SeekFrom;
    use data::packet::Packet;

    struct DummyDes {
        d: Descr,
    }

    struct DummyDemuxer {}

    impl Demuxer for DummyDemuxer {
        fn read_headers(&mut self, buf: &Box<Buffered>, _info: &mut GlobalInfo) -> Result<SeekFrom> {
            let len = buf.data().len();
            if 9 > len {
                let needed = 9 - len;
                Err(Error::MoreDataNeeded(needed))
            } else {
                Ok(SeekFrom::Current(9))
            }
        }
        fn read_event(&mut self, buf: &Box<Buffered>) -> Result<(SeekFrom, Event)> {
            let size = 2;
            let len = buf.data().len();
            if size > len {
                Err(Error::MoreDataNeeded(size - len))
            } else {
                println!("{:?}", buf.data());
                match &buf.data()[..2] {
                    b"p1" => {
                        Ok((SeekFrom::Current(3), Event::NewPacket(Packet::new())))
                    },
                    b"e1" => {
                        Ok((SeekFrom::Current(3), Event::MoreDataNeeded(0)))
                    }
                    _ => Err(Error::InvalidData.into())
                }
            }
        }
    }

    impl Descriptor for DummyDes {
        fn create(&self) -> Box<Demuxer> {
            Box::new(DummyDemuxer {})
        }
        fn describe<'a>(&'a self) -> &'a Descr {
            &self.d
        }
        fn probe(&self, data: &[u8]) -> u8 {
            match data {
                b"dummy" => 100,
                _ => 0,
            }
        }
    }

    const DUMMY_DES: &Descriptor = &DummyDes {
        d: Descr {
            name: "dummy",
            demuxer: "dummy",
            description: "Dummy dem",
            extensions: &["dm", "dum"],
            mime: &["application/dummy"],
        },
    };

    #[test]
    fn probe() {
        let demuxers: &[&'static Descriptor] = &[DUMMY_DES];

        demuxers.probe(b"dummy").unwrap();
    }

    use std::io::Cursor;
    use buffer::*;

    #[test]
    fn read_headers() {
        let buf = b"dummy header";
        let r = AccReader::with_capacity(4, Cursor::new(buf));
        let d = DUMMY_DES.create();
        let mut c = Context::new(d, Box::new(r));

        c.read_headers().unwrap();
    }

    #[test]
    fn read_event() {
        let buf = b"dummy header p1 e1 p1 ";

        let r = AccReader::with_capacity(4, Cursor::new(buf));
        let d = DUMMY_DES.create();
        let mut c = Context::new(d, Box::new(r));

        c.read_headers().unwrap();

        println!("{:?}", c.read_event());
        println!("{:?}", c.read_event());
        println!("{:?}", c.read_event());
        println!("{:?}", c.read_event());
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

struct Test {
    repo: &'static str,
    name: &'static str,
    sha: &'static str,
    lock: Option<&'static str>,
    packages: &'static [&'static str],
}

const TEST_REPOS: &[Test] = &[
    Test {
        name: "iron",
        repo: "https://github.com/iron/iron",
        sha: "cf056ea5e8052c1feea6141e40ab0306715a2c33",
        lock: None,
        packages: &[],
    },
    Test {
        name: "ripgrep",
        repo: "https://github.com/BurntSushi/ripgrep",
        sha: "3de31f752729525d85a3d1575ac1978733b3f7e7",
        lock: None,
        packages: &[],
    },
    Test {
        name: "tokei",
        repo: "https://github.com/XAMPPRocky/tokei",
        sha: "fdf3f8cb279a7aeac0696c87e5d8b0cd946e4f9e",
        lock: None,
        packages: &[],
    },
    Test {
        name: "xsv",
        repo: "https://github.com/BurntSushi/xsv",
        sha: "66956b6bfd62d6ac767a6b6499c982eae20a2c9f",
        lock: None,
        packages: &[],
    },
    Test {
        name: "servo",
        repo: "https://github.com/servo/servo",
        sha: "caac107ae8145ef2fd20365e2b8fadaf09c2eb3b",
        lock: None,
        // Only test Stylo a.k.a. Quantum CSS, the parts of Servo going into Firefox.
        // This takes much less time to build than all of Servo and supports stable Rust.
        packages: &["selectors"],
    },
];

fn main() {
    let args = env::args().collect::<Vec<_>>();
    let cargo = &args[1];
    let out_dir = Path::new(&args[2]);
    let cargo = &Path::new(cargo);

    for test in TEST_REPOS.iter().rev() {
        test_repo(cargo, out_dir, test);
    }
}

fn test_repo(cargo: &Path, out_dir: &Path, test: &Test) {
    println!("testing {}", test.repo);
    let dir = clone_repo(test, out_dir);
    if let Some(lockfile) = test.lock {
        fs::write(&dir.join("Cargo.lock"), lockfile).unwrap();
    }
    if !run_cargo_test(cargo, &dir, test.packages) {
        panic!("tests failed for {}", test.repo);
    }
}

fn clone_repo(test: &Test, out_dir: &Path) -> PathBuf {
    let out_dir = out_dir.join(test.name);

    if !out_dir.join(".git").is_dir() {
        let status = Command::new("git").arg("init").arg(&out_dir).status().unwrap();
        assert!(status.success());
    }

    // Try progressively deeper fetch depths to find the commit
    let mut found = false;
    for depth in &[0, 1, 10, 100, 1000, 100000] {
        if *depth > 0 {
            let status = Command::new("git")
                .arg("fetch")
                .arg(test.repo)
                .arg("master")
                .arg(&format!("--depth={}", depth))
                .current_dir(&out_dir)
                .status()
                .unwrap();
            assert!(status.success());
        }

        let status = Command::new("git")
            .arg("reset")
            .arg(test.sha)
            .arg("--hard")
            .current_dir(&out_dir)
            .status()
            .unwrap();

        if status.success() {
            found = true;
            break;
        }
    }

    if !found {
        panic!("unable to find commit {}", test.sha)
    }
    let status =
        Command::new("git").arg("clean").arg("-fdx").current_dir(&out_dir).status().unwrap();
    assert!(status.success());

    out_dir
}

fn run_cargo_test(cargo_path: &Path, crate_path: &Path, packages: &[&str]) -> bool {
    let mut command = Command::new(cargo_path);
    command.arg("test");
    for name in packages {
        command.arg("-p").arg(name);
    }
    let status = command
        // Disable rust-lang/cargo's cross-compile tests
        .env("CFG_DISABLE_CROSS_TESTS", "1")
        // Relax #![deny(warnings)] in some crates
        .env("RUSTFLAGS", "--cap-lints warn")
        .current_dir(crate_path)
        .status()
        .unwrap();

    status.success()
}
<commit_msg>Rollup merge of #79803 - jyn514:xsv, r=Mark-Simulacrum<commit_after>use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

struct Test {
    repo: &'static str,
    name: &'static str,
    sha: &'static str,
    lock: Option<&'static str>,
    packages: &'static [&'static str],
}

const TEST_REPOS: &[Test] = &[
    Test {
        name: "iron",
        repo: "https://github.com/iron/iron",
        sha: "cf056ea5e8052c1feea6141e40ab0306715a2c33",
        lock: None,
        packages: &[],
    },
    Test {
        name: "ripgrep",
        repo: "https://github.com/BurntSushi/ripgrep",
        sha: "3de31f752729525d85a3d1575ac1978733b3f7e7",
        lock: None,
        packages: &[],
    },
    Test {
        name: "tokei",
        repo: "https://github.com/XAMPPRocky/tokei",
        sha: "fdf3f8cb279a7aeac0696c87e5d8b0cd946e4f9e",
        lock: None,
        packages: &[],
    },
    Test {
        name: "xsv",
        repo: "https://github.com/BurntSushi/xsv",
        sha: "3de6c04269a7d315f7e9864b9013451cd9580a08",
        lock: None,
        packages: &[],
    },
    Test {
        name: "servo",
        repo: "https://github.com/servo/servo",
        sha: "caac107ae8145ef2fd20365e2b8fadaf09c2eb3b",
        lock: None,
        // Only test Stylo a.k.a. Quantum CSS, the parts of Servo going into Firefox.
        // This takes much less time to build than all of Servo and supports stable Rust.
        packages: &["selectors"],
    },
];

fn main() {
    let args = env::args().collect::<Vec<_>>();
    let cargo = &args[1];
    let out_dir = Path::new(&args[2]);
    let cargo = &Path::new(cargo);

    for test in TEST_REPOS.iter().rev() {
        test_repo(cargo, out_dir, test);
    }
}

fn test_repo(cargo: &Path, out_dir: &Path, test: &Test) {
    println!("testing {}", test.repo);
    let dir = clone_repo(test, out_dir);
    if let Some(lockfile) = test.lock {
        fs::write(&dir.join("Cargo.lock"), lockfile).unwrap();
    }
    if !run_cargo_test(cargo, &dir, test.packages) {
        panic!("tests failed for {}", test.repo);
    }
}

fn clone_repo(test: &Test, out_dir: &Path) -> PathBuf {
    let out_dir = out_dir.join(test.name);

    if !out_dir.join(".git").is_dir() {
        let status = Command::new("git").arg("init").arg(&out_dir).status().unwrap();
        assert!(status.success());
    }

    // Try progressively deeper fetch depths to find the commit
    let mut found = false;
    for depth in &[0, 1, 10, 100, 1000, 100000] {
        if *depth > 0 {
            let status = Command::new("git")
                .arg("fetch")
                .arg(test.repo)
                .arg("master")
                .arg(&format!("--depth={}", depth))
                .current_dir(&out_dir)
                .status()
                .unwrap();
            assert!(status.success());
        }

        let status = Command::new("git")
            .arg("reset")
            .arg(test.sha)
            .arg("--hard")
            .current_dir(&out_dir)
            .status()
            .unwrap();

        if status.success() {
            found = true;
            break;
        }
    }

    if !found {
        panic!("unable to find commit {}", test.sha)
    }
    let status =
        Command::new("git").arg("clean").arg("-fdx").current_dir(&out_dir).status().unwrap();
    assert!(status.success());

    out_dir
}

fn run_cargo_test(cargo_path: &Path, crate_path: &Path, packages: &[&str]) -> bool {
    let mut command = Command::new(cargo_path);
    command.arg("test");
    for name in packages {
        command.arg("-p").arg(name);
    }
    let status = command
        // Disable rust-lang/cargo's cross-compile tests
        .env("CFG_DISABLE_CROSS_TESTS", "1")
        // Relax #![deny(warnings)] in some crates
        .env("RUSTFLAGS", "--cap-lints warn")
        .current_dir(crate_path)
        .status()
        .unwrap();

    status.success()
}
<|endoftext|>