<commit_before>use config::Config;
use email_address::EmailAddress;
use error::BrokerError;
use futures::future::{self, Future};
use std::error::Error;
use std::rc::Rc;
use std::str::FromStr;
use store_cache::{CacheKey, fetch_json_url};
use url::Url;

/// Portier webfinger relation
pub const WEBFINGER_PORTIER_REL: &str = "https://portier.io/specs/auth/1.0/idp";
/// Portier + Google webfinger relation
pub const WEBFINGER_GOOGLE_REL: &str = "https://portier.io/specs/auth/1.0/idp/google";

/// Deserialization types
#[derive(Deserialize)]
pub struct DescriptorDef {
    #[serde(default)]
    pub links: Vec<LinkDef>,
}

#[derive(Clone,Debug,Deserialize)]
pub struct LinkDef {
    #[serde(default)]
    pub rel: String,
    #[serde(default)]
    pub href: String,
}

/// Parsed and validated webfinger relation
#[derive(Clone,Copy,Debug)]
pub enum Relation {
    Portier,
    Google,
}

impl FromStr for Relation {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Relation, &'static str> {
        match s {
            WEBFINGER_PORTIER_REL => Ok(Relation::Portier),
            WEBFINGER_GOOGLE_REL => Ok(Relation::Google),
            _ => Err("unsupported value"),
        }
    }
}

/// Parsed and validated webfinger link
#[derive(Clone,Debug)]
pub struct Link {
    pub rel: Relation,
    pub href: Url,
}

impl Link {
    /// Parse and validate a deserialized link
    pub fn from_de_link(link: &LinkDef) -> Result<Link, String> {
        match (link.rel.parse(), link.href.parse()) {
            (Ok(rel), Ok(href)) => Ok(Link { rel, href }),
            (Err(e), _) => Err(format!("invalid rel: {}", e)),
            (_, Err(e)) => Err(format!("invalid href: {}", e)),
        }
    }
}

/// Query webfinger for the given email address
///
/// This queries the webfinger endpoint of the domain for the given email
/// address. The resource queried is the email address itself, as an `acct` URL.
/// Request failures of any kind simply result in an empty list.
pub fn query(app: &Rc<Config>, email_addr: &Rc<EmailAddress>)
    -> Box<Future<Item=Vec<Link>, Error=BrokerError>> {

    // Look for a configuration override.
    if let Some(mapped) = app.domain_overrides.get(email_addr.domain()) {
        return Box::new(future::ok(mapped.clone()));
    }

    // Build the webfinger query URL. We can safely do string concatenation here, because the
    // domain has already been validated using the `url` crate.
    #[cfg(feature = "insecure")]
    let url = format!("http://{}/.well-known/webfinger", email_addr.domain());
    #[cfg(not(feature = "insecure"))]
    let url = format!("https://{}/.well-known/webfinger", email_addr.domain());

    let url = match Url::parse_with_params(&url, &[
        ("resource", format!("acct:{}", email_addr).as_str()),
        ("rel", WEBFINGER_PORTIER_REL),
        ("rel", WEBFINGER_GOOGLE_REL),
    ]) {
        Ok(url) => url,
        Err(e) => return Box::new(future::err(BrokerError::Internal(
            format!("could not build webfinger query url: {}", e.description())))),
    };

    // Make the request.
    let f = fetch_json_url(app, url, &CacheKey::Discovery { acct: email_addr.as_str() });

    // Parse the relations.
    let f = f.map(|descriptor: DescriptorDef| {
        descriptor.links.iter()
            .filter_map(|link| Link::from_de_link(link).ok())
            .collect()
    });

    Box::new(f)
}
<commit_msg>Skip webfinger results that refer to ourselves<commit_after>use config::Config;
use email_address::EmailAddress;
use error::BrokerError;
use futures::future::{self, Future};
use std::error::Error;
use std::rc::Rc;
use std::str::FromStr;
use store_cache::{CacheKey, fetch_json_url};
use url::Url;

/// Portier webfinger relation
pub const WEBFINGER_PORTIER_REL: &str = "https://portier.io/specs/auth/1.0/idp";
/// Portier + Google webfinger relation
pub const WEBFINGER_GOOGLE_REL: &str = "https://portier.io/specs/auth/1.0/idp/google";

/// Deserialization types
#[derive(Deserialize)]
pub struct DescriptorDef {
    #[serde(default)]
    pub links: Vec<LinkDef>,
}

#[derive(Clone,Debug,Deserialize)]
pub struct LinkDef {
    #[serde(default)]
    pub rel: String,
    #[serde(default)]
    pub href: String,
}

/// Parsed and validated webfinger relation
#[derive(Clone,Copy,Debug)]
pub enum Relation {
    Portier,
    Google,
}

impl FromStr for Relation {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Relation, &'static str> {
        match s {
            WEBFINGER_PORTIER_REL => Ok(Relation::Portier),
            WEBFINGER_GOOGLE_REL => Ok(Relation::Google),
            _ => Err("unsupported value"),
        }
    }
}

/// Parsed and validated webfinger link
#[derive(Clone,Debug)]
pub struct Link {
    pub rel: Relation,
    pub href: Url,
}

impl Link {
    /// Parse and validate a deserialized link
    pub fn from_de_link(link: &LinkDef) -> Result<Link, String> {
        match (link.rel.parse(), link.href.parse()) {
            (Ok(rel), Ok(href)) => Ok(Link { rel, href }),
            (Err(e), _) => Err(format!("invalid rel: {}", e)),
            (_, Err(e)) => Err(format!("invalid href: {}", e)),
        }
    }
}

/// Query webfinger for the given email address
///
/// This queries the webfinger endpoint of the domain for the given email
/// address. The resource queried is the email address itself, as an `acct` URL.
/// Request failures of any kind simply result in an empty list.
pub fn query(app: &Rc<Config>, email_addr: &Rc<EmailAddress>)
    -> Box<Future<Item=Vec<Link>, Error=BrokerError>> {

    // Look for a configuration override.
    if let Some(mapped) = app.domain_overrides.get(email_addr.domain()) {
        return Box::new(future::ok(mapped.clone()));
    }

    // Build the webfinger query URL. We can safely do string concatenation here, because the
    // domain has already been validated using the `url` crate.
    #[cfg(feature = "insecure")]
    let url = format!("http://{}/.well-known/webfinger", email_addr.domain());
    #[cfg(not(feature = "insecure"))]
    let url = format!("https://{}/.well-known/webfinger", email_addr.domain());

    let url = match Url::parse_with_params(&url, &[
        ("resource", format!("acct:{}", email_addr).as_str()),
        ("rel", WEBFINGER_PORTIER_REL),
        ("rel", WEBFINGER_GOOGLE_REL),
    ]) {
        Ok(url) => url,
        Err(e) => return Box::new(future::err(BrokerError::Internal(
            format!("could not build webfinger query url: {}", e.description())))),
    };

    // Make the request.
    let f = fetch_json_url(app, url, &CacheKey::Discovery { acct: email_addr.as_str() });

    // Parse the relations.
    let app = app.clone();
    let f = f.map(move |descriptor: DescriptorDef| {
        descriptor.links.iter()
            .filter_map(|link| Link::from_de_link(link).ok())
            // Sanity check: skip results that refer to ourselves.
            .filter(|link| link.href.as_str() != app.public_url)
            .collect()
    });

    Box::new(f)
}
<|endoftext|>


<commit_before>
use {
    BackEnd,
    CanTransform,
    Clear,
    Color,
    Borrowed,
    Field,
    Fill,
    HasTransform,
    Matrix2d,
    Rectangle,
    RelativeRectangle,
    Value,
    View,
};
use vecmath::{
    identity,
    margin_rectangle,
    relative_rectangle,
};
use triangulation::{
    with_ellipse_tri_list_xy_f32_rgba_f32
};

/// An ellipse color context.
pub struct EllipseColorContext<'a> {
    /// Base/original transformation.
    pub base: Field<'a, Matrix2d>,
    /// Current transformation.
    pub transform: Field<'a, Matrix2d>,
    /// Current rectangle.
    pub rect: Field<'a, Rectangle>,
    /// Current color.
    pub color: Field<'a, Color>,
}

impl<'a> HasTransform<'a, Matrix2d> for EllipseColorContext<'a> {
    #[inline(always)]
    fn get_transform(&'a self) -> &'a Matrix2d {
        self.transform.get()
    }
}

