<commit_before>extern crate mmap;
extern crate time;
extern crate timely;
extern crate docopt;

use docopt::Docopt;

use std::fs::File;
use std::thread;
use std::io::{Read, BufRead, BufReader};

use timely::progress::timestamp::RootTimestamp;
use timely::progress::scope::Scope;
use timely::progress::nested::Summary::Local;
use timely::example_shared::*;
use timely::example_shared::operators::*;
use timely::communication::*;
use timely::communication::pact::Exchange;

use timely::networking::initialize_networking;

use timely::drain::DrainExt;

mod typedrw;
mod graphmap;
use graphmap::GraphMMap;

mod sorting;
use sorting::{SegmentList, radix_sort_32};

static USAGE: &'static str = "
Usage: pagerank <source> [options] [<arguments>...]

Options:
    -w <arg>, --workers <arg>    number of workers per process [default: 1]
    -p <arg>, --processid <arg>  identity of this process      [default: 0]
    -n <arg>, --processes <arg>  number of processes involved  [default: 1]
    -h <arg>, --hosts <arg>      file containing list of host:port for workers
";

fn main () {
    let args = Docopt::new(USAGE).and_then(|dopt| dopt.parse()).unwrap_or_else(|e| e.exit());

    let source = args.get_str("<source>").to_owned();

    let workers: u64 = if let Ok(threads) = args.get_str("-w").parse() { threads }
                       else { panic!("invalid setting for --workers: {}", args.get_str("-t")) };
    let process_id: u64 = if let Ok(proc_id) = args.get_str("-p").parse() { proc_id }
                          else { panic!("invalid setting for --processid: {}", args.get_str("-p")) };
    let processes: u64 = if let Ok(processes) = args.get_str("-n").parse() { processes }
                         else { panic!("invalid setting for --processes: {}", args.get_str("-n")) };

    println!("Starting pagerank dataflow with");
    println!("\tworkers:\t{}", workers);
    println!("\tprocesses:\t{}", processes);
    println!("\tprocessid:\t{}", process_id);

    // vector holding communicators to use; one per local worker.
    if processes > 1 {
        println!("Initializing BinaryCommunicator");

        let hosts = args.get_str("-h");
        let addresses: Vec<_> = if hosts != "" {
            let reader = BufReader::new(File::open(hosts).unwrap());
            reader.lines().take(processes as usize).map(|x| x.unwrap()).collect()
        }
        else {
            (0..processes).map(|index| format!("localhost:{}", 2101 + index).to_string()).collect()
        };

        if addresses.len() != processes as usize { panic!("only {} hosts for -p: {}", addresses.len(), processes); }

        let communicators = initialize_networking(addresses, process_id, workers).ok().expect("error initializing networking");

        pagerank_spawn(communicators, source);
    }
    else if workers > 1 { pagerank_spawn(ProcessCommunicator::new_vector(workers), source); }
    else { pagerank_spawn(vec![ThreadCommunicator], source); };
}

fn pagerank_spawn<C>(communicators: Vec<C>, filename: String)
where C: Communicator+Send {
    let mut guards = Vec::new();
    let workers = communicators.len();
    for communicator in communicators.into_iter() {
        let filename = filename.clone();
        guards.push(thread::Builder::new().name(format!("timely worker {}", communicator.index()))
                                          .spawn(move || pagerank_thread(communicator, filename, workers))
                                          .unwrap());
    }

    for guard in guards { guard.join().unwrap(); }
}

// returns [src/peers] degrees, (dst, deg) pairs, and a list of [src/peers] endpoints
fn transpose(mut edges: Vec<Vec<(u32, u32)>>, peers: usize, nodes: usize) -> (Vec<u32>, Vec<(u32, u32)>, Vec<u32>)  {

    let mut deg = vec![0; (nodes as usize / peers) + 1];
    for list in &edges {
        for &(s, _) in list {
            deg[s as usize / peers] += 1;
        }
    }

    radix_sort_32(&mut edges, &mut Vec::new(), &|&(_,d)| d);

    let mut rev = Vec::<(u32,u32)>::with_capacity(deg.len());
    let mut trn = Vec::with_capacity(edges.len() * 1024);
    for list in edges {
        for (s,d) in list {
            let len = rev.len();
            if (len == 0) || (rev[len-1].0 < d) {
                rev.push((d, 0u32));
            }

            let len = rev.len();
            rev[len-1].1 += 1;
            trn.push(s / peers as u32);
        }
    }

    return (deg, rev, trn);
}

// pagerank dataflow graph has a set of edges as input, and a binary vertex that for each epoch of
// received edges initiates an iterative subcomputation to compute the pagerank.

fn pagerank_thread<C>(communicator: C, filename: String, _workers: usize)
where C: Communicator {
    let index = communicator.index() as usize;
    let peers = communicator.peers() as usize;

    let mut root = GraphRoot::new(communicator);

    let start = time::precise_time_s();

    let nodes = GraphMMap::new(&filename).nodes();

    let mut segments = SegmentList::new(1024); // list of edge segments

    let mut src = vec![];   // holds ranks
    let mut deg = vec![];   // holds source degrees
    let mut rev = vec![];   // holds (dst, deg) pairs
    let mut trn = vec![];   // holds transposed sources

    let mut going = start;

    let mut input = root.subcomputation(|builder| {

        let (input, edges) = builder.new_input::<(u32, u32)>();
        let (cycle, ranks) = builder.loop_variable::<(u32, f32)>(RootTimestamp::new(20), Local(1));

        let ranks = edges.binary_notify(&ranks,
                            Exchange::new(|x: &(u32,u32)| x.0 as u64),
                            Exchange::new(|x: &(u32,f32)| x.0 as u64),
                            format!("pagerank"),
                            vec![RootTimestamp::new(0)],
                            move |input1, input2, output, notificator| {

            // receive incoming edges (should only be iter 0)
            while let Some((_index, data)) = input1.pull() {
                segments.push(data.drain_temp());
            }

            // all inputs received for iter, commence multiplication
            while let Some((iter, _)) = notificator.next() {

                let now = time::now();

                if index == 0 { println!("{}:{}:{}.{} starting iteration {}", now.tm_hour, now.tm_min, now.tm_sec, now.tm_nsec, iter.inner); }

                // if the very first iteration, prepare some stuff.
                // specifically, transpose edges and sort by destination.
                if iter.inner == 0 {
                    let (a, b, c) = transpose(segments.finalize(), peers, nodes);
                    deg = a; rev = b; trn = c;
                    src = vec![0.0f32; deg.len()];
                }

                // record some timings in order to estimate per-iteration times
                if iter.inner == 0  && index == 0 { println!("src: {}, dst: {}, edges: {}", src.len(), rev.len(), trn.len()); }
                if iter.inner == 10 && index == 0 { going = time::precise_time_s(); }
                if iter.inner == 20 && index == 0 { println!("average: {}", (time::precise_time_s() - going) / 10.0 ); }

                // prepare src for transmitting to destinations
                for s in 0..src.len() { src[s] = 0.15 + 0.85 * src[s] / deg[s] as f32; }

                // wander through destinations
                let mut trn_slice = &trn[..];
                let mut rev_slice = &rev[..];
                while rev_slice.len() > 0 {
                    // TODO: session should flush automatically...
                    let mut session = output.session(&iter);
                    let next = std::cmp::min(200_000, rev_slice.len());
                    for &(dst, deg) in &rev_slice[..next] {
                        let mut accum = 0.0;
                        for &s in &trn_slice[..deg as usize] {
                            accum += src[s as usize];
                        }
                        trn_slice = &trn_slice[deg as usize..];
                        session.give((dst, accum));
                    }
                    rev_slice = &rev_slice[next..];
                }

                // clean out src to accumulate into
                for s in &mut src { *s = 0.0; }
            }

            // receive data from workers, accumulate in src
            while let Some((iter, data)) = input2.pull() {
                notificator.notify_at(&iter);
                for x in data.drain_temp() {
                    src[x.0 as usize / peers] += x.1;
                }
            }
        });

        // // optionally, do process-local accumulation
        // let local_base = _workers * (index / _workers);
        // let local_index = index % _workers;
        // let mut acc = vec![0.0; (nodes / _workers) + 1];   // holds ranks
        // let ranks = ranks.unary_notify(
        //     Exchange::new(move |x: &(u32,f32)| (local_base as u64 + (x.0 as u64 % _workers as u64))),
        //     format!("Aggregation"),
        //     vec![],
        //     move |input, output, iterator| {
        //         while let Some((iter, data)) = input.pull() {
        //             iterator.notify_at(&iter);
        //             for x in data.drain_temp() {
        //                 acc[x.0 as usize / _workers] += x.1;
        //             }
        //         }
        //
        //         while let Some((item, _)) = iterator.next() {
        //             output.give_at(&item, acc.drain_temp().enumerate().filter(|x| x.1 != 0.0)
        //                                      .map(|(u,f)| ((u * _workers + local_index) as u32, f)));
        //
        //             for _ in 0..(1 + (nodes/_workers)) { acc.push(0.0); }
        //         }
        //     }
        // );

        ranks.connect_loop(cycle);

        input
    });

    // introduce edges into the computation;
    // allow mmaped file to drop
    {
        let graph = GraphMMap::new(&filename);

        let mut edges = Vec::new();
        for node in 0..graph.nodes() {
            if node % peers == index {
                for dst in graph.edges(node) {
                    edges.push((node as u32, *dst as u32));
                }
                if edges.len() > 100_000 {
                    input.send_at(0, edges.drain_temp());
                    root.step();
                }
            }
        }

        input.send_at(0, edges.drain_temp());
    }
    input.close();
    while root.step() { }

    if index == 0 { println!("elapsed: {}", time::precise_time_s() - start); }
}
<commit_msg>Made aggregation strategy a command line flag.<commit_after>extern crate mmap;
extern crate time;
extern crate timely;
extern crate docopt;

