<commit_before>// This file is released under the same terms as Rust itself.

use ci;
use db::{Db, PendingEntry, QueueEntry, RunningEntry};
use std::marker::PhantomData;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use ui::{self, Pr};
use vcs::{self, Commit};

pub trait Worker<E: Send + Clone, M: Send + Clone> {
    fn run(&mut self, recv_msg: Receiver<M>, send_event: Sender<E>);
}

pub struct WorkerThread<E: Send + Clone + 'static, M: Send + Clone + 'static> {
    pub recv_event: Receiver<E>,
    pub send_msg: Sender<M>,
}

impl<E: Send + Clone + 'static, M: Send + Clone + 'static> WorkerThread<E, M> {
    pub fn start<T: Worker<E, M> + Send + 'static>(mut worker: T) -> Self {
        let (send_msg, recv_msg) = channel();
        let (send_event, recv_event) = channel();
        thread::spawn(move || {
            worker.run(recv_msg, send_event);
        });
        WorkerThread {
            recv_event: recv_event,
            send_msg: send_msg,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct PipelineId(pub i32);

pub trait Ci<C: Commit> {
    fn start_build(&self, pipeline_id: PipelineId, commit: C);
}

impl<C: Commit> Ci<C> for WorkerThread<ci::Event<C>, ci::Message<C>> {
    fn start_build(&self, pipeline_id: PipelineId, commit: C) {
        self.send_msg.send(ci::Message::StartBuild(pipeline_id, commit))
            .unwrap();
    }
}

pub trait Ui<P: Pr> {
    fn send_result(&self, PipelineId, P, ui::Status<P>);
}

impl<P> Ui<P> for WorkerThread<ui::Event<P>, ui::Message<P>>
where P: Pr
{
    fn send_result(
        &self,
        pipeline_id: PipelineId,
        pr: P,
        status: ui::Status<P>,
    ) {
        self.send_msg.send(ui::Message::SendResult(pipeline_id, pr, status))
            .unwrap();
    }
}

pub trait Vcs<C: Commit> {
    fn merge_to_staging(&self, PipelineId, C, String, C::Remote);
    fn move_staging_to_master(&self, PipelineId, C);
}

impl<C: Commit> Vcs<C> for WorkerThread<vcs::Event<C>, vcs::Message<C>> {
    fn merge_to_staging(
        &self,
        pipeline_id: PipelineId,
        pull_commit: C,
        message: String,
        remote: C::Remote
    ) {
        self.send_msg.send(vcs::Message::MergeToStaging(
            pipeline_id, pull_commit, message, remote
        )).unwrap();
    }
    fn move_staging_to_master(
        &self,
        pipeline_id: PipelineId,
        merge_commit: C
    ) {
        self.send_msg.send(vcs::Message::MoveStagingToMaster(
            pipeline_id, merge_commit
        )).unwrap();
    }
}

// TODO: When Rust starts enforcing lifetimes on type aliases,
// use a type alias with something like:
//
//     pub type WorkerPipeline<'cntx, P: Pr + 'static> =
//         Pipeline<
//             'cntx,
//             P,
//             WorkerThread<ci::Event<P::C>, ci::Message<P::C>>,
//             WorkerThread<ui::Event<P::C, P>, ui::Message<P>>,
//             WorkerThread<vcs::Event<P::C>, vcs::Message<P>>,
//         >;
//
// That way, we can avoid all these ackward generics in main.
pub struct Pipeline<'cntx, P, B, U, V>
where P: Pr + 'static,
      B: Ci<P::C> + 'cntx,
      U: Ui<P> + 'cntx,
      V: Vcs<P::C> + 'cntx
{
    pub _pr: PhantomData<P>,
    pub id: PipelineId,
    pub ci: &'cntx B,
    pub ui: &'cntx U,
    pub vcs: &'cntx V,
}

#[derive(Clone)]
pub enum Event<P: Pr + 'static> {
    UiEvent(ui::Event<P>),
    VcsEvent(vcs::Event<P::C>),
    CiEvent(ci::Event<P::C>),
}

pub trait GetPipelineId {
    fn pipeline_id(&self) -> PipelineId;
}

impl<P: Pr + 'static> GetPipelineId for Event<P> {
    fn pipeline_id(&self) -> PipelineId {
        match *self {
            Event::UiEvent(ref e) => e.pipeline_id(),
            Event::CiEvent(ref e) => e.pipeline_id(),
            Event::VcsEvent(ref e) => e.pipeline_id(),
        }
    }
}