impl<'a> CanTransform<'a, EllipseColorContext<'a>, Matrix2d> for EllipseColorContext<'a> {
    #[inline(always)]
    fn transform(&'a self, value: Matrix2d) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Value(value),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }
}

impl<'a> Fill<'a> for EllipseColorContext<'a> {
    fn fill<B: BackEnd>(&'a self, back_end: &mut B) {
        if back_end.supports_tri_list_xy_f32_rgba_f32() {
            let rect = self.rect.get();
            let color = self.color.get();
            let color: [f32, ..4] = [color[0], color[1], color[2], color[3]];
            // Complete transparency does not need to be rendered.
            if color[3] == 0.0 { return; }
            // Turn on alpha blending if not completely opaque.
            let needs_alpha = color[3] != 1.0;
            if needs_alpha { back_end.enable_alpha_blend(); }
            with_ellipse_tri_list_xy_f32_rgba_f32(
                128,
                self.transform.get(),
                rect,
                color,
                |vertices, colors| {
                    back_end.tri_list_xy_f32_rgba_f32(vertices, colors)
                }
            );
            if needs_alpha { back_end.disable_alpha_blend(); }
        } else {
            unimplemented!();
        }
    }
}

impl<'a> Clear for EllipseColorContext<'a> {
    fn clear<B: BackEnd>(&self, back_end: &mut B) {
        if back_end.supports_clear_rgba() {
            let color = self.color.get();
            back_end.clear_rgba(color[0], color[1], color[2], color[3]);
        } else {
            unimplemented!();
        }
    }
}

impl<'a> RelativeRectangle<'a> for EllipseColorContext<'a> {
    #[inline(always)]
    fn margin(&'a self, m: f64) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.transform.get()),
            color: Borrowed(self.color.get()),
            rect: Value(margin_rectangle(self.rect.get(), m)),
        }
    }

    #[inline(always)]
    fn rel(&'a self, x: f64, y: f64) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.transform.get()),
            color: Borrowed(self.color.get()),
            rect: Value(relative_rectangle(self.rect.get(), x, y)),
        }
    }
}

impl<'a> View<'a> for EllipseColorContext<'a> {
    #[inline(always)]
    fn view(&'a self) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.base.get()),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }

    #[inline(always)]
    fn reset(&'a self) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Value(identity()),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }

    #[inline(always)]
    fn store_view(&'a self) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.transform.get()),
            transform: Borrowed(self.transform.get()),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }
}
<commit_msg>Implemented `HasColor` and `CanColor` for `EllipseColorContext`<commit_after>
use {
    BackEnd,
    CanColor,
    CanTransform,
    Clear,
    Color,
    Borrowed,
    Field,
    Fill,
    HasColor,
    HasTransform,
    Matrix2d,
    Rectangle,
    RelativeRectangle,
    Value,
    View,
};
use vecmath::{
    identity,
    margin_rectangle,
    relative_rectangle,
};
use triangulation::{
    with_ellipse_tri_list_xy_f32_rgba_f32
};

/// An ellipse color context.
pub struct EllipseColorContext<'a> {
    /// Base/original transformation.
    pub base: Field<'a, Matrix2d>,
    /// Current transformation.
    pub transform: Field<'a, Matrix2d>,
    /// Current rectangle.
    pub rect: Field<'a, Rectangle>,
    /// Current color.
    pub color: Field<'a, Color>,
}

impl<'a> HasTransform<'a, Matrix2d> for EllipseColorContext<'a> {
    #[inline(always)]
    fn get_transform(&'a self) -> &'a Matrix2d {
        self.transform.get()
    }
}

impl<'a> CanTransform<'a, EllipseColorContext<'a>, Matrix2d> for EllipseColorContext<'a> {
    #[inline(always)]
    fn transform(&'a self, value: Matrix2d) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Value(value),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }
}

impl<'a> HasColor<'a, Color> for EllipseColorContext<'a> {
    #[inline(always)]
    fn get_color(&'a self) -> &'a Color {
        self.color.get()
    }
}

impl<'a> CanColor<'a, EllipseColorContext<'a>, Color> for EllipseColorContext<'a> {
    #[inline(always)]
    fn color(&'a self, value: Color) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.transform.get()),
            color: Value(value),
            rect: Borrowed(self.rect.get()),
        }
    }
}

impl<'a> Fill<'a> for EllipseColorContext<'a> {
    fn fill<B: BackEnd>(&'a self, back_end: &mut B) {
        if back_end.supports_tri_list_xy_f32_rgba_f32() {
            let rect = self.rect.get();
            let color = self.color.get();
            let color: [f32, ..4] = [color[0], color[1], color[2], color[3]];
            // Complete transparency does not need to be rendered.
            if color[3] == 0.0 { return; }
            // Turn on alpha blending if not completely opaque.
            let needs_alpha = color[3] != 1.0;
            if needs_alpha { back_end.enable_alpha_blend(); }
            with_ellipse_tri_list_xy_f32_rgba_f32(
                128,
                self.transform.get(),
                rect,
                color,
                |vertices, colors| {
                    back_end.tri_list_xy_f32_rgba_f32(vertices, colors)
                }
            );
            if needs_alpha { back_end.disable_alpha_blend(); }
        } else {
            unimplemented!();
        }
    }
}

impl<'a> Clear for EllipseColorContext<'a> {
    fn clear<B: BackEnd>(&self, back_end: &mut B) {
        if back_end.supports_clear_rgba() {
            let color = self.color.get();
            back_end.clear_rgba(color[0], color[1], color[2], color[3]);
        } else {
            unimplemented!();
        }
    }
}

impl<'a> RelativeRectangle<'a> for EllipseColorContext<'a> {
    #[inline(always)]
    fn margin(&'a self, m: f64) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.transform.get()),
            color: Borrowed(self.color.get()),
            rect: Value(margin_rectangle(self.rect.get(), m)),
        }
    }

    #[inline(always)]
    fn rel(&'a self, x: f64, y: f64) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.transform.get()),
            color: Borrowed(self.color.get()),
            rect: Value(relative_rectangle(self.rect.get(), x, y)),
        }
    }
}

impl<'a> View<'a> for EllipseColorContext<'a> {
    #[inline(always)]
    fn view(&'a self) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Borrowed(self.base.get()),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }

    #[inline(always)]
    fn reset(&'a self) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.base.get()),
            transform: Value(identity()),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }

    #[inline(always)]
    fn store_view(&'a self) -> EllipseColorContext<'a> {
        EllipseColorContext {
            base: Borrowed(self.transform.get()),
            transform: Borrowed(self.transform.get()),
            rect: Borrowed(self.rect.get()),
            color: Borrowed(self.color.get()),
        }
    }
}
<|endoftext|>


<commit_before>use chunk::CreateChunk;
use config::Config;
use inventory::Inventory;
use known_nodes::KnownNodes;
use message::{InventoryVector,Message};
use net::to_socket_addr;
use std::sync::mpsc::SendError;
use std::net::{Ipv4Addr,SocketAddr,SocketAddrV4};
use time::{get_time};

use super::{MAX_INV_COUNT,MAX_NODES_COUNT};

pub struct MessageResponder {
    config: Config,
    known_nodes: KnownNodes,
    inventory: Inventory,
    peer_addr: SocketAddr
}

impl MessageResponder {
    pub fn new(config: &Config, known_nodes: &KnownNodes, inventory: &Inventory, peer_addr: SocketAddr) -> MessageResponder {
        MessageResponder {
            config: config.clone(),
            known_nodes: known_nodes.clone(),
            inventory: inventory.clone(),
            peer_addr: peer_addr
        }
    }

    pub fn send_version<F>(&self, f: F) -> Result<(), SendError<Message>>
        where F : Fn(Message) -> Result<(), SendError<Message>> {
        f(self.create_version_message())
    }

    pub fn respond<F>(&mut self, message: Message, send: F) -> Result<(), SendError<Message>>
        where F : Fn(Message) -> Result<(), SendError<Message>> {
        match message {
            Message::Version { .. } => {
                // TODO - record the peer as a known_node???
                // TODO - check the version, stream, timestamp, nonce, etc.???
                try!(send(Message::Verack));
            },
            Message::Verack => {
                try!(send(self.create_addr_message()));

                let inventory_iterator = self.inventory.iterator();
                let chunk_iterator = inventory_iterator.chunk(MAX_INV_COUNT);
                for inventory_chunk in chunk_iterator {
                    try!(send(self.create_inv_message(inventory_chunk)));
                }
            },
            Message::Addr { addr_list } => {
                for known_node in addr_list.iter() {
                    self.known_nodes.add_known_node(&known_node);
                }
            },
            Message::Inv { inventory: inventory_chunk } => {
                let get_data = self.inventory.unknown(inventory_chunk);
                try!(send(self.create_getdata_message(get_data)));
            },
            Message::GetData { inventory: inventory_chunk } => {
                for inventory_vector in inventory_chunk {
                    if let Some(object_message) = self.inventory.get_object_message(&inventory_vector) {
                        try!(send(object_message));
                    }
                }
            },
            m @ Message::Object { .. } => {
                self.inventory.add_object_message(&m);
            }
        };

        Ok(())
    }

    fn create_version_message(&self) -> Message {
        let port = self.config.port();
        let our_addr = to_socket_addr(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port));
        let nonce = self.config.nonce();
        let user_agent = self.config.user_agent().to_string();
        let streams = vec![ 1 ];

        Message::Version {
            version: 3,
            services: 1,
            timestamp: get_time(),
            addr_recv: self.peer_addr,
            addr_from: our_addr,
            nonce: nonce,
            user_agent: user_agent,
            streams: streams
        }
    }

    fn create_addr_message(&self) -> Message {
        let addr_list = self.known_nodes.get_random_selection_but_not(MAX_NODES_COUNT, vec![ self.peer_addr ]);
        Message::Addr {
            addr_list: addr_list
        }
    }

    fn create_inv_message(&self, inventory_chunk: Vec<InventoryVector>) -> Message {
        Message::Inv {
            inventory: inventory_chunk
        }
    }

    fn create_getdata_message(&self, inventory_chunk: Vec<InventoryVector>) -> Message {
        Message::GetData {
            inventory: inventory_chunk
        }
    }
}