use docopt::Docopt;

use std::fs::File;
use std::thread;
use std::io::{Read, BufRead, BufReader};

use timely::progress::timestamp::RootTimestamp;
use timely::progress::scope::Scope;
use timely::progress::nested::Summary::Local;
use timely::example_shared::*;
use timely::example_shared::operators::*;
use timely::communication::*;
use timely::communication::pact::Exchange;

use timely::networking::initialize_networking;

use timely::drain::DrainExt;

mod typedrw;
mod graphmap;
use graphmap::GraphMMap;

mod sorting;
use sorting::{SegmentList, radix_sort_32};

static USAGE: &'static str = "
Usage: pagerank <source> [options] [<arguments>...]

Options:
    -a <arg>, --aggregation <arg>  the aggregation strategy, \"worker\" or \"process\" [default: worker]
    -h <arg>, --hosts <arg>        file with list of host:port for workers
    -n <arg>, --processes <arg>    number of processes involved  [default: 1]
    -p <arg>, --processid <arg>    identity of this process      [default: 0]
    -w <arg>, --workers <arg>      number of workers per process [default: 1]
";

fn main () {
    let args = Docopt::new(USAGE).and_then(|dopt| dopt.parse()).unwrap_or_else(|e| e.exit());

    let source = args.get_str("<source>").to_owned();

    let agg_strategy = args.get_str("-a").to_owned();

    if agg_strategy != "process" && agg_strategy != "worker" {
        panic!("invalid setting for --aggregation: {}", args.get_str("-a"));
    }

    let workers: u64 = if let Ok(threads) = args.get_str("-w").parse() { threads }
                       else { panic!("invalid setting for --workers: {}", args.get_str("-t")) };
    let process_id: u64 = if let Ok(proc_id) = args.get_str("-p").parse() { proc_id }
                          else { panic!("invalid setting for --processid: {}", args.get_str("-p")) };
    let processes: u64 = if let Ok(processes) = args.get_str("-n").parse() { processes }
                         else { panic!("invalid setting for --processes: {}", args.get_str("-n")) };

    println!("Starting pagerank dataflow with");
    println!("\tworkers:\t{}", workers);
    println!("\tprocesses:\t{}", processes);
    println!("\tprocessid:\t{}", process_id);
    println!("\taggregation:\t{}", agg_strategy);

    // vector holding communicators to use; one per local worker.
    if processes > 1 {
        println!("Initializing BinaryCommunicator");

        let hosts = args.get_str("-h");
        let addresses: Vec<_> = if hosts != "" {
            let reader = BufReader::new(File::open(hosts).unwrap());
            reader.lines().take(processes as usize).map(|x| x.unwrap()).collect()
        }
        else {
            (0..processes).map(|index| format!("localhost:{}", 2101 + index).to_string()).collect()
        };

        if addresses.len() != processes as usize { panic!("only {} hosts for -p: {}", addresses.len(), processes); }

        let communicators = initialize_networking(addresses, process_id, workers).ok().expect("error initializing networking");

        pagerank_spawn(communicators, source, agg_strategy);
    }
    else if workers > 1 { pagerank_spawn(ProcessCommunicator::new_vector(workers), source, agg_strategy); }
    else { pagerank_spawn(vec![ThreadCommunicator], source, agg_strategy); };
}

fn pagerank_spawn<C>(communicators: Vec<C>, filename: String, agg_strategy: String)
where C: Communicator+Send {
    let mut guards = Vec::new();
    let workers = communicators.len();
    let process_agg = agg_strategy == "process";
    for communicator in communicators.into_iter() {
        let filename = filename.clone();
        guards.push(thread::Builder::new().name(format!("timely worker {}", communicator.index()))
                                          .spawn(move || pagerank_thread(communicator, filename, workers, process_agg))
                                          .unwrap());
    }

    for guard in guards { guard.join().unwrap(); }
}

// returns [src/peers] degrees, (dst, deg) pairs, and a list of [src/peers] endpoints
fn transpose(mut edges: Vec<Vec<(u32, u32)>>, peers: usize, nodes: usize) -> (Vec<u32>, Vec<(u32, u32)>, Vec<u32>)  {

    let mut deg = vec![0; (nodes as usize / peers) + 1];
    for list in &edges {
        for &(s, _) in list {
            deg[s as usize / peers] += 1;
        }
    }

    radix_sort_32(&mut edges, &mut Vec::new(), &|&(_,d)| d);

    let mut rev = Vec::<(u32,u32)>::with_capacity(deg.len());
    let mut trn = Vec::with_capacity(edges.len() * 1024);
    for list in edges {
        for (s,d) in list {
            let len = rev.len();
            if (len == 0) || (rev[len-1].0 < d) {
                rev.push((d, 0u32));
            }

            let len = rev.len();
            rev[len-1].1 += 1;
            trn.push(s / peers as u32);
        }
    }

    return (deg, rev, trn);
}

// pagerank dataflow graph has a set of edges as input, and a binary vertex that for each epoch of
// received edges initiates an iterative subcomputation to compute the pagerank.

fn pagerank_thread<C>(communicator: C, filename: String, _workers: usize, _process_aggregate: bool)
where C: Communicator {
    let index = communicator.index() as usize;
    let peers = communicator.peers() as usize;

    let mut root = GraphRoot::new(communicator);

    let start = time::precise_time_s();

    let nodes = GraphMMap::new(&filename).nodes();

    let mut segments = SegmentList::new(1024); // list of edge segments

    let mut src = vec![];   // holds ranks
    let mut deg = vec![];   // holds source degrees
    let mut rev = vec![];   // holds (dst, deg) pairs
    let mut trn = vec![];   // holds transposed sources

    let mut going = start;

    let mut input = root.subcomputation(|builder| {

        let (input, edges) = builder.new_input::<(u32, u32)>();
        let (cycle, ranks) = builder.loop_variable::<(u32, f32)>(RootTimestamp::new(20), Local(1));

        let ranks = edges.binary_notify(&ranks,
                            Exchange::new(|x: &(u32,u32)| x.0 as u64),
                            Exchange::new(|x: &(u32,f32)| x.0 as u64),
                            format!("pagerank"),
                            vec![RootTimestamp::new(0)],
                            move |input1, input2, output, notificator| {

            // receive incoming edges (should only be iter 0)
            while let Some((_index, data)) = input1.pull() {
                segments.push(data.drain_temp());
            }

            // all inputs received for iter, commence multiplication
            while let Some((iter, _)) = notificator.next() {

                let now = time::now();

                if index == 0 { println!("{}:{}:{}.{} starting iteration {}", now.tm_hour, now.tm_min, now.tm_sec, now.tm_nsec, iter.inner); }

                // if the very first iteration, prepare some stuff.
                // specifically, transpose edges and sort by destination.
                if iter.inner == 0 {
                    let (a, b, c) = transpose(segments.finalize(), peers, nodes);
                    deg = a; rev = b; trn = c;
                    src = vec![0.0f32; deg.len()];
                }

                // record some timings in order to estimate per-iteration times
                if iter.inner == 0  && index == 0 { println!("src: {}, dst: {}, edges: {}", src.len(), rev.len(), trn.len()); }
                if iter.inner == 10 && index == 0 { going = time::precise_time_s(); }
                if iter.inner == 20 && index == 0 { println!("average: {}", (time::precise_time_s() - going) / 10.0 ); }

                // prepare src for transmitting to destinations
                for s in 0..src.len() { src[s] = 0.15 + 0.85 * src[s] / deg[s] as f32; }

                // wander through destinations
                let mut trn_slice = &trn[..];
                let mut rev_slice = &rev[..];
                while rev_slice.len() > 0 {
                    // TODO: session should flush automatically...
                    let mut session = output.session(&iter);
                    let next = std::cmp::min(200_000, rev_slice.len());
                    for &(dst, deg) in &rev_slice[..next] {
                        let mut accum = 0.0;
                        for &s in &trn_slice[..deg as usize] {
                            accum += src[s as usize];
                        }
                        trn_slice = &trn_slice[deg as usize..];
                        session.give((dst, accum));
                    }
                    rev_slice = &rev_slice[next..];
                }

                // clean out src to accumulate into
                for s in &mut src { *s = 0.0; }
            }

            // receive data from workers, accumulate in src
            while let Some((iter, data)) = input2.pull() {
                notificator.notify_at(&iter);
                for x in data.drain_temp() {
                    src[x.0 as usize / peers] += x.1;
                }
            }
        });

        // optionally, do process-local accumulation
        if _process_aggregate {
            let local_base = _workers * (index / _workers);
            let local_index = index % _workers;
            let mut acc = vec![0.0; (nodes / _workers) + 1];   // holds ranks
            let ranks = ranks.unary_notify(
                Exchange::new(move |x: &(u32,f32)| (local_base as u64 + (x.0 as u64 % _workers as u64))),
                format!("Aggregation"),
                vec![],
                move |input, output, iterator| {
                    while let Some((iter, data)) = input.pull() {
                        iterator.notify_at(&iter);
                        for x in data.drain_temp() {
                            acc[x.0 as usize / _workers] += x.1;
                        }
                    }

                    while let Some((item, _)) = iterator.next() {
                        output.give_at(&item, acc.drain_temp().enumerate().filter(|x| x.1 != 0.0)
                                                 .map(|(u,f)| ((u * _workers + local_index) as u32, f)));

                        for _ in 0..(1 + (nodes/_workers)) { acc.push(0.0); }
                    }
                }
            );
        }

        ranks.connect_loop(cycle);

        input
    });

    // introduce edges into the computation;
    // allow mmaped file to drop
    {
        let graph = GraphMMap::new(&filename);

        let mut edges = Vec::new();
        for node in 0..graph.nodes() {
            if node % peers == index {
                for dst in graph.edges(node) {
                    edges.push((node as u32, *dst as u32));
                }
                if edges.len() > 100_000 {
                    input.send_at(0, edges.drain_temp());
                    root.step();
                }
            }
        }

        input.send_at(0, edges.drain_temp());
    }
    input.close();
    while root.step() { }

    if index == 0 { println!("elapsed: {}", time::precise_time_s() - start); }
}
<|endoftext|>


