<commit_before>// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use rustc::session::Session;
use rustc::util::common::{ProfQDumpParams, ProfileQueriesMsg, profq_msg, profq_set_chan};
use std::sync::mpsc::{Receiver};
use std::io::{Write};
use rustc::dep_graph::{DepNode};
use std::time::{Duration, Instant};

pub mod trace;

/// begin a profile thread, if not already running
pub fn begin(sess: &Session) {
    use std::thread;
    use std::sync::mpsc::{channel};
    let (tx, rx) = channel();
    if profq_set_chan(sess, tx) {
        thread::spawn(move || profile_queries_thread(rx));
    }
}

/// dump files with profiling information to the given base path, and
/// wait for this dump to complete.
///
/// wraps the RPC (send/recv channel logic) of requesting a dump.
pub fn dump(sess: &Session, path: String) {
    use std::sync::mpsc::{channel};
    let (tx, rx) = channel();
    let params = ProfQDumpParams {
        path,
        ack: tx,
        // FIXME: Add another compiler flag to toggle whether this log
        // is written; false for now
        dump_profq_msg_log: true,
    };
    profq_msg(sess, ProfileQueriesMsg::Dump(params));
    let _ = rx.recv().unwrap();
}

// State for parsing recursive trace structure in separate thread, via messages
#[derive(Clone, Eq, PartialEq)]
enum ParseState {
    // No (local) parse state; may be parsing a tree, focused on a
    // sub-tree that could be anything.
    Clear,
    // Have Query information from the last message
    HaveQuery(trace::Query, Instant),
    // Have "time-begin" information from the last message (doit flag, and message)
    HaveTimeBegin(String, Instant),
    // Have "task-begin" information from the last message
    HaveTaskBegin(DepNode, Instant),
}
struct StackFrame {
    pub parse_st: ParseState,
    pub traces:   Vec<trace::Rec>,
}

fn total_duration(traces: &[trace::Rec]) -> Duration {
    let mut sum : Duration = Duration::new(0, 0);
    for t in traces.iter() { sum += t.dur_total; }
    return sum
}

// profiling thread; retains state (in local variables) and dump traces, upon request.
fn profile_queries_thread(r: Receiver<ProfileQueriesMsg>) {
    use self::trace::*;
    use std::fs::File;
    use std::time::{Instant};

    let mut profq_msgs: Vec<ProfileQueriesMsg> = vec![];
    let mut frame: StackFrame = StackFrame { parse_st: ParseState::Clear, traces: vec![] };
    let mut stack: Vec<StackFrame> = vec![];
    loop {
        let msg = r.recv();
        if let Err(_recv_err) = msg {
            // FIXME: Perhaps do something smarter than simply quitting?
            break
        };
        let msg = msg.unwrap();
        debug!("profile_queries_thread: {:?}", msg);

        // Meta-level versus _actual_ queries messages
        match msg {
            ProfileQueriesMsg::Halt => return,
            ProfileQueriesMsg::Dump(params) => {
                assert!(stack.is_empty());
                assert!(frame.parse_st == ParseState::Clear);
                {
                    // write log of all messages
                    if params.dump_profq_msg_log {
                        let mut log_file =
                            File::create(format!("{}.log.txt", params.path)).unwrap();
                        for m in profq_msgs.iter() {
                            writeln!(&mut log_file, "{:?}", m).unwrap()
                        };
                    }

                    // write HTML file, and counts file
                    let html_path = format!("{}.html", params.path);
                    let mut html_file = File::create(&html_path).unwrap();

                    let counts_path = format!("{}.counts.txt", params.path);
                    let mut counts_file = File::create(&counts_path).unwrap();

                    writeln!(html_file,
                        "\n\n<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">",
                        "profile_queries.css").unwrap();
                    writeln!(html_file, "\n\n").unwrap();
                    trace::write_traces(&mut html_file, &mut counts_file, &frame.traces);
                    writeln!(html_file, "\n").unwrap();

                    let ack_path = format!("{}.ack", params.path);
                    let ack_file = File::create(&ack_path).unwrap();
                    drop(ack_file);

                    // Tell main thread that we are done, e.g., so it can exit
                    params.ack.send(()).unwrap();
                }
                continue
            }
            // Actual query message:
            msg => {
                // Record msg in our log
                profq_msgs.push(msg.clone());
                // Respond to the message, knowing that we've already handled Halt and Dump, above.
                match (frame.parse_st.clone(), msg) {
                    (_,ProfileQueriesMsg::Halt) => unreachable!(),
                    (_,ProfileQueriesMsg::Dump(_)) => unreachable!(),

                    // Parse State: Clear
                    (ParseState::Clear,
                     ProfileQueriesMsg::QueryBegin(span, querymsg)) => {
                        let start = Instant::now();
                        frame.parse_st = ParseState::HaveQuery
                            (Query { span, msg: querymsg }, start)
                    },
                    (ParseState::Clear,
                     ProfileQueriesMsg::CacheHit) => {
                        panic!("parse error: unexpected CacheHit; expected QueryBegin")
                    },
                    (ParseState::Clear,
                     ProfileQueriesMsg::ProviderBegin) => {
                        panic!("parse error: expected QueryBegin before beginning a provider")
                    },
                    (ParseState::Clear,
                     ProfileQueriesMsg::ProviderEnd) => {
                        let provider_extent = frame.traces;
                        match stack.pop() {
                            None =>
                                panic!("parse error: expected a stack frame; found an empty stack"),
                            Some(old_frame) => {
                                match old_frame.parse_st {
                                    ParseState::HaveQuery(q, start) => {
                                        let duration = start.elapsed();
                                        frame = StackFrame{
                                            parse_st:ParseState::Clear,
                                            traces:old_frame.traces
                                        };
                                        let dur_extent = total_duration(&provider_extent);
                                        let trace = Rec {
                                            effect: Effect::QueryBegin(q, CacheCase::Miss),
                                            extent: Box::new(provider_extent),
                                            start: start,
                                            dur_self: duration - dur_extent,
                                            dur_total: duration,
                                        };
                                        frame.traces.push( trace );
                                    },
                                    _ => panic!("internal parse error: malformed parse stack")
                                }
                            }
                        }
                    },

                    (ParseState::Clear,
                     ProfileQueriesMsg::TimeBegin(msg)) => {
                        let start = Instant::now();
                        frame.parse_st = ParseState::HaveTimeBegin(msg, start);
                        stack.push(frame);
                        frame = StackFrame{parse_st:ParseState::Clear, traces:vec![]};
                    },
                    (_, ProfileQueriesMsg::TimeBegin(_)) =>
                        panic!("parse error; did not expect time begin here"),

                    (ParseState::Clear,
                     ProfileQueriesMsg::TimeEnd) => {
                        let provider_extent = frame.traces;
                        match stack.pop() {
                            None =>
                                panic!("parse error: expected a stack frame; found an empty stack"),
                            Some(old_frame) => {
                                match old_frame.parse_st {
                                    ParseState::HaveTimeBegin(msg, start) => {
                                        let duration = start.elapsed();
                                        frame = StackFrame{
                                            parse_st:ParseState::Clear,
                                            traces:old_frame.traces
                                        };
                                        let dur_extent = total_duration(&provider_extent);
                                        let trace = Rec {
                                            effect: Effect::TimeBegin(msg),
                                            extent: Box::new(provider_extent),
                                            start: start,
                                            dur_total: duration,
                                            dur_self: duration - dur_extent,
                                        };
                                        frame.traces.push( trace );
                                    },
                                    _ => panic!("internal parse error: malformed parse stack")
                                }
                            }
                        }
                    },
                    (_, ProfileQueriesMsg::TimeEnd) => { panic!("parse error") }

                    (ParseState::Clear,
                     ProfileQueriesMsg::TaskBegin(key)) => {
                        let start = Instant::now();
                        frame.parse_st = ParseState::HaveTaskBegin(key, start);
                        stack.push(frame);
                        frame = StackFrame{parse_st:ParseState::Clear, traces:vec![]};
                    },
                    (_, ProfileQueriesMsg::TaskBegin(_)) =>
                        panic!("parse error; did not expect time begin here"),

                    (ParseState::Clear,
                     ProfileQueriesMsg::TaskEnd) => {
                        let provider_extent = frame.traces;
                        match stack.pop() {
                            None =>
                                panic!("parse error: expected a stack frame; found an empty stack"),
                            Some(old_frame) => {
                                match old_frame.parse_st {
                                    ParseState::HaveTaskBegin(key, start) => {
                                        let duration = start.elapsed();
                                        frame = StackFrame{
                                            parse_st:ParseState::Clear,
                                            traces:old_frame.traces
                                        };
                                        let dur_extent = total_duration(&provider_extent);
                                        let trace = Rec {
                                            effect: Effect::TaskBegin(key),
                                            extent: Box::new(provider_extent),
                                            start: start,
                                            dur_total: duration,
                                            dur_self: duration - dur_extent,
                                        };
                                        frame.traces.push( trace );
                                    },
                                    _ => panic!("internal parse error: malformed parse stack")
                                }
                            }
                        }
                    },
                    (_, ProfileQueriesMsg::TaskEnd) => { panic!("parse error") }

                    // Parse State: HaveQuery
                    (ParseState::HaveQuery(q,start),
                     ProfileQueriesMsg::CacheHit) => {
                        let duration = start.elapsed();
                        let trace : Rec = Rec{
                            effect: Effect::QueryBegin(q, CacheCase::Hit),
                            extent: Box::new(vec![]),
                            start: start,
                            dur_self: duration,
                            dur_total: duration,
                        };
                        frame.traces.push( trace );
                        frame.parse_st = ParseState::Clear;
                    },
                    (ParseState::HaveQuery(_,_),
                     ProfileQueriesMsg::ProviderBegin) => {
                        stack.push(frame);
                        frame = StackFrame{parse_st:ParseState::Clear, traces:vec![]};
                    },

                    // Parse errors:

                    (ParseState::HaveQuery(q,_),
                     ProfileQueriesMsg::ProviderEnd) => {
                        panic!("parse error: unexpected ProviderEnd; \
                                expected something else to follow BeginQuery for {:?}", q)
                    },
                    (ParseState::HaveQuery(q1,_),
                     ProfileQueriesMsg::QueryBegin(span2,querymsg2)) => {
                        panic!("parse error: unexpected QueryBegin; \
                                earlier query is unfinished: {:?} and now {:?}",
                               q1, Query{span:span2, msg:querymsg2})
                    },

                    (ParseState::HaveTimeBegin(_, _), _) => {
                        unreachable!()
                    },
                    (ParseState::HaveTaskBegin(_, _), _) => {
                        unreachable!()
                    },
                }
            }
        }
    }
}
<commit_msg>profiler: simplify total_duration<commit_after>// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use rustc::session::Session;
use rustc::util::common::{ProfQDumpParams, ProfileQueriesMsg, profq_msg, profq_set_chan};
use std::sync::mpsc::{Receiver};
use std::io::{Write};
use rustc::dep_graph::{DepNode};
use std::time::{Duration, Instant};