#[cfg(test)]
mod tests {
    use config::Config;
    use inventory::{Inventory,calculate_inventory_vector};
    use known_nodes::KnownNodes;
    use message::{Message,KnownNode,Object,GetPubKey};
    use net::to_socket_addr;
    use persist::Persister;
    use std::sync::Mutex;
    use std::sync::mpsc::SendError;
    use time::{Timespec,get_time};
    use super::MessageResponder;

    struct Output {
        pub messages: Mutex<Vec<Message>>
    }

    impl Output {
        fn new() -> Output {
            Output {
                messages: Mutex::new(vec![])
            }
        }

        fn add(&self, message: Message) -> Result<(), SendError<Message>> {
            self.messages.lock().unwrap().push(message);
            Ok(())
        }

        fn get_messages(&self) -> Vec<Message> {
            self.messages.lock().unwrap().clone()
        }
    }

    #[test]
    fn test_get_version_send_verack() {
        let input = Message::Version {
            version: 3,
            services: 1,
            timestamp: get_time(),
            addr_recv: to_socket_addr("127.0.0.1:8555"),
            addr_from: to_socket_addr("127.0.0.1:8444"),
            nonce: 0x0102030405060708,
            user_agent: "test".to_string(),
            streams: vec![ 1 ]
        };
        let output = run_test(input, Persister::new());
        assert_eq!(1, output.len());

        let message = &output[0];
        match message {
            &Message::Verack => {},
            _ => panic!("Not a Verack message: {:?}", message)
        }
    }

    #[test]
    fn test_get_verack_empty_persister_send_empty_addr() {
        let input = Message::Verack;
        let output = run_test(input, Persister::new());
        assert_eq!(1, output.len());

        let message = &output[0];
        match message {
            &Message::Addr { ref addr_list } => { assert_eq!(0, addr_list.len()) },
            _ => panic!("Not an Addr message: {:?}", message)
        }
    }

    #[test]
    fn test_get_verack_populated_persister_send_addr_and_inv() {
        let mut persister = Persister::new();
        let known_node = KnownNode {
            last_seen: Timespec::new(5, 0),
            stream: 1,
            services: 1,
            socket_addr: to_socket_addr("12.13.14.15:1000")
        };
        persister.add_known_node(&known_node);
        let mut inventory = Inventory::new(persister.clone());
        let persisted_message = Message::Object {
            nonce: 1,
            expiry: Timespec::new(2, 0),
            version: 3,
            stream: 1,
            object: Object::GetPubKey(GetPubKey::V3 { ripe: vec![4; 20] })
        };
        inventory.add_object_message(&persisted_message);

        let input = Message::Verack;
        let output = run_test(input, persister);
        assert_eq!(2, output.len());

        let message1 = &output[0];
        match message1 {
            &Message::Addr { ref addr_list } => {
                assert_eq!(1, addr_list.len());
                let output_node = &addr_list[0];
                assert_eq!(Timespec::new(5, 0), output_node.last_seen);
                assert_eq!(1, output_node.stream);
                assert_eq!(1, output_node.services);
                assert_eq!(to_socket_addr("12.13.14.15:1000"), output_node.socket_addr);
            },
            _ => panic!("Not an Addr message: {:?}", message1)
        }

        let message2 = &output[1];
        match message2 {
            &Message::Inv { ref inventory } => {
                assert_eq!(1, inventory.len());
                let inventory_vector = &inventory[0];
                assert_eq!(&calculate_inventory_vector(&persisted_message), inventory_vector);
            },
            _ => panic!("Not an Addr message: {:?}", message1)
        }
    }

    #[test]
    fn test_get_addr_populates_persister() {
        let persister = Persister::new();
        let known_node = KnownNode {
            last_seen: Timespec::new(6, 0),
            stream: 1,
            services: 1,
            socket_addr: to_socket_addr("22.33.44.55:6666")
        };
        let input = Message::Addr {
            addr_list: vec![ known_node.clone() ]
        };
        let output = run_test(input, persister.clone());
        assert_eq!(0, output.len());

        let known_nodes = persister.get_known_nodes();
        assert_eq!(1, known_nodes.len());

        let known_node_recovered = &known_nodes[0];
        assert_eq!(&known_node, known_node_recovered);
    }

    fn run_test(input: Message, persister: Persister) -> Vec<Message> {
        let config = Config::new();
        let known_nodes = KnownNodes::new(persister.clone());
        let inventory = Inventory::new(persister.clone());
        let peer_addr = to_socket_addr("127.0.0.1:8444");
        let mut responder = MessageResponder::new(&config, &known_nodes, &inventory, peer_addr);

        let output = Output::new();
        responder.respond(input, |m| { output.add(m) } ).unwrap();

        output.get_messages()
    }
}
<commit_msg>CG: Added test for inv messages in MessageResponder<commit_after>use chunk::CreateChunk;
use config::Config;
use inventory::Inventory;
use known_nodes::KnownNodes;
use message::{InventoryVector,Message};
use net::to_socket_addr;
use std::sync::mpsc::SendError;
use std::net::{Ipv4Addr,SocketAddr,SocketAddrV4};
use time::{get_time};

use super::{MAX_INV_COUNT,MAX_NODES_COUNT};

pub struct MessageResponder {
    config: Config,
    known_nodes: KnownNodes,
    inventory: Inventory,
    peer_addr: SocketAddr
}

impl MessageResponder {
    pub fn new(config: &Config, known_nodes: &KnownNodes, inventory: &Inventory, peer_addr: SocketAddr) -> MessageResponder {
        MessageResponder {
            config: config.clone(),
            known_nodes: known_nodes.clone(),
            inventory: inventory.clone(),
            peer_addr: peer_addr
        }
    }

    pub fn send_version<F>(&self, f: F) -> Result<(), SendError<Message>>
        where F : Fn(Message) -> Result<(), SendError<Message>> {
        f(self.create_version_message())
    }

    pub fn respond<F>(&mut self, message: Message, send: F) -> Result<(), SendError<Message>>
        where F : Fn(Message) -> Result<(), SendError<Message>> {
        match message {
            Message::Version { .. } => {
                // TODO - record the peer as a known_node???
                // TODO - check the version, stream, timestamp, nonce, etc.???
                try!(send(Message::Verack));
            },
            Message::Verack => {
                try!(send(self.create_addr_message()));

                let inventory_iterator = self.inventory.iterator();
                let chunk_iterator = inventory_iterator.chunk(MAX_INV_COUNT);
                for inventory_chunk in chunk_iterator {
                    try!(send(self.create_inv_message(inventory_chunk)));
                }
            },
            Message::Addr { addr_list } => {
                for known_node in addr_list.iter() {
                    self.known_nodes.add_known_node(&known_node);
                }
            },
            Message::Inv { inventory: inventory_chunk } => {
                let get_data = self.inventory.unknown(inventory_chunk);
                try!(send(self.create_getdata_message(get_data)));
            },
            Message::GetData { inventory: inventory_chunk } => {
                for inventory_vector in inventory_chunk {
                    if let Some(object_message) = self.inventory.get_object_message(&inventory_vector) {
                        try!(send(object_message));
                    }
                }
            },
            m @ Message::Object { .. } => {
                self.inventory.add_object_message(&m);
            }
        };

        Ok(())
    }