<commit_before>use clap::{App, Arg, SubCommand};
use sala::{
    config,
    Error::{self, *},
    Output::{self, *},
};
use std::env;
use std::ffi::OsString;
use std::path::{Path, PathBuf};

fn main() {
    let app_m = App::new("sala")
        .version("1.4")
        .about("Store passwords and other sensitive information to plain text files")
        .arg(
            Arg::with_name("raw")
                .short("r")
                .long("raw")
                .global(true)
                .help("Use a simple output format for machine processing"),
        )
        .arg(
            Arg::with_name("directory")
                .short("C")
                .long("directory")
                .takes_value(true)
                .value_name("DIR")
                .global(true)
                .help("Use the password repository in DIR instead of current directory"),
        )
        .subcommand(
            SubCommand::with_name("init")
                .display_order(0)
                .about("Initialize a repository"),
        )
        .subcommand(
            SubCommand::with_name("get")
                .display_order(1)
                .about("Read a secret")
                .arg(
                    Arg::with_name("path")
                        .required(true)
                        .help("Path of the file to read"),
                ),
        )
        .subcommand(
            SubCommand::with_name("set")
                .display_order(2)
                .about("Create or update a secret")
                .arg(
                    Arg::with_name("path")
                        .required(true)
                        .help("Path of the file to write"),
                ),
        )
        .arg(Arg::with_name("path").hidden(true))
        .get_matches();

    let repo_path = PathBuf::from(
        app_m
            .value_of_os("directory")
            .as_ref()
            .map(|x| x.to_os_string())
            .or_else(|| env::var_os("SALADIR"))
            .unwrap_or(OsString::from(".")),
    );

    let config = match config::load(&repo_path) {
        Ok(config) => config,
        Err(err) => {
            eprintln!("Error loading config file {}:", err.path.to_string_lossy());
            eprintln!("{}", err.error);
            std::process::exit(1);
        }
    };

    let raw = app_m.is_present("raw");
    let result = match (app_m.subcommand(), app_m.value_of_os("path")) {
        (("init", Some(_)), _) => sala::init(&repo_path, &config),
        (("get", Some(sub_m)), _) => sala::get(
            &repo_path,
            Path::new(sub_m.value_of_os("path").unwrap()),
            raw,
        ),
        (("set", Some(sub_m)), _) => sala::set(
            &repo_path,
            Path::new(sub_m.value_of_os("path").unwrap()),
            &config,
        ),
        (_, Some(path)) => sala::get_or_set(&repo_path, Path::new(path), &config, raw),
        _ => Err(sala::Error::Usage),
    };

    match result {
        Ok(output) => {
            print_output(&output);
        }
        Err(error) => {
            print_error(&error);
            std::process::exit(1);
        }
    };
}

fn print_output(output: &Output) {
    match output {
        Get(path, secret, raw) => {
            let secret_utf8 = String::from_utf8_lossy(&secret);
            if *raw {
                println!("{}", secret_utf8);
            } else {
                println!("");
                println!("{}: {}", path.to_string_lossy(), secret_utf8,);
                println!("");
            }
        }
        NoOutput => {}
    }
}

fn print_error(error: &Error) {
    match error {
        AlreadyInitialized => {
            eprintln!("Error: The master key already exists");
        }
        CannotInitRepo => {
            eprintln!("Error: Failed to initialize a new repository");
        }
        FileDoesNotExist(path) => {
            eprintln!(
                "Error: File does not exist or invalid: {}",
                path.to_string_lossy()
            );
        }
        InputsDidntMatch => {
            eprintln!("");
            eprintln!("Inputs did not match.");
        }
        NoRepo => {
            eprintln!("No repository. Run `sala init' first",);
        }
        UnlockFailed => {
            eprintln!("");
            eprintln!("Error: Unable to unlock the encryption key");
        }
        Usage => {
            eprintln!("Try `sala --help'");
        }
        TargetIsDirectory(path) => {
            eprintln!("Error: Target is a directory: {}", path.to_string_lossy());
        }
        CannotCreateDirectory(path) => {
            eprintln!("Error: Cannot create directory: {}", path.to_string_lossy());
        }
    }
}
<commit_msg>Small cleanup (Rust)<commit_after>use clap::{App, Arg, SubCommand};
use sala::{
    config,
    Error::{self, *},
    Output::{self, *},
};
use std::env;
use std::ffi::OsString;
use std::path::{Path, PathBuf};

fn main() {
    let app_m = App::new("sala")
        .version("1.4")
        .about("Store passwords and other sensitive information to plain text files")
        .arg(
            Arg::with_name("raw")
                .short("r")
                .long("raw")
                .global(true)
                .help("Use a simple output format for machine processing"),
        )
        .arg(
            Arg::with_name("directory")
                .short("C")
                .long("directory")
                .takes_value(true)
                .value_name("DIR")
                .global(true)
                .help("Use the password repository in DIR instead of current directory"),
        )
        .subcommand(
            SubCommand::with_name("init")
                .display_order(0)
                .about("Initialize a repository"),
        )
        .subcommand(
            SubCommand::with_name("get")
                .display_order(1)
                .about("Read a secret")
                .arg(
                    Arg::with_name("path")
                        .required(true)
                        .help("Path of the file to read"),
                ),
        )
        .subcommand(
            SubCommand::with_name("set")
                .display_order(2)
                .about("Create or update a secret")
                .arg(
                    Arg::with_name("path")
                        .required(true)
                        .help("Path of the file to write"),
                ),
        )
        .arg(Arg::with_name("path").hidden(true))
        .get_matches();

    let repo_path = PathBuf::from(
        app_m
            .value_of_os("directory")
            .as_ref()
            .map(|x| x.to_os_string())
            .or_else(|| env::var_os("SALADIR"))
            .unwrap_or(OsString::from(".")),
    );

    let config = match config::load(&repo_path) {
        Ok(config) => config,
        Err(err) => {
            eprintln!("Error loading config file {}:", err.path.to_string_lossy());
            eprintln!("{}", err.error);
            std::process::exit(1);
        }
    };

    let raw = app_m.is_present("raw");
    let result = match (app_m.subcommand(), app_m.value_of_os("path")) {
        (("init", Some(_)), _) => sala::init(&repo_path, &config),
        (("get", Some(sub_m)), _) => sala::get(
            &repo_path,
            Path::new(sub_m.value_of_os("path").unwrap()),
            raw,
        ),
        (("set", Some(sub_m)), _) => sala::set(
            &repo_path,
            Path::new(sub_m.value_of_os("path").unwrap()),
            &config,
        ),
        (_, Some(path)) => sala::get_or_set(&repo_path, Path::new(path), &config, raw),
        _ => Err(Error::Usage),
    };

    match result {
        Ok(output) => {
            print_output(&output);
        }
        Err(error) => {
            print_error(&error);
            std::process::exit(1);
        }
    };
}