pub mod trace;

/// begin a profile thread, if not already running
pub fn begin(sess: &Session) {
    use std::thread;
    use std::sync::mpsc::{channel};
    let (tx, rx) = channel();
    if profq_set_chan(sess, tx) {
        thread::spawn(move || profile_queries_thread(rx));
    }
}

/// dump files with profiling information to the given base path, and
/// wait for this dump to complete.
///
/// wraps the RPC (send/recv channel logic) of requesting a dump.
pub fn dump(sess: &Session, path: String) {
    use std::sync::mpsc::{channel};
    let (tx, rx) = channel();
    let params = ProfQDumpParams {
        path,
        ack: tx,
        // FIXME: Add another compiler flag to toggle whether this log
        // is written; false for now
        dump_profq_msg_log: true,
    };
    profq_msg(sess, ProfileQueriesMsg::Dump(params));
    let _ = rx.recv().unwrap();
}

// State for parsing recursive trace structure in separate thread, via messages
#[derive(Clone, Eq, PartialEq)]
enum ParseState {
    // No (local) parse state; may be parsing a tree, focused on a
    // sub-tree that could be anything.
    Clear,
    // Have Query information from the last message
    HaveQuery(trace::Query, Instant),
    // Have "time-begin" information from the last message (doit flag, and message)
    HaveTimeBegin(String, Instant),
    // Have "task-begin" information from the last message
    HaveTaskBegin(DepNode, Instant),
}
struct StackFrame {
    pub parse_st: ParseState,
    pub traces:   Vec<trace::Rec>,
}

fn total_duration(traces: &[trace::Rec]) -> Duration {
    Duration::new(0, 0) + traces.iter().map(|t| t.dur_total).sum()
}