    fn create_version_message(&self) -> Message {
        let port = self.config.port();
        let our_addr = to_socket_addr(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port));
        let nonce = self.config.nonce();
        let user_agent = self.config.user_agent().to_string();
        let streams = vec![ 1 ];

        Message::Version {
            version: 3,
            services: 1,
            timestamp: get_time(),
            addr_recv: self.peer_addr,
            addr_from: our_addr,
            nonce: nonce,
            user_agent: user_agent,
            streams: streams
        }
    }

    fn create_addr_message(&self) -> Message {
        let addr_list = self.known_nodes.get_random_selection_but_not(MAX_NODES_COUNT, vec![ self.peer_addr ]);
        Message::Addr {
            addr_list: addr_list
        }
    }

    fn create_inv_message(&self, inventory_chunk: Vec<InventoryVector>) -> Message {
        Message::Inv {
            inventory: inventory_chunk
        }
    }

    fn create_getdata_message(&self, inventory_chunk: Vec<InventoryVector>) -> Message {
        Message::GetData {
            inventory: inventory_chunk
        }
    }
}

#[cfg(test)]
mod tests {
    use config::Config;
    use inventory::{Inventory,calculate_inventory_vector};
    use known_nodes::KnownNodes;
    use message::{InventoryVector,KnownNode,Message,Object,GetPubKey};
    use net::to_socket_addr;
    use persist::Persister;
    use std::sync::Mutex;
    use std::sync::mpsc::SendError;
    use time::{Timespec,get_time};
    use super::MessageResponder;

    struct Output {
        pub messages: Mutex<Vec<Message>>
    }

    impl Output {
        fn new() -> Output {
            Output {
                messages: Mutex::new(vec![])
            }
        }

        fn add(&self, message: Message) -> Result<(), SendError<Message>> {
            self.messages.lock().unwrap().push(message);
            Ok(())
        }

        fn get_messages(&self) -> Vec<Message> {
            self.messages.lock().unwrap().clone()
        }
    }

    #[test]
    fn test_get_version_send_verack() {
        let input = Message::Version {
            version: 3,
            services: 1,
            timestamp: get_time(),
            addr_recv: to_socket_addr("127.0.0.1:8555"),
            addr_from: to_socket_addr("127.0.0.1:8444"),
            nonce: 0x0102030405060708,
            user_agent: "test".to_string(),
            streams: vec![ 1 ]
        };
        let output = run_test(input, Persister::new());
        assert_eq!(1, output.len());

        let message = &output[0];
        match message {
            &Message::Verack => {},
            _ => panic!("Not a Verack message: {:?}", message)
        }
    }

    #[test]
    fn test_get_verack_empty_persister_send_empty_addr() {
        let input = Message::Verack;
        let output = run_test(input, Persister::new());
        assert_eq!(1, output.len());

        let message = &output[0];
        match message {
            &Message::Addr { ref addr_list } => { assert_eq!(0, addr_list.len()) },
            _ => panic!("Not an Addr message: {:?}", message)
        }
    }

    #[test]
    fn test_get_verack_populated_persister_send_addr_and_inv() {
        let mut persister = Persister::new();
        let known_node = KnownNode {
            last_seen: Timespec::new(5, 0),
            stream: 1,
            services: 1,
            socket_addr: to_socket_addr("12.13.14.15:1000")
        };
        persister.add_known_node(&known_node);
        let mut inventory = Inventory::new(persister.clone());
        let persisted_message = create_object_message();
        inventory.add_object_message(&persisted_message);

        let input = Message::Verack;
        let output = run_test(input, persister);
        assert_eq!(2, output.len());

        let message1 = &output[0];
        match message1 {
            &Message::Addr { ref addr_list } => {
                assert_eq!(1, addr_list.len());
                let output_node = &addr_list[0];
                assert_eq!(Timespec::new(5, 0), output_node.last_seen);
                assert_eq!(1, output_node.stream);
                assert_eq!(1, output_node.services);
                assert_eq!(to_socket_addr("12.13.14.15:1000"), output_node.socket_addr);
            },
            _ => panic!("Not an Addr message: {:?}", message1)
        }

        let message2 = &output[1];
        match message2 {
            &Message::Inv { ref inventory } => {
                assert_eq!(1, inventory.len());
                let inventory_vector = &inventory[0];
                assert_eq!(&calculate_inventory_vector(&persisted_message), inventory_vector);
            },
            _ => panic!("Not an Addr message: {:?}", message1)
        }
    }

    #[test]
    fn test_get_addr_populates_persister() {
        let persister = Persister::new();
        let known_node = KnownNode {
            last_seen: Timespec::new(6, 0),
            stream: 1,
            services: 1,
            socket_addr: to_socket_addr("22.33.44.55:6666")
        };
        let input = Message::Addr {
            addr_list: vec![ known_node.clone() ]
        };
        let output = run_test(input, persister.clone());
        assert_eq!(0, output.len());

        let known_nodes = persister.get_known_nodes();
        assert_eq!(1, known_nodes.len());

        let known_node_recovered = &known_nodes[0];
        assert_eq!(&known_node, known_node_recovered);
    }

    #[test]
    fn test_get_inv_empty_persister_send_getdata_for_all() {
        let inventory_vector1 = InventoryVector { hash: vec![1; 20] };
        let inventory_vector2 = InventoryVector { hash: vec![2; 20] };
        let input = Message::Inv {
            inventory: vec![
                inventory_vector1.clone(),
                inventory_vector2.clone()
            ]
        };
        let output = run_test(input, Persister::new());
        assert_eq!(1, output.len());

        let message = &output[0];
        match message {
            &Message::GetData { ref inventory } => {
                assert_eq!(2, inventory.len());
                assert_eq!(&inventory[0], &inventory_vector1);
                assert_eq!(&inventory[1], &inventory_vector2);
            },
            _ => panic!("Not an Inv message: {:?}", message)
        }
    }

    #[test]
    fn test_get_inv_only_getdata_for_vectors_not_in_persister() {
        let inventory_vector1 = InventoryVector { hash: vec![1; 20] };
        let inventory_vector2 = InventoryVector { hash: vec![2; 20] };
        let input = Message::Inv {
            inventory: vec![
                inventory_vector1.clone(),
                inventory_vector2.clone()
            ]
        };
        let mut persister = Persister::new();
        persister.add_object_message(&inventory_vector1, &create_object_message());
        let output = run_test(input, persister);
        assert_eq!(1, output.len());

        let message = &output[0];
        match message {
            &Message::GetData { ref inventory } => {
                assert_eq!(1, inventory.len());
                assert_eq!(&inventory[0], &inventory_vector2);
            },
            _ => panic!("Not an Inv message: {:?}", message)
        }
    }

    fn create_object_message() -> Message {
        Message::Object {
            nonce: 1,
            expiry: Timespec::new(2, 0),
            version: 3,
            stream: 1,
            object: Object::GetPubKey(GetPubKey::V3 { ripe: vec![4; 20] })
        }
    }

    fn run_test(input: Message, persister: Persister) -> Vec<Message> {
        let config = Config::new();
        let known_nodes = KnownNodes::new(persister.clone());
        let inventory = Inventory::new(persister.clone());
        let peer_addr = to_socket_addr("127.0.0.1:8444");
        let mut responder = MessageResponder::new(&config, &known_nodes, &inventory, peer_addr);

        let output = Output::new();
        responder.respond(input, |m| { output.add(m) } ).unwrap();

        output.get_messages()
    }
}
<|endoftext|>


<commit_before>//! Contains the WebSocket server component.

use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;

use uuid::Uuid;
use websockets::Server as WebSocketServer;
use websockets::{Message, Sender, Receiver};
use websockets::header::WebSocketProtocol;
use websockets::message::Type;

/// The WebSocket server.
///
/// Manages WebSocket connections from clients of the HTTP server.
pub struct Server {

    /// The port that the server is listening on.
    pub port: u16,
    active_connections: Arc<Mutex<HashMap<Uuid, mpsc::Sender<String>>>>,
}

impl Server {