fn print_output(output: &Output) {
    match output {
        Get(path, secret, raw) => {
            let secret_utf8 = String::from_utf8_lossy(&secret);
            if *raw {
                println!("{}", secret_utf8);
            } else {
                println!("");
                println!("{}: {}", path.to_string_lossy(), secret_utf8,);
                println!("");
            }
        }
        NoOutput => {}
    }
}

fn print_error(error: &Error) {
    match error {
        AlreadyInitialized => {
            eprintln!("Error: The master key already exists");
        }
        CannotInitRepo => {
            eprintln!("Error: Failed to initialize a new repository");
        }
        FileDoesNotExist(path) => {
            eprintln!(
                "Error: File does not exist or invalid: {}",
                path.to_string_lossy()
            );
        }
        InputsDidntMatch => {
            eprintln!("");
            eprintln!("Inputs did not match.");
        }
        NoRepo => {
            eprintln!("No repository. Run `sala init' first",);
        }
        UnlockFailed => {
            eprintln!("");
            eprintln!("Error: Unable to unlock the encryption key");
        }
        Usage => {
            eprintln!("Try `sala --help'");
        }
        TargetIsDirectory(path) => {
            eprintln!("Error: Target is a directory: {}", path.to_string_lossy());
        }
        CannotCreateDirectory(path) => {
            eprintln!("Error: Cannot create directory: {}", path.to_string_lossy());
        }
    }
}
<|endoftext|>


<commit_before>extern crate byteorder;
use std::io::Cursor;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};

macro_rules! define_instructions {
    ($($variant:ident, $value:expr, $name:expr, $num_operands:expr;)*) => (
        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
        #[repr(u8)]
        enum Inst {
            $($variant = $value),*
        }

        impl Inst {
            fn from_u8(inst: u8) -> Option<Inst> {
                match inst {
                    $($value => Some(Inst::$variant),)*
                    _ => None
                }
            }

            fn from_str(inst: &str) -> Option<Inst> {
                match inst {
                    $($name => Some(Inst::$variant),)*
                    _ => None
                }
            }

            fn num_operands(&self) -> u8 {
                match *self {
                    $(Inst::$variant => $num_operands),*
                }
            }
        }
    )
}

// Bytecode instruction opcodes. The values of these opcodes should never change, to remain
// compatible with existing bytecode programs.
define_instructions! {
    Nop,   0,  "nop",   0;
    Print, 1,  "print", 0;
    Halt,  2,  "halt",  0;
    Push,  3,  "push",  1;
    Dup,   4,  "dup",   0;
    Pop,   5,  "pop",   0;
    Swap,  6,  "swap",  0;
    Add,   7,  "add",   0;
    Sub,   8,  "sub",   0;
    Mul,   9,  "mul",   0;
    Div,   10, "div",   0;
    Mod,   11, "mod",   0;
    Eq,    12, "eq",    0;
    Lt,    13, "lt",    0;
    Lte,   14, "lte",   0;
    Gt,    15, "gt",    0;
    Gte,   16, "gte",   0;
    Jz,    17, "jz",    1;
    Jnz,   18, "jnz",   1;
    Jump,  19, "jump",  1;
    Call,  20, "call",  1;
    Ret,   21, "ret",   0;
    CPush, 22, "cpush", 0;
    CPop,  23, "cpop",  0;
    CDup,  24, "cdup",  0;
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum VmError {
    InvalidOpcode,
    UnexpectedProgramEnd, // Hit end of program while reading operand.
    StackOverflow,
    StackUnderflow,
    ControlStackOverflow,
    ControlStackUnderflow,
}

#[derive(Debug, Eq, PartialEq)]
struct Vm {
    stack: Box<[i32]>,
    stack_idx: usize,
    control_stack: Box<[i32]>,
    control_stack_idx: usize,
}

impl Vm {
    fn execute(&mut self, program: &[u8]) -> Result<(), VmError> {
        use VmError::*;

        let stack = &mut self.stack;
        let mut stack_idx = self.stack_idx;
        let mut opcodes = Cursor::new(program);

        while let Ok(opcode) = opcodes.read_u8() {
            let inst = try!(Inst::from_u8(opcode).ok_or(InvalidOpcode));
            match inst {
                Inst::Nop => {},

                Inst::Print => {
                    let val = *try!(stack.get(stack_idx - 1).ok_or(StackUnderflow));
                    println!("{}", val);
                    stack_idx -= 1;
                },

                Inst::Halt => {
                    break;
                },

                Inst::Push => {
                    let val = try!(opcodes.read_i32::<LittleEndian>().or(Err(UnexpectedProgramEnd)));
                    let stack_top = try!(stack.get_mut(stack_idx).ok_or(StackOverflow));
                    *stack_top = val;
                    stack_idx += 1;
                },

                Inst::Dup => {
                    if stack_idx < 1 { return Err(StackUnderflow); }
                    if stack_idx >= stack.len() { return Err(StackOverflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx) = *stack.get_unchecked(stack_idx - 1);
                    }
                    stack_idx += 1;
                },

                Inst::Pop => {
                    if stack_idx < 1 { return Err(StackUnderflow); }
                    stack_idx -= 1;
                },

                Inst::Swap => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        let tmp = *stack.get_unchecked(stack_idx - 1);
                        *stack.get_unchecked_mut(stack_idx - 1) = *stack.get_unchecked(stack_idx - 2);
                        *stack.get_unchecked_mut(stack_idx - 2) = tmp;
                    }
                },

                Inst::Add => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) += *stack.get_unchecked(stack_idx - 1);
                    }
                    stack_idx -= 1;
                },

                Inst::Sub => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) -= *stack.get_unchecked(stack_idx - 1);
                    }
                    stack_idx -= 1;
                },

                Inst::Mul => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) *= *stack.get_unchecked(stack_idx - 1);
                    }
                    stack_idx -= 1;
                },

                Inst::Div => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) /= *stack.get_unchecked(stack_idx - 1);
                    }
                    stack_idx -= 1;
                },

                Inst::Mod => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) %= *stack.get_unchecked(stack_idx - 1);
                    }
                    stack_idx -= 1;
                },

                Inst::Eq => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 == val1) as i32;
                    }
                    stack_idx -= 1;
                },

                Inst::Lt => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 < val1) as i32;
                    }
                    stack_idx -= 1;
                },

                Inst::Lte => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 <= val1) as i32;
                    }
                    stack_idx -= 1;
                },

                Inst::Gt => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 > val1) as i32;
                    }
                    stack_idx -= 1;
                },

                Inst::Gte => {
                    if stack_idx < 2 { return Err(StackUnderflow); }
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 >= val1) as i32;
                    }
                    stack_idx -= 1;
                },

                Inst::Jz => {
                    if stack_idx < 1 { return Err(StackUnderflow); }
                    let condition = unsafe { *stack.get_unchecked(stack_idx - 1) };
                    try!(jump(&mut opcodes, condition == 0));
                    stack_idx -= 1;
                },

                Inst::Jnz => {
                    if stack_idx < 1 { return Err(StackUnderflow); }
                    let condition = unsafe { *stack.get_unchecked(stack_idx - 1) };
                    try!(jump(&mut opcodes, condition != 0));
                    stack_idx -= 1;
                },

                Inst::Jump => {
                    try!(jump(&mut opcodes, true));
                },

                Inst::Call => {
                    let control_stack_top = try!(self.control_stack.get_mut(self.control_stack_idx).ok_or(ControlStackOverflow));
                    *control_stack_top = opcodes.position() as i32 + 4;
                    try!(jump(&mut opcodes, true));
                    self.control_stack_idx += 1;
                },

                Inst::Ret => {
                    let addr = *try!(self.control_stack.get_mut(self.control_stack_idx - 1).ok_or(ControlStackOverflow));
                    opcodes.set_position(addr as u64);
                    self.control_stack_idx -= 1;
                },

                Inst::CPush => {
                    if stack_idx < 1 { return Err(StackUnderflow); }
                    if self.control_stack_idx >= self.control_stack.len() { return Err(ControlStackOverflow); }
                    unsafe {
                        *self.control_stack.get_unchecked_mut(self.control_stack_idx) = *stack.get_unchecked(stack_idx - 1);
                    }
                    self.control_stack_idx += 1;
                    stack_idx -= 1;
                },

                Inst::CPop => {
                    if self.control_stack_idx < 1 { return Err(ControlStackUnderflow); }
                    if stack_idx >= stack.len() { return Err(StackOverflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx) = *self.control_stack.get_unchecked(self.control_stack_idx - 1);
                    }
                    self.control_stack_idx -= 1;
                    stack_idx += 1;
                },

                Inst::CDup => {
                    if self.control_stack_idx < 1 { return Err(ControlStackUnderflow); }
                    if stack_idx >= stack.len() { return Err(StackOverflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx) = *self.control_stack.get_unchecked(self.control_stack_idx - 1);
                    }
                    stack_idx += 1;
                },
            }
        }

        #[inline(always)]
        fn jump(opcodes: &mut Cursor<&[u8]>, condition: bool) -> Result<(), VmError> {
            let delta = try!(opcodes.read_i32::<LittleEndian>().or(Err(UnexpectedProgramEnd)));
            if condition {
                let operand_size = std::mem::size_of::<i32>() as i64;
                let addr = (opcodes.position() as i64 + delta as i64 - operand_size) as u64;
                opcodes.set_position(addr);
            }
            Ok(())
        }

        self.stack_idx = stack_idx;
        Ok(())
    }
}