// profiling thread; retains state (in local variables) and dump traces, upon request.
fn profile_queries_thread(r: Receiver<ProfileQueriesMsg>) {
    use self::trace::*;
    use std::fs::File;
    use std::time::{Instant};

    let mut profq_msgs: Vec<ProfileQueriesMsg> = vec![];
    let mut frame: StackFrame = StackFrame { parse_st: ParseState::Clear, traces: vec![] };
    let mut stack: Vec<StackFrame> = vec![];
    loop {
        let msg = r.recv();
        if let Err(_recv_err) = msg {
            // FIXME: Perhaps do something smarter than simply quitting?
            break
        };
        let msg = msg.unwrap();
        debug!("profile_queries_thread: {:?}", msg);

        // Meta-level versus _actual_ queries messages
        match msg {
            ProfileQueriesMsg::Halt => return,
            ProfileQueriesMsg::Dump(params) => {
                assert!(stack.is_empty());
                assert!(frame.parse_st == ParseState::Clear);
                {
                    // write log of all messages
                    if params.dump_profq_msg_log {
                        let mut log_file =
                            File::create(format!("{}.log.txt", params.path)).unwrap();
                        for m in profq_msgs.iter() {
                            writeln!(&mut log_file, "{:?}", m).unwrap()
                        };
                    }

                    // write HTML file, and counts file
                    let html_path = format!("{}.html", params.path);
                    let mut html_file = File::create(&html_path).unwrap();

                    let counts_path = format!("{}.counts.txt", params.path);
                    let mut counts_file = File::create(&counts_path).unwrap();

                    writeln!(html_file,
                        "\n\n<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">",
                        "profile_queries.css").unwrap();
                    writeln!(html_file, "\n\n").unwrap();
                    trace::write_traces(&mut html_file, &mut counts_file, &frame.traces);
                    writeln!(html_file, "\n").unwrap();

                    let ack_path = format!("{}.ack", params.path);
                    let ack_file = File::create(&ack_path).unwrap();
                    drop(ack_file);

                    // Tell main thread that we are done, e.g., so it can exit
                    params.ack.send(()).unwrap();
                }
                continue
            }
            // Actual query message:
            msg => {
                // Record msg in our log
                profq_msgs.push(msg.clone());
                // Respond to the message, knowing that we've already handled Halt and Dump, above.
                match (frame.parse_st.clone(), msg) {
                    (_,ProfileQueriesMsg::Halt) => unreachable!(),
                    (_,ProfileQueriesMsg::Dump(_)) => unreachable!(),

                    // Parse State: Clear
                    (ParseState::Clear,
                     ProfileQueriesMsg::QueryBegin(span, querymsg)) => {
                        let start = Instant::now();
                        frame.parse_st = ParseState::HaveQuery
                            (Query { span, msg: querymsg }, start)
                    },
                    (ParseState::Clear,
                     ProfileQueriesMsg::CacheHit) => {
                        panic!("parse error: unexpected CacheHit; expected QueryBegin")
                    },
                    (ParseState::Clear,
                     ProfileQueriesMsg::ProviderBegin) => {
                        panic!("parse error: expected QueryBegin before beginning a provider")
                    },
                    (ParseState::Clear,
                     ProfileQueriesMsg::ProviderEnd) => {
                        let provider_extent = frame.traces;
                        match stack.pop() {
                            None =>
                                panic!("parse error: expected a stack frame; found an empty stack"),
                            Some(old_frame) => {
                                match old_frame.parse_st {
                                    ParseState::HaveQuery(q, start) => {
                                        let duration = start.elapsed();
                                        frame = StackFrame{
                                            parse_st:ParseState::Clear,
                                            traces:old_frame.traces
                                        };
                                        let dur_extent = total_duration(&provider_extent);
                                        let trace = Rec {
                                            effect: Effect::QueryBegin(q, CacheCase::Miss),
                                            extent: Box::new(provider_extent),
                                            start: start,
                                            dur_self: duration - dur_extent,
                                            dur_total: duration,
                                        };
                                        frame.traces.push( trace );
                                    },
                                    _ => panic!("internal parse error: malformed parse stack")
                                }
                            }
                        }
                    },

                    (ParseState::Clear,
                     ProfileQueriesMsg::TimeBegin(msg)) => {
                        let start = Instant::now();
                        frame.parse_st = ParseState::HaveTimeBegin(msg, start);
                        stack.push(frame);
                        frame = StackFrame{parse_st:ParseState::Clear, traces:vec![]};
                    },
                    (_, ProfileQueriesMsg::TimeBegin(_)) =>
                        panic!("parse error; did not expect time begin here"),

                    (ParseState::Clear,
                     ProfileQueriesMsg::TimeEnd) => {
                        let provider_extent = frame.traces;
                        match stack.pop() {
                            None =>
                                panic!("parse error: expected a stack frame; found an empty stack"),
                            Some(old_frame) => {
                                match old_frame.parse_st {
                                    ParseState::HaveTimeBegin(msg, start) => {
                                        let duration = start.elapsed();
                                        frame = StackFrame{
                                            parse_st:ParseState::Clear,
                                            traces:old_frame.traces
                                        };
                                        let dur_extent = total_duration(&provider_extent);
                                        let trace = Rec {
                                            effect: Effect::TimeBegin(msg),
                                            extent: Box::new(provider_extent),
                                            start: start,
                                            dur_total: duration,
                                            dur_self: duration - dur_extent,
                                        };
                                        frame.traces.push( trace );
                                    },
                                    _ => panic!("internal parse error: malformed parse stack")
                                }
                            }
                        }
                    },
                    (_, ProfileQueriesMsg::TimeEnd) => { panic!("parse error") }

                    (ParseState::Clear,
                     ProfileQueriesMsg::TaskBegin(key)) => {
                        let start = Instant::now();
                        frame.parse_st = ParseState::HaveTaskBegin(key, start);
                        stack.push(frame);
                        frame = StackFrame{parse_st:ParseState::Clear, traces:vec![]};
                    },
                    (_, ProfileQueriesMsg::TaskBegin(_)) =>
                        panic!("parse error; did not expect time begin here"),

                    (ParseState::Clear,
                     ProfileQueriesMsg::TaskEnd) => {
                        let provider_extent = frame.traces;
                        match stack.pop() {
                            None =>
                                panic!("parse error: expected a stack frame; found an empty stack"),
                            Some(old_frame) => {
                                match old_frame.parse_st {
                                    ParseState::HaveTaskBegin(key, start) => {
                                        let duration = start.elapsed();
                                        frame = StackFrame{
                                            parse_st:ParseState::Clear,
                                            traces:old_frame.traces
                                        };
                                        let dur_extent = total_duration(&provider_extent);
                                        let trace = Rec {
                                            effect: Effect::TaskBegin(key),
                                            extent: Box::new(provider_extent),
                                            start: start,
                                            dur_total: duration,
                                            dur_self: duration - dur_extent,
                                        };
                                        frame.traces.push( trace );
                                    },
                                    _ => panic!("internal parse error: malformed parse stack")
                                }
                            }
                        }
                    },
                    (_, ProfileQueriesMsg::TaskEnd) => { panic!("parse error") }

                    // Parse State: HaveQuery
                    (ParseState::HaveQuery(q,start),
                     ProfileQueriesMsg::CacheHit) => {
                        let duration = start.elapsed();
                        let trace : Rec = Rec{
                            effect: Effect::QueryBegin(q, CacheCase::Hit),
                            extent: Box::new(vec![]),
                            start: start,
                            dur_self: duration,
                            dur_total: duration,
                        };
                        frame.traces.push( trace );
                        frame.parse_st = ParseState::Clear;
                    },
                    (ParseState::HaveQuery(_,_),
                     ProfileQueriesMsg::ProviderBegin) => {
                        stack.push(frame);
                        frame = StackFrame{parse_st:ParseState::Clear, traces:vec![]};
                    },

                    // Parse errors:

                    (ParseState::HaveQuery(q,_),
                     ProfileQueriesMsg::ProviderEnd) => {
                        panic!("parse error: unexpected ProviderEnd; \
                                expected something else to follow BeginQuery for {:?}", q)
                    },
                    (ParseState::HaveQuery(q1,_),
                     ProfileQueriesMsg::QueryBegin(span2,querymsg2)) => {
                        panic!("parse error: unexpected QueryBegin; \
                                earlier query is unfinished: {:?} and now {:?}",
                               q1, Query{span:span2, msg:querymsg2})
                    },

                    (ParseState::HaveTimeBegin(_, _), _) => {
                        unreachable!()
                    },
                    (ParseState::HaveTaskBegin(_, _), _) => {
                        unreachable!()
                    },
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>#![allow(non_upper_case_globals)]
#![allow(unused_assignments)]
#![feature(asm)]

#[macro_use]
extern crate serde_derive;

pub mod bit_ops;
pub mod board;
mod find_moves_fast;
mod do_moves_fast;

pub use board::Board;
pub use board::Turn;
pub use board::Move;
pub use board::MoveList;
pub use board::MoveOrder;
pub use board::MAX_MOVES;
pub use board::empty_movelist;

#[cfg(test)]
mod tests {
    extern crate rand;

    use bit_ops;

    use board::Board;
    use board::Turn;
    use board::Move;
    use board::MoveList;
    use board::MAX_MOVES;

    use std::time::Instant;

    use self::rand::Rng;

    #[test]
    fn it_works() {
        let mut b = Board::new();

        println!("{}", b);

        b.do_move(Move::new(3,2));

        println!("{}", b);
    }

    #[test]
    fn fast_find_moves_test() {
        for _ in 0..1024 {
            let mut b1 : Board = Board::new();
            let mut b2 : Board = Board::new();

            let mut rng = rand::thread_rng();

            assert!(b1 == b2);

            let mut t = Turn::BLACK;

            while !b1.is_done() {
                let mut moves : MoveList = [Move::null(); MAX_MOVES];

                let n = b1.get_moves(&mut moves);

                if n != 0 {
                    let m = moves[rng.gen::<usize>()%(n as usize)];

                    b1.do_move(m);
                    b1.update_moves();
                    b2.do_move(m);
                    b2.update_moves_fast();
                }

                assert!(b1 == b2);

                t = !t;
            }
        }
    }

    #[test]
    fn fast_do_move_test() {
        let mut rng = rand::thread_rng();
        for _ in 0..1024 {
            let mut b1 : Board = Board::new();
            let mut b2 : Board = Board::new();

            assert!(b1 == b2);

            let mut t = Turn::BLACK;

            while !b1.is_done() {
                let mut moves : MoveList = [Move::null(); MAX_MOVES];

                let n = b1.get_moves(&mut moves);

                let bb1 = b1;
                let bb2 = b2;

                let mut f1 = 0;
                let mut f2 = 0;
                let mut m = Move::null();
                if n != 0 {
                    m = moves[rng.gen::<usize>()%(n as usize)];

                    f1 = b1.do_move(m);
                    b1.update_moves();
                    f2 = b2.f_do_move(m);
                    b2.update_moves_fast();
                }

                if (b1 != b2) {
                    println!("{}\n\n{}", bb1, bb2);
                    println!("{}\n\n{}", b1, b2);

                    println!("Move: {}", m);

                    bit_ops::print_bitboard(f1);
                    bit_ops::print_bitboard(f2);

                    println!("{:064b}\n{:064b}", bb2.pieces().0, bb2.pieces().1);
                }

                assert!(b1 == b2);

                t = !t;
            }
        }
    }

    #[test]
    fn get_moves_bench() {
        let b = Board::new();

        let iters = 100000;

        let t = bench(|| {
            let mut list : MoveList = [Move::null(); MAX_MOVES];
            b.get_moves(&mut list);
        }, iters);

        println!("get_moves: {} ns/iter", (t as f32/iters as f32));
    }

    #[test]
    fn update_moves_bench() {

        let iters = 100000;

        let t = bench(|| {
            let mut b = Board::new();
            b.update_moves();
        }, iters);

        println!("find_moves: {} ns/iter", (t as f32/iters as f32));
    }

    #[test]
    fn update_moves_fast_bench() {

        let iters = 100000;

        let t = bench(|| {
            let mut b = Board::new();
            b.update_moves_fast();
        }, iters);

        println!("fast_find_moves: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn do_move_bench() {
        let iters = 100000;
        let t = bench(|| {
            let mut b = Board::new();
            b.do_move(Move::new(3,2));
        }, iters);
        println!("do_move: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn f_do_move_bench() {
        let iters = 100000;
        let t = bench(|| {
            let mut b = Board::new();
            b.f_do_move(Move::new(3,2));
        }, iters);
        println!("f_do_move: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn fast_do_move_bench() {
        let iters = 100000;
        let mut rng = rand::thread_rng();
        let p = rng.gen::<u64>();
        let mut o = rng.gen::<u64>();
        o &= !p;
        let m = rng.gen::<u8>();

        use do_moves_fast::fast_do_move;
        let t = bench(|| {
            fast_do_move(m, m & 7, (m >> 3) & 7, p, o);
        }, iters);

        println!("fast_do_move: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn test_fast_min() {
        use do_moves_fast::fast_min;

        println!("{}", fast_min(0,5));
        println!("{}", fast_min(5,2));
        println!("{}", fast_min(100,5));
        println!("{}", fast_min(2,7));
        println!("{}", fast_min(2,2));

        assert!(fast_min(0,5) == 0);
        assert!(fast_min(5,5) == 5);
        assert!(fast_min(100,5) == 5);
        assert!(fast_min(2,7) == 2);
    }

    fn bench<F>(sample : F, iters : usize) -> u64 where F: Fn() {
        let now = Instant::now();
        for _ in 0..iters {
            sample();
        }
        let dur = now.elapsed();

        dur.as_secs() * 1_000_000_000u64 + dur.subsec_nanos() as u64
    }
}
<commit_msg>added a benchmark test to see how many full games could be simulated in 1 second<commit_after>#![allow(non_upper_case_globals)]
#![allow(unused_assignments)]
#![feature(asm)]

#[macro_use]
extern crate serde_derive;

pub mod bit_ops;
pub mod board;
mod find_moves_fast;
mod do_moves_fast;

pub use board::Board;
pub use board::Turn;
pub use board::Move;
pub use board::MoveList;
pub use board::MoveOrder;
pub use board::MAX_MOVES;
pub use board::empty_movelist;

#[cfg(test)]
mod tests {
    extern crate rand;

    use bit_ops;

    use board::Board;
    use board::Turn;
    use board::Move;
    use board::MoveList;
    use board::MAX_MOVES;
    use board::empty_movelist;

    use std::time::Instant;

    use self::rand::Rng;

    #[test]
    fn it_works() {
        let mut b = Board::new();

        println!("{}", b);

        b.do_move(Move::new(3,2));

        println!("{}", b);
    }

    #[test]
    fn fast_find_moves_test() {
        for _ in 0..1024 {
            let mut b1 : Board = Board::new();
            let mut b2 : Board = Board::new();

            let mut rng = rand::thread_rng();

            assert!(b1 == b2);

            let mut t = Turn::BLACK;

            while !b1.is_done() {
                let mut moves : MoveList = [Move::null(); MAX_MOVES];

                let n = b1.get_moves(&mut moves);

                if n != 0 {
                    let m = moves[rng.gen::<usize>()%(n as usize)];

                    b1.do_move(m);
                    b1.update_moves();
                    b2.do_move(m);
                    b2.update_moves_fast();
                }

                assert!(b1 == b2);

                t = !t;
            }
        }
    }

    #[test]
    fn fast_do_move_test() {
        let mut rng = rand::thread_rng();
        for _ in 0..1024 {
            let mut b1 : Board = Board::new();
            let mut b2 : Board = Board::new();

            assert!(b1 == b2);

            let mut t = Turn::BLACK;

            while !b1.is_done() {
                let mut moves : MoveList = [Move::null(); MAX_MOVES];

                let n = b1.get_moves(&mut moves);

                let bb1 = b1;
                let bb2 = b2;

                let mut f1 = 0;
                let mut f2 = 0;
                let mut m = Move::null();
                if n != 0 {
                    m = moves[rng.gen::<usize>()%(n as usize)];

                    f1 = b1.do_move(m);
                    b1.update_moves();
                    f2 = b2.f_do_move(m);
                    b2.update_moves_fast();
                }

                if (b1 != b2) {
                    println!("{}\n\n{}", bb1, bb2);
                    println!("{}\n\n{}", b1, b2);

                    println!("Move: {}", m);

                    bit_ops::print_bitboard(f1);
                    bit_ops::print_bitboard(f2);

                    println!("{:064b}\n{:064b}", bb2.pieces().0, bb2.pieces().1);
                }

                assert!(b1 == b2);

                t = !t;
            }
        }
    }

    #[test]
    fn get_moves_bench() {
        let b = Board::new();

        let iters = 100000;

        let t = bench(|| {
            let mut list : MoveList = [Move::null(); MAX_MOVES];
            b.get_moves(&mut list);
        }, iters);

        println!("get_moves: {} ns/iter", (t as f32/iters as f32));
    }

    #[test]
    fn update_moves_bench() {

        let iters = 100000;

        let t = bench(|| {
            let mut b = Board::new();
            b.update_moves();
        }, iters);

        println!("find_moves: {} ns/iter", (t as f32/iters as f32));
    }

    #[test]
    fn update_moves_fast_bench() {

        let iters = 100000;

        let t = bench(|| {
            let mut b = Board::new();
            b.update_moves_fast();
        }, iters);

        println!("fast_find_moves: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn do_move_bench() {
        let iters = 100000;
        let t = bench(|| {
            let mut b = Board::new();
            b.do_move(Move::new(3,2));
        }, iters);
        println!("do_move: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn f_do_move_bench() {
        let iters = 100000;
        let t = bench(|| {
            let mut b = Board::new();
            b.f_do_move(Move::new(3,2));
        }, iters);
        println!("f_do_move: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn fast_do_move_bench() {
        let iters = 100000;
        let mut rng = rand::thread_rng();
        let p = rng.gen::<u64>();
        let mut o = rng.gen::<u64>();
        o &= !p;
        let m = rng.gen::<u8>();

        use do_moves_fast::fast_do_move;
        let t = bench(|| {
            fast_do_move(m, m & 7, (m >> 3) & 7, p, o);
        }, iters);

        println!("fast_do_move: {} ns/iter", (t as f64/iters as f64));
    }

    #[test]
    fn simulate_bench() {
        let iters = 100000;
        let mut rng = rand::thread_rng();

        let rands = (0..60_000_000).map(|_| rng.gen::<u8>()).collect::<Vec<_>>();

        let mut i = 0;
        let mut test_fn = || {
            let mut b = Board::new();

            while !b.is_done() {
                let mut mvs = empty_movelist();

                let n = b.get_moves(&mut mvs);

                b.do_move(mvs[(rands[i] % n) as usize]);

                i += 1;
            }
        };

        let t = bench(test_fn, iters);

        let mut ns = (t as f64/iters as f64);

        println!("fast_do_move: {:.2} ns/iter ({:.2} iter/s)", ns, 1_000_000_000.0/ns);
    }

    #[test]
    fn test_fast_min() {
        use do_moves_fast::fast_min;

        println!("{}", fast_min(0,5));
        println!("{}", fast_min(5,2));
        println!("{}", fast_min(100,5));
        println!("{}", fast_min(2,7));
        println!("{}", fast_min(2,2));

        assert!(fast_min(0,5) == 0);
        assert!(fast_min(5,5) == 5);
        assert!(fast_min(100,5) == 5);
        assert!(fast_min(2,7) == 2);
    }

    fn bench<F>(mut sample : F, iters : usize) -> u64 where F: FnMut() {
        let now = Instant::now();
        for _ in 0..iters {
            sample();
        }
        let dur = now.elapsed();

        dur.as_secs() * 1_000_000_000u64 + dur.subsec_nanos() as u64
    }
}
<|endoftext|>


<commit_before>// Spans are encoded using 1-bit tag and 2 different encoding formats (one for each tag value).
// One format is used for keeping span data inline,
// another contains index into an out-of-line span interner.
// The encoding format for inline spans were obtained by optimizing over crates in rustc/libstd.
// See https://internals.rust-lang.org/t/rfc-compiler-refactoring-spans/1357/28

use crate::GLOBALS;
use crate::{BytePos, SpanData};
use crate::hygiene::SyntaxContext;

use rustc_data_structures::fx::FxHashMap;
use std::hash::{Hash, Hasher};

/// A compressed span.
/// Contains either fields of `SpanData` inline if they are small, or index into span interner.
/// The primary goal of `Span` is to be as small as possible and fit into other structures
/// (that's why it uses `packed` as well). Decoding speed is the second priority.
/// See `SpanData` for the info on span fields in decoded representation.
#[repr(packed)]
pub struct Span(u32);

impl Copy for Span {}
impl Clone for Span {
    #[inline]
    fn clone(&self) -> Span {
        *self
    }
}
impl PartialEq for Span {
    #[inline]
    fn eq(&self, other: &Span) -> bool {
        let a = self.0;
        let b = other.0;
        a == b
    }
}
impl Eq for Span {}
impl Hash for Span {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        let a = self.0;
        a.hash(state)
    }
}

/// Dummy span, both position and length are zero, syntax context is zero as well.
/// This span is kept inline and encoded with format 0.
pub const DUMMY_SP: Span = Span(0);

impl Span {
    #[inline]
    pub fn new(lo: BytePos, hi: BytePos, ctxt: SyntaxContext) -> Self {
        encode(&match lo <= hi {
            true => SpanData { lo, hi, ctxt },
            false => SpanData { lo: hi, hi: lo, ctxt },
        })
    }

    #[inline]
    pub fn data(self) -> SpanData {
        decode(self)
    }
}

// Tags
const TAG_INLINE: u32 = 0;
const TAG_INTERNED: u32 = 1;
const TAG_MASK: u32 = 1;

// Fields indexes
const BASE_INDEX: usize = 0;
const LEN_INDEX: usize = 1;
const CTXT_INDEX: usize = 2;

// Tag = 0, inline format.
// -------------------------------------------------------------
// | base 31:8  | len 7:1  | ctxt (currently 0 bits) | tag 0:0 |
// -------------------------------------------------------------
// Since there are zero bits for ctxt, only SpanData with a 0 SyntaxContext
// can be inline.
const INLINE_SIZES: [u32; 3] = [24, 7, 0];
const INLINE_OFFSETS: [u32; 3] = [8, 1, 1];

// Tag = 1, interned format.
// ------------------------
// | index 31:1 | tag 0:0 |
// ------------------------
const INTERNED_INDEX_SIZE: u32 = 31;
const INTERNED_INDEX_OFFSET: u32 = 1;

#[inline]
fn encode(sd: &SpanData) -> Span {
    let (base, len, ctxt) = (sd.lo.0, sd.hi.0 - sd.lo.0, sd.ctxt.as_u32());

    let val = if (base >> INLINE_SIZES[BASE_INDEX]) == 0 &&
                 (len >> INLINE_SIZES[LEN_INDEX]) == 0 &&
                 (ctxt >> INLINE_SIZES[CTXT_INDEX]) == 0 {
        (base << INLINE_OFFSETS[BASE_INDEX]) | (len << INLINE_OFFSETS[LEN_INDEX]) |
        (ctxt << INLINE_OFFSETS[CTXT_INDEX]) | TAG_INLINE
    } else {
        let index = with_span_interner(|interner| interner.intern(sd));
        (index << INTERNED_INDEX_OFFSET) | TAG_INTERNED
    };
    Span(val)
}

#[inline]
fn decode(span: Span) -> SpanData {
    let val = span.0;

    // Extract a field at position `pos` having size `size`.
    let extract = |pos: u32, size: u32| {
        let mask = ((!0u32) as u64 >> (32 - size)) as u32; // Can't shift u32 by 32
        (val >> pos) & mask
    };

    let (base, len, ctxt) = if val & TAG_MASK == TAG_INLINE {(
        extract(INLINE_OFFSETS[BASE_INDEX], INLINE_SIZES[BASE_INDEX]),
        extract(INLINE_OFFSETS[LEN_INDEX], INLINE_SIZES[LEN_INDEX]),
        extract(INLINE_OFFSETS[CTXT_INDEX], INLINE_SIZES[CTXT_INDEX]),
    )} else {
        let index = extract(INTERNED_INDEX_OFFSET, INTERNED_INDEX_SIZE);
        return with_span_interner(|interner| *interner.get(index));
    };
    SpanData { lo: BytePos(base), hi: BytePos(base + len), ctxt: SyntaxContext::from_u32(ctxt) }
}

#[derive(Default)]
pub struct SpanInterner {
    spans: FxHashMap<SpanData, u32>,
    span_data: Vec<SpanData>,
}

impl SpanInterner {
    fn intern(&mut self, span_data: &SpanData) -> u32 {
        if let Some(index) = self.spans.get(span_data) {
            return *index;
        }

        let index = self.spans.len() as u32;
        self.span_data.push(*span_data);
        self.spans.insert(*span_data, index);
        index
    }

    #[inline]
    fn get(&self, index: u32) -> &SpanData {
        &self.span_data[index as usize]
    }
}

// If an interner exists, return it. Otherwise, prepare a fresh one.
#[inline]
fn with_span_interner<T, F: FnOnce(&mut SpanInterner) -> T>(f: F) -> T {
    GLOBALS.with(|globals| f(&mut *globals.span_interner.lock()))
}
<commit_msg>Auto merge of #58458 - nnethercote:tweak-Span-encoding, r=petrochenkov<commit_after>// Spans are encoded using 1-bit tag and 2 different encoding formats (one for each tag value).
// One format is used for keeping span data inline,
// another contains index into an out-of-line span interner.
// The encoding format for inline spans were obtained by optimizing over crates in rustc/libstd.
// See https://internals.rust-lang.org/t/rfc-compiler-refactoring-spans/1357/28

use crate::GLOBALS;
use crate::{BytePos, SpanData};
use crate::hygiene::SyntaxContext;

use rustc_data_structures::fx::FxHashMap;
use std::hash::{Hash, Hasher};

/// A compressed span.
/// Contains either fields of `SpanData` inline if they are small, or index into span interner.
/// The primary goal of `Span` is to be as small as possible and fit into other structures
/// (that's why it uses `packed` as well). Decoding speed is the second priority.
/// See `SpanData` for the info on span fields in decoded representation.
#[repr(packed)]
pub struct Span(u32);

impl Copy for Span {}
impl Clone for Span {
    #[inline]
    fn clone(&self) -> Span {
        *self
    }
}
impl PartialEq for Span {
    #[inline]
    fn eq(&self, other: &Span) -> bool {
        let a = self.0;
        let b = other.0;
        a == b
    }
}
impl Eq for Span {}
impl Hash for Span {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        let a = self.0;
        a.hash(state)
    }
}

/// Dummy span, both position and length are zero, syntax context is zero as well.
/// This span is kept inline and encoded with format 0.
pub const DUMMY_SP: Span = Span(0);

impl Span {
    #[inline]
    pub fn new(lo: BytePos, hi: BytePos, ctxt: SyntaxContext) -> Self {
        encode(&match lo <= hi {
            true => SpanData { lo, hi, ctxt },
            false => SpanData { lo: hi, hi: lo, ctxt },
        })
    }

    #[inline]
    pub fn data(self) -> SpanData {
        decode(self)
    }
}

// Tags
const TAG_INLINE: u32 = 0;
const TAG_INTERNED: u32 = 1;
const TAG_MASK: u32 = 1;

// Fields indexes
const BASE_INDEX: usize = 0;
const LEN_INDEX: usize = 1;
const CTXT_INDEX: usize = 2;

// Tag = 0, inline format.
// -------------------------------------------------------------
// | base 31:7  | len 6:1  | ctxt (currently 0 bits) | tag 0:0 |
// -------------------------------------------------------------
// Since there are zero bits for ctxt, only SpanData with a 0 SyntaxContext
// can be inline.
const INLINE_SIZES: [u32; 3] = [25, 6, 0];
const INLINE_OFFSETS: [u32; 3] = [7, 1, 1];

// Tag = 1, interned format.
// ------------------------
// | index 31:1 | tag 0:0 |
// ------------------------
const INTERNED_INDEX_SIZE: u32 = 31;
const INTERNED_INDEX_OFFSET: u32 = 1;

#[inline]
fn encode(sd: &SpanData) -> Span {
    let (base, len, ctxt) = (sd.lo.0, sd.hi.0 - sd.lo.0, sd.ctxt.as_u32());

    let val = if (base >> INLINE_SIZES[BASE_INDEX]) == 0 &&
                 (len >> INLINE_SIZES[LEN_INDEX]) == 0 &&
                 (ctxt >> INLINE_SIZES[CTXT_INDEX]) == 0 {
        (base << INLINE_OFFSETS[BASE_INDEX]) | (len << INLINE_OFFSETS[LEN_INDEX]) |
        (ctxt << INLINE_OFFSETS[CTXT_INDEX]) | TAG_INLINE
    } else {
        let index = with_span_interner(|interner| interner.intern(sd));
        (index << INTERNED_INDEX_OFFSET) | TAG_INTERNED
    };
    Span(val)
}

#[inline]
fn decode(span: Span) -> SpanData {
    let val = span.0;

    // Extract a field at position `pos` having size `size`.
    let extract = |pos: u32, size: u32| {
        let mask = ((!0u32) as u64 >> (32 - size)) as u32; // Can't shift u32 by 32
        (val >> pos) & mask
    };

    let (base, len, ctxt) = if val & TAG_MASK == TAG_INLINE {(
        extract(INLINE_OFFSETS[BASE_INDEX], INLINE_SIZES[BASE_INDEX]),
        extract(INLINE_OFFSETS[LEN_INDEX], INLINE_SIZES[LEN_INDEX]),
        extract(INLINE_OFFSETS[CTXT_INDEX], INLINE_SIZES[CTXT_INDEX]),
    )} else {
        let index = extract(INTERNED_INDEX_OFFSET, INTERNED_INDEX_SIZE);
        return with_span_interner(|interner| *interner.get(index));
    };
    SpanData { lo: BytePos(base), hi: BytePos(base + len), ctxt: SyntaxContext::from_u32(ctxt) }
}

#[derive(Default)]
pub struct SpanInterner {
    spans: FxHashMap<SpanData, u32>,
    span_data: Vec<SpanData>,
}

impl SpanInterner {
    fn intern(&mut self, span_data: &SpanData) -> u32 {
        if let Some(index) = self.spans.get(span_data) {
            return *index;
        }

        let index = self.spans.len() as u32;
        self.span_data.push(*span_data);
        self.spans.insert(*span_data, index);
        index
    }

    #[inline]
    fn get(&self, index: u32) -> &SpanData {
        &self.span_data[index as usize]
    }
}

// If an interner exists, return it. Otherwise, prepare a fresh one.
#[inline]
fn with_span_interner<T, F: FnOnce(&mut SpanInterner) -> T>(f: F) -> T {
    GLOBALS.with(|globals| f(&mut *globals.span_interner.lock()))
}
<|endoftext|>


<commit_before>use handlebars::RenderError;

use super::*;

impl TaskHandler {
    /// Users can specify a callback that's fired whenever a task finishes.
    /// Execute the callback by spawning a new subprocess.
    pub fn spawn_callback(&mut self, task: &Task) {
        // Return early, if there's no callback specified
        let template_string = if let Some(callback) = &self.callback {
            callback
        } else {
            return;
        };

        // Build the command to be called from the template string in the configuration file.
        let callback_command = match self.build_callback_command(task, template_string) {
            Ok(callback_command) => callback_command,
            Err(err) => {
                error!(
                    "Failed to create callback command from template with error: {}",
                    err
                );
                return;
            }
        };

        let mut command = compile_shell_command(&callback_command);

        // Spawn the callback subprocess and log if it fails.
        let spawn_result = command.spawn();
        let child = match spawn_result {
            Err(error) => {
                error!("Failed to spawn callback with error: {}", error);
                return;
            }
            Ok(child) => child,
        };

        debug!("Spawned callback for task {}", task.id);
        self.callbacks.push(child);
    }

    /// Take the callback template string from the configuration and insert all parameters from the
    /// finished task.
    pub fn build_callback_command(
        &self,
        task: &Task,
        template_string: &str,
    ) -> Result<String, RenderError> {
        // Init Handlebars. We set to strict, as we want to show an error on missing variables.
        let mut handlebars = Handlebars::new();
        handlebars.set_strict_mode(true);

        // Add templating variables.
        let mut parameters = HashMap::new();
        parameters.insert("id", task.id.to_string());
        parameters.insert("command", task.command.clone());
        parameters.insert("path", task.path.clone());
        parameters.insert("group", task.group.clone());

        // Result takes the TaskResult Enum strings, unless it didn't finish yet.
        if let TaskStatus::Done(result) = &task.status {
            parameters.insert("result", result.to_string());
        } else {
            parameters.insert("result", "None".into());
        }

        // Format and insert start and end times.
        let print_time = |time: Option<DateTime<Local>>| {
            time.map(|time| time.timestamp().to_string())
                .unwrap_or_else(String::new)
        };
        parameters.insert("start", print_time(task.start));
        parameters.insert("end", print_time(task.end));

        // Read the last lines of the process' output and make it available.
        if let Ok((stdout, stderr)) =
            read_last_log_file_lines(task.id, &self.pueue_directory, self.callback_log_lines)
        {
            parameters.insert("stdout", stdout);
            parameters.insert("stderr", stderr);
        } else {
            parameters.insert("stdout", "".to_string());
            parameters.insert("stderr", "".to_string());
        }

        // Get the exit code
        if let TaskStatus::Done(result) = &task.status {
            match result {
                TaskResult::Success => parameters.insert("exit_code", "0".into()),
                TaskResult::Failed(code) => parameters.insert("exit_code", code.to_string()),
                _ => parameters.insert("exit_code", "None".into()),
            };
        } else {
            parameters.insert("exit_code", "None".into());
        }

        handlebars.render_template(template_string, &parameters)
    }

    /// Look at all running callbacks and log any errors.
    /// If everything went smoothly, simply remove them from the list.
    pub fn check_callbacks(&mut self) {
        let mut finished = Vec::new();
        for (id, child) in self.callbacks.iter_mut().enumerate() {
            match child.try_wait() {
                // Handle a child error.
                Err(error) => {
                    error!("Callback failed with error {:?}", error);
                    finished.push(id);
                }
                // Child process did not exit yet.
                Ok(None) => continue,
                Ok(exit_status) => {
                    info!("Callback finished with exit code {:?}", exit_status);
                    finished.push(id);
                }
            }
        }

        finished.reverse();
        for id in finished.iter() {
            self.callbacks.remove(*id);
        }
    }
}
<commit_msg>Add output paths to callback template parameters<commit_after>use handlebars::RenderError;

use super::*;

impl TaskHandler {
    /// Users can specify a callback that's fired whenever a task finishes.
    /// Execute the callback by spawning a new subprocess.
    pub fn spawn_callback(&mut self, task: &Task) {
        // Return early, if there's no callback specified
        let template_string = if let Some(callback) = &self.callback {
            callback
        } else {
            return;
        };

        // Build the command to be called from the template string in the configuration file.
        let callback_command = match self.build_callback_command(task, template_string) {
            Ok(callback_command) => callback_command,
            Err(err) => {
                error!(
                    "Failed to create callback command from template with error: {}",
                    err
                );
                return;
            }
        };

        let mut command = compile_shell_command(&callback_command);

        // Spawn the callback subprocess and log if it fails.
        let spawn_result = command.spawn();
        let child = match spawn_result {
            Err(error) => {
                error!("Failed to spawn callback with error: {}", error);
                return;
            }
            Ok(child) => child,
        };

        debug!("Spawned callback for task {}", task.id);
        self.callbacks.push(child);
    }

    /// Take the callback template string from the configuration and insert all parameters from the
    /// finished task.
    pub fn build_callback_command(
        &self,
        task: &Task,
        template_string: &str,
    ) -> Result<String, RenderError> {
        // Init Handlebars. We set to strict, as we want to show an error on missing variables.
        let mut handlebars = Handlebars::new();
        handlebars.set_strict_mode(true);

        // Add templating variables.
        let mut parameters = HashMap::new();
        parameters.insert("id", task.id.to_string());
        parameters.insert("command", task.command.clone());
        parameters.insert("path", task.path.clone());
        parameters.insert("group", task.group.clone());

        // Result takes the TaskResult Enum strings, unless it didn't finish yet.
        if let TaskStatus::Done(result) = &task.status {
            parameters.insert("result", result.to_string());
        } else {
            parameters.insert("result", "None".into());
        }

        // Format and insert start and end times.
        let print_time = |time: Option<DateTime<Local>>| {
            time.map(|time| time.timestamp().to_string())
                .unwrap_or_else(String::new)
        };
        parameters.insert("start", print_time(task.start));
        parameters.insert("end", print_time(task.end));

        // Read the last lines of the process' output and make it available.
        if let Ok((stdout, stderr)) =
            read_last_log_file_lines(task.id, &self.pueue_directory, self.callback_log_lines)
        {
            parameters.insert("stdout", stdout);
            parameters.insert("stderr", stderr);
        } else {
            parameters.insert("stdout", "".to_string());
            parameters.insert("stderr", "".to_string());
        }

        let (out_path, err_path) = get_log_paths(task.id, &self.pueue_directory);
        // Using Display impl of PathBuf which isn't necessarily a perfect
        // representation of the path but should work for most cases here
        parameters.insert("stdout_path", out_path.display().to_string());
        parameters.insert("stderr_path", err_path.display().to_string());

        // Get the exit code
        if let TaskStatus::Done(result) = &task.status {
            match result {
                TaskResult::Success => parameters.insert("exit_code", "0".into()),
                TaskResult::Failed(code) => parameters.insert("exit_code", code.to_string()),
                _ => parameters.insert("exit_code", "None".into()),
            };
        } else {
            parameters.insert("exit_code", "None".into());
        }

        handlebars.render_template(template_string, &parameters)
    }

    /// Look at all running callbacks and log any errors.
    /// If everything went smoothly, simply remove them from the list.
    pub fn check_callbacks(&mut self) {
        let mut finished = Vec::new();
        for (id, child) in self.callbacks.iter_mut().enumerate() {
            match child.try_wait() {
                // Handle a child error.
                Err(error) => {
                    error!("Callback failed with error {:?}", error);
                    finished.push(id);
                }
                // Child process did not exit yet.
                Ok(None) => continue,
                Ok(exit_status) => {
                    info!("Callback finished with exit code {:?}", exit_status);
                    finished.push(id);
                }
            }
        }

        finished.reverse();
        for id in finished.iter() {
            self.callbacks.remove(*id);
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    counters,
    epoch_manager::EpochManager,
    network::NetworkTask,
    network_interface::{ConsensusMsg, ConsensusNetworkEvents, ConsensusNetworkSender},
    network_tests::{NetworkPlayground, TwinId},
    test_utils::{
        consensus_runtime, timed_block_on, MockStateComputer, MockStorage, MockTransactionManager,
    },
    util::time_service::ClockTimeService,
};
use channel::{self, libra_channel, message_queues::QueueStyle};
use consensus_types::{
    block::Block,
    common::{Author, Payload},
};
use futures::channel::mpsc;
use libra_config::{
    config::{
        ConsensusProposerType::{self, FixedProposer, RotatingProposer},
        NodeConfig, WaypointConfig,
    },
    generator::{self, ValidatorSwarm},
};
use libra_mempool::mocks::MockSharedMempool;
use libra_types::{
    ledger_info::LedgerInfoWithSignatures,
    on_chain_config::{OnChainConfig, OnChainConfigPayload, ValidatorSet},
    validator_info::ValidatorInfo,
    waypoint::Waypoint,
};
use network::{
    peer_manager::{conn_notifs_channel, ConnectionRequestSender, PeerManagerRequestSender},
    protocols::network::{NewNetworkEvents, NewNetworkSender},
};
use std::{collections::HashMap, num::NonZeroUsize, sync::Arc};
use tokio::runtime::{Builder, Runtime};

/// Auxiliary struct that is preparing SMR for the test
struct SMRNode {
    config: NodeConfig,
    smr_id: usize,
    runtime: Runtime,
    commit_cb_receiver: mpsc::UnboundedReceiver<LedgerInfoWithSignatures>,
    storage: Arc<MockStorage>,
    state_sync: mpsc::UnboundedReceiver<Payload>,
    shared_mempool: MockSharedMempool,
}

impl SMRNode {
    fn start(
        playground: &mut NetworkPlayground,
        mut config: NodeConfig,
        smr_id: usize,
        storage: Arc<MockStorage>,
        twin_id: TwinId,
    ) -> Self {
        let (network_reqs_tx, network_reqs_rx) =
            libra_channel::new(QueueStyle::FIFO, NonZeroUsize::new(8).unwrap(), None);
        let (connection_reqs_tx, _) =
            libra_channel::new(QueueStyle::FIFO, NonZeroUsize::new(8).unwrap(), None);
        let (consensus_tx, consensus_rx) =
            libra_channel::new(QueueStyle::FIFO, NonZeroUsize::new(8).unwrap(), None);
        let (_conn_mgr_reqs_tx, conn_mgr_reqs_rx) = channel::new_test(8);
        let (_, conn_notifs_channel) = conn_notifs_channel::new();
        let network_sender = ConsensusNetworkSender::new(
            PeerManagerRequestSender::new(network_reqs_tx),
            ConnectionRequestSender::new(connection_reqs_tx),
        );
        let network_events = ConsensusNetworkEvents::new(consensus_rx, conn_notifs_channel);

        playground.add_node(twin_id, consensus_tx, network_reqs_rx, conn_mgr_reqs_rx);

        let (state_sync_client, state_sync) = mpsc::unbounded();
        let (commit_cb_sender, commit_cb_receiver) = mpsc::unbounded::<LedgerInfoWithSignatures>();
        let shared_mempool = MockSharedMempool::new(None);
        let consensus_to_mempool_sender = shared_mempool.consensus_sender.clone();
        let state_computer = Arc::new(MockStateComputer::new(
            state_sync_client,
            commit_cb_sender,
            Arc::clone(&storage),
        ));
        let txn_manager = Arc::new(MockTransactionManager::new(Some(
            consensus_to_mempool_sender,
        )));
        let (mut reconfig_sender, reconfig_events) =
            libra_channel::new(QueueStyle::LIFO, NonZeroUsize::new(1).unwrap(), None);
        let mut configs = HashMap::new();
        configs.insert(
            ValidatorSet::CONFIG_ID,
            lcs::to_bytes(storage.get_validator_set()).unwrap(),
        );
        let payload = OnChainConfigPayload::new(1, Arc::new(configs));
        reconfig_sender.push((), payload).unwrap();

        let runtime = Builder::new()
            .thread_name(format!("node-{}", smr_id))
            .threaded_scheduler()
            .enable_all()
            .build()
            .unwrap();

        let time_service = Arc::new(ClockTimeService::new(runtime.handle().clone()));

        let (timeout_sender, timeout_receiver) =
            channel::new(1_024, &counters::PENDING_ROUND_TIMEOUTS);
        let (self_sender, self_receiver) = channel::new(1_024, &counters::PENDING_SELF_MESSAGES);

        let epoch_mgr = EpochManager::new(
            &mut config,
            time_service,
            self_sender,
            network_sender,
            timeout_sender,
            txn_manager,
            state_computer,
            storage.clone(),
        );
        let (network_task, network_receiver) = NetworkTask::new(network_events, self_receiver);

        runtime.spawn(network_task.start());
        runtime.spawn(epoch_mgr.start(timeout_receiver, network_receiver, reconfig_events));
        Self {
            config,
            smr_id,
            runtime,
            commit_cb_receiver,
            storage,
            state_sync,
            shared_mempool,
        }
    }

    /// Starts a given number of nodes and their twins
    #[cfg(any(test, feature = "fuzzing"))]
    fn start_num_nodes_with_twins(
        num_nodes: usize,
        num_twins: usize,
        playground: &mut NetworkPlayground,
        proposer_type: ConsensusProposerType,
    ) -> (Vec<Self>, Vec<Author>) {
        assert!(num_nodes >= num_twins);
        let ValidatorSwarm { mut nodes } = generator::validator_swarm_for_testing(num_nodes);

        let validator_set = ValidatorSet::new(
            nodes
                .iter()
                .map(|config| {
                    let sr_test_config = config.consensus.safety_rules.test.as_ref().unwrap();
                    ValidatorInfo::new_with_test_network_keys(
                        sr_test_config.author,
                        sr_test_config
                            .consensus_keypair
                            .as_ref()
                            .unwrap()
                            .public_key(),
                        1,
                    )
                })
                .collect(),
        );

        // We don't add twins to ValidatorSet above because a node with
        // twins should be treated the same at the consensus level
        for i in 0..num_twins {
            let twin = nodes[i].clone();
            nodes.push(twin);
        }

        let mut smr_nodes = vec![];
        let mut node_authors = vec![];

        for (smr_id, mut config) in nodes.into_iter().enumerate() {
            let (_, storage) = MockStorage::start_for_testing(validator_set.clone());

            let waypoint = Waypoint::new_epoch_boundary(&storage.get_ledger_info())
                .expect("Unable to produce waypoint with the provided LedgerInfo");
            config
                .consensus
                .safety_rules
                .test
                .as_mut()
                .unwrap()
                .waypoint = Some(waypoint);
            config.base.waypoint = WaypointConfig::FromConfig(waypoint);
            config.consensus.proposer_type = proposer_type;
            config.consensus.safety_rules.verify_vote_proposal_signature = false;

            let author = config.validator_network.as_ref().unwrap().peer_id();

            let twin_id = TwinId { id: smr_id, author };

            smr_nodes.push(Self::start(playground, config, smr_id, storage, twin_id));
            node_authors.push(author);
        }
        (smr_nodes, node_authors)
    }
}

#[test]
/// This test checks that the first proposal has its parent and
/// QC pointing to the genesis block.
///
/// Setup:
///
/// 4 honest nodes, and 0 twins
///
/// Run the test:
/// cargo xtest -p consensus basic_start_test -- --nocapture
#[test]
fn basic_start_test() {
    let mut runtime = consensus_runtime();
    let mut playground = NetworkPlayground::new(runtime.handle().clone());
    let num_nodes = 4;
    let num_twins = 0;
    let (nodes, _) = SMRNode::start_num_nodes_with_twins(
        num_nodes,
        num_twins,
        &mut playground,
        RotatingProposer,
    );
    let genesis = Block::make_genesis_block_from_ledger_info(&nodes[0].storage.get_ledger_info());
    timed_block_on(&mut runtime, async {
        let msg = playground
            .wait_for_messages(1, NetworkPlayground::proposals_only)
            .await;
        let first_proposal = match &msg[0].1 {
            ConsensusMsg::ProposalMsg(proposal) => proposal,
            _ => panic!("Unexpected message found"),
        };
        assert_eq!(first_proposal.proposal().parent_id(), genesis.id());
        assert_eq!(
            first_proposal
                .proposal()
                .quorum_cert()
                .certified_block()
                .id(),
            genesis.id()
        );
    });
}

/// This test checks that the split_network function works
/// as expected, that is: nodes in a partition with less nodes
/// than required for quorum do not commit anything.
///
/// Setup:
///
/// 4 honest nodes (n0, n1, n2, n3), and 0 twins.
/// Create two partitions p1=[n1], and p2=[n0, n2, n3] with
/// a proposer in p2.
///
/// Test:
///
/// Run consensus for enough rounds to potentially form a commit.
/// Check that n1 has no commits, and n0 has commits.
///
/// Run the test:
/// cargo xtest -p consensus drop_config_test -- --nocapture
#[test]
fn drop_config_test() {
    let mut runtime = consensus_runtime();
    let mut playground = NetworkPlayground::new(runtime.handle().clone());
    let num_nodes = 4;
    let num_twins = 0;
    let (mut nodes, node_authors) =
        SMRNode::start_num_nodes_with_twins(num_nodes, num_twins, &mut playground, FixedProposer);

    // 4 honest nodes
    let n0_twin_id = *playground.get_twin_ids(node_authors[0]).get(0).unwrap();
    let n1_twin_id = *playground.get_twin_ids(node_authors[1]).get(0).unwrap();
    let n2_twin_id = *playground.get_twin_ids(node_authors[2]).get(0).unwrap();
    let n3_twin_id = *playground.get_twin_ids(node_authors[3]).get(0).unwrap();
    assert!(playground.split_network(vec![n1_twin_id], vec![n0_twin_id, n2_twin_id, n3_twin_id]));

    timed_block_on(&mut runtime, async {
        playground
            .wait_for_messages(1, NetworkPlayground::proposals_only)
            .await;

        // Pull enough votes to get a few commits
        // The proposer's votes are implicit and do not go in the queue.
        playground
            .wait_for_messages(30, NetworkPlayground::votes_only)
            .await;

        // Check that the commit log for n0 is not empty
        let mut commit_seen = false;
        nodes[0].commit_cb_receiver.close();
        if let Ok(Some(_node_commit)) = nodes[0].commit_cb_receiver.try_next() {
            commit_seen = true;
        }
        assert!(commit_seen);

        // Check that the commit log for n1 is empty
        commit_seen = false;
        nodes[1].commit_cb_receiver.close();
        if let Ok(Some(_node_commit)) = nodes[1].commit_cb_receiver.try_next() {
            commit_seen = true;
        }
        assert!(!commit_seen);
    });
}

#[test]
/// This test checks that the vote of a node and its twin
/// should be counted as duplicate vote (because they have
/// the same public keys)
///
/// Setup:
///
/// 4 honest nodes (n0, n1, n2, n3), and 1 twin (twin0)
/// Create 2 partitions, p1=[n1, n3], p2=[n0, twin0, n2]
///
/// Test:
///
/// Extract enough votes to potentially form commits. Check
/// that no node commits any block. This is because we need
/// 3 nodes to form a quorum and no partition has enough votes
/// (note there are 3 nodes in p2, but one of them is a twin,
/// and its vote will be counted as duplicate of n0).
///
/// Run the test:
/// cargo xtest -p consensus twins_vote_dedup_test -- --nocapture
#[cfg(test)]
fn twins_vote_dedup_test() {
    let mut runtime = consensus_runtime();
    let mut playground = NetworkPlayground::new(runtime.handle().clone());
    let num_nodes = 4;
    let num_twins = 1;
    let (mut nodes, node_authors) = SMRNode::start_num_nodes_with_twins(
        num_nodes,
        num_twins,
        &mut playground,
        RotatingProposer,
    );

    // 4 honest nodes
    let n0_twin_id = *playground.get_twin_ids(node_authors[0]).get(0).unwrap();
    // twin of n0 has same author as node_authors[0]
    let twin0_twin_id = *playground.get_twin_ids(node_authors[0]).get(1).unwrap();
    let n1_twin_id = *playground.get_twin_ids(node_authors[1]).get(0).unwrap();
    let n2_twin_id = *playground.get_twin_ids(node_authors[2]).get(0).unwrap();
    let n3_twin_id = *playground.get_twin_ids(node_authors[3]).get(0).unwrap();

    assert!(playground.split_network(
        vec![n1_twin_id, n3_twin_id],
        vec![twin0_twin_id, n0_twin_id, n2_twin_id],
    ));

    timed_block_on(&mut runtime, async {
        playground
            .wait_for_messages(1, NetworkPlayground::proposals_only)
            .await;

        // Pull enough votes to get a few commits.
        // The proposer's votes are implicit and do not go in the queue.
        playground
            .wait_for_messages(30, NetworkPlayground::votes_only)
            .await;

        // No node should be able to commit because of the way partitions
        // have been created
        let mut commit_seen = false;
        for node in &mut nodes {
            node.commit_cb_receiver.close();
            if let Ok(Some(_node_commit)) = node.commit_cb_receiver.try_next() {
                commit_seen = true;
            }
        }
        assert!(!commit_seen);
    });
}
<commit_msg>[consensus] Disable drop_config_test as it is flaky.<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    counters,
    epoch_manager::EpochManager,
    network::NetworkTask,
    network_interface::{ConsensusMsg, ConsensusNetworkEvents, ConsensusNetworkSender},
    network_tests::{NetworkPlayground, TwinId},
    test_utils::{
        consensus_runtime, timed_block_on, MockStateComputer, MockStorage, MockTransactionManager,
    },
    util::time_service::ClockTimeService,
};
use channel::{self, libra_channel, message_queues::QueueStyle};
use consensus_types::{
    block::Block,
    common::{Author, Payload},
};
use futures::channel::mpsc;
use libra_config::{
    config::{
        ConsensusProposerType::{self, FixedProposer, RotatingProposer},
        NodeConfig, WaypointConfig,
    },
    generator::{self, ValidatorSwarm},
};
use libra_mempool::mocks::MockSharedMempool;
use libra_types::{
    ledger_info::LedgerInfoWithSignatures,
    on_chain_config::{OnChainConfig, OnChainConfigPayload, ValidatorSet},
    validator_info::ValidatorInfo,
    waypoint::Waypoint,
};
use network::{
    peer_manager::{conn_notifs_channel, ConnectionRequestSender, PeerManagerRequestSender},
    protocols::network::{NewNetworkEvents, NewNetworkSender},
};
use std::{collections::HashMap, num::NonZeroUsize, sync::Arc};
use tokio::runtime::{Builder, Runtime};

/// Auxiliary struct that is preparing SMR for the test
struct SMRNode {
    config: NodeConfig,
    smr_id: usize,
    runtime: Runtime,
    commit_cb_receiver: mpsc::UnboundedReceiver<LedgerInfoWithSignatures>,
    storage: Arc<MockStorage>,
    state_sync: mpsc::UnboundedReceiver<Payload>,
    shared_mempool: MockSharedMempool,
}

impl SMRNode {
    fn start(
        playground: &mut NetworkPlayground,
        mut config: NodeConfig,
        smr_id: usize,
        storage: Arc<MockStorage>,
        twin_id: TwinId,
    ) -> Self {
        let (network_reqs_tx, network_reqs_rx) =
            libra_channel::new(QueueStyle::FIFO, NonZeroUsize::new(8).unwrap(), None);
        let (connection_reqs_tx, _) =
            libra_channel::new(QueueStyle::FIFO, NonZeroUsize::new(8).unwrap(), None);
        let (consensus_tx, consensus_rx) =
            libra_channel::new(QueueStyle::FIFO, NonZeroUsize::new(8).unwrap(), None);
        let (_conn_mgr_reqs_tx, conn_mgr_reqs_rx) = channel::new_test(8);
        let (_, conn_notifs_channel) = conn_notifs_channel::new();
        let network_sender = ConsensusNetworkSender::new(
            PeerManagerRequestSender::new(network_reqs_tx),
            ConnectionRequestSender::new(connection_reqs_tx),
        );
        let network_events = ConsensusNetworkEvents::new(consensus_rx, conn_notifs_channel);

        playground.add_node(twin_id, consensus_tx, network_reqs_rx, conn_mgr_reqs_rx);

        let (state_sync_client, state_sync) = mpsc::unbounded();
        let (commit_cb_sender, commit_cb_receiver) = mpsc::unbounded::<LedgerInfoWithSignatures>();
        let shared_mempool = MockSharedMempool::new(None);
        let consensus_to_mempool_sender = shared_mempool.consensus_sender.clone();
        let state_computer = Arc::new(MockStateComputer::new(
            state_sync_client,
            commit_cb_sender,
            Arc::clone(&storage),
        ));
        let txn_manager = Arc::new(MockTransactionManager::new(Some(
            consensus_to_mempool_sender,
        )));
        let (mut reconfig_sender, reconfig_events) =
            libra_channel::new(QueueStyle::LIFO, NonZeroUsize::new(1).unwrap(), None);
        let mut configs = HashMap::new();
        configs.insert(
            ValidatorSet::CONFIG_ID,
            lcs::to_bytes(storage.get_validator_set()).unwrap(),
        );
        let payload = OnChainConfigPayload::new(1, Arc::new(configs));
        reconfig_sender.push((), payload).unwrap();

        let runtime = Builder::new()
            .thread_name(format!("node-{}", smr_id))
            .threaded_scheduler()
            .enable_all()
            .build()
            .unwrap();

        let time_service = Arc::new(ClockTimeService::new(runtime.handle().clone()));

        let (timeout_sender, timeout_receiver) =
            channel::new(1_024, &counters::PENDING_ROUND_TIMEOUTS);
        let (self_sender, self_receiver) = channel::new(1_024, &counters::PENDING_SELF_MESSAGES);

        let epoch_mgr = EpochManager::new(
            &mut config,
            time_service,
            self_sender,
            network_sender,
            timeout_sender,
            txn_manager,
            state_computer,
            storage.clone(),
        );
        let (network_task, network_receiver) = NetworkTask::new(network_events, self_receiver);

        runtime.spawn(network_task.start());
        runtime.spawn(epoch_mgr.start(timeout_receiver, network_receiver, reconfig_events));
        Self {
            config,
            smr_id,
            runtime,
            commit_cb_receiver,
            storage,
            state_sync,
            shared_mempool,
        }
    }

    /// Starts a given number of nodes and their twins
    #[cfg(any(test, feature = "fuzzing"))]
    fn start_num_nodes_with_twins(
        num_nodes: usize,
        num_twins: usize,
        playground: &mut NetworkPlayground,
        proposer_type: ConsensusProposerType,
    ) -> (Vec<Self>, Vec<Author>) {
        assert!(num_nodes >= num_twins);
        let ValidatorSwarm { mut nodes } = generator::validator_swarm_for_testing(num_nodes);

        let validator_set = ValidatorSet::new(
            nodes
                .iter()
                .map(|config| {
                    let sr_test_config = config.consensus.safety_rules.test.as_ref().unwrap();
                    ValidatorInfo::new_with_test_network_keys(
                        sr_test_config.author,
                        sr_test_config
                            .consensus_keypair
                            .as_ref()
                            .unwrap()
                            .public_key(),
                        1,
                    )
                })
                .collect(),
        );

        // We don't add twins to ValidatorSet above because a node with
        // twins should be treated the same at the consensus level
        for i in 0..num_twins {
            let twin = nodes[i].clone();
            nodes.push(twin);
        }

        let mut smr_nodes = vec![];
        let mut node_authors = vec![];

        for (smr_id, mut config) in nodes.into_iter().enumerate() {
            let (_, storage) = MockStorage::start_for_testing(validator_set.clone());

            let waypoint = Waypoint::new_epoch_boundary(&storage.get_ledger_info())
                .expect("Unable to produce waypoint with the provided LedgerInfo");
            config
                .consensus
                .safety_rules
                .test
                .as_mut()
                .unwrap()
                .waypoint = Some(waypoint);
            config.base.waypoint = WaypointConfig::FromConfig(waypoint);
            config.consensus.proposer_type = proposer_type;
            config.consensus.safety_rules.verify_vote_proposal_signature = false;

            let author = config.validator_network.as_ref().unwrap().peer_id();

            let twin_id = TwinId { id: smr_id, author };

            smr_nodes.push(Self::start(playground, config, smr_id, storage, twin_id));
            node_authors.push(author);
        }
        (smr_nodes, node_authors)
    }
}

#[test]
/// This test checks that the first proposal has its parent and
/// QC pointing to the genesis block.
///
/// Setup:
///
/// 4 honest nodes, and 0 twins
///
/// Run the test:
/// cargo xtest -p consensus basic_start_test -- --nocapture
#[test]
fn basic_start_test() {
    let mut runtime = consensus_runtime();
    let mut playground = NetworkPlayground::new(runtime.handle().clone());
    let num_nodes = 4;
    let num_twins = 0;
    let (nodes, _) = SMRNode::start_num_nodes_with_twins(
        num_nodes,
        num_twins,
        &mut playground,
        RotatingProposer,
    );
    let genesis = Block::make_genesis_block_from_ledger_info(&nodes[0].storage.get_ledger_info());
    timed_block_on(&mut runtime, async {
        let msg = playground
            .wait_for_messages(1, NetworkPlayground::proposals_only)
            .await;
        let first_proposal = match &msg[0].1 {
            ConsensusMsg::ProposalMsg(proposal) => proposal,
            _ => panic!("Unexpected message found"),
        };
        assert_eq!(first_proposal.proposal().parent_id(), genesis.id());
        assert_eq!(
            first_proposal
                .proposal()
                .quorum_cert()
                .certified_block()
                .id(),
            genesis.id()
        );
    });
}

/// This test checks that the split_network function works
/// as expected, that is: nodes in a partition with less nodes
/// than required for quorum do not commit anything.
///
/// Setup:
///
/// 4 honest nodes (n0, n1, n2, n3), and 0 twins.
/// Create two partitions p1=[n1], and p2=[n0, n2, n3] with
/// a proposer in p2.
///
/// Test:
///
/// Run consensus for enough rounds to potentially form a commit.
/// Check that n1 has no commits, and n0 has commits.
///
/// Run the test:
/// cargo xtest -p consensus drop_config_test -- --nocapture
#[test]
#[ignore]
/// See https://github.com/libra/libra/issues/4899
fn drop_config_test() {
    let mut runtime = consensus_runtime();
    let mut playground = NetworkPlayground::new(runtime.handle().clone());
    let num_nodes = 4;
    let num_twins = 0;
    let (mut nodes, node_authors) =
        SMRNode::start_num_nodes_with_twins(num_nodes, num_twins, &mut playground, FixedProposer);

    // 4 honest nodes
    let n0_twin_id = *playground.get_twin_ids(node_authors[0]).get(0).unwrap();
    let n1_twin_id = *playground.get_twin_ids(node_authors[1]).get(0).unwrap();
    let n2_twin_id = *playground.get_twin_ids(node_authors[2]).get(0).unwrap();
    let n3_twin_id = *playground.get_twin_ids(node_authors[3]).get(0).unwrap();
    assert!(playground.split_network(vec![n1_twin_id], vec![n0_twin_id, n2_twin_id, n3_twin_id]));

    timed_block_on(&mut runtime, async {
        playground
            .wait_for_messages(1, NetworkPlayground::proposals_only)
            .await;

        // Pull enough votes to get a few commits
        // The proposer's votes are implicit and do not go in the queue.
        playground
            .wait_for_messages(30, NetworkPlayground::votes_only)
            .await;

        // Check that the commit log for n0 is not empty
        let mut commit_seen = false;
        nodes[0].commit_cb_receiver.close();
        if let Ok(Some(_node_commit)) = nodes[0].commit_cb_receiver.try_next() {
            commit_seen = true;
        }
        assert!(commit_seen);

        // Check that the commit log for n1 is empty
        commit_seen = false;
        nodes[1].commit_cb_receiver.close();
        if let Ok(Some(_node_commit)) = nodes[1].commit_cb_receiver.try_next() {
            commit_seen = true;
        }
        assert!(!commit_seen);
    });
}

#[test]
/// This test checks that the vote of a node and its twin
/// should be counted as duplicate vote (because they have
/// the same public keys)
///
/// Setup:
///
/// 4 honest nodes (n0, n1, n2, n3), and 1 twin (twin0)
/// Create 2 partitions, p1=[n1, n3], p2=[n0, twin0, n2]
///
/// Test:
///
/// Extract enough votes to potentially form commits. Check
/// that no node commits any block. This is because we need
/// 3 nodes to form a quorum and no partition has enough votes
/// (note there are 3 nodes in p2, but one of them is a twin,
/// and its vote will be counted as duplicate of n0).
///
/// Run the test:
/// cargo xtest -p consensus twins_vote_dedup_test -- --nocapture
#[cfg(test)]
fn twins_vote_dedup_test() {
    let mut runtime = consensus_runtime();
    let mut playground = NetworkPlayground::new(runtime.handle().clone());
    let num_nodes = 4;
    let num_twins = 1;
    let (mut nodes, node_authors) = SMRNode::start_num_nodes_with_twins(
        num_nodes,
        num_twins,
        &mut playground,
        RotatingProposer,
    );

    // 4 honest nodes
    let n0_twin_id = *playground.get_twin_ids(node_authors[0]).get(0).unwrap();
    // twin of n0 has same author as node_authors[0]
    let twin0_twin_id = *playground.get_twin_ids(node_authors[0]).get(1).unwrap();
    let n1_twin_id = *playground.get_twin_ids(node_authors[1]).get(0).unwrap();
    let n2_twin_id = *playground.get_twin_ids(node_authors[2]).get(0).unwrap();
    let n3_twin_id = *playground.get_twin_ids(node_authors[3]).get(0).unwrap();

    assert!(playground.split_network(
        vec![n1_twin_id, n3_twin_id],
        vec![twin0_twin_id, n0_twin_id, n2_twin_id],
    ));

    timed_block_on(&mut runtime, async {
        playground
            .wait_for_messages(1, NetworkPlayground::proposals_only)
            .await;

        // Pull enough votes to get a few commits.
        // The proposer's votes are implicit and do not go in the queue.
        playground
            .wait_for_messages(30, NetworkPlayground::votes_only)
            .await;

        // No node should be able to commit because of the way partitions
        // have been created
        let mut commit_seen = false;
        for node in &mut nodes {
            node.commit_cb_receiver.close();
            if let Ok(Some(_node_commit)) = node.commit_cb_receiver.try_next() {
                commit_seen = true;
            }
        }
        assert!(!commit_seen);
    });
}
<|endoftext|>