    /// Creates a new server that listens on port `port`.
    pub fn new(port: u16) -> Server {
        Server {
            port: port,
            active_connections: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Starts the server.
    ///
    /// This method does not return.
    pub fn start(&self) {
        self.listen_forever()
    }

    /// Sends HTML data to all open WebSocket connections on the server.
    pub fn notify(&self, html: String) {
        debug!("received html: {}", &html);
        for (uuid, sender) in self.active_connections.lock().unwrap().iter_mut() {
            debug!("notifying websocket {}", uuid);
            sender.send(html.to_owned()).unwrap();
        }
    }

    /// Listen for WebSocket connections.
    fn listen_forever(&self) {
        let server = WebSocketServer::bind(("localhost", self.port)).unwrap();
        info!("WebSockets listening on {}", self.port);

        for connection in server {
            let active_connections = self.active_connections.clone();

            // Spawn a new thread for each new connection.
            thread::spawn(move || {
                let request = connection.unwrap().read_request().unwrap();
                let headers = request.headers.clone();

                request.validate().unwrap();

                let mut response = request.accept();

                if let Some(&WebSocketProtocol(ref protocols)) = headers.get() {
                    if protocols.contains(&("rust-websocket".to_string())) {
                        response.headers.set(WebSocketProtocol(vec!["rust-websocket".to_string()]));
                    }
                }

                let mut client = response.send().unwrap();

                let ip = client.get_mut_sender()
                    .get_mut()
                    .peer_addr()
                    .unwrap();

                info!("Connection from {}", ip);

                // Create the send and recieve channdels for the websocket.
                let (mut sender, mut receiver) = client.split();

                // Create a two tranmitters from this channel so we can send messages from other
                // threads.
                let (tx, rx) = channel();
                let tx_2 = tx.clone();

                // Store the sender in the active connections.
                let uuid = Uuid::new_v4();
                active_connections.lock().unwrap().insert(uuid, tx);

                // Start a separate thread to manage sending messages to the client.
                thread::spawn(move || {
                    loop {
                        let message: String = match rx.recv() {
                            Ok(m) => m,
                            Err(e) => {
                                debug!("Send loop got error: {:?}", e);
                                return;
                            }
                        };
                        tx_2.send(message).unwrap();
                    }
                });

                // Use this thread for handling messages from the client.
                for message in receiver.incoming_messages() {
                    let message: Message = match message {
                        Ok(m) => {
                            debug!("Got message {:?}", m);
                            m
                        },
                        Err(e) => {
                            debug!("Receive loop got error: {}", e);
                            sender.send_message(&Message::close()).unwrap();
                            return;
                        }
                    };
                    match message.opcode {
                        Type::Close => {
                            // The client has closed the connection.
                            sender.send_message(&Message::close()).unwrap();
                            active_connections.lock().unwrap().remove(&uuid);
                            return;
                        },
                        Type::Ping => match sender.send_message(&Message::pong(message.payload)) {
                            Ok(()) => (),
                            Err(e) => {
                                debug!("Receive Loop got error: {:?}", e);
                                return;
                            }
                        },
                        _ => () // Ignore all other messages.
                    }
                }
            });
        }
    }
}
<commit_msg>clean up websocket logic<commit_after>//! Contains the WebSocket server component.

use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;

use uuid::Uuid;
use websockets::Server as WebSocketServer;
use websockets::{Message, Sender, Receiver};
use websockets::header::WebSocketProtocol;
use websockets::message::Type;

/// The WebSocket server.
///
/// Manages WebSocket connections from clients of the HTTP server.
pub struct Server {

    /// The port that the server is listening on.
    pub port: u16,
    active_connections: Arc<Mutex<HashMap<Uuid, mpsc::Sender<String>>>>,
}

impl Server {

    /// Creates a new server that listens on port `port`.
    pub fn new(port: u16) -> Server {
        Server {
            port: port,
            active_connections: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Starts the server.
    ///
    /// This method does not return.
    pub fn start(&self) {
        self.listen_forever()
    }

    /// Sends HTML data to all open WebSocket connections on the server.
    pub fn notify(&self, html: String) {
        for (uuid, sender) in self.active_connections.lock().unwrap().iter_mut() {
            debug!("notifying websocket {}", uuid);
            sender.send(html.to_owned()).unwrap();
        }
    }

    /// Listen for WebSocket connections.
    fn listen_forever(&self) {
        let server = WebSocketServer::bind(("localhost", self.port)).unwrap();
        info!("WebSockets listening on {}", self.port);

        for connection in server {
            let active_connections = self.active_connections.clone();

            // Spawn a new thread for each new connection.
            thread::spawn(move || {
                let request = connection.unwrap().read_request().unwrap();
                let headers = request.headers.clone();

                request.validate().unwrap();

                let mut response = request.accept();

                if let Some(&WebSocketProtocol(ref protocols)) = headers.get() {
                    if protocols.contains(&("rust-websocket".to_string())) {
                        response.headers.set(WebSocketProtocol(vec!["rust-websocket".to_string()]));
                    }
                }

                let mut client = response.send().unwrap();

                let ip = client.get_mut_sender()
                    .get_mut()
                    .peer_addr()
                    .unwrap();

                info!("Connection from {}", ip);

                // Create the send and recieve channdels for the websocket.
                let (mut sender, mut receiver) = client.split();

                // Create a two tranmitters from this channel so we can send messages from other
                // threads.
                // Created senders that will send markdown between threads
                let (md_tx, md_rx) = channel();
                let (message_tx, message_rx) = channel();
                let message_tx_2 = message_tx.clone();

                // Store the sender in the active connections.
                let uuid = Uuid::new_v4();
                active_connections.lock().unwrap().insert(uuid, md_tx);

                // Start a separate thread to manage sending messages to the client.
                thread::spawn(move || {
                    loop {
                        match md_rx.recv() {
                            Ok(m) => message_tx.send(Message::text(m)).unwrap(),
                            Err(e) => {
                                debug!("Send loop got error: {:?}", e);
                                return;
                            }
                        };
                    }
                });

                // Websocket send loop
                thread::spawn(move || {
                    loop {
                        let message: Message = match message_rx.recv() {
                            Ok(m) => m,
                            Err(e) => {
                                debug!("Send Loop: {:?}", e);
                                return;
                            }
                        };
                        match message.opcode {
                            Type::Close => {
                                let _ = sender.send_message(&message);
                                // If it's a close message, just send it and then return.
                                return;
                            },
                            _ => (),
                        }
                        // Send the message
                        match sender.send_message(&message) {
                            Ok(()) => (),
                            Err(e) => {
                                debug!("Send Loop: {:?}", e);
                                let _ = sender.send_message(&Message::close());
                                return;
                            }
                        }
                    }
                });

                // Websocket receive loop
                for message in receiver.incoming_messages() {
                    let message: Message = match message {
                        Ok(m) => m,
                        Err(e) => {
                            debug!("Receive loop got error: {}", e);
                            message_tx_2.send(Message::close()).unwrap();
                            return;
                        }
                    };
                    match message.opcode {
                        Type::Close => {
                            // The client has closed the connection.
                            message_tx_2.send(Message::close()).unwrap();
                            active_connections.lock().unwrap().remove(&uuid);
                            return;
                        },
                        Type::Ping => match message_tx_2.send(Message::pong(message.payload)) {
                            Ok(()) => (),
                            Err(e) => {
                                debug!("Receive Loop got error: {:?}", e);
                                return;
                            }
                        },
                        _ => () // Ignore all other messages.
                    }
                }
            });
        }
    }
}
<|endoftext|>


<commit_before>//! Buffer and working directory management.

use buffer::Buffer;
use std::path::PathBuf;

/// An owned collection of buffers and associated path,
/// representing a running editor environment.
pub struct Workspace {
    pub path: PathBuf,
    buffers: Vec<Buffer>,
    current_buffer_index: Option<usize>,
}

impl Workspace {
    /// Adds a buffer to the workspace and selects it.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    /// ```
    pub fn add_buffer(&mut self, buf: Buffer) {
        self.buffers.push(buf);
        self.current_buffer_index = Some(self.buffers.len()-1);
    }

    /// Returns a mutable reference to the currently
    /// selected buffer, unless the workspace is empty.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Get a reference to the current buffer.
    /// let buffer_reference = workspace.current_buffer().unwrap();
    /// ```
    pub fn current_buffer(&mut self) -> Option<&mut Buffer> {
        match self.current_buffer_index {
            Some(index) => Some(&mut self.buffers[index]),
            None => None,
        }
    }

    /// Removes the currently selected buffer from the collection.
    /// If the workspace is empty, this method does nothing.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Close the current buffer.
    /// workspace.close_current_buffer();
    /// ```
    pub fn close_current_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                self.buffers.remove(index);

                if self.buffers.is_empty() {
                    self.current_buffer_index = None;
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            }
            None => return,
        };
    }

    /// Selects the previous buffer in the workspace (buffers are ordered as
    /// they are added to the workspace). If the currently selected buffer is
    /// the first in the collection, this will wrap and select the last buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Select the previous buffer.
    /// workspace.previous_buffer();
    /// ```
    pub fn previous_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index > 0 {
                    self.current_buffer_index = Some(index-1);
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            },
            None => return,
        }
    }