fn assemble(source: &str) -> Vec<u8> {
    use std::collections::HashMap;

    let mut program: Vec<u8> = Vec::new();
    let mut label_definitions: HashMap<&str, usize> = HashMap::new();
    let mut label_uses: Vec<(&str, usize)> = Vec::new();

    for line in source.split(|c| c == '\n' || c == ';') {
        let mut tokens = line.split(char::is_whitespace).filter(|s| !s.is_empty());
        let mut first_token = tokens.next();

        // Parse an optional label at the start of the line.
        if let Some(label) = first_token {
            if label.chars().next_back() == Some(':') {
                let label_name = &label[..label.len() - 1];
                if label_definitions.insert(label_name, program.len()).is_some() {
                    panic!("Attempted to redefine label '{}'", label_name);
                }
                first_token = tokens.next()
            }
        }

        // Parse the rest of the line if it's not blank.
        if let Some(opcode) = first_token {
            // Parse the instruction name.
            let inst = Inst::from_str(opcode).unwrap_or_else(|| {
                panic!("Unrecognized instruction '{}'", opcode)
            });
            program.push(inst as u8);

            // Parse the operands.
            for _ in 0..inst.num_operands() {
                let operand = tokens.next().unwrap_or_else(|| {
                    panic!("Missing one or more operands after '{}'", opcode)
                });

                if operand.chars().next() == Some('@') {
                    let label_name = &operand[1..];
                    label_uses.push((label_name, program.len()));

                    // Push four zero bytes to be overwritten by the label location later.
                    for _ in 0..4 { program.push(0); }
                } else if let Ok(number) = operand.parse::<i32>() {
                    let operand_index = program.len();
                    for _ in 0..4 { program.push(0); }
                    (&mut program[operand_index..]).write_i32::<LittleEndian>(number).unwrap();
                } else {
                    panic!("Expected label or valid 32-bit signed integer after '{}', not '{}'",
                           opcode, operand);
                }
            }
        }
    }

    // Resolve label references and fill in their values in the bytecode.
    for (label_name, use_index) in label_uses {
        let target_index = *label_definitions.get(label_name).unwrap_or_else(|| {
            panic!("Reference to undefined label '{}'", label_name);
        });
        let delta = target_index as i32 - use_index as i32;
        (&mut program[use_index..]).write_i32::<LittleEndian>(delta).unwrap();
    }

    program
}

fn main() {
    // let source = r"
    //     push 1
// loop:   call @plus2
    //     dup; print
    //     jump @loop

// plus2:  push 2
    //     add
    //     ret
    // ";

    let source = r"
        push 10
        call @fact
        print
        halt

fact:   push 1
        swap
loop:   dup; jz @done
        dup; cpush
        mul
        cpop; push 1; sub
        jump @loop
done:   pop
        ret
    ";

    let program = assemble(source);

    let mut vm = Vm {
        stack: Box::new([0; 256]),
        stack_idx: 0,
        control_stack: Box::new([0; 256]),
        control_stack_idx: 0,
    };
    vm.execute(&program).unwrap();
}
<commit_msg>Factor out stack checking code.<commit_after>extern crate byteorder;
use std::io::Cursor;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};

macro_rules! define_instructions {
    (variant, value, name, operands, stack_args, stack_effect
     $($variant:ident,
       $value:expr,
       $name:expr,
       $num_operands:expr,
       $num_stack_args:expr,
       $stack_effect:expr)*) => (

        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
        #[repr(u8)]
        enum Inst { $($variant = $value),* }

        impl Inst {
            fn from_u8(inst: u8) -> Option<Inst> {
                match inst {
                    $($value => Some(Inst::$variant),)*
                    _ => None
                }
            }

            fn from_str(inst: &str) -> Option<Inst> {
                match inst {
                    $($name => Some(Inst::$variant),)*
                    _ => None
                }
            }

            fn num_operands(self)   -> u8 { match self { $(Inst::$variant => $num_operands),* } }
            fn num_stack_args(self) -> u8 { match self { $(Inst::$variant => $num_stack_args),* } }
            fn stack_effect(self)   -> i8 { match self { $(Inst::$variant => $stack_effect),* } }
        }
    )
}