impl<'cntx, P, B, U, V> Pipeline<'cntx, P, B, U, V>
where P: Pr + 'static,
      B: Ci<P::C> + 'cntx,
      U: Ui<P> + 'cntx,
      V: Vcs<P::C> + 'cntx
{
    pub fn new(
        id: PipelineId,
        ci: &'cntx B,
        ui: &'cntx U,
        vcs: &'cntx V,
    ) -> Self {
        Pipeline {
            _pr: PhantomData,
            id: id,
            ci: ci,
            ui: ui,
            vcs: vcs,
        }
    }
    pub fn handle_event<D: Db<P>>(
        &mut self,
        db: &mut D,
        event: Event<P>,
    ) {
        match event {
            Event::UiEvent(ui::Event::Approved(
                pipeline_id,
                pr,
                commit,
                message,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                let commit = match (
                    commit,
                    db.peek_pending_by_pr(self.id, &pr).map(|p| p.commit),
                ) {
                    (Some(reviewed_pr), Some(current_pr)) => {
                        if reviewed_pr != current_pr {
                            self.ui.send_result(
                                self.id,
                                pr.clone(),
                                ui::Status::Invalidated,
                            );
                            None
                        } else {
                            Some(reviewed_pr)
                        }
                    }
                    (Some(reviewed_pr), None) => {
                        Some(reviewed_pr)
                    }
                    (None, Some(current_pr)) => {
                        Some(current_pr)
                    }
                    (None, None) => {
                        self.ui.send_result(
                            self.id,
                            pr.clone(),
                            ui::Status::NoCommit,
                        );
                        None
                    }
                };
                if let Some(commit) = commit {
                    db.cancel_by_pr(self.id, &pr);
                    db.push_queue(self.id, QueueEntry{
                        commit: commit,
                        pr: pr,
                        message: message,
                    });
                }
            },
            Event::UiEvent(ui::Event::Opened(
                pipeline_id, pr, commit, title, url
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                db.add_pending(self.id, PendingEntry{
                    commit: commit,
                    pr: pr,
                    title: title,
                    url: url,
                });
            },
            Event::UiEvent(ui::Event::Changed(
                pipeline_id, pr, commit, title, url
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if db.cancel_by_pr_different_commit(self.id, &pr, &commit) {
                    self.ui.send_result(
                        self.id,
                        pr.clone(),
                        ui::Status::Invalidated,
                    );
                }
                db.add_pending(self.id, PendingEntry{
                    commit: commit,
                    pr: pr,
                    title: title,
                    url: url,
                });
            },
            Event::UiEvent(ui::Event::Closed(pipeline_id, pr)) => {
                assert_eq!(&pipeline_id, &self.id);
                db.take_pending_by_pr(self.id, &pr);
                db.cancel_by_pr(self.id, &pr);
            },
            Event::UiEvent(ui::Event::Canceled(pipeline_id, pr)) => {
                assert_eq!(&pipeline_id, &self.id);
                db.cancel_by_pr(self.id, &pr);
            },
            Event::VcsEvent(vcs::Event::MergedToStaging(
                pipeline_id,
                pull_commit,
                merge_commit
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(mut running) = db.take_running(self.id) {
                    if running.pull_commit != pull_commit {
                        warn!("VCS merged event with wrong commit");
                    } else if running.merge_commit.is_some() {
                        warn!("VCS merged event with running commit");
                    } else if running.canceled {
                        // Drop it on the floor. It's canceled.
                    } else if running.built {
                        warn!("Got merge finished after finished building!");
                    } else {
                        running.merge_commit = Some(merge_commit.clone());
                        self.ci.start_build(
                            pipeline_id,
                            merge_commit.clone(),
                        );
                        self.ui.send_result(
                            self.id,
                            running.pr.clone(),
                            ui::Status::StartingBuild(
                                pull_commit,
                                merge_commit,
                            ),
                        );
                        db.put_running(self.id, running);
                    }
                } else {
                    warn!("VCS merged event with no queued PR");
                }
            },
            Event::VcsEvent(vcs::Event::FailedMergeToStaging(
                pipeline_id,
                pull_commit,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if running.pull_commit != pull_commit {
                        warn!("VCS merged event with wrong commit");
                    } else if running.merge_commit.is_some() {
                        warn!("VCS merged event with running commit");
                    } else if running.built {
                        warn!("Got merge failed after finished building!");
                    } else if running.canceled {
                        // Drop it on the floor. It's canceled.
                    } else {
                        self.ui.send_result(
                            self.id,
                            running.pr.clone(),
                            ui::Status::Unmergeable(pull_commit),
                        );
                    }
                } else {
                    warn!("VCS merged event with no queued PR");
                }
            },
            Event::CiEvent(ci::Event::BuildStarted(
                pipeline_id,
                building_commit,
                url,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.peek_running(self.id) {
                    if let Some(merged_commit) = running.merge_commit {
                        if merged_commit != building_commit {
                            warn!("Building a different commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if running.built {
                            warn!("Got CI build started after done building!");
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr.clone(),
                                ui::Status::Testing(
                                    running.pull_commit.clone(),
                                    building_commit,
                                    url,
                                ),
                            );
                        }
                    } else {
                        warn!("Building a commit that never merged");
                    }
                } else {
                    warn!("CI build started event with no queued PR");
                }
            },
            Event::CiEvent(ci::Event::BuildFailed(
                pipeline_id,
                built_commit,
                url,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if let Some(ref merged_commit) = running.merge_commit {
                        if merged_commit != &built_commit {
                            warn!("Finished building a different commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if running.built {
                            warn!("Got duplicate BuildFailed event");
                            // Put it back
                            db.put_running(self.id, running.clone());
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr.clone(),
                                ui::Status::Failure(
                                    running.pull_commit.clone(),
                                    merged_commit.clone(),
                                    url,
                                ),
                            );
                        }
                    } else {
                        warn!("Finished building a commit that never merged");
                    }
                } else {
                    warn!("CI build failed event with no queued PR");
                }
            },
            Event::CiEvent(ci::Event::BuildSucceeded(
                pipeline_id,
                built_commit,
                url,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(mut running) = db.take_running(self.id) {
                    if let Some(ref merged_commit) = running.merge_commit {
                        if merged_commit != &built_commit {
                            warn!("Finished building a different commit")
                        } else if running.canceled {
                            // Canceled; drop on the floor.
                        } else if running.built {
                            warn!("Got duplicate BuildSucceeded event");
                            // Put it back.
                            db.put_running(self.id, running.clone());
                        } else {
                            self.vcs.move_staging_to_master(
                                self.id,
                                merged_commit.clone(),
                            );
                            self.ui.send_result(
                                self.id,
                                running.pr.clone(),
                                ui::Status::Success(
                                    running.pull_commit.clone(),
                                    merged_commit.clone(),
                                    url,
                                ),
                            );
                            // Put it back with it marked as built.
                            running.built = true;
                            db.put_running(self.id, running.clone());
                        }
                    } else {
                        warn!("Finished building a commit that never merged");
                    }
                } else {
                    warn!("CI build succeeded event with no queued PR");
                }
            },
            Event::VcsEvent(vcs::Event::FailedMoveToMaster(
                pipeline_id,
                merge_commit,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if let Some(running_merge_commit) = running.merge_commit {
                        if running_merge_commit != merge_commit {
                            warn!("VCS move event with wrong commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if !running.built {
                            warn!("Failed move to master before built!");
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr,
                                ui::Status::Unmoveable(
                                    running.pull_commit,
                                    running_merge_commit,
                                ),
                            );
                        }
                    } else {
                        warn!("VCS move event with commit that never ran");
                    }
                } else {
                    warn!("VCS move event with no queued PR");
                }
            },
            Event::VcsEvent(vcs::Event::MovedToMaster(
                pipeline_id,
                merge_commit,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if let Some(running_merge_commit) = running.merge_commit {
                        if running_merge_commit != merge_commit {
                            warn!("VCS move event with wrong commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if !running.built {
                            warn!("Moved to master before done building!");
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr,
                                ui::Status::Completed(
                                    running.pull_commit,
                                    running_merge_commit,
                                ),
                            );
                        }
                    } else {
                        warn!("VCS move event with commit that never ran");
                    }
                } else {
                    warn!("VCS move event with no queued PR");
                }
            }
        }
        if db.peek_running(self.id).is_none() {
            if let Some(next) = db.pop_queue(self.id) {
                self.vcs.merge_to_staging(
                    self.id,
                    next.commit.clone(),
                    next.message.clone(),
                    next.pr.remote(),
                );
                db.put_running(self.id, RunningEntry{
                    pr: next.pr,
                    message: next.message,
                    pull_commit: next.commit,
                    merge_commit: None,
                    canceled: false,
                    built: false,
                });
            }
        }
    }
}

#[cfg(test)] mod test;
<commit_msg>Harden pipeline<commit_after>// This file is released under the same terms as Rust itself.

use ci;
use db::{Db, PendingEntry, QueueEntry, RunningEntry};
use std::marker::PhantomData;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use ui::{self, Pr};
use vcs::{self, Commit};

pub trait Worker<E: Send + Clone, M: Send + Clone> {
    fn run(&mut self, recv_msg: Receiver<M>, send_event: Sender<E>);
}

pub struct WorkerThread<E: Send + Clone + 'static, M: Send + Clone + 'static> {
    pub recv_event: Receiver<E>,
    pub send_msg: Sender<M>,
}

impl<E: Send + Clone + 'static, M: Send + Clone + 'static> WorkerThread<E, M> {
    pub fn start<T: Worker<E, M> + Send + 'static>(mut worker: T) -> Self {
        let (send_msg, recv_msg) = channel();
        let (send_event, recv_event) = channel();
        thread::spawn(move || {
            worker.run(recv_msg, send_event);
        });
        WorkerThread {
            recv_event: recv_event,
            send_msg: send_msg,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct PipelineId(pub i32);

pub trait Ci<C: Commit> {
    fn start_build(&self, pipeline_id: PipelineId, commit: C);
}

impl<C: Commit> Ci<C> for WorkerThread<ci::Event<C>, ci::Message<C>> {
    fn start_build(&self, pipeline_id: PipelineId, commit: C) {
        self.send_msg.send(ci::Message::StartBuild(pipeline_id, commit))
            .unwrap();
    }
}

pub trait Ui<P: Pr> {
    fn send_result(&self, PipelineId, P, ui::Status<P>);
}

impl<P> Ui<P> for WorkerThread<ui::Event<P>, ui::Message<P>>
where P: Pr
{
    fn send_result(
        &self,
        pipeline_id: PipelineId,
        pr: P,
        status: ui::Status<P>,
    ) {
        self.send_msg.send(ui::Message::SendResult(pipeline_id, pr, status))
            .unwrap();
    }
}

pub trait Vcs<C: Commit> {
    fn merge_to_staging(&self, PipelineId, C, String, C::Remote);
    fn move_staging_to_master(&self, PipelineId, C);
}

impl<C: Commit> Vcs<C> for WorkerThread<vcs::Event<C>, vcs::Message<C>> {
    fn merge_to_staging(
        &self,
        pipeline_id: PipelineId,
        pull_commit: C,
        message: String,
        remote: C::Remote
    ) {
        self.send_msg.send(vcs::Message::MergeToStaging(
            pipeline_id, pull_commit, message, remote
        )).unwrap();
    }
    fn move_staging_to_master(
        &self,
        pipeline_id: PipelineId,
        merge_commit: C
    ) {
        self.send_msg.send(vcs::Message::MoveStagingToMaster(
            pipeline_id, merge_commit
        )).unwrap();
    }
}

// TODO: When Rust starts enforcing lifetimes on type aliases,
// use a type alias with something like:
//
//     pub type WorkerPipeline<'cntx, P: Pr + 'static> =
//         Pipeline<
//             'cntx,
//             P,
//             WorkerThread<ci::Event<P::C>, ci::Message<P::C>>,
//             WorkerThread<ui::Event<P::C, P>, ui::Message<P>>,
//             WorkerThread<vcs::Event<P::C>, vcs::Message<P>>,
//         >;
//
// That way, we can avoid all these ackward generics in main.
pub struct Pipeline<'cntx, P, B, U, V>
where P: Pr + 'static,
      B: Ci<P::C> + 'cntx,
      U: Ui<P> + 'cntx,
      V: Vcs<P::C> + 'cntx
{
    pub _pr: PhantomData<P>,
    pub id: PipelineId,
    pub ci: &'cntx B,
    pub ui: &'cntx U,
    pub vcs: &'cntx V,
}

#[derive(Clone)]
pub enum Event<P: Pr + 'static> {
    UiEvent(ui::Event<P>),
    VcsEvent(vcs::Event<P::C>),
    CiEvent(ci::Event<P::C>),
}

pub trait GetPipelineId {
    fn pipeline_id(&self) -> PipelineId;
}

impl<P: Pr + 'static> GetPipelineId for Event<P> {
    fn pipeline_id(&self) -> PipelineId {
        match *self {
            Event::UiEvent(ref e) => e.pipeline_id(),
            Event::CiEvent(ref e) => e.pipeline_id(),
            Event::VcsEvent(ref e) => e.pipeline_id(),
        }
    }
}

impl<'cntx, P, B, U, V> Pipeline<'cntx, P, B, U, V>
where P: Pr + 'static,
      B: Ci<P::C> + 'cntx,
      U: Ui<P> + 'cntx,
      V: Vcs<P::C> + 'cntx
{
    pub fn new(
        id: PipelineId,
        ci: &'cntx B,
        ui: &'cntx U,
        vcs: &'cntx V,
    ) -> Self {
        Pipeline {
            _pr: PhantomData,
            id: id,
            ci: ci,
            ui: ui,
            vcs: vcs,
        }
    }
    pub fn handle_event<D: Db<P>>(
        &mut self,
        db: &mut D,
        event: Event<P>,
    ) {
        match event {
            Event::UiEvent(ui::Event::Approved(
                pipeline_id,
                pr,
                commit,
                message,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                let commit = match (
                    commit,
                    db.peek_pending_by_pr(self.id, &pr).map(|p| p.commit),
                ) {
                    (Some(reviewed_pr), Some(current_pr)) => {
                        if reviewed_pr != current_pr {
                            self.ui.send_result(
                                self.id,
                                pr.clone(),
                                ui::Status::Invalidated,
                            );
                            None
                        } else {
                            Some(reviewed_pr)
                        }
                    }
                    (Some(reviewed_pr), None) => {
                        Some(reviewed_pr)
                    }
                    (None, Some(current_pr)) => {
                        Some(current_pr)
                    }
                    (None, None) => {
                        self.ui.send_result(
                            self.id,
                            pr.clone(),
                            ui::Status::NoCommit,
                        );
                        None
                    }
                };
                if let Some(commit) = commit {
                    db.cancel_by_pr(self.id, &pr);
                    db.push_queue(self.id, QueueEntry{
                        commit: commit,
                        pr: pr,
                        message: message,
                    });
                }
            },
            Event::UiEvent(ui::Event::Opened(
                pipeline_id, pr, commit, title, url
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                db.add_pending(self.id, PendingEntry{
                    commit: commit,
                    pr: pr,
                    title: title,
                    url: url,
                });
            },
            Event::UiEvent(ui::Event::Changed(
                pipeline_id, pr, commit, title, url
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if db.cancel_by_pr_different_commit(self.id, &pr, &commit) {
                    self.ui.send_result(
                        self.id,
                        pr.clone(),
                        ui::Status::Invalidated,
                    );
                }
                db.add_pending(self.id, PendingEntry{
                    commit: commit,
                    pr: pr,
                    title: title,
                    url: url,
                });
            },
            Event::UiEvent(ui::Event::Closed(pipeline_id, pr)) => {
                assert_eq!(&pipeline_id, &self.id);
                db.take_pending_by_pr(self.id, &pr);
                db.cancel_by_pr(self.id, &pr);
            },
            Event::UiEvent(ui::Event::Canceled(pipeline_id, pr)) => {
                assert_eq!(&pipeline_id, &self.id);
                db.cancel_by_pr(self.id, &pr);
            },
            Event::VcsEvent(vcs::Event::MergedToStaging(
                pipeline_id,
                pull_commit,
                merge_commit
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(mut running) = db.take_running(self.id) {
                    if running.pull_commit != pull_commit {
                        warn!("VCS merged event with wrong commit");
                    } else if running.merge_commit.is_some() {
                        warn!("VCS merged event with running commit");
                    } else if running.canceled {
                        // Drop it on the floor. It's canceled.
                    } else if running.built {
                        warn!("Got merge finished after finished building!");
                    } else {
                        running.merge_commit = Some(merge_commit.clone());
                        self.ci.start_build(
                            pipeline_id,
                            merge_commit.clone(),
                        );
                        self.ui.send_result(
                            self.id,
                            running.pr.clone(),
                            ui::Status::StartingBuild(
                                pull_commit,
                                merge_commit,
                            ),
                        );
                        db.put_running(self.id, running);
                    }
                } else {
                    warn!("VCS merged event with no queued PR");
                }
            },
            Event::VcsEvent(vcs::Event::FailedMergeToStaging(
                pipeline_id,
                pull_commit,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if running.pull_commit != pull_commit {
                        warn!("VCS merged event with wrong commit");
                    } else if running.merge_commit.is_some() {
                        warn!("VCS merged event with running commit");
                    } else if running.built {
                        warn!("Got merge failed after finished building!");
                    } else if running.canceled {
                        // Drop it on the floor. It's canceled.
                    } else {
                        self.ui.send_result(
                            self.id,
                            running.pr.clone(),
                            ui::Status::Unmergeable(pull_commit),
                        );
                    }
                } else {
                    warn!("VCS merged event with no queued PR");
                }
            },
            Event::CiEvent(ci::Event::BuildStarted(
                pipeline_id,
                building_commit,
                url,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.peek_running(self.id) {
                    if let Some(merged_commit) = running.merge_commit {
                        if merged_commit != building_commit {
                            warn!("Building a different commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if running.built {
                            warn!("Got CI build started after done building!");
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr.clone(),
                                ui::Status::Testing(
                                    running.pull_commit.clone(),
                                    building_commit,
                                    url,
                                ),
                            );
                        }
                    } else {
                        warn!("Building a commit that never merged");
                    }
                } else {
                    warn!("CI build started event with no queued PR");
                }
            },
            Event::CiEvent(ci::Event::BuildFailed(
                pipeline_id,
                built_commit,
                url,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if let Some(ref merged_commit) = running.merge_commit {
                        if merged_commit != &built_commit {
                            warn!("Finished building a different commit");
                            db.put_running(self.id, running.clone());
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if running.built {
                            warn!("Got duplicate BuildFailed event");
                            // Put it back
                            db.put_running(self.id, running.clone());
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr.clone(),
                                ui::Status::Failure(
                                    running.pull_commit.clone(),
                                    merged_commit.clone(),
                                    url,
                                ),
                            );
                        }
                    } else {
                        warn!("Finished building a commit that never merged");
                    }
                } else {
                    warn!("CI build failed event with no queued PR");
                }
            },
            Event::CiEvent(ci::Event::BuildSucceeded(
                pipeline_id,
                built_commit,
                url,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(mut running) = db.take_running(self.id) {
                    if let Some(ref merged_commit) = running.merge_commit {
                        if merged_commit != &built_commit {
                            warn!("Finished building a different commit")
                        } else if running.canceled {
                            // Canceled; drop on the floor.
                        } else if running.built {
                            warn!("Got duplicate BuildSucceeded event");
                            // Put it back.
                            db.put_running(self.id, running.clone());
                        } else {
                            self.vcs.move_staging_to_master(
                                self.id,
                                merged_commit.clone(),
                            );
                            self.ui.send_result(
                                self.id,
                                running.pr.clone(),
                                ui::Status::Success(
                                    running.pull_commit.clone(),
                                    merged_commit.clone(),
                                    url,
                                ),
                            );
                            // Put it back with it marked as built.
                            running.built = true;
                            db.put_running(self.id, running.clone());
                        }
                    } else {
                        warn!("Finished building a commit that never merged");
                    }
                } else {
                    warn!("CI build succeeded event with no queued PR");
                }
            },
            Event::VcsEvent(vcs::Event::FailedMoveToMaster(
                pipeline_id,
                merge_commit,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if let Some(running_merge_commit) = running.merge_commit {
                        if running_merge_commit != merge_commit {
                            warn!("VCS move event with wrong commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if !running.built {
                            warn!("Failed move to master before built!");
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr,
                                ui::Status::Unmoveable(
                                    running.pull_commit,
                                    running_merge_commit,
                                ),
                            );
                        }
                    } else {
                        warn!("VCS move event with commit that never ran");
                    }
                } else {
                    warn!("VCS move event with no queued PR");
                }
            },
            Event::VcsEvent(vcs::Event::MovedToMaster(
                pipeline_id,
                merge_commit,
            )) => {
                assert_eq!(&pipeline_id, &self.id);
                if let Some(running) = db.take_running(self.id) {
                    if let Some(running_merge_commit) = running.merge_commit {
                        if running_merge_commit != merge_commit {
                            warn!("VCS move event with wrong commit");
                        } else if running.canceled {
                            // Drop it on the floor. It's canceled.
                        } else if !running.built {
                            warn!("Moved to master before done building!");
                        } else {
                            self.ui.send_result(
                                self.id,
                                running.pr,
                                ui::Status::Completed(
                                    running.pull_commit,
                                    running_merge_commit,
                                ),
                            );
                        }
                    } else {
                        warn!("VCS move event with commit that never ran");
                    }
                } else {
                    warn!("VCS move event with no queued PR");
                }
            }
        }
        if db.peek_running(self.id).is_none() {
            if let Some(next) = db.pop_queue(self.id) {
                self.vcs.merge_to_staging(
                    self.id,
                    next.commit.clone(),
                    next.message.clone(),
                    next.pr.remote(),
                );
                db.put_running(self.id, RunningEntry{
                    pr: next.pr,
                    message: next.message,
                    pull_commit: next.commit,
                    merge_commit: None,
                    canceled: false,
                    built: false,
                });
            }
        }
    }
}

#[cfg(test)] mod test;
<|endoftext|>


<commit_before>#[macro_use] extern crate demo_hack;

two_fn!(two);

fn main() {
    let x = two();
    let nine = add_one!(x) + add_one!(2 + 3);
    println!("nine = {}", nine);
}
<commit_msg>#[macro_use] gets its own line<commit_after>#[macro_use]
extern crate demo_hack;

two_fn!(two);

fn main() {
    let x = two();
    let nine = add_one!(x) + add_one!(2 + 3);
    println!("nine = {}", nine);
}
<|endoftext|>


<commit_before>extern crate zip;

use std::io;
use std::fs;

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

fn main() {
    std::process::exit(real_main());
}

fn real_main() -> i32
{
    let args: Vec<_> = std::env::args().collect();
    if args.len() < 2 {
        println!("Usage: {} <filename>", args[0]);
        return 1;
    }
    let fname = std::path::Path::new(&*args[1]);
    let file = fs::File::open(&fname).unwrap();

    let mut archive = zip::ZipArchive::new(file).unwrap();

    for i in 0..archive.len()
    {
        let mut file = archive.by_index(i).unwrap();
        let outpath = sanitize_filename(file.name());
        println!("{}", outpath.display());

        {
            let comment = file.comment();
            if comment.len() > 0 { println!("  File comment: {}", comment); }
        }

        create_directory(outpath.parent().unwrap_or(std::path::Path::new("")), None);

        let perms = convert_permissions(file.unix_mode());

        if (&*file.name()).ends_with("/") {
            create_directory(&outpath, perms);

        }
        else {
            write_file(&mut file, &outpath, perms);
        }
    }

    return 0;
}

#[cfg(unix)]
fn convert_permissions(mode: Option<u32>) -> Option<fs::Permissions>
{
    match mode {
        Some(mode) => Some(fs::Permissions::from_mode(mode)),
        None => None,
    }
}
#[cfg(not(unix))]
fn convert_permissions(_mode: Option<u32>) -> Option<fs::Permissions>
{
    None
}

fn write_file(file: &mut zip::read::ZipFile, outpath: &std::path::Path, perms: Option<fs::Permissions>)
{
    let mut outfile = fs::File::create(&outpath).unwrap();
    io::copy(file, &mut outfile).unwrap();
    if let Some(perms) = perms {
        fs::set_permissions(outpath, perms).unwrap();
    }
}

fn create_directory(outpath: &std::path::Path, perms: Option<fs::Permissions>)
{
    fs::create_dir_all(&outpath).unwrap();
    if let Some(perms) = perms {
        fs::set_permissions(outpath, perms).unwrap();
    }
}

fn sanitize_filename(filename: &str) -> std::path::PathBuf
{
    let no_null_filename = match filename.find('\0') {
        Some(index) => &filename[0..index],
        None => filename,
    };

    std::path::Path::new(no_null_filename)
        .components()
        .filter(|component| match *component {
            std::path::Component::Normal(..) => true,
            _ => false
        })
        .fold(std::path::PathBuf::new(), |mut path, ref cur| {
            path.push(cur.as_os_str());
            path
        })
}
<commit_msg>Modify examples/extract.rs, reduce code.<commit_after>extern crate zip;

use std::io;
use std::fs;

fn main() {
    std::process::exit(real_main());
}

fn real_main() -> i32 {
    let args: Vec<_> = std::env::args().collect();
    if args.len() < 2 {
        println!("Usage: {} <filename>", args[0]);
        return 1;
    }
    let fname = std::path::Path::new(&*args[1]);
    let file = fs::File::open(&fname).unwrap();

    let mut archive = zip::ZipArchive::new(file).unwrap();

    for i in 0..archive.len() {
        let mut file = archive.by_index(i).unwrap();
        let outpath = sanitize_filename(file.name());

        {
            let comment = file.comment();
            if !comment.is_empty() {
                println!("${} comment: {}", i, comment);
            }
        }

        if (&*file.name()).ends_with('/') {
            println!("${}-> {}", i, outpath.as_path().display());
            fs::create_dir_all(&outpath).unwrap();
        } else {
            println!("${}-> {}: {}", i, outpath.as_path().display(), file.size());
            if let Some(p) = outpath.parent() {
                if !p.exists() {
                    fs::create_dir_all(&p).unwrap();
                }
            }
            let mut outfile = fs::File::create(&outpath).unwrap();
            io::copy(&mut file, &mut outfile).unwrap();
        }

        // Get and Set permissions
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;

            if let Some(mode) = file.unix_mode() {
                fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap();
            }
        }
    }
    return 0;
}

fn sanitize_filename(filename: &str) -> std::path::PathBuf {
    let no_null_filename = match filename.find('\0') {
        Some(index) => &filename[0..index],
        None => filename,
    };

    std::path::Path::new(no_null_filename)
        .components()
        .filter(|component| match *component {
            std::path::Component::Normal(..) => true,
            _ => false,
        })
        .fold(std::path::PathBuf::new(), |mut path, ref cur| {
            path.push(cur.as_os_str());
            path
        })
}
<|endoftext|>


<commit_before>use std::io;
use std::path::{PathBuf, Path};
use std::fs::File;
use std::cmp::{min, Ordering, PartialEq, Ord};
use std::collections::HashMap;
use object::{Object, File as OFile};
use memmap::MmapOptions;
use gimli::*;
use rustc_demangle::demangle;
use cargo::core::Workspace;

use config::Config;
use source_analysis::*;
use traces::*;

/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
type FuncDesc = (u64, u64, FunctionType);

#[derive(Clone, Copy, PartialEq)]
enum FunctionType {
    Generated,
    Test,
    Standard
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LineType {
    /// Generated test main. Shouldn't be traced.
    TestMain,
    /// Entry of function known to be a test
    TestEntry(u64),
    /// Entry of function. May or may not be test
    FunctionEntry(u64),
    /// Standard statement
    Statement,
    /// Condition
    Condition,
    /// Unknown type
    Unknown,
    /// Unused meta-code
    UnusedGeneric,
}

#[derive(Clone, PartialEq, Eq, Hash)]
struct SourceLocation {
    pub path: PathBuf,
    pub line: u64,
}

#[derive(Debug, Clone, Eq, PartialOrd)]
pub struct TracerData {
    pub path: PathBuf,
    pub line: u64,
    pub address: Option<u64>,
    pub trace_type: LineType,
    pub hits: u64,
}

impl PartialEq for TracerData {
    fn eq(&self, other: &TracerData) -> bool {
        (self.path == other.path) && (self.line == other.line)
    }
}

impl Ord for TracerData {

    fn cmp(&self, other: &TracerData) -> Ordering {
        if self == other {
            Ordering::Equal
        } else if self.path == other.path {
            if self.line > other.line {
                Ordering::Greater
            } else {
                Ordering::Less
            }
        } else if self.path > other.path {
            Ordering::Greater
        } else {
            Ordering::Less
        }
    }
}

fn generate_func_desc<R, Offset>(die: &DebuggingInformationEntry<R, Offset>,
                                 debug_str: &DebugStr<R>) -> Result<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut func_type = FunctionType::Standard;
    let low = die.attr_value(DW_AT_low_pc)?;
    let high = die.attr_value(DW_AT_high_pc)?;
    let linkage = die.attr_value(DW_AT_linkage_name)?;

    // Low is a program counter address so stored in an Addr
    let low = match low {
        Some(AttributeValue::Addr(x)) => x,
        _ => 0u64,
    };
    // High is an offset from the base pc, therefore is u64 data.
    let high = match high {
        Some(AttributeValue::Udata(x)) => x,
        _ => 0u64,
    };
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
        let name = debug_str.get_str(offset)
            .and_then(|r| r.to_string().map(|s| s.to_string()))
            .unwrap_or("".into());
        let name = demangle(name.as_ref()).to_string();
        // Simplest test is whether it's in tests namespace.
        // Rust guidelines recommend all tests are in a tests module.
        func_type = if name.contains("tests::") {
            FunctionType::Test
        } else if name.contains("__test::main") {
            FunctionType::Generated
        } else {
            FunctionType::Standard
        };
    }
    Ok((low, high, func_type))
}

/// Finds all function entry points and returns a vector
/// This will identify definite tests, but may be prone to false negatives.
fn get_entry_points<R, Offset>(debug_info: &CompilationUnitHeader<R, Offset>,
                               debug_abbrev: &Abbreviations,
                               debug_str: &DebugStr<R>) -> Vec<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result:Vec<FuncDesc> = Vec::new();
    let mut cursor = debug_info.entries(debug_abbrev);
    // skip compilation unit root.
    let _ = cursor.next_entry();
    while let Ok(Some((_, node))) = cursor.next_dfs() {
        // Function DIE
        if node.tag() == DW_TAG_subprogram {

            if let Ok(fd) = generate_func_desc(node, debug_str) {
                result.push(fd);
            }
        }
    }
    result
}

fn get_addresses_from_program<R, Offset>(prog: IncompleteLineNumberProgram<R>,
                                         entries: &[(u64, LineType)],
                                         project: &Path) -> Result<HashMap<SourceLocation, TracerData>>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result: HashMap<SourceLocation, TracerData> = HashMap::new();
    let ( cprog, seq) = prog.sequences()?;
    for s in seq {
        let mut sm = cprog.resume_from(&s);
         while let Ok(Some((header, &ln_row))) = sm.next_row() {
            if let Some(file) = ln_row.file(header) {
                let mut path = PathBuf::new();

                if let Some(dir) = file.directory(header) {
                    if let Ok(temp) = dir.to_string() {
                        path.push(temp.as_ref());
                    }
                }
                if let Ok(p) = path.canonicalize() {
                    path = p;
                }
                let is_target = path.starts_with(project.join("target"));
                // Fix relative paths and determine if in target directory
                // Source in target directory shouldn't be covered as it's either
                // autogenerated or resulting from the projects Cargo.lock
                let is_target = if path.is_relative() {
                    path.starts_with("target")
                } else {
                    path.starts_with(project.join("target"))
                };
                // Source is part of project so we cover it.
                if !is_target && path.starts_with(project) {
                    let force_test = path.starts_with(project.join("tests"));
                    if let Some(file) = ln_row.file(header) {
                        // If we can't map to line, we can't trace it.
                        let line = match ln_row.line() {
                            Some(l) => l,
                            None => continue,
                        };

                        let file = file.path_name();

                        if let Ok(file) = file.to_string() {
                            path.push(file.as_ref());
                            if !path.is_file() {
                                // Not really a source file!
                                continue;
                            }
                            let address = ln_row.address();

                            let desc = entries.iter()
                                              .filter(|&&(addr, _)| addr == address )
                                              .map(|&(_, t)| t)
                                              .nth(0)
                                              .unwrap_or(LineType::Unknown);
                            let loc = SourceLocation {
                                path: path.clone(),
                                line: line,
                            };
                            if result.contains_key(&loc) {
                                let mut x = result.get_mut(&loc).unwrap();
                                if let Some(mut addr) = x.address {
                                    addr = min(address, addr);
                                }
                            } else {
                                result.insert(loc, TracerData {
                                    path: path.clone(),
                                    line: line,
                                    address: Some(address),
                                    trace_type: desc,
                                    hits: 0u64
                                });
                            }
                        }
                    }
                }
            }
        }
    }
    Ok(result)
}

fn get_line_addresses(endian: RunTimeEndian,
                      project: &Path,
                      obj: &OFile,
                      analysis: &HashMap<PathBuf, LineAnalysis>,
                      config: &Config) -> Result<Vec<TracerData>>  {

    let mut result: Vec<TracerData> = Vec::new();
    let debug_info = obj.section_data_by_name(".debug_info").unwrap_or(&[]);
    let debug_info = DebugInfo::new(debug_info, endian);
    let debug_abbrev = obj.section_data_by_name(".debug_abbrev").unwrap_or(&[]);
    let debug_abbrev = DebugAbbrev::new(debug_abbrev, endian);
    let debug_strings = obj.section_data_by_name(".debug_str").unwrap_or(&[]);
    let debug_strings = DebugStr::new(debug_strings, endian);
    let debug_line = obj.section_data_by_name(".debug_line").unwrap_or(&[]);
    let debug_line = DebugLine::new(debug_line, endian);

    let mut iter = debug_info.units();
    while let Ok(Some(cu)) = iter.next() {
        let addr_size = cu.address_size();
        let abbr = match cu.abbreviations(&debug_abbrev) {
            Ok(a) => a,
            _ => continue,
        };
        let entries = get_entry_points(&cu, &abbr, &debug_strings)
            .iter()
            .map(|&(a, b, c)| {
                match c {
                    FunctionType::Test => (a, LineType::TestEntry(b)),
                    FunctionType::Standard => (a, LineType::FunctionEntry(b)),
                    FunctionType::Generated => (a, LineType::TestMain),
                }
            }).collect::<Vec<_>>();
        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
            let offset = match root.attr_value(DW_AT_stmt_list) {
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
                _ => continue,
            };
            let prog = debug_line.program(offset, addr_size, None, None)?;
            if let Ok(mut addresses) = get_addresses_from_program(prog, &entries, project) {
                for val in addresses.values() {
                    result.push(val.clone());
                }
            }
        }
    }
    // Due to rust being a higher level language multiple instructions may map
    // to the same line. This prunes these to just the first instruction address
    let mut result = result.iter()
                           .filter(|x| !(config.ignore_tests && x.path.starts_with(project.join("tests"))))
                           .filter(|x| !(config.exclude_path(&x.path)))
                           .filter(|x| !analysis.should_ignore(x.path.as_ref(), &(x.line as usize)))
                           .filter(|x| x.trace_type != LineType::TestMain)
                           .cloned()
                           .collect::<Vec<TracerData>>();

    let addresses = result.iter()
                          .map(|x| x.address.unwrap())
                          .collect::<Vec<_>>();
    // TODO Probably a more idiomatic way to do this.
    {
        let test_entries = result.iter_mut()
                                 .filter(|x| match x.trace_type {
                                     LineType::TestEntry(_) => true,
                                     _ => false,
                                 });

        for test_entry in test_entries {
            let endpoint = match test_entry.trace_type {
                LineType::TestEntry(x) => x,
                _ => continue,
            };
            let addr = test_entry.address.unwrap();
            let max_address = addresses.iter()
                                       .fold(0, |acc, x| {
                                           if x > &(addr + acc) && (x-addr) <= endpoint {
                                               *x - addr
                                           } else {
                                               acc
                                           }
                                       });
            test_entry.trace_type = LineType::TestEntry(max_address);
        }
    }
    for (file, ref line_analysis) in analysis.iter() {
        if config.exclude_path(file) {
            continue;
        }
        for line in &line_analysis.cover {
            let line64 = *line as u64;
            let contain = result.iter().any(|ref x| &x.path == file && line64 == x.line);
            if !contain && !line_analysis.should_ignore(line) {
                result.push(TracerData {
                    line: line64,
                    path: file.to_path_buf(),
                    address: None,
                    hits: 0,
                    trace_type: LineType::UnusedGeneric,
                });
            }
        }
    }
    Ok(result)
}

pub fn generate_tracemap<'a>(project: &Workspace, test: &Path, config: &'a Config) -> io::Result<TraceMap<'a>> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let mut result = TraceMap::new(config);
        let mut h: HashMap<SourceLocation, TracerData> = HashMap::new();
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        for (k, v) in &h {
            result.add_trace(&k.path, Trace {
                line: k.line,
                address: v.address,
                length: 1,
                stats: CoverageStat::Line(0)
            });
        }
        Ok(result)
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

/// Generates a list of lines we want to trace the coverage of. Used to instrument the
/// traces into the test executable
pub fn generate_tracer_data(project: &Workspace, test: &Path, config: &Config) -> io::Result<Vec<TracerData>> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        get_line_addresses(endian,
                           manifest,
                           &obj,
                           &analysis,
                           config)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Error while parsing"))
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

<commit_msg>Deleted a bunch from test_loader<commit_after>use std::io;
use std::path::{PathBuf, Path};
use std::fs::File;
use std::cmp::{min, Ordering, PartialEq, Ord};
use std::collections::HashMap;
use object::{Object, File as OFile};
use memmap::MmapOptions;
use gimli::*;
use rustc_demangle::demangle;
use cargo::core::Workspace;

use config::Config;
use source_analysis::*;
use traces::*;

/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
type FuncDesc = (u64, u64, FunctionType);

#[derive(Clone, Copy, PartialEq)]
enum FunctionType {
    Generated,
    Test,
    Standard
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LineType {
    /// Generated test main. Shouldn't be traced.
    TestMain,
    /// Entry of function known to be a test
    TestEntry(u64),
    /// Entry of function. May or may not be test
    FunctionEntry(u64),
    /// Standard statement
    Statement,
    /// Condition
    Condition,
    /// Unknown type
    Unknown,
    /// Unused meta-code
    UnusedGeneric,
}

#[derive(Clone, PartialEq, Eq, Hash)]
struct SourceLocation {
    pub path: PathBuf,
    pub line: u64,
}

#[derive(Debug, Clone, Eq, PartialOrd)]
pub struct TracerData {
    pub path: PathBuf,
    pub line: u64,
    pub address: Option<u64>,
    pub trace_type: LineType,
    pub hits: u64,
}

impl PartialEq for TracerData {
    fn eq(&self, other: &TracerData) -> bool {
        (self.path == other.path) && (self.line == other.line)
    }
}

impl Ord for TracerData {

    fn cmp(&self, other: &TracerData) -> Ordering {
        if self == other {
            Ordering::Equal
        } else if self.path == other.path {
            if self.line > other.line {
                Ordering::Greater
            } else {
                Ordering::Less
            }
        } else if self.path > other.path {
            Ordering::Greater
        } else {
            Ordering::Less
        }
    }
}

fn generate_func_desc<R, Offset>(die: &DebuggingInformationEntry<R, Offset>,
                                 debug_str: &DebugStr<R>) -> Result<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut func_type = FunctionType::Standard;
    let low = die.attr_value(DW_AT_low_pc)?;
    let high = die.attr_value(DW_AT_high_pc)?;
    let linkage = die.attr_value(DW_AT_linkage_name)?;

    // Low is a program counter address so stored in an Addr
    let low = match low {
        Some(AttributeValue::Addr(x)) => x,
        _ => 0u64,
    };
    // High is an offset from the base pc, therefore is u64 data.
    let high = match high {
        Some(AttributeValue::Udata(x)) => x,
        _ => 0u64,
    };
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
        let name = debug_str.get_str(offset)
            .and_then(|r| r.to_string().map(|s| s.to_string()))
            .unwrap_or("".into());
        let name = demangle(name.as_ref()).to_string();
        // Simplest test is whether it's in tests namespace.
        // Rust guidelines recommend all tests are in a tests module.
        func_type = if name.contains("tests::") {
            FunctionType::Test
        } else if name.contains("__test::main") {
            FunctionType::Generated
        } else {
            FunctionType::Standard
        };
    }
    Ok((low, high, func_type))
}

/// Finds all function entry points and returns a vector
/// This will identify definite tests, but may be prone to false negatives.
fn get_entry_points<R, Offset>(debug_info: &CompilationUnitHeader<R, Offset>,
                               debug_abbrev: &Abbreviations,
                               debug_str: &DebugStr<R>) -> Vec<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result:Vec<FuncDesc> = Vec::new();
    let mut cursor = debug_info.entries(debug_abbrev);
    // skip compilation unit root.
    let _ = cursor.next_entry();
    while let Ok(Some((_, node))) = cursor.next_dfs() {
        // Function DIE
        if node.tag() == DW_TAG_subprogram {

            if let Ok(fd) = generate_func_desc(node, debug_str) {
                result.push(fd);
            }
        }
    }
    result
}

fn get_addresses_from_program<R, Offset>(prog: IncompleteLineNumberProgram<R>,
                                         entries: &[(u64, LineType)],
                                         project: &Path) -> Result<HashMap<SourceLocation, TracerData>>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result: HashMap<SourceLocation, TracerData> = HashMap::new();
    let ( cprog, seq) = prog.sequences()?;
    for s in seq {
        let mut sm = cprog.resume_from(&s);
         while let Ok(Some((header, &ln_row))) = sm.next_row() {
            if let Some(file) = ln_row.file(header) {
                let mut path = PathBuf::new();

                if let Some(dir) = file.directory(header) {
                    if let Ok(temp) = dir.to_string() {
                        path.push(temp.as_ref());
                    }
                }
                if let Ok(p) = path.canonicalize() {
                    path = p;
                }
                // Fix relative paths and determine if in target directory
                // Source in target directory shouldn't be covered as it's either
                // autogenerated or resulting from the projects Cargo.lock
                let is_target = if path.is_relative() {
                    path.starts_with("target")
                } else {
                    path.starts_with(project.join("target"))
                };
                // Source is part of project so we cover it.
                if !is_target && path.starts_with(project) {
                    if let Some(file) = ln_row.file(header) {
                        // If we can't map to line, we can't trace it.
                        let line = match ln_row.line() {
                            Some(l) => l,
                            None => continue,
                        };

                        let file = file.path_name();

                        if let Ok(file) = file.to_string() {
                            path.push(file.as_ref());
                            if !path.is_file() {
                                // Not really a source file!
                                continue;
                            }
                            let address = ln_row.address();

                            let desc = entries.iter()
                                              .filter(|&&(addr, _)| addr == address )
                                              .map(|&(_, t)| t)
                                              .nth(0)
                                              .unwrap_or(LineType::Unknown);
                            let loc = SourceLocation {
                                path: path.clone(),
                                line: line,
                            };
                            if result.contains_key(&loc) {
                                let mut x = result.get_mut(&loc).unwrap();
                                if let Some(mut addr) = x.address {
                                    x.address = Some(min(address, addr));
                                }
                            } else {
                                result.insert(loc, TracerData {
                                    path: path.clone(),
                                    line: line,
                                    address: Some(address),
                                    trace_type: desc,
                                    hits: 0u64
                                });
                            }
                        }
                    }
                }
            }
        }
    }
    Ok(result)
}

fn get_line_addresses(endian: RunTimeEndian,
                      project: &Path,
                      obj: &OFile,
                      analysis: &HashMap<PathBuf, LineAnalysis>,
                      config: &Config) -> Result<Vec<TracerData>>  {

    let mut result: Vec<TracerData> = Vec::new();
    let debug_info = obj.section_data_by_name(".debug_info").unwrap_or(&[]);
    let debug_info = DebugInfo::new(debug_info, endian);
    let debug_abbrev = obj.section_data_by_name(".debug_abbrev").unwrap_or(&[]);
    let debug_abbrev = DebugAbbrev::new(debug_abbrev, endian);
    let debug_strings = obj.section_data_by_name(".debug_str").unwrap_or(&[]);
    let debug_strings = DebugStr::new(debug_strings, endian);
    let debug_line = obj.section_data_by_name(".debug_line").unwrap_or(&[]);
    let debug_line = DebugLine::new(debug_line, endian);

    let mut iter = debug_info.units();
    while let Ok(Some(cu)) = iter.next() {
        let addr_size = cu.address_size();
        let abbr = match cu.abbreviations(&debug_abbrev) {
            Ok(a) => a,
            _ => continue,
        };
        let entries = get_entry_points(&cu, &abbr, &debug_strings)
            .iter()
            .map(|&(a, b, c)| {
                match c {
                    FunctionType::Test => (a, LineType::TestEntry(b)),
                    FunctionType::Standard => (a, LineType::FunctionEntry(b)),
                    FunctionType::Generated => (a, LineType::TestMain),
                }
            }).collect::<Vec<_>>();
        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
            let offset = match root.attr_value(DW_AT_stmt_list) {
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
                _ => continue,
            };
            let prog = debug_line.program(offset, addr_size, None, None)?;
            if let Ok(mut addresses) = get_addresses_from_program(prog, &entries, project) {
                for val in addresses.values() {
                    result.push(val.clone());
                }
            }
        }
    }
    // Due to rust being a higher level language multiple instructions may map
    // to the same line. This prunes these to just the first instruction address
    let mut result = result.iter()
                           .filter(|x| !(config.ignore_tests && x.path.starts_with(project.join("tests"))))
                           .filter(|x| !(config.exclude_path(&x.path)))
                           .filter(|x| !analysis.should_ignore(x.path.as_ref(), &(x.line as usize)))
                           .filter(|x| x.trace_type != LineType::TestMain)
                           .cloned()
                           .collect::<Vec<TracerData>>();

    for (file, ref line_analysis) in analysis.iter() {
        if config.exclude_path(file) {
            continue;
        }
        for line in &line_analysis.cover {
            let line64 = *line as u64;
            let contain = result.iter().any(|ref x| &x.path == file && line64 == x.line);
            if !contain && !line_analysis.should_ignore(line) {
                result.push(TracerData {
                    line: line64,
                    path: file.to_path_buf(),
                    address: None,
                    hits: 0,
                    trace_type: LineType::UnusedGeneric,
                });
            }
        }
    }
    Ok(result)
}

pub fn generate_tracemap<'a>(project: &Workspace, test: &Path, config: &'a Config) -> io::Result<TraceMap<'a>> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let mut result = TraceMap::new(config);
        let mut h: HashMap<SourceLocation, TracerData> = HashMap::new();
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        for (k, v) in &h {
            result.add_trace(&k.path, Trace {
                line: k.line,
                address: v.address,
                length: 1,
                stats: CoverageStat::Line(0)
            });
        }
        Ok(result)
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

/// Generates a list of lines we want to trace the coverage of. Used to instrument the
/// traces into the test executable
pub fn generate_tracer_data(project: &Workspace, test: &Path, config: &Config) -> io::Result<Vec<TracerData>> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        get_line_addresses(endian,
                           manifest,
                           &obj,
                           &analysis,
                           config)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Error while parsing"))
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

<|endoftext|>


<commit_before>#![allow(unused_variables,unused_must_use)]
#[macro_use] extern crate log;
extern crate env_logger;
extern crate yxorp;
extern crate openssl;
extern crate mio;

use std::thread;
use std::sync::mpsc;
use yxorp::messages;
use yxorp::network::{self,ProxyOrder};

fn main() {
  env_logger::init().unwrap();
  info!("starting up");

  let config = messages::HttpProxyConfiguration {
    front: "198.51.100.0:80".parse().unwrap(),
    ..Default::default()
  };

  let poll          = mio::Poll::new().unwrap();
  let (tx, rx)      = mio::channel::channel::<ProxyOrder>();
  let (sender, rec) = mpsc::channel::<network::ServerMessage>();

  let jg            = thread::spawn(move || {
    network::http::start_listener(config, sender, poll, rx);
  });

  let http_front = messages::HttpFront {
    app_id:     String::from("test"),
    hostname:   String::from("example.com"),
    path_begin: String::from("/")
  };
  let http_instance = messages::Instance {
    app_id:     String::from("test"),
    ip_address: String::from("192.0.2.1"),
    port:       8080
  };

  tx.send(network::ProxyOrder::Command(
    String::from("ID_ABCD"),
    messages::Command::AddHttpFront(http_front)
  ));

  tx.send(network::ProxyOrder::Command(
    String::from("ID_EFGH"),
    messages::Command::AddInstance(http_instance)
  ));

  println!("HTTP -> {:?}", rec.recv().unwrap());
  println!("HTTP -> {:?}", rec.recv().unwrap());

  let _ = jg.join();
  info!("good bye");
}

<commit_msg>make the minimal example start correctly<commit_after>#![allow(unused_variables,unused_must_use)]
#[macro_use] extern crate log;
extern crate env_logger;
extern crate yxorp;
extern crate openssl;
extern crate mio;

use std::thread;
use std::sync::mpsc;
use yxorp::messages;
use yxorp::network::{self,ProxyOrder};

fn main() {
  env_logger::init().unwrap();
  info!("starting up");

  let config = messages::HttpProxyConfiguration {
    front: "127.0.0.1:8080".parse().unwrap(),
    ..Default::default()
  };

  let poll          = mio::Poll::new().unwrap();
  let (tx, rx)      = mio::channel::channel::<ProxyOrder>();
  let (sender, rec) = mpsc::channel::<network::ServerMessage>();

  let jg            = thread::spawn(move || {
    network::http::start_listener(config, sender, poll, rx);
  });

  let http_front = messages::HttpFront {
    app_id:     String::from("test"),
    hostname:   String::from("example.com"),
    path_begin: String::from("/")
  };
  let http_instance = messages::Instance {
    app_id:     String::from("test"),
    ip_address: String::from("127.0.0.1"),
    port:       8000
  };

  tx.send(network::ProxyOrder::Command(
    String::from("ID_ABCD"),
    messages::Command::AddHttpFront(http_front)
  ));

  tx.send(network::ProxyOrder::Command(
    String::from("ID_EFGH"),
    messages::Command::AddInstance(http_instance)
  ));

  println!("HTTP -> {:?}", rec.recv().unwrap());
  println!("HTTP -> {:?}", rec.recv().unwrap());

  let _ = jg.join();
  info!("good bye");
}

<|endoftext|>


<commit_before>use std::collections::{HashSet, HashMap};
use std::net::IpAddr;
use std::sync::{Arc, Mutex};
use tracker::{Result, ResultExt, ErrorKind};
use std::os::unix::io::{AsRawFd, RawFd};
use {amy, c_ares};

#[derive(Debug)]
pub struct QueryResponse {
    pub id: usize,
    pub res: Result<IpAddr>,
}

pub struct Resolver {
    reg: amy::Registrar,
    socks: HashMap<usize, c_ares::Socket>,
    csocks: HashMap<c_ares::Socket, usize>,
    chan: c_ares::Channel,
    sender: Arc<Mutex<amy::Sender<QueryResponse>>>,
}

struct CSockWrapper(c_ares::Socket);

impl AsRawFd for CSockWrapper {
    fn as_raw_fd(&self) -> RawFd {
        self.0
    }
}

impl Resolver {
    pub fn new(reg: amy::Registrar, send: amy::Sender<QueryResponse>) -> Resolver {
        let mut opts = c_ares::Options::new();
        opts.set_timeout(3000).set_tries(4);
        Resolver {
            reg,
            socks: HashMap::new(),
            csocks: HashMap::new(),
            chan: c_ares::Channel::with_options(opts).unwrap(),
            sender: Arc::new(Mutex::new(send)),
        }
    }

    pub fn contains(&self, id: usize) -> bool {
        self.socks.contains_key(&id)
    }

    pub fn readable(&mut self, id: usize) {
        self.chan.process_fd(self.socks[&id], c_ares::SOCKET_BAD);
    }

    pub fn writable(&mut self, id: usize) {
        self.chan.process_fd(self.socks[&id], c_ares::SOCKET_BAD);
    }

    pub fn tick(&mut self) {
        let mut marked = HashSet::new();
        // Add any unrecognized fd's to amy for polling, remove
        // any fd's in amy which are no longer handled by c_ares
        for (fd, _, _) in &self.chan.get_sock() {
            // Not efficient, but the max number of queries active at at time is likely limited
            if let Some(id) = self.csocks.get(&fd).cloned() {
                marked.insert(id);
            } else {
                let id = self.reg
                    .register(&CSockWrapper(fd), amy::Event::Both)
                    .unwrap();
                marked.insert(id);
                self.socks.insert(id, fd);
                self.csocks.insert(fd, id);
            }
        }

        // Any socks remaining in csocks should be deregistered and destroyed
        Resolver::remove_socks(marked, &mut self.socks, &mut self.csocks, &self.reg);
    }

    fn remove_socks(
        marked: HashSet<usize>,
        socks: &mut HashMap<usize, c_ares::Socket>,
        csocks: &mut HashMap<c_ares::Socket, usize>,
        reg: &amy::Registrar,
    ) {
        socks.retain(|id, fd| {
            if marked.contains(id) {
                true
            } else {
                csocks.remove(fd);
                if reg.deregister(&CSockWrapper(*fd)).is_err() {
                    // Probably from EINTR, so we're shutting down
                    // and it doesn't matter if it's ignored
                }
                false
            }
        });
    }

    pub fn new_query(&mut self, id: usize, host: &str) {
        // TODO: handle ipv6 too
        let s = self.sender.clone();
        self.chan.get_host_by_name(
            host,
            c_ares::AddressFamily::INET,
            move |res| {
                let res = res.chain_err(|| ErrorKind::DNS).and_then(|ips| {
                    ips.addresses().next().ok_or(ErrorKind::DNS.into())
                });
                let resp = QueryResponse { id, res };
                if s.lock().unwrap().send(resp).is_err() {
                    // Other end was shutdown, ignore
                }
            },
        );
    }
}
<commit_msg>Cleanup DNS resolution<commit_after>use std::collections::{HashSet, HashMap};
use std::net::IpAddr;
use std::sync::{Arc, Mutex};
use tracker::{Result, ResultExt, ErrorKind};
use std::os::unix::io::{AsRawFd, RawFd};
use {amy, c_ares};

#[derive(Debug)]
pub struct QueryResponse {
    pub id: usize,
    pub res: Result<IpAddr>,
}

pub struct Resolver {
    reg: amy::Registrar,
    socks: HashMap<usize, c_ares::Socket>,
    csocks: HashMap<c_ares::Socket, usize>,
    chan: c_ares::Channel,
    sender: Arc<Mutex<amy::Sender<QueryResponse>>>,
    marked: HashSet<usize>,
}

struct CSockWrapper(c_ares::Socket);

impl AsRawFd for CSockWrapper {
    fn as_raw_fd(&self) -> RawFd {
        self.0
    }
}

impl Resolver {
    pub fn new(reg: amy::Registrar, send: amy::Sender<QueryResponse>) -> Resolver {
        let mut opts = c_ares::Options::new();
        opts.set_timeout(3000).set_tries(4);
        Resolver {
            reg,
            socks: HashMap::new(),
            csocks: HashMap::new(),
            chan: c_ares::Channel::with_options(opts).unwrap(),
            sender: Arc::new(Mutex::new(send)),
            marked: HashSet::new(),
        }
    }

    pub fn contains(&self, id: usize) -> bool {
        self.socks.contains_key(&id)
    }

    pub fn readable(&mut self, id: usize) {
        self.chan.process_fd(self.socks[&id], c_ares::SOCKET_BAD);
    }

    pub fn writable(&mut self, id: usize) {
        self.chan.process_fd(c_ares::SOCKET_BAD, self.socks[&id]);
    }

    pub fn tick(&mut self) {
        self.marked.clear();
        // Add any unrecognized fd's to amy for polling, remove
        // any fd's in amy which are no longer handled by c_ares
        for (fd, _, _) in &self.chan.get_sock() {
            // Not efficient, but the max number of queries active at at time is likely limited
            if let Some(id) = self.csocks.get(&fd).cloned() {
                self.marked.insert(id);
            } else {
                let id = self.reg
                    .register(&CSockWrapper(fd), amy::Event::Both)
                    .unwrap();
                self.marked.insert(id);
                self.socks.insert(id, fd);
                self.csocks.insert(fd, id);
            }
        }

        // Any socks remaining in csocks should be deregistered and destroyed
        let socks = &mut self.socks;
        let csocks = &mut self.csocks;
        let marked = &self.marked;
        let reg = &self.reg;
        socks.retain(|id, fd| {
            if marked.contains(id) {
                true
            } else {
                csocks.remove(fd);
                reg.deregister(&CSockWrapper(*fd)).ok();
                false
            }
        });
    }

    pub fn new_query(&mut self, id: usize, host: &str) {
        // TODO: handle ipv6 too
        let s = self.sender.clone();
        self.chan.get_host_by_name(
            host,
            c_ares::AddressFamily::INET,
            move |res| {
                let res = res.chain_err(|| ErrorKind::DNS).and_then(|ips| {
                    ips.addresses().next().ok_or(ErrorKind::DNS.into())
                });
                let resp = QueryResponse { id, res };
                if s.lock().unwrap().send(resp).is_err() {
                    // Other end was shutdown, ignore
                }
            },
        );
    }
}
<|endoftext|>