    /// Selects the next buffer in the workspace (buffers are ordered as
    /// they are added to the workspace). If the currently selected buffer is
    /// the last in the collection, this will wrap and select the first buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Select the next buffer.
    /// workspace.next_buffer();
    /// ```
    pub fn next_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index == self.buffers.len()-1 {
                    self.current_buffer_index = Some(0);
                } else {
                    self.current_buffer_index = Some(index+1);
                }
            },
            None => return,
        }
    }

    /// Whether or not the workspace contains a buffer with the specified path.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path.clone()).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// assert!(workspace.contains_buffer_with_path(&file_path));
    /// ```
    pub fn contains_buffer_with_path(&self, path: &PathBuf) -> bool {
        self.buffers.iter().any(|buffer| {
            match buffer.path {
                Some(ref buffer_path) => path == buffer_path,
                None => false,
            }
        })
    }
}

/// Creates a new workspace.
pub fn new(path: PathBuf) -> Workspace {
    Workspace{ path: path, buffers: Vec::new(), current_buffer_index: None }
}

#[cfg(test)]
mod tests {
    use super::new;
    use buffer;
    use std::path::PathBuf;

    #[test]
    fn add_buffer_adds_and_selects_the_passed_buffer() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);

        assert_eq!(workspace.buffers.len(), 1);
        assert_eq!(workspace.current_buffer().unwrap().data(), "it works!\n");
    }

    #[test]
    fn current_buffer_returns_none_when_there_are_no_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn current_buffer_returns_one_when_there_are_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);
        assert!(workspace.current_buffer().is_some());
    }

    #[test]
    fn close_current_buffer_does_nothing_when_none_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn close_current_buffer_cleans_up_when_only_one_buffer_is_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.add_buffer(buffer::new());
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
        assert!(workspace.current_buffer_index.is_none());
    }

    #[test]
    fn close_current_buffer_when_two_are_open_selects_the_other() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);

        // Ensure that the second buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        workspace.close_current_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");
    }

    #[test]
    fn previous_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.previous_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn previous_buffer_when_three_are_open_selects_previous_wrapping_to_last() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that the second buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the first buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that it wraps back to the third buffer.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }

    #[test]
    fn next_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.next_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn next_buffer_when_three_are_open_selects_next_wrapping_to_first() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that it wraps back to the first buffer.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that the second buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the third buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }
}
<commit_msg>Add open_buffer method to workspace type.<commit_after>//! Buffer and working directory management.

use buffer;
use buffer::Buffer;
use std::io::Error;
use std::path::PathBuf;

/// An owned collection of buffers and associated path,
/// representing a running editor environment.
pub struct Workspace {
    pub path: PathBuf,
    buffers: Vec<Buffer>,
    current_buffer_index: Option<usize>,
}

impl Workspace {
    /// Adds a buffer to the workspace and selects it.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    /// ```
    pub fn add_buffer(&mut self, buf: Buffer) {
        self.buffers.push(buf);
        self.current_buffer_index = Some(self.buffers.len()-1);
    }

    /// Opens a buffer at the specified path and selects it.
    /// If a buffer with the specified path already exists,
    /// it is selected, rather than opening a duplicate buffer.
    /// Any errors encountered while opening the buffer are returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Open a buffer in the workspace.
    /// workspace.open_buffer(file_path.clone());
    /// ```
    pub fn open_buffer(&mut self, path: PathBuf) -> Option<Error> {
        if self.contains_buffer_with_path(&path) {
            // We already have this buffer in the workspace.
            // Loop through the buffers until it's selected.
            loop {
                match self.current_buffer() {
                    Some(buffer) => {
                        match buffer.path {
                            Some(ref current_path) => {
                                if *current_path == path {
                                    break;
                                }
                            },
                            None => (),
                        }
                    },
                    None => (),
                }

                self.next_buffer()
            }

            // Not going to run into IO errors if we're not opening a buffer.
            None
        } else {
            match buffer::from_file(path) {
                Ok(buffer) => {
                    self.add_buffer(buffer);
                    None
                },
                Err(error) => Some(error),
            }
        }
    }

    /// Returns a mutable reference to the currently
    /// selected buffer, unless the workspace is empty.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Get a reference to the current buffer.
    /// let buffer_reference = workspace.current_buffer().unwrap();
    /// ```
    pub fn current_buffer(&mut self) -> Option<&mut Buffer> {
        match self.current_buffer_index {
            Some(index) => Some(&mut self.buffers[index]),
            None => None,
        }
    }

    /// Removes the currently selected buffer from the collection.
    /// If the workspace is empty, this method does nothing.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Close the current buffer.
    /// workspace.close_current_buffer();
    /// ```
    pub fn close_current_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                self.buffers.remove(index);

                if self.buffers.is_empty() {
                    self.current_buffer_index = None;
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            }
            None => return,
        };
    }

    /// Selects the previous buffer in the workspace (buffers are ordered as
    /// they are added to the workspace). If the currently selected buffer is
    /// the first in the collection, this will wrap and select the last buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Select the previous buffer.
    /// workspace.previous_buffer();
    /// ```
    pub fn previous_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index > 0 {
                    self.current_buffer_index = Some(index-1);
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            },
            None => return,
        }
    }

    /// Selects the next buffer in the workspace (buffers are ordered as
    /// they are added to the workspace). If the currently selected buffer is
    /// the last in the collection, this will wrap and select the first buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// // Select the next buffer.
    /// workspace.next_buffer();
    /// ```
    pub fn next_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index == self.buffers.len()-1 {
                    self.current_buffer_index = Some(0);
                } else {
                    self.current_buffer_index = Some(index+1);
                }
            },
            None => return,
        }
    }

    /// Whether or not the workspace contains a buffer with the specified path.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::PathBuf;
    ///
    /// // Set up the paths we'll use.
    /// let directory_path = PathBuf::from("tests/sample");
    /// let file_path = PathBuf::from("tests/sample/file");
    ///
    /// // Create a workspace.
    /// let mut workspace = scribe::workspace::new(directory_path);
    ///
    /// // Add a buffer to the workspace.
    /// let buf = scribe::buffer::from_file(file_path.clone()).unwrap();
    /// workspace.add_buffer(buf);
    ///
    /// assert!(workspace.contains_buffer_with_path(&file_path));
    /// ```
    pub fn contains_buffer_with_path(&self, path: &PathBuf) -> bool {
        self.buffers.iter().any(|buffer| {
            match buffer.path {
                Some(ref buffer_path) => path == buffer_path,
                None => false,
            }
        })
    }
}

/// Creates a new workspace.
pub fn new(path: PathBuf) -> Workspace {
    Workspace{ path: path, buffers: Vec::new(), current_buffer_index: None }
}

#[cfg(test)]
mod tests {
    use super::new;
    use buffer;
    use std::path::PathBuf;