// Bytecode instruction opcodes. The values of these opcodes should never change, to remain
// compatible with existing bytecode programs.
define_instructions! {
    variant, value, name,    operands, stack_args, stack_effect
    Nop,     0,     "nop",   0,        0,           0
    Print,   1,     "print", 0,        1,          -1
    Halt,    2,     "halt",  0,        0,           0
    Push,    3,     "push",  1,        0,           1
    Dup,     4,     "dup",   0,        1,           1
    Pop,     5,     "pop",   0,        1,          -1
    Swap,    6,     "swap",  0,        2,           0
    Add,     7,     "add",   0,        2,          -1
    Sub,     8,     "sub",   0,        2,          -1
    Mul,     9,     "mul",   0,        2,          -1
    Div,     10,    "div",   0,        2,          -1
    Mod,     11,    "mod",   0,        2,          -1
    Eq,      12,    "eq",    0,        2,          -1
    Lt,      13,    "lt",    0,        2,          -1
    Lte,     14,    "lte",   0,        2,          -1
    Gt,      15,    "gt",    0,        2,          -1
    Gte,     16,    "gte",   0,        2,          -1
    Jz,      17,    "jz",    1,        1,          -1
    Jnz,     18,    "jnz",   1,        1,          -1
    Jump,    19,    "jump",  1,        0,           0
    Call,    20,    "call",  1,        0,           0
    Ret,     21,    "ret",   0,        0,           0
    CPush,   22,    "cpush", 0,        1,          -1
    CPop,    23,    "cpop",  0,        0,           1
    CDup,    24,    "cdup",  0,        0,           1
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum VmError {
    InvalidOpcode,
    UnexpectedProgramEnd, // Hit end of program while reading operand.
    StackOverflow,
    StackUnderflow,
    ControlStackOverflow,
    ControlStackUnderflow,
}

#[derive(Debug, Eq, PartialEq)]
struct Vm {
    stack: Box<[i32]>,
    stack_idx: usize,
    control_stack: Box<[i32]>,
    control_stack_idx: usize,
}

impl Vm {
    fn execute(&mut self, program: &[u8]) -> Result<(), VmError> {
        use VmError::*;

        let stack = &mut self.stack;
        let mut stack_idx = self.stack_idx;
        let mut opcodes = Cursor::new(program);

        while let Ok(opcode) = opcodes.read_u8() {
            let inst = try!(Inst::from_u8(opcode).ok_or(InvalidOpcode));

            if stack_idx < inst.num_stack_args() as usize { return Err(StackUnderflow); }
            if stack_idx as isize >= stack.len() as isize - inst.stack_effect() as isize {
                return Err(StackOverflow);
            }

            match inst {
                Inst::Nop => {},

                Inst::Print => {
                    let val = *try!(stack.get(stack_idx - 1).ok_or(StackUnderflow));
                    println!("{}", val);
                },

                Inst::Halt => {
                    break;
                },

                Inst::Push => {
                    let val = try!(opcodes.read_i32::<LittleEndian>()
                                   .or(Err(UnexpectedProgramEnd)));
                    let stack_top = try!(stack.get_mut(stack_idx).ok_or(StackOverflow));
                    *stack_top = val;
                },

                Inst::Dup => {
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx) = *stack.get_unchecked(stack_idx - 1);
                    }
                },

                Inst::Pop => {},

                Inst::Swap => {
                    unsafe {
                        let tmp = *stack.get_unchecked(stack_idx - 1);
                        *stack.get_unchecked_mut(stack_idx - 1) =
                            *stack.get_unchecked(stack_idx - 2);
                        *stack.get_unchecked_mut(stack_idx - 2) = tmp;
                    }
                },

                Inst::Add => {
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) +=
                            *stack.get_unchecked(stack_idx - 1);
                    }
                },

                Inst::Sub => {
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) -=
                            *stack.get_unchecked(stack_idx - 1);
                    }
                },

                Inst::Mul => {
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) *=
                            *stack.get_unchecked(stack_idx - 1);
                    }
                },

                Inst::Div => {
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) /=
                            *stack.get_unchecked(stack_idx - 1);
                    }
                },

                Inst::Mod => {
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx - 2) %=
                            *stack.get_unchecked(stack_idx - 1);
                    }
                },

                Inst::Eq => {
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 == val1) as i32;
                    }
                },

                Inst::Lt => {
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 < val1) as i32;
                    }
                },

                Inst::Lte => {
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 <= val1) as i32;
                    }
                },

                Inst::Gt => {
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 > val1) as i32;
                    }
                },

                Inst::Gte => {
                    unsafe {
                        let val1 = *stack.get_unchecked_mut(stack_idx - 1);
                        let ptr2 = stack.get_unchecked_mut(stack_idx - 2);
                        *ptr2 = (*ptr2 >= val1) as i32;
                    }
                },

                Inst::Jz => {
                    let condition = unsafe { *stack.get_unchecked(stack_idx - 1) };
                    try!(jump(&mut opcodes, condition == 0));
                },

                Inst::Jnz => {
                    let condition = unsafe { *stack.get_unchecked(stack_idx - 1) };
                    try!(jump(&mut opcodes, condition != 0));
                },

                Inst::Jump => {
                    try!(jump(&mut opcodes, true));
                },

                Inst::Call => {
                    let control_stack_top = try!(self.control_stack.get_mut(self.control_stack_idx)
                                                 .ok_or(ControlStackOverflow));
                    *control_stack_top = opcodes.position() as i32 + 4;
                    try!(jump(&mut opcodes, true));
                    self.control_stack_idx += 1;
                },

                Inst::Ret => {
                    let addr = *try!(self.control_stack.get_mut(self.control_stack_idx - 1)
                                     .ok_or(ControlStackOverflow));
                    opcodes.set_position(addr as u64);
                    self.control_stack_idx -= 1;
                },

                Inst::CPush => {
                    if self.control_stack_idx >= self.control_stack.len() {
                        return Err(ControlStackOverflow);
                    }
                    unsafe {
                        *self.control_stack.get_unchecked_mut(self.control_stack_idx) =
                            *stack.get_unchecked(stack_idx - 1);
                    }
                    self.control_stack_idx += 1;
                },

                Inst::CPop => {
                    if self.control_stack_idx < 1 { return Err(ControlStackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx) =
                            *self.control_stack.get_unchecked(self.control_stack_idx - 1);
                    }
                    self.control_stack_idx -= 1;
                },

                Inst::CDup => {
                    if self.control_stack_idx < 1 { return Err(ControlStackUnderflow); }
                    unsafe {
                        *stack.get_unchecked_mut(stack_idx) =
                            *self.control_stack.get_unchecked(self.control_stack_idx - 1);
                    }
                },
            }

            stack_idx = (stack_idx as isize + inst.stack_effect() as isize) as usize;
        }

        #[inline(always)]
        fn jump(opcodes: &mut Cursor<&[u8]>, condition: bool) -> Result<(), VmError> {
            let delta = try!(opcodes.read_i32::<LittleEndian>().or(Err(UnexpectedProgramEnd)));
            if condition {
                let operand_size = std::mem::size_of::<i32>() as i64;
                let addr = (opcodes.position() as i64 + delta as i64 - operand_size) as u64;
                opcodes.set_position(addr);
            }
            Ok(())
        }

        self.stack_idx = stack_idx;
        Ok(())
    }
}

fn assemble(source: &str) -> Vec<u8> {
    use std::collections::HashMap;

    let mut program: Vec<u8> = Vec::new();
    let mut label_definitions: HashMap<&str, usize> = HashMap::new();
    let mut label_uses: Vec<(&str, usize)> = Vec::new();

    for line in source.split(|c| c == '\n' || c == ';') {
        let mut tokens = line.split(char::is_whitespace).filter(|s| !s.is_empty());
        let mut first_token = tokens.next();

        // Parse an optional label at the start of the line.
        if let Some(label) = first_token {
            if label.chars().next_back() == Some(':') {
                let label_name = &label[..label.len() - 1];
                if label_definitions.insert(label_name, program.len()).is_some() {
                    panic!("Attempted to redefine label '{}'", label_name);
                }
                first_token = tokens.next()
            }
        }

        // Parse the rest of the line if it's not blank.
        if let Some(opcode) = first_token {
            // Parse the instruction name.
            let inst = Inst::from_str(opcode).unwrap_or_else(|| {
                panic!("Unrecognized instruction '{}'", opcode)
            });
            program.push(inst as u8);

            // Parse the operands.
            for _ in 0..inst.num_operands() {
                let operand = tokens.next().unwrap_or_else(|| {
                    panic!("Missing one or more operands after '{}'", opcode)
                });

                if operand.chars().next() == Some('@') {
                    let label_name = &operand[1..];
                    label_uses.push((label_name, program.len()));

                    // Push four zero bytes to be overwritten by the label location later.
                    for _ in 0..4 { program.push(0); }
                } else if let Ok(number) = operand.parse::<i32>() {
                    let operand_index = program.len();
                    for _ in 0..4 { program.push(0); }
                    (&mut program[operand_index..]).write_i32::<LittleEndian>(number).unwrap();
                } else {
                    panic!("Expected label or valid 32-bit signed integer after '{}', not '{}'",
                           opcode, operand);
                }
            }
        }
    }

    // Resolve label references and fill in their values in the bytecode.
    for (label_name, use_index) in label_uses {
        let target_index = *label_definitions.get(label_name).unwrap_or_else(|| {
            panic!("Reference to undefined label '{}'", label_name);
        });
        let delta = target_index as i32 - use_index as i32;
        (&mut program[use_index..]).write_i32::<LittleEndian>(delta).unwrap();
    }

    program
}

fn main() {
    // let source = r"
    //     push 1
// loop:   call @plus2
    //     dup; print
    //     jump @loop

// plus2:  push 2
    //     add
    //     ret
    // ";

    let source = r"
        push 10
        call @fact
        print
        halt

fact:   push 1
        swap
loop:   dup; jz @done
        dup; cpush
        mul
        cpop; push 1; sub
        jump @loop
done:   pop
        ret
    ";

    let program = assemble(source);

    let mut vm = Vm {
        stack: Box::new([0; 256]),
        stack_idx: 0,
        control_stack: Box::new([0; 256]),
        control_stack_idx: 0,
    };
    vm.execute(&program).unwrap();
}
<|endoftext|>


<commit_before>use std::io::{self, BufReader};
use std::io::BufRead;
use std::fs::File;
extern crate time;
use time::Tm;
use time::strptime;
use time::Duration;

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub enum HttpStatus {
    Continue,
    SwitchingProtocols,
    Processing,
    OK,
    Created,
    Accepted,
    NonAuthoritativeInformation,
    NoContent,
    ResetContent,
    PartialContent,
    MultiStatus,
    AlreadyReported,
    IMUsed,
    MultipleChoices,
    MovedPermanently,
    Found,
    SeeOther,
    NotModified,
    UseProxy,
    TemporaryRedirect,
    PermanentRedirect,
    BadRequest,
    Unauthorized,
    PaymentRequired,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    ProxyAuthenticationRequired,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    PreconditionFailed,
    PayloadTooLarge,
    URITooLong,
    UnsupportedMediaType,
    RangeNotSatisfiable,
    ExpectationFailed,
    ImaTeapot,
    MisdirectedRequest,
    UnprocessableEntity,
    Locked,
    FailedDependency,
    UpgradeRequired,
    PreconditionRequired,
    TooManyRequests,
    RequestHeaderFieldsTooLarge,
    UnavailableForLegalReasons,
    InternalServerError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
    GatewayTimeout,
    HTTPVersionNotSupported,
    VariantAlsoNegotiates,
    InsufficientStorage,
    LoopDetected,
    NotExtended,
    NetworkAuthenticationRequired,
    Unregistered,
}