    #[test]
    fn add_buffer_adds_and_selects_the_passed_buffer() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);

        assert_eq!(workspace.buffers.len(), 1);
        assert_eq!(workspace.current_buffer().unwrap().data(), "it works!\n");
    }

    #[test]
    fn open_buffer_adds_and_selects_the_buffer_at_the_specified_path() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.open_buffer(PathBuf::from("tests/sample/file"));

        assert_eq!(workspace.buffers.len(), 1);
        assert_eq!(workspace.current_buffer().unwrap().data(), "it works!\n");
    }

    #[test]
    fn open_buffer_does_not_open_a_buffer_already_in_the_workspace() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.open_buffer(PathBuf::from("tests/sample/file"));
        workspace.open_buffer(PathBuf::from("tests/sample/file"));

        assert_eq!(workspace.buffers.len(), 1);
    }

    #[test]
    fn open_buffer_selects_buffer_if_it_already_exists_in_workspace() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.open_buffer(PathBuf::from("tests/sample/file"));

        // Add and select another buffer.
        let mut buf = buffer::new();
        buf.insert("scribe");
        workspace.add_buffer(buf);
        assert_eq!(workspace.current_buffer().unwrap().data(), "scribe");

        // Try to add the first buffer again.
        workspace.open_buffer(PathBuf::from("tests/sample/file"));

        // Ensure there are only two buffers, and that the
        // one requested via open_buffer is now selected.
        assert_eq!(workspace.buffers.len(), 2);
        assert_eq!(workspace.current_buffer().unwrap().data(), "it works!\n");
    }

    #[test]
    fn current_buffer_returns_none_when_there_are_no_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn current_buffer_returns_one_when_there_are_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);
        assert!(workspace.current_buffer().is_some());
    }

    #[test]
    fn close_current_buffer_does_nothing_when_none_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn close_current_buffer_cleans_up_when_only_one_buffer_is_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.add_buffer(buffer::new());
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
        assert!(workspace.current_buffer_index.is_none());
    }

    #[test]
    fn close_current_buffer_when_two_are_open_selects_the_other() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);

        // Ensure that the second buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        workspace.close_current_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");
    }

    #[test]
    fn previous_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.previous_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn previous_buffer_when_three_are_open_selects_previous_wrapping_to_last() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that the second buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the first buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that it wraps back to the third buffer.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }

    #[test]
    fn next_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.next_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn next_buffer_when_three_are_open_selects_next_wrapping_to_first() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that it wraps back to the first buffer.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that the second buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the third buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{convert::TryInto, fs::File, io::Write, path::Path};

use assert_cmd::Command;
use autocxx_integration_tests::build_from_folder;
use tempdir::TempDir;

static MAIN_RS: &str = include_str!("../../../demo/src/main.rs");
static INPUT_H: &str = include_str!("../../../demo/src/input.h");
static BLANK: &str = "// Blank autocxx placeholder";

const KEEP_TEMPDIRS: bool = false;

#[test]
fn test_help() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("autocxx-gen")?;
    cmd.arg("-h").assert().success();
    Ok(())
}

fn base_test<F>(tmp_dir: &TempDir, arg_modifier: F) -> Result<(), Box<dyn std::error::Error>>
where
    F: FnOnce(&mut Command),
{
    let result = base_test_ex(tmp_dir, arg_modifier, false);
    assert_contentful(tmp_dir, "gen0.cc");
    result
}

fn base_test_ex<F>(
    tmp_dir: &TempDir,
    arg_modifier: F,
    use_complete_rs: bool,
) -> Result<(), Box<dyn std::error::Error>>
where
    F: FnOnce(&mut Command),
{
    let demo_code_dir = tmp_dir.path().join("demo");
    std::fs::create_dir(&demo_code_dir).unwrap();
    write_to_file(&demo_code_dir, "input.h", INPUT_H.as_bytes());
    write_to_file(&demo_code_dir, "main.rs", MAIN_RS.as_bytes());
    let demo_rs = demo_code_dir.join("main.rs");
    let mut cmd = Command::cargo_bin("autocxx-gen")?;
    arg_modifier(&mut cmd);
    cmd.arg("--inc")
        .arg(demo_code_dir.to_str().unwrap())
        .arg(demo_rs)
        .arg("--outdir")
        .arg(tmp_dir.path().to_str().unwrap())
        .arg("--gen-cpp");
    if use_complete_rs {
        cmd.arg("--gen-rs-complete");
    } else {
        cmd.arg("--gen-rs-include");
    }
    cmd.assert().success();
    Ok(())
}

#[test]
fn test_gen() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test(&tmp_dir, |_| {})?;
    File::create(tmp_dir.path().join("cxx.h"))
        .and_then(|mut cxx_h| cxx_h.write_all(&autocxx_engine::HEADER.as_bytes()))?;
    std::env::set_var("OUT_DIR", tmp_dir.path().to_str().unwrap());
    let r = build_from_folder(
        tmp_dir.path(),
        &tmp_dir.path().join("demo/main.rs"),
        vec![tmp_dir.path().join("autocxx-ffi-default-gen.rs")],
        &["gen0.cc"],
    );
    if KEEP_TEMPDIRS {
        println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
    }
    r.unwrap();
    Ok(())
}

#[test]
fn test_include_prefixes() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test(&tmp_dir, |cmd| {
        cmd.arg("--cxx-h-path")
            .arg("foo/")
            .arg("--cxxgen-h-path")
            .arg("bar/")
            .arg("--generate-exact")
            .arg("3");
    })?;
    assert_contains(&tmp_dir, "gen0.h", "foo/cxx.h");
    // Currently we don't test cxxgen-h-path because we build the demo code
    // which doesn't refer to generated cxx header code.
    Ok(())
}

#[test]
fn test_gen_fixed_num() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test_ex(
        &tmp_dir,
        |cmd| {
            cmd.arg("--generate-exact").arg("3");
        },
        true,
    )?;
    assert_contentful(&tmp_dir, "gen0.cc");
    assert_contentful(&tmp_dir, "gen0.h");
    // TODO: This file will actually try #including one of our .h files if there's anything to put
    // in it. Figure out how to make that happen to test that it works.
    assert_not_contentful(&tmp_dir, "gen1.cc");
    assert_not_contentful(&tmp_dir, "gen1.h");
    assert_not_contentful(&tmp_dir, "gen2.cc");
    assert_not_contentful(&tmp_dir, "gen2.h");
    assert_contentful(&tmp_dir, "cxxgen.h");
    assert_contentful(&tmp_dir, "gen.complete.rs");
    File::create(tmp_dir.path().join("cxx.h"))
        .and_then(|mut cxx_h| cxx_h.write_all(&autocxx_engine::HEADER.as_bytes()))?;
    let r = build_from_folder(
        tmp_dir.path(),
        &tmp_dir.path().join("gen.complete.rs"),
        vec![],
        &["gen0.cc"],
    );
    if KEEP_TEMPDIRS {
        println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
    }
    r.unwrap();
    Ok(())
}

#[test]
fn test_gen_preprocess() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    let prepro_path = tmp_dir.path().join("preprocessed.h");
    base_test(&tmp_dir, |cmd| {
        cmd.env("AUTOCXX_PREPROCESS", prepro_path.to_str().unwrap());
    })?;
    assert_contentful(&tmp_dir, "preprocessed.h");
    // Check that a random thing from one of the headers in
    // `ALL_KNOWN_SYSTEM_HEADERS` is included.
    assert!(std::fs::read_to_string(prepro_path)?.contains("integer_sequence"));
    Ok(())
}

#[test]
fn test_gen_repro() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    let repro_path = tmp_dir.path().join("repro.json");
    base_test(&tmp_dir, |cmd| {
        cmd.env("AUTOCXX_REPRO_CASE", repro_path.to_str().unwrap());
    })?;
    assert_contentful(&tmp_dir, "repro.json");
    // Check that a random thing from one of the headers in
    // `ALL_KNOWN_SYSTEM_HEADERS` is included.
    assert!(std::fs::read_to_string(repro_path)?.contains("integer_sequence"));
    Ok(())
}

#[test]
fn test_skip_cxx_gen() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test_ex(
        &tmp_dir,
        |cmd| {
            cmd.arg("--generate-exact")
                .arg("3")
                .arg("--fix-rs-include-name")
                .arg("--skip-cxx-gen");
        },
        false,
    )?;
    assert_contentful(&tmp_dir, "gen0.h");
    assert_not_contentful(&tmp_dir, "gen0.cc");
    assert_exists(&tmp_dir, "gen1.cc");
    assert_exists(&tmp_dir, "gen1.h");
    assert_exists(&tmp_dir, "gen2.cc");
    assert_exists(&tmp_dir, "gen2.h");
    assert_contentful(&tmp_dir, "gen0.include.rs");
    assert_exists(&tmp_dir, "gen1.include.rs");
    assert_exists(&tmp_dir, "gen2.include.rs");
    Ok(())
}

fn write_to_file(dir: &Path, filename: &str, content: &[u8]) {
    let path = dir.join(filename);
    let mut f = File::create(&path).expect("Unable to create file");
    f.write_all(content).expect("Unable to write file");
}

fn assert_contentful(outdir: &TempDir, fname: &str) {
    let p = outdir.path().join(fname);
    if !p.exists() {
        panic!("File {} didn't exist", p.to_string_lossy());
    }
    assert!(
        p.metadata().unwrap().len() > BLANK.len().try_into().unwrap(),
        "File {} is empty",
        fname
    );
}

fn assert_not_contentful(outdir: &TempDir, fname: &str) {
    let p = outdir.path().join(fname);
    if !p.exists() {
        panic!("File {} didn't exist", p.to_string_lossy());
    }
    assert!(
        p.metadata().unwrap().len() <= BLANK.len().try_into().unwrap(),
        "File {} is not empty",
        fname
    );
}

fn assert_exists(outdir: &TempDir, fname: &str) {
    let p = outdir.path().join(fname);
    if !p.exists() {
        panic!("File {} didn't exist", p.to_string_lossy());
    }
}

fn assert_contains(outdir: &TempDir, fname: &str, pattern: &str) {
    let p = outdir.path().join(fname);
    let content = std::fs::read_to_string(&p).expect(fname);
    eprintln!("content = {}", content);
    assert!(content.contains(pattern));
}
<commit_msg>More clippy<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{convert::TryInto, fs::File, io::Write, path::Path};

use assert_cmd::Command;
use autocxx_integration_tests::build_from_folder;
use tempdir::TempDir;

static MAIN_RS: &str = include_str!("../../../demo/src/main.rs");
static INPUT_H: &str = include_str!("../../../demo/src/input.h");
static BLANK: &str = "// Blank autocxx placeholder";

const KEEP_TEMPDIRS: bool = false;

#[test]
fn test_help() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("autocxx-gen")?;
    cmd.arg("-h").assert().success();
    Ok(())
}

fn base_test<F>(tmp_dir: &TempDir, arg_modifier: F) -> Result<(), Box<dyn std::error::Error>>
where
    F: FnOnce(&mut Command),
{
    let result = base_test_ex(tmp_dir, arg_modifier, false);
    assert_contentful(tmp_dir, "gen0.cc");
    result
}

fn base_test_ex<F>(
    tmp_dir: &TempDir,
    arg_modifier: F,
    use_complete_rs: bool,
) -> Result<(), Box<dyn std::error::Error>>
where
    F: FnOnce(&mut Command),
{
    let demo_code_dir = tmp_dir.path().join("demo");
    std::fs::create_dir(&demo_code_dir).unwrap();
    write_to_file(&demo_code_dir, "input.h", INPUT_H.as_bytes());
    write_to_file(&demo_code_dir, "main.rs", MAIN_RS.as_bytes());
    let demo_rs = demo_code_dir.join("main.rs");
    let mut cmd = Command::cargo_bin("autocxx-gen")?;
    arg_modifier(&mut cmd);
    cmd.arg("--inc")
        .arg(demo_code_dir.to_str().unwrap())
        .arg(demo_rs)
        .arg("--outdir")
        .arg(tmp_dir.path().to_str().unwrap())
        .arg("--gen-cpp");
    if use_complete_rs {
        cmd.arg("--gen-rs-complete");
    } else {
        cmd.arg("--gen-rs-include");
    }
    cmd.assert().success();
    Ok(())
}

#[test]
fn test_gen() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test(&tmp_dir, |_| {})?;
    File::create(tmp_dir.path().join("cxx.h"))
        .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?;
    std::env::set_var("OUT_DIR", tmp_dir.path().to_str().unwrap());
    let r = build_from_folder(
        tmp_dir.path(),
        &tmp_dir.path().join("demo/main.rs"),
        vec![tmp_dir.path().join("autocxx-ffi-default-gen.rs")],
        &["gen0.cc"],
    );
    if KEEP_TEMPDIRS {
        println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
    }
    r.unwrap();
    Ok(())
}

#[test]
fn test_include_prefixes() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test(&tmp_dir, |cmd| {
        cmd.arg("--cxx-h-path")
            .arg("foo/")
            .arg("--cxxgen-h-path")
            .arg("bar/")
            .arg("--generate-exact")
            .arg("3");
    })?;
    assert_contains(&tmp_dir, "gen0.h", "foo/cxx.h");
    // Currently we don't test cxxgen-h-path because we build the demo code
    // which doesn't refer to generated cxx header code.
    Ok(())
}

#[test]
fn test_gen_fixed_num() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test_ex(
        &tmp_dir,
        |cmd| {
            cmd.arg("--generate-exact").arg("3");
        },
        true,
    )?;
    assert_contentful(&tmp_dir, "gen0.cc");
    assert_contentful(&tmp_dir, "gen0.h");
    // TODO: This file will actually try #including one of our .h files if there's anything to put
    // in it. Figure out how to make that happen to test that it works.
    assert_not_contentful(&tmp_dir, "gen1.cc");
    assert_not_contentful(&tmp_dir, "gen1.h");
    assert_not_contentful(&tmp_dir, "gen2.cc");
    assert_not_contentful(&tmp_dir, "gen2.h");
    assert_contentful(&tmp_dir, "cxxgen.h");
    assert_contentful(&tmp_dir, "gen.complete.rs");
    File::create(tmp_dir.path().join("cxx.h"))
        .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?;
    let r = build_from_folder(
        tmp_dir.path(),
        &tmp_dir.path().join("gen.complete.rs"),
        vec![],
        &["gen0.cc"],
    );
    if KEEP_TEMPDIRS {
        println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
    }
    r.unwrap();
    Ok(())
}

#[test]
fn test_gen_preprocess() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    let prepro_path = tmp_dir.path().join("preprocessed.h");
    base_test(&tmp_dir, |cmd| {
        cmd.env("AUTOCXX_PREPROCESS", prepro_path.to_str().unwrap());
    })?;
    assert_contentful(&tmp_dir, "preprocessed.h");
    // Check that a random thing from one of the headers in
    // `ALL_KNOWN_SYSTEM_HEADERS` is included.
    assert!(std::fs::read_to_string(prepro_path)?.contains("integer_sequence"));
    Ok(())
}

#[test]
fn test_gen_repro() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    let repro_path = tmp_dir.path().join("repro.json");
    base_test(&tmp_dir, |cmd| {
        cmd.env("AUTOCXX_REPRO_CASE", repro_path.to_str().unwrap());
    })?;
    assert_contentful(&tmp_dir, "repro.json");
    // Check that a random thing from one of the headers in
    // `ALL_KNOWN_SYSTEM_HEADERS` is included.
    assert!(std::fs::read_to_string(repro_path)?.contains("integer_sequence"));
    Ok(())
}

#[test]
fn test_skip_cxx_gen() -> Result<(), Box<dyn std::error::Error>> {
    let tmp_dir = TempDir::new("example")?;
    base_test_ex(
        &tmp_dir,
        |cmd| {
            cmd.arg("--generate-exact")
                .arg("3")
                .arg("--fix-rs-include-name")
                .arg("--skip-cxx-gen");
        },
        false,
    )?;
    assert_contentful(&tmp_dir, "gen0.h");
    assert_not_contentful(&tmp_dir, "gen0.cc");
    assert_exists(&tmp_dir, "gen1.cc");
    assert_exists(&tmp_dir, "gen1.h");
    assert_exists(&tmp_dir, "gen2.cc");
    assert_exists(&tmp_dir, "gen2.h");
    assert_contentful(&tmp_dir, "gen0.include.rs");
    assert_exists(&tmp_dir, "gen1.include.rs");
    assert_exists(&tmp_dir, "gen2.include.rs");
    Ok(())
}

fn write_to_file(dir: &Path, filename: &str, content: &[u8]) {
    let path = dir.join(filename);
    let mut f = File::create(&path).expect("Unable to create file");
    f.write_all(content).expect("Unable to write file");
}

fn assert_contentful(outdir: &TempDir, fname: &str) {
    let p = outdir.path().join(fname);
    if !p.exists() {
        panic!("File {} didn't exist", p.to_string_lossy());
    }
    assert!(
        p.metadata().unwrap().len() > BLANK.len().try_into().unwrap(),
        "File {} is empty",
        fname
    );
}

fn assert_not_contentful(outdir: &TempDir, fname: &str) {
    let p = outdir.path().join(fname);
    if !p.exists() {
        panic!("File {} didn't exist", p.to_string_lossy());
    }
    assert!(
        p.metadata().unwrap().len() <= BLANK.len().try_into().unwrap(),
        "File {} is not empty",
        fname
    );
}

fn assert_exists(outdir: &TempDir, fname: &str) {
    let p = outdir.path().join(fname);
    if !p.exists() {
        panic!("File {} didn't exist", p.to_string_lossy());
    }
}

fn assert_contains(outdir: &TempDir, fname: &str, pattern: &str) {
    let p = outdir.path().join(fname);
    let content = std::fs::read_to_string(&p).expect(fname);
    eprintln!("content = {}", content);
    assert!(content.contains(pattern));
}
<|endoftext|>