impl HttpStatus {
    fn from_code(code: i32) -> HttpStatus {
        match code {
            100 => HttpStatus::Continue,
            101 => HttpStatus::SwitchingProtocols,
            102 => HttpStatus::Processing,
            200 => HttpStatus::OK,
            201 => HttpStatus::Created,
            202 => HttpStatus::Accepted,
            203 => HttpStatus::NonAuthoritativeInformation,
            204 => HttpStatus::NoContent,
            205 => HttpStatus::ResetContent,
            206 => HttpStatus::PartialContent,
            207 => HttpStatus::MultiStatus,
            208 => HttpStatus::AlreadyReported,
            226 => HttpStatus::IMUsed,
            300 => HttpStatus::MultipleChoices,
            301 => HttpStatus::MovedPermanently,
            302 => HttpStatus::Found,
            303 => HttpStatus::SeeOther,
            304 => HttpStatus::NotModified,
            305 => HttpStatus::UseProxy,
            307 => HttpStatus::TemporaryRedirect,
            308 => HttpStatus::PermanentRedirect,
            400 => HttpStatus::BadRequest,
            401 => HttpStatus::Unauthorized,
            402 => HttpStatus::PaymentRequired,
            403 => HttpStatus::Forbidden,
            404 => HttpStatus::NotFound,
            405 => HttpStatus::MethodNotAllowed,
            406 => HttpStatus::NotAcceptable,
            407 => HttpStatus::ProxyAuthenticationRequired,
            408 => HttpStatus::RequestTimeout,
            409 => HttpStatus::Conflict,
            410 => HttpStatus::Gone,
            411 => HttpStatus::LengthRequired,
            412 => HttpStatus::PreconditionFailed,
            413 => HttpStatus::PayloadTooLarge,
            414 => HttpStatus::URITooLong,
            415 => HttpStatus::UnsupportedMediaType,
            416 => HttpStatus::RangeNotSatisfiable,
            417 => HttpStatus::ExpectationFailed,
            418 => HttpStatus::ImaTeapot,
            421 => HttpStatus::MisdirectedRequest,
            422 => HttpStatus::UnprocessableEntity,
            423 => HttpStatus::Locked,
            424 => HttpStatus::FailedDependency,
            426 => HttpStatus::UpgradeRequired,
            428 => HttpStatus::PreconditionRequired,
            429 => HttpStatus::TooManyRequests,
            431 => HttpStatus::RequestHeaderFieldsTooLarge,
            451 => HttpStatus::UnavailableForLegalReasons,
            500 => HttpStatus::InternalServerError,
            501 => HttpStatus::NotImplemented,
            502 => HttpStatus::BadGateway,
            503 => HttpStatus::ServiceUnavailable,
            504 => HttpStatus::GatewayTimeout,
            505 => HttpStatus::HTTPVersionNotSupported,
            506 => HttpStatus::VariantAlsoNegotiates,
            507 => HttpStatus::InsufficientStorage,
            508 => HttpStatus::LoopDetected,
            510 => HttpStatus::NotExtended,
            511 => HttpStatus::NetworkAuthenticationRequired,
            _ => HttpStatus::Unregistered,
        }
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Request {
    id: i32,
    time: Tm,
    url: String,
}

impl Request {
    fn get_matching_response<'a>(&'a self, responses: &'a Vec<Response>) -> Option<&Response> {
        for response in responses {
            if self.id == response.id {
                return Some(response)
            }
        }

        None
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Response {
    id: i32,
    time: Tm,
    mime_type: String,
    response_time: Duration,
    http_status: HttpStatus,
}

pub fn open_logfile(path: &str) -> Result<(Vec<Request>,Vec<Response>), io::Error> {
    let f = try!(File::open(path));

    let f = BufReader::new(f);

    let mut requests: Vec<Request> = Vec::new();
    let mut responses: Vec<Response> = Vec::new();

    for line in f.lines() {
        let line_value = &line.unwrap();

        if line_value.contains("->") {
            let r = try!(parse_request_line(&line_value));
            println!("{:#?}", r);
            requests.push(r)
        }

        if line_value.contains("<-") {
            let r = try!(parse_response_line(&line_value));
            println!("{:#?}", r);
            responses.push(r)
        }

    }

    Ok((requests, responses))
}

pub fn parse_request_line(log_line: &String) -> Result<Request, io::Error> {
    let parts: Vec<&str> = log_line.split(" ").collect();

    let id = parts[2];
    let url = parts[5];

    Ok(Request {
        id: id[1..id.len()-1].parse().unwrap(),
        time: strptime(parts[0], "%d/%b/%Y:%H:%M:%S").unwrap(),
        url: url.to_string()
    })
}

pub fn parse_response_line(log_line: &String) -> Result<Response, io::Error> {
    let parts: Vec<&str> = log_line.split(" ").collect();

    let id = parts[2];
    let response_time = parts[parts.len()-1];
    let mime_type = parts[5];
    let status_code = parts[4];

    Ok(Response {
        id: id[1..id.len()-1].parse().unwrap(),
        time: strptime(parts[0], "%d/%b/%Y:%H:%M:%S").unwrap(),
        response_time: Duration::milliseconds(response_time[0..response_time.len()-2].parse().unwrap()),
        mime_type: mime_type.to_string(),
        http_status: HttpStatus::from_code(status_code.parse().unwrap()),
    })
}

pub struct RequestResponsePair {
    request: Request,
    response: Response
}

pub fn pair_requests_responses(requests:Vec<Request>, responses: Vec<Response>) -> Vec<RequestResponsePair> {
    let mut request_response_pairs: Vec<RequestResponsePair> = Vec::new();

    for request in requests  {
        match request.get_matching_response(&responses) {
            Some(response) => request_response_pairs.push(RequestResponsePair{
                request: request.clone(),
                response: response.clone()
            }),
            None => println!("none"),
        }
    }

    request_response_pairs
}

fn main() {
    let lines = open_logfile("src/test/simple-1.log");
    let (requests, responses) = lines.unwrap();
    println!("So many requests: {}", requests.len());
    println!("So many responses: {}", responses.len());
}

#[cfg(test)]
mod tests {
	use super::*;
    extern crate time;
    use time::strptime;
    use::time::Duration;

    #[test]
    fn test_parse_request_line() {
        let line = "08/Apr/2016:09:58:47 +0200 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected = Request {
            id: 2,
            time: strptime("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            url: "/content/some/other.html".to_string()
        };

        let result = parse_request_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line() {
        let line = "08/Apr/2016:09:58:48 +0200 [02] <- 200 text/html 10ms".to_string();

        let expected = Response {
            id: 2,
            time: strptime("08/Apr/2016:09:58:48 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(10),
            http_status: HttpStatus::OK,
        };

        let result = parse_response_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_open_logfile() {
        let lines = open_logfile("src/test/simple-1.log");
        let (requests, responses) = lines.unwrap();

        assert_eq!(requests.len(), 2);
        assert_eq!(responses.len(), 2);
    }

    #[test]
    fn test_get_matching_response() {
        let lines = open_logfile("src/test/simple-1.log");
        let (requests, responses) = lines.unwrap();

        let result = requests[0].get_matching_response(&responses);

        let expected = Response {
            id: 1,
            time: strptime("08/Apr/2016:09:57:47 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(7),
            http_status: HttpStatus::OK,
        };

        assert_eq!(*result.unwrap(), expected);
    }

    #[test]
    fn test_get_matching_response_none_found() {
        let lines = open_logfile("src/test/simple-1.log");
        let (_, responses) = lines.unwrap();

        let request_without_matching = Request {
            id: 999,
            time: strptime("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            url: "/content/some/other.html".to_string()
        };

        let result = request_without_matching.get_matching_response(&responses);

        assert!(result.is_none());
    }

    #[test]
    fn test_pair_requests_resonses() {
        let lines = open_logfile("src/test/simple-1.log");
        let (requests, responses) = lines.unwrap();

        let result = pair_requests_responses(requests, responses);

        assert_eq!(result.len(), 2);

        assert_eq!(result[0].request.id, result[0].response.id);
        assert_eq!(result[1].request.id, result[1].response.id);
    }
}
<commit_msg>Sum up the total duration as an example calculation<commit_after>use std::io::{self, BufReader};
use std::io::BufRead;
use std::fs::File;
extern crate time;
use time::Tm;
use time::strptime;
use time::Duration;

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub enum HttpStatus {
    Continue,
    SwitchingProtocols,
    Processing,
    OK,
    Created,
    Accepted,
    NonAuthoritativeInformation,
    NoContent,
    ResetContent,
    PartialContent,
    MultiStatus,
    AlreadyReported,
    IMUsed,
    MultipleChoices,
    MovedPermanently,
    Found,
    SeeOther,
    NotModified,
    UseProxy,
    TemporaryRedirect,
    PermanentRedirect,
    BadRequest,
    Unauthorized,
    PaymentRequired,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    ProxyAuthenticationRequired,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    PreconditionFailed,
    PayloadTooLarge,
    URITooLong,
    UnsupportedMediaType,
    RangeNotSatisfiable,
    ExpectationFailed,
    ImaTeapot,
    MisdirectedRequest,
    UnprocessableEntity,
    Locked,
    FailedDependency,
    UpgradeRequired,
    PreconditionRequired,
    TooManyRequests,
    RequestHeaderFieldsTooLarge,
    UnavailableForLegalReasons,
    InternalServerError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
    GatewayTimeout,
    HTTPVersionNotSupported,
    VariantAlsoNegotiates,
    InsufficientStorage,
    LoopDetected,
    NotExtended,
    NetworkAuthenticationRequired,
    Unregistered,
}

impl HttpStatus {
    fn from_code(code: i32) -> HttpStatus {
        match code {
            100 => HttpStatus::Continue,
            101 => HttpStatus::SwitchingProtocols,
            102 => HttpStatus::Processing,
            200 => HttpStatus::OK,
            201 => HttpStatus::Created,
            202 => HttpStatus::Accepted,
            203 => HttpStatus::NonAuthoritativeInformation,
            204 => HttpStatus::NoContent,
            205 => HttpStatus::ResetContent,
            206 => HttpStatus::PartialContent,
            207 => HttpStatus::MultiStatus,
            208 => HttpStatus::AlreadyReported,
            226 => HttpStatus::IMUsed,
            300 => HttpStatus::MultipleChoices,
            301 => HttpStatus::MovedPermanently,
            302 => HttpStatus::Found,
            303 => HttpStatus::SeeOther,
            304 => HttpStatus::NotModified,
            305 => HttpStatus::UseProxy,
            307 => HttpStatus::TemporaryRedirect,
            308 => HttpStatus::PermanentRedirect,
            400 => HttpStatus::BadRequest,
            401 => HttpStatus::Unauthorized,
            402 => HttpStatus::PaymentRequired,
            403 => HttpStatus::Forbidden,
            404 => HttpStatus::NotFound,
            405 => HttpStatus::MethodNotAllowed,
            406 => HttpStatus::NotAcceptable,
            407 => HttpStatus::ProxyAuthenticationRequired,
            408 => HttpStatus::RequestTimeout,
            409 => HttpStatus::Conflict,
            410 => HttpStatus::Gone,
            411 => HttpStatus::LengthRequired,
            412 => HttpStatus::PreconditionFailed,
            413 => HttpStatus::PayloadTooLarge,
            414 => HttpStatus::URITooLong,
            415 => HttpStatus::UnsupportedMediaType,
            416 => HttpStatus::RangeNotSatisfiable,
            417 => HttpStatus::ExpectationFailed,
            418 => HttpStatus::ImaTeapot,
            421 => HttpStatus::MisdirectedRequest,
            422 => HttpStatus::UnprocessableEntity,
            423 => HttpStatus::Locked,
            424 => HttpStatus::FailedDependency,
            426 => HttpStatus::UpgradeRequired,
            428 => HttpStatus::PreconditionRequired,
            429 => HttpStatus::TooManyRequests,
            431 => HttpStatus::RequestHeaderFieldsTooLarge,
            451 => HttpStatus::UnavailableForLegalReasons,
            500 => HttpStatus::InternalServerError,
            501 => HttpStatus::NotImplemented,
            502 => HttpStatus::BadGateway,
            503 => HttpStatus::ServiceUnavailable,
            504 => HttpStatus::GatewayTimeout,
            505 => HttpStatus::HTTPVersionNotSupported,
            506 => HttpStatus::VariantAlsoNegotiates,
            507 => HttpStatus::InsufficientStorage,
            508 => HttpStatus::LoopDetected,
            510 => HttpStatus::NotExtended,
            511 => HttpStatus::NetworkAuthenticationRequired,
            _ => HttpStatus::Unregistered,
        }
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Request {
    id: i32,
    time: Tm,
    url: String,
}

impl Request {
    fn get_matching_response<'a>(&'a self, responses: &'a Vec<Response>) -> Option<&Response> {
        for response in responses {
            if self.id == response.id {
                return Some(response)
            }
        }

        None
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Response {
    id: i32,
    time: Tm,
    mime_type: String,
    response_time: Duration,
    http_status: HttpStatus,
}

pub fn open_logfile(path: &str) -> Result<(Vec<Request>,Vec<Response>), io::Error> {
    let f = try!(File::open(path));

    let f = BufReader::new(f);

    let mut requests: Vec<Request> = Vec::new();
    let mut responses: Vec<Response> = Vec::new();

    for line in f.lines() {
        let line_value = &line.unwrap();

        if line_value.contains("->") {
            let r = try!(parse_request_line(&line_value));
            println!("{:#?}", r);
            requests.push(r)
        }

        if line_value.contains("<-") {
            let r = try!(parse_response_line(&line_value));
            println!("{:#?}", r);
            responses.push(r)
        }

    }

    Ok((requests, responses))
}

pub fn parse_request_line(log_line: &String) -> Result<Request, io::Error> {
    let parts: Vec<&str> = log_line.split(" ").collect();

    let id = parts[2];
    let url = parts[5];

    Ok(Request {
        id: id[1..id.len()-1].parse().unwrap(),
        time: strptime(parts[0], "%d/%b/%Y:%H:%M:%S").unwrap(),
        url: url.to_string()
    })
}

pub fn parse_response_line(log_line: &String) -> Result<Response, io::Error> {
    let parts: Vec<&str> = log_line.split(" ").collect();

    let id = parts[2];
    let response_time = parts[parts.len()-1];
    let mime_type = parts[5];
    let status_code = parts[4];

    Ok(Response {
        id: id[1..id.len()-1].parse().unwrap(),
        time: strptime(parts[0], "%d/%b/%Y:%H:%M:%S").unwrap(),
        response_time: Duration::milliseconds(response_time[0..response_time.len()-2].parse().unwrap()),
        mime_type: mime_type.to_string(),
        http_status: HttpStatus::from_code(status_code.parse().unwrap()),
    })
}

pub struct RequestResponsePair {
    request: Request,
    response: Response
}

pub fn pair_requests_responses(requests:Vec<Request>, responses: Vec<Response>) -> Vec<RequestResponsePair> {
    let mut request_response_pairs: Vec<RequestResponsePair> = Vec::new();

    for request in requests  {
        match request.get_matching_response(&responses) {
            Some(response) => request_response_pairs.push(RequestResponsePair{
                request: request.clone(),
                response: response.clone()
            }),
            None => println!("none"),
        }
    }

    request_response_pairs
}

fn main() {
    let lines = open_logfile("src/test/simple-1.log");
    let (requests, responses) = lines.unwrap();
    println!("So many requests: {}", requests.len());
    println!("So many responses: {}", responses.len());

    let pairs: Vec<RequestResponsePair> = pair_requests_responses(requests, responses);

    let times: Vec<i64> = pairs.iter().map(|rr| rr.response.response_time.num_milliseconds()).collect();
    let sum: i64 = times.iter().sum();
    println!("{}", sum);
}

#[cfg(test)]
mod tests {
	use super::*;
    extern crate time;
    use time::strptime;
    use::time::Duration;

    #[test]
    fn test_parse_request_line() {
        let line = "08/Apr/2016:09:58:47 +0200 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected = Request {
            id: 2,
            time: strptime("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            url: "/content/some/other.html".to_string()
        };

        let result = parse_request_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line() {
        let line = "08/Apr/2016:09:58:48 +0200 [02] <- 200 text/html 10ms".to_string();

        let expected = Response {
            id: 2,
            time: strptime("08/Apr/2016:09:58:48 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(10),
            http_status: HttpStatus::OK,
        };

        let result = parse_response_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_open_logfile() {
        let lines = open_logfile("src/test/simple-1.log");
        let (requests, responses) = lines.unwrap();

        assert_eq!(requests.len(), 2);
        assert_eq!(responses.len(), 2);
    }

    #[test]
    fn test_get_matching_response() {
        let lines = open_logfile("src/test/simple-1.log");
        let (requests, responses) = lines.unwrap();

        let result = requests[0].get_matching_response(&responses);

        let expected = Response {
            id: 1,
            time: strptime("08/Apr/2016:09:57:47 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(7),
            http_status: HttpStatus::OK,
        };

        assert_eq!(*result.unwrap(), expected);
    }

    #[test]
    fn test_get_matching_response_none_found() {
        let lines = open_logfile("src/test/simple-1.log");
        let (_, responses) = lines.unwrap();

        let request_without_matching = Request {
            id: 999,
            time: strptime("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S").unwrap(),
            url: "/content/some/other.html".to_string()
        };

        let result = request_without_matching.get_matching_response(&responses);

        assert!(result.is_none());
    }

    #[test]
    fn test_pair_requests_resonses() {
        let lines = open_logfile("src/test/simple-1.log");
        let (requests, responses) = lines.unwrap();

        let result = pair_requests_responses(requests, responses);

        assert_eq!(result.len(), 2);

        assert_eq!(result[0].request.id, result[0].response.id);
        assert_eq!(result[1].request.id, result[1].response.id);
    }
}
<|endoftext|>