<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use super::objects::{MimirObject, Admin};
use chrono;
use regex;
use hyper;
use hyper::status::StatusCode;
use rs_es::error::EsError;
use rs_es;
use rs_es::EsResponse;
use serde_json;
use rs_es::operations::search::ScanResult;

use super::objects::{AliasOperations, AliasOperation, AliasParameter};
use rs_es::units::Duration;

const SYNONYMS: [&'static str; 17] = ["cc,centre commercial",
                                      "hotel de ville,mairie",
                                      "gare sncf,gare",
                                      "chu,chr,hopital",
                                      "ld,lieu-dit",
                                      "st,saint",
                                      "ste,sainte",
                                      "bvd,bld,bd,boulevard",
                                      "pt,pont",
                                      "rle,ruelle",
                                      "rte,route",
                                      "vla,villa",
                                      "grand-champ,grandchamp",
                                      "fac,faculté,ufr,université",
                                      "embarcadère,gare maritime",
                                      "CPAM,sécurité sociale",
                                      "ANPE,Pole Emploi"];

// Rubber is an wrapper around elasticsearch API
pub struct Rubber {
    es_client: rs_es::Client,
    // some operation are not implemented in rs_es, we need to use a raw http client
    http_client: hyper::client::Client,
}

/// return the index associated to the given type and dataset
/// this will be an alias over another real index
fn get_main_type_and_dataset_index(doc_type: &str, dataset: &str) -> String {
    format!("munin_{}_{}", doc_type, dataset)
}

/// return the index associated to the given type
/// this will be an alias over another real index
fn get_main_type_index(doc_type: &str) -> String {
    format!("munin_{}", doc_type)
}

impl Rubber {
    // build a rubber with a connection string (http://host:port/)
    pub fn new(cnx: &str) -> Rubber {
        let re = regex::Regex::new(r"(?:https?://)?(?P<host>.+?):(?P<port>\d+)").unwrap();
        let cap = re.captures(cnx).unwrap();
        let host = cap.name("host").unwrap().as_str();
        let port = cap.name("port").unwrap().as_str().parse::<u32>().unwrap();
        info!("elastic search host {:?} port {:?}", host, port);

        Rubber {
            es_client: rs_es::Client::new(&host, port),
            http_client: hyper::client::Client::new(),
        }
    }

    pub fn get(&self, path: &str) -> Result<hyper::client::response::Response, EsError> {
        // Note: a bit duplicate on rs_es because some ES operations are not implemented
        debug!("doing a get on {}", path);
        let url = self.es_client.full_url(path);
        let result = try!(self.http_client
            .get(&url)
            .send());
        rs_es::do_req(result)
    }
    fn put(&self, path: &str, body: &str) -> Result<hyper::client::response::Response, EsError> {
        // Note: a bit duplicate on rs_es because some ES operations are not implemented
        debug!("doing a put on {} with {}", path, body);
        let url = self.es_client.full_url(path);
        let result = try!(self.http_client
            .put(&url)
            .body(body)
            .send());
        rs_es::do_req(result)
    }
    fn post(&self, path: &str, body: &str) -> Result<hyper::client::response::Response, EsError> {
        // Note: a bit duplicate on rs_es because some ES operations are not implemented
        debug!("doing a post on {} with {}", path, body);
        let url = self.es_client.full_url(path);
        let result = try!(self.http_client
            .post(&url)
            .body(body)
            .send());
        rs_es::do_req(result)
    }

    pub fn make_index(&self, doc_type: &str, dataset: &str) -> Result<String, String> {
        let current_time = chrono::UTC::now().format("%Y%m%d_%H%M%S_%f");
        let index_name = format!("munin_{}_{}_{}", doc_type, dataset, current_time);
        info!("creating index {}", index_name);
        self.create_index(&index_name.to_string()).map(|_| index_name)
    }

    fn create_index(&self, name: &String) -> Result<(), String> {
        debug!("creating index");
        // Note: in rs_es it can be done with MappingOperation but for the moment I think
        // storing the mapping in json is more convenient
        let analysis = include_str!("../../../json/settings.json");

        let mut analysis_json_value = try!(serde_json::from_str::<serde_json::Value>(&analysis)
            .map_err(|err| format!("{}", err)));

        let synonyms: Vec<_> = SYNONYMS.to_vec()
            .into_iter()
            .map(|s| serde_json::Value::String(s.to_string()))
            .collect();

        *analysis_json_value.pointer_mut("/settings/analysis/filter/synonym_filter/synonyms")
            .unwrap() = serde_json::Value::Array(synonyms);

        self.put(name, &analysis_json_value.to_string())
            .map_err(|e| {
                info!("Error while creating new index {}", name);
                e.to_string()
            })
            .and_then(|res| if res.status == StatusCode::Ok {
                Ok(())
            } else {
                Err(format!("cannot create index: {:?}", res))
            })
    }

    fn get_last_index(&self, doc_type: &str, dataset: &str) -> Result<Vec<String>, String> {
        let base_index = get_main_type_and_dataset_index(doc_type, dataset);
        self.get(&format!("{}/_aliases", base_index))
            .map_err(|e| e.to_string())
            .and_then(|res| match res.status {
                StatusCode::Ok => {
                    let value: serde_json::Value = try!(res.read_response()
                        .map_err(|e| e.to_string()));
                    Ok(value.as_object()
                        .and_then(|aliases| Some(aliases.keys().cloned().collect()))
                        .unwrap_or_else(|| {
                            info!("no previous index to delete for type {} and dataset {}",
                                  doc_type,
                                  dataset);
                            vec![]
                        }))
                }
                StatusCode::NotFound => {
                    info!("impossible to find alias {}, no last index to remove",
                          base_index);
                    Ok(vec![])
                }
                _ => Err(format!("invalid elasticsearch response: {:?}", res)),
            })
    }

    /// publish the index as the new index for this doc_type and this dataset
    /// move the index alias of the doc_type and the dataset to point to this indexes
    /// and remove the old index
    pub fn publish_index(&mut self,
                         doc_type: &str,
                         dataset: &str,
                         index: String,
                         is_geo_data: bool)
                         -> Result<(), String> {
        debug!("publishing index");
        let last_indexes = try!(self.get_last_index(doc_type, dataset));

        let dataset_index = get_main_type_and_dataset_index(doc_type, dataset);
        try!(self.alias(&dataset_index, &vec![index.clone()], &last_indexes));

        let type_index = get_main_type_index(doc_type);
        try!(self.alias(&type_index, &vec![dataset_index.clone()], &last_indexes));

        if is_geo_data {
            try!(self.alias("munin_geo_data", &vec![type_index.to_string()], &vec![]));
            try!(self.alias("munin", &vec!["munin_geo_data".to_string()], &vec![]));
        } else {
            try!(self.alias("munin", &vec![type_index.to_string()], &vec![]));
        }
        for i in last_indexes {
            try!(self.delete_index(&i));
        }
        Ok(())
    }

    pub fn is_existing_index(&self, name: &String) -> Result<bool, String> {
        self.get(&name)
            .map_err(|e| e.to_string())
            .map(|res| res.status == StatusCode::Ok)
    }

    /// add a list of new indexes to the alias
    /// remove a list of indexes from the alias
    fn alias(&self, alias: &str, add: &Vec<String>, remove: &Vec<String>) -> Result<(), String> {
        info!("for {}, adding alias {:?}, removing {:?}",
              alias,
              add,
              remove);
        let add_operations = add.iter().map(|x| {
            AliasOperation {
                remove: None,
                add: Some(AliasParameter {
                    index: x.clone(),
                    alias: alias.to_string(),
                }),
            }
        });
        let remove_operations = remove.iter().map(|x| {
            AliasOperation {
                add: None,
                remove: Some(AliasParameter {
                    index: x.clone(),
                    alias: alias.to_string(),
                }),
            }
        });
        let operations =
            AliasOperations { actions: add_operations.chain(remove_operations).collect() };
        let json = serde_json::to_string(&operations).unwrap();
        self.post("_aliases", &json)
            .map_err(|e| e.to_string())
            .and_then(|res| if res.status == StatusCode::Ok {
                Ok(())
            } else {
                error!("failed to change aliases for {}, es response: {:?}",
                       alias,
                       res);
                Err(format!("failed to post aliases for {}: {:?}", alias, res).to_string())
            })
    }

    pub fn delete_index(&mut self, index: &String) -> Result<(), String> {
        debug!("deleting index {}", &index);
        let res = self.es_client
            .delete_index(&index)
            .map(|res| res.acknowledged)
            .unwrap_or(false);
        if !res {
            Err(format!("Error deleting index {}", &index).into())
        } else {
            Ok(())
        }
    }

    pub fn bulk_index<T, I>(&mut self,
                            index: &String,
                            iter: I)
                            -> Result<usize, rs_es::error::EsError>
        where T: MimirObject,
              I: Iterator<Item = T>
    {
        use rs_es::operations::bulk::Action;
        let mut nb = 0;
        let chunk_size = 1000;
        // fold is used for creating the action and optionally set the id of the object
        let mut actions = iter.map(|v| {
            v.es_id()
                .into_iter()
                .fold(Action::index(v), |action, id| action.with_id(id))
        });
        loop {
            let chunk = actions.by_ref().take(chunk_size).collect::<Vec<_>>();
            nb += chunk.len();
            try!(self.es_client
                .bulk(&chunk)
                .with_index(&index)
                .with_doc_type(T::doc_type())
                .send());

            if chunk.len() < chunk_size {
                break;
            }
        }

        Ok(nb)
    }

    /// add all the element of 'iter' into elasticsearch
    ///
    /// To have zero downtime:
    /// first all the elements are added in a temporary index and when all has been indexed
    /// the index is published and the old index is removed
    pub fn index<T, I>(&mut self, dataset: &str, iter: I) -> Result<usize, String>
        where T: MimirObject,
              I: Iterator<Item = T>
    {
        // TODO better error handling
        let index = try!(self.make_index(T::doc_type(), dataset));
        let nb_elements = try!(self.bulk_index(&index, iter).map_err(|e| e.to_string()));
        try!(self.publish_index(T::doc_type(), dataset, index, T::is_geo_data()));
        Ok(nb_elements)
    }

    pub fn get_admins_from_dataset(&mut self,
                                   dataset: &str)
                                   -> Result<Vec<Admin>, rs_es::error::EsError> {
        self.get_admins_from_index(&get_main_type_and_dataset_index(Admin::doc_type(), dataset))
    }

    pub fn get_all_admins(&mut self) -> Result<Vec<Admin>, rs_es::error::EsError> {
        self.get_admins_from_index(&get_main_type_index(Admin::doc_type()))
    }

    fn get_admins_from_index(&mut self, index: &str) -> Result<Vec<Admin>, rs_es::error::EsError> {
        let mut result: Vec<Admin> = vec![];
        let mut scan: ScanResult<Admin> = try!(self.es_client
            .search_query()
            .with_indexes(&[&index])
            .with_size(1000)
            .with_types(&[&Admin::doc_type()])
            .scan(&Duration::minutes(1)));
        loop {
            let page = try!(scan.scroll(&mut self.es_client, &Duration::minutes(1)));
            if page.hits.hits.len() == 0 {
                break;
            }
            result.extend(page.hits.hits.into_iter().filter_map(|hit| hit.source).map(|ad| *ad));
        }
        try!(scan.close(&mut self.es_client));
        Ok(result)
    }
}

#[test]
pub fn test_valid_url() {
    Rubber::new("http://localhost:9200");
    Rubber::new("localhost:9200");
}

#[test]
#[should_panic]
pub fn test_invalid_url() {
    Rubber::new("http://bob");
}

#[test]
#[should_panic]
pub fn test_invalid_url_no_port() {
    Rubber::new("localhost");
}
<commit_msg>set synonyms in lowercase<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use super::objects::{MimirObject, Admin};
use chrono;
use regex;
use hyper;
use hyper::status::StatusCode;
use rs_es::error::EsError;
use rs_es;
use rs_es::EsResponse;
use serde_json;
use rs_es::operations::search::ScanResult;

use super::objects::{AliasOperations, AliasOperation, AliasParameter};
use rs_es::units::Duration;

const SYNONYMS: [&'static str; 17] = ["cc,centre commercial",
                                      "hotel de ville,mairie",
                                      "gare sncf,gare",
                                      "chu,chr,hopital",
                                      "ld,lieu-dit",
                                      "st,saint",
                                      "ste,sainte",
                                      "bvd,bld,bd,boulevard",
                                      "pt,pont",
                                      "rle,ruelle",
                                      "rte,route",
                                      "vla,villa",
                                      "grand-champ,grandchamp",
                                      "fac,faculte,ufr,universite",
                                      "embarcadere,gare maritime",
                                      "cpam,securite sociale",
                                      "anpe,pole emploi"];

// Rubber is an wrapper around elasticsearch API
pub struct Rubber {
    es_client: rs_es::Client,
    // some operation are not implemented in rs_es, we need to use a raw http client
    http_client: hyper::client::Client,
}

/// return the index associated to the given type and dataset
/// this will be an alias over another real index
fn get_main_type_and_dataset_index(doc_type: &str, dataset: &str) -> String {
    format!("munin_{}_{}", doc_type, dataset)
}

/// return the index associated to the given type
/// this will be an alias over another real index
fn get_main_type_index(doc_type: &str) -> String {
    format!("munin_{}", doc_type)
}

impl Rubber {
    // build a rubber with a connection string (http://host:port/)
    pub fn new(cnx: &str) -> Rubber {
        let re = regex::Regex::new(r"(?:https?://)?(?P<host>.+?):(?P<port>\d+)").unwrap();
        let cap = re.captures(cnx).unwrap();
        let host = cap.name("host").unwrap().as_str();
        let port = cap.name("port").unwrap().as_str().parse::<u32>().unwrap();
        info!("elastic search host {:?} port {:?}", host, port);

        Rubber {
            es_client: rs_es::Client::new(&host, port),
            http_client: hyper::client::Client::new(),
        }
    }

    pub fn get(&self, path: &str) -> Result<hyper::client::response::Response, EsError> {
        // Note: a bit duplicate on rs_es because some ES operations are not implemented
        debug!("doing a get on {}", path);
        let url = self.es_client.full_url(path);
        let result = try!(self.http_client
            .get(&url)
            .send());
        rs_es::do_req(result)
    }
    fn put(&self, path: &str, body: &str) -> Result<hyper::client::response::Response, EsError> {
        // Note: a bit duplicate on rs_es because some ES operations are not implemented
        debug!("doing a put on {} with {}", path, body);
        let url = self.es_client.full_url(path);
        let result = try!(self.http_client
            .put(&url)
            .body(body)
            .send());
        rs_es::do_req(result)
    }
    fn post(&self, path: &str, body: &str) -> Result<hyper::client::response::Response, EsError> {
        // Note: a bit duplicate on rs_es because some ES operations are not implemented
        debug!("doing a post on {} with {}", path, body);
        let url = self.es_client.full_url(path);
        let result = try!(self.http_client
            .post(&url)
            .body(body)
            .send());
        rs_es::do_req(result)
    }

    pub fn make_index(&self, doc_type: &str, dataset: &str) -> Result<String, String> {
        let current_time = chrono::UTC::now().format("%Y%m%d_%H%M%S_%f");
        let index_name = format!("munin_{}_{}_{}", doc_type, dataset, current_time);
        info!("creating index {}", index_name);
        self.create_index(&index_name.to_string()).map(|_| index_name)
    }

    fn create_index(&self, name: &String) -> Result<(), String> {
        debug!("creating index");
        // Note: in rs_es it can be done with MappingOperation but for the moment I think
        // storing the mapping in json is more convenient
        let analysis = include_str!("../../../json/settings.json");

        let mut analysis_json_value = try!(serde_json::from_str::<serde_json::Value>(&analysis)
            .map_err(|err| format!("{}", err)));

        let synonyms: Vec<_> = SYNONYMS.to_vec()
            .into_iter()
            .map(|s| serde_json::Value::String(s.to_string()))
            .collect();

        *analysis_json_value.pointer_mut("/settings/analysis/filter/synonym_filter/synonyms")
            .unwrap() = serde_json::Value::Array(synonyms);

        self.put(name, &analysis_json_value.to_string())
            .map_err(|e| {
                info!("Error while creating new index {}", name);
                e.to_string()
            })
            .and_then(|res| if res.status == StatusCode::Ok {
                Ok(())
            } else {
                Err(format!("cannot create index: {:?}", res))
            })
    }

    fn get_last_index(&self, doc_type: &str, dataset: &str) -> Result<Vec<String>, String> {
        let base_index = get_main_type_and_dataset_index(doc_type, dataset);
        self.get(&format!("{}/_aliases", base_index))
            .map_err(|e| e.to_string())
            .and_then(|res| match res.status {
                StatusCode::Ok => {
                    let value: serde_json::Value = try!(res.read_response()
                        .map_err(|e| e.to_string()));
                    Ok(value.as_object()
                        .and_then(|aliases| Some(aliases.keys().cloned().collect()))
                        .unwrap_or_else(|| {
                            info!("no previous index to delete for type {} and dataset {}",
                                  doc_type,
                                  dataset);
                            vec![]
                        }))
                }
                StatusCode::NotFound => {
                    info!("impossible to find alias {}, no last index to remove",
                          base_index);
                    Ok(vec![])
                }
                _ => Err(format!("invalid elasticsearch response: {:?}", res)),
            })
    }

    /// publish the index as the new index for this doc_type and this dataset
    /// move the index alias of the doc_type and the dataset to point to this indexes
    /// and remove the old index
    pub fn publish_index(&mut self,
                         doc_type: &str,
                         dataset: &str,
                         index: String,
                         is_geo_data: bool)
                         -> Result<(), String> {
        debug!("publishing index");
        let last_indexes = try!(self.get_last_index(doc_type, dataset));

        let dataset_index = get_main_type_and_dataset_index(doc_type, dataset);
        try!(self.alias(&dataset_index, &vec![index.clone()], &last_indexes));

        let type_index = get_main_type_index(doc_type);
        try!(self.alias(&type_index, &vec![dataset_index.clone()], &last_indexes));

        if is_geo_data {
            try!(self.alias("munin_geo_data", &vec![type_index.to_string()], &vec![]));
            try!(self.alias("munin", &vec!["munin_geo_data".to_string()], &vec![]));
        } else {
            try!(self.alias("munin", &vec![type_index.to_string()], &vec![]));
        }
        for i in last_indexes {
            try!(self.delete_index(&i));
        }
        Ok(())
    }

    pub fn is_existing_index(&self, name: &String) -> Result<bool, String> {
        self.get(&name)
            .map_err(|e| e.to_string())
            .map(|res| res.status == StatusCode::Ok)
    }

    /// add a list of new indexes to the alias
    /// remove a list of indexes from the alias
    fn alias(&self, alias: &str, add: &Vec<String>, remove: &Vec<String>) -> Result<(), String> {
        info!("for {}, adding alias {:?}, removing {:?}",
              alias,
              add,
              remove);
        let add_operations = add.iter().map(|x| {
            AliasOperation {
                remove: None,
                add: Some(AliasParameter {
                    index: x.clone(),
                    alias: alias.to_string(),
                }),
            }
        });
        let remove_operations = remove.iter().map(|x| {
            AliasOperation {
                add: None,
                remove: Some(AliasParameter {
                    index: x.clone(),
                    alias: alias.to_string(),
                }),
            }
        });
        let operations =
            AliasOperations { actions: add_operations.chain(remove_operations).collect() };
        let json = serde_json::to_string(&operations).unwrap();
        self.post("_aliases", &json)
            .map_err(|e| e.to_string())
            .and_then(|res| if res.status == StatusCode::Ok {
                Ok(())
            } else {
                error!("failed to change aliases for {}, es response: {:?}",
                       alias,
                       res);
                Err(format!("failed to post aliases for {}: {:?}", alias, res).to_string())
            })
    }

    pub fn delete_index(&mut self, index: &String) -> Result<(), String> {
        debug!("deleting index {}", &index);
        let res = self.es_client
            .delete_index(&index)
            .map(|res| res.acknowledged)
            .unwrap_or(false);
        if !res {
            Err(format!("Error deleting index {}", &index).into())
        } else {
            Ok(())
        }
    }

    pub fn bulk_index<T, I>(&mut self,
                            index: &String,
                            iter: I)
                            -> Result<usize, rs_es::error::EsError>
        where T: MimirObject,
              I: Iterator<Item = T>
    {
        use rs_es::operations::bulk::Action;
        let mut nb = 0;
        let chunk_size = 1000;
        // fold is used for creating the action and optionally set the id of the object
        let mut actions = iter.map(|v| {
            v.es_id()
                .into_iter()
                .fold(Action::index(v), |action, id| action.with_id(id))
        });
        loop {
            let chunk = actions.by_ref().take(chunk_size).collect::<Vec<_>>();
            nb += chunk.len();
            try!(self.es_client
                .bulk(&chunk)
                .with_index(&index)
                .with_doc_type(T::doc_type())
                .send());

            if chunk.len() < chunk_size {
                break;
            }
        }

        Ok(nb)
    }

    /// add all the element of 'iter' into elasticsearch
    ///
    /// To have zero downtime:
    /// first all the elements are added in a temporary index and when all has been indexed
    /// the index is published and the old index is removed
    pub fn index<T, I>(&mut self, dataset: &str, iter: I) -> Result<usize, String>
        where T: MimirObject,
              I: Iterator<Item = T>
    {
        // TODO better error handling
        let index = try!(self.make_index(T::doc_type(), dataset));
        let nb_elements = try!(self.bulk_index(&index, iter).map_err(|e| e.to_string()));
        try!(self.publish_index(T::doc_type(), dataset, index, T::is_geo_data()));
        Ok(nb_elements)
    }

    pub fn get_admins_from_dataset(&mut self,
                                   dataset: &str)
                                   -> Result<Vec<Admin>, rs_es::error::EsError> {
        self.get_admins_from_index(&get_main_type_and_dataset_index(Admin::doc_type(), dataset))
    }

    pub fn get_all_admins(&mut self) -> Result<Vec<Admin>, rs_es::error::EsError> {
        self.get_admins_from_index(&get_main_type_index(Admin::doc_type()))
    }

    fn get_admins_from_index(&mut self, index: &str) -> Result<Vec<Admin>, rs_es::error::EsError> {
        let mut result: Vec<Admin> = vec![];
        let mut scan: ScanResult<Admin> = try!(self.es_client
            .search_query()
            .with_indexes(&[&index])
            .with_size(1000)
            .with_types(&[&Admin::doc_type()])
            .scan(&Duration::minutes(1)));
        loop {
            let page = try!(scan.scroll(&mut self.es_client, &Duration::minutes(1)));
            if page.hits.hits.len() == 0 {
                break;
            }
            result.extend(page.hits.hits.into_iter().filter_map(|hit| hit.source).map(|ad| *ad));
        }
        try!(scan.close(&mut self.es_client));
        Ok(result)
    }
}

#[test]
pub fn test_valid_url() {
    Rubber::new("http://localhost:9200");
    Rubber::new("localhost:9200");
}

#[test]
#[should_panic]
pub fn test_invalid_url() {
    Rubber::new("http://bob");
}

#[test]
#[should_panic]
pub fn test_invalid_url_no_port() {
    Rubber::new("localhost");
}
<|endoftext|>


<commit_before>#![cfg(feature = "alloc")]
//#![feature(trace_macros)]

#[macro_use]
extern crate nom;

use nom::{is_alphanumeric, recognize_float};

use std::str;
use std::collections::HashMap;

#[derive(Debug, PartialEq)]
pub enum JsonValue {
  Str(String),
  Num(f32),
  Array(Vec<JsonValue>),
  Object(HashMap<String, JsonValue>),
}

named!(float<f32>, flat_map!(recognize_float, parse_to!(f32)));

//FIXME: verify how json strings are formatted
named!(
  string<&str>,
  delimited!(
    tag!("\""),
    //map_res!(escaped!(call!(alphanumeric), '\\', is_a!("\"n\\")), str::from_utf8),
    map_res!(
      escaped!(take_while1!(is_alphanumeric), '\\', one_of!("\"n\\")),
      str::from_utf8
    ),
    tag!("\"")
  )
);

named!(
  array<Vec<JsonValue>>,
  ws!(delimited!(
    tag!("["),
    separated_list!(tag!(","), value),
    tag!("]")
  ))
);

named!(
  key_value<(&str, JsonValue)>,
  ws!(separated_pair!(string, tag!(":"), value))
);

named!(
  hash<HashMap<String, JsonValue>>,
  ws!(map!(
    delimited!(tag!("{"), separated_list!(tag!(","), key_value), tag!("}")),
    |tuple_vec| {
      let mut h: HashMap<String, JsonValue> = HashMap::new();
      for (k, v) in tuple_vec {
        h.insert(String::from(k), v);
      }
      h
    }
  ))
);

named!(
  value<JsonValue>,
  ws!(alt!(
      hash   => { |h|   JsonValue::Object(h)            } |
      array  => { |v|   JsonValue::Array(v)             } |
      string => { |s|   JsonValue::Str(String::from(s)) } |
      float  => { |num| JsonValue::Num(num)             }
    ))
);

#[test]
fn hash_test() {
  let test = &b"  { \"a\"\t: 42,
  \"b\": \"x\"
  }\0";

  //FIXME: top level value must be an object?
  println!("{:?}", value(&test[..]).unwrap());
  //assert!(false);
}

#[test]
fn parse_example_test() {
  let test = &b"  { \"a\"\t: 42,
  \"b\": [ \"x\", \"y\", 12 ] ,
  \"c\": { \"hello\" : \"world\"
  }
  }\0";

  //FIXME: top level value must be an object?
  println!("{:?}", value(&test[..]).unwrap());
  //assert!(false);
}
<commit_msg>Improved json test<commit_after>#![cfg(feature = "alloc")]
//#![feature(trace_macros)]

#[macro_use]
extern crate nom;

use nom::{is_alphanumeric, recognize_float};

use std::str;
use std::collections::HashMap;

#[derive(Debug, PartialEq)]
pub enum JsonValue {
  Str(String),
  Num(f32),
  Array(Vec<JsonValue>),
  Object(HashMap<String, JsonValue>),
}

named!(float<f32>, flat_map!(recognize_float, parse_to!(f32)));

//FIXME: verify how json strings are formatted
named!(
  string<&str>,
  delimited!(
    char!('"'),
    //map_res!(escaped!(call!(alphanumeric), '\\', is_a!("\"n\\")), str::from_utf8),
    map_res!(
      escaped!(take_while1!(is_alphanumeric), '\\', one_of!("\"n\\")),
      str::from_utf8
    ),
    char!('"')
  )
);

named!(
  array<Vec<JsonValue>>,
  ws!(delimited!(
    char!('['),
    separated_list!(char!(','), value),
    char!(']')
  ))
);

named!(
  key_value<(&str, JsonValue)>,
  ws!(separated_pair!(string, char!(':'), value))
);

named!(
  hash<HashMap<String, JsonValue>>,
  ws!(map!(
    delimited!(char!('{'), separated_list!(char!(','), key_value), char!('}')),
    |tuple_vec| {
      let mut h: HashMap<String, JsonValue> = HashMap::new();
      for (k, v) in tuple_vec {
        h.insert(String::from(k), v);
      }
      h
    }
  ))
);

named!(
  value<JsonValue>,
  ws!(alt!(
      hash   => { |h|   JsonValue::Object(h)            } |
      array  => { |v|   JsonValue::Array(v)             } |
      string => { |s|   JsonValue::Str(String::from(s)) } |
      float  => { |num| JsonValue::Num(num)             }
    ))
);

#[test]
fn hash_test() {
  let input =
    r#"{
      "a": 42,
      "b": "x"
    }\0"#;

  let mut expected_map = HashMap::new();
  expected_map.insert(String::from("a"), JsonValue::Num(42f32));
  expected_map.insert(String::from("b"), JsonValue::Str(String::from("x")));
  let expected = JsonValue::Object(expected_map);

  assert_eq!(expected, value(input.as_bytes()).unwrap().1);
}
<|endoftext|>


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

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

mod helpers;

mod open {
    use rand::{random};
    use std::fs::File;
    use std::fs;
    use std::path::PathBuf;
    use std::io::prelude::*;
    use helpers::*;

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

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

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

        (project_name, project_path.clone())
    }

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

    fn test_with_contents(contents: &'static [u8]) -> TmuxSession {
        let (project_name, config_path) = setup(contents);
        open_muxed(&project_name, config_path.parent().unwrap());
        let completed = PathBuf::from(format!("/tmp/{}.complete", project_name));
        let exec = format!("touch {}", completed.display());
        send_keys(&project_name, &exec);
        wait_on(&completed);
        let session = TmuxSession::from_string(&list_windows(&project_name.to_string()));
        cleanup(&project_name, &config_path);
        session
    }

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

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

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

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

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

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

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

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

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

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

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

mod helpers;

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

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

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

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

        (project_name, project_path.clone())
    }

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

    fn test_with_contents(contents: &'static [u8]) -> TmuxSession {
        let (project_name, config_path) = setup(contents);
        open_muxed(&project_name, config_path.parent().unwrap());
        let completed = PathBuf::from(format!("/tmp/{}.complete", project_name));
        let exec = format!("touch {}", completed.display());
        send_keys(&project_name, &exec);
        wait_on(&completed);
        let session = TmuxSession::from_string(&list_windows(&project_name.to_string()));
        cleanup(&project_name, &config_path);
        session
    }

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

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

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

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

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

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

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

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

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

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

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

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


<commit_before>// Copyright (c) Facebook, Inc. and its affiliates
// SPDX-License-Identifier: MIT OR Apache-2.0

use heck::CamelCase;
use serde_generate::{
    golang, test_utils,
    test_utils::{Choice, Runtime, Test},
    CodeGeneratorConfig,
};
use std::fs::File;
use std::io::Write;
use std::process::Command;
use tempfile::tempdir;

#[test]
fn test_golang_runtime_autotests() {
    let runtime_mod_path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .join("../../../serde-generate/runtime/golang");

    let status = Command::new("go")
        .current_dir(runtime_mod_path.to_str().unwrap())
        .arg("test")
        .arg("./...")
        .status()
        .unwrap();
    assert!(status.success());
}

#[test]
fn test_golang_lcs_runtime_on_simple_data() {
    test_golang_runtime_on_simple_data(Runtime::Lcs);
}

#[test]
fn test_golang_bincode_runtime_on_simple_data() {
    test_golang_runtime_on_simple_data(Runtime::Bincode);
}

fn test_golang_runtime_on_simple_data(runtime: Runtime) {
    let registry = test_utils::get_simple_registry().unwrap();
    let dir = tempdir().unwrap();
    let source_path = dir.path().join("test.go");
    let mut source = File::create(&source_path).unwrap();

    let config = CodeGeneratorConfig::new("main".to_string())
        .with_encodings(vec![runtime.into()])
        .with_external_definitions(
            vec![("github.com/google/go-cmp/cmp".to_string(), vec![])]
                .into_iter()
                .collect(),
        );
    let generator = golang::CodeGenerator::new(&config);
    generator.output(&mut source, &registry).unwrap();

    let reference = runtime.serialize(&Test {
        a: vec![4, 6],
        b: (-3, 5),
        c: Choice::C { x: 7 },
    });

    writeln!(
        source,
        r#"
func main() {{
	input := []byte{{{0}}}
	value, err := {1}DeserializeTest(input)
	if err != nil {{ panic("failed to deserialize") }}

        value2 := Test {{
		A: []uint32{{ 4, 6 }},
		B: struct {{ Field0 int64; Field1 uint64 }} {{ -3, 5 }},
		C: &Choice__C {{ X: 7 }},
	}}
	if !cmp.Equal(value, value2) {{ panic("value != value2") }}

	output, err := value2.{1}Serialize()
	if err != nil {{ panic("failed to serialize") }}
	if !cmp.Equal(input, output) {{ panic("input != output") }}

	input2 := []byte{{{0}, 1}}
	value2, err2 := {1}DeserializeTest(input2)
	if err2 == nil {{ panic("was expecting an error") }}
}}
"#,
        reference
            .iter()
            .map(|x| format!("{}", x))
            .collect::<Vec<_>>()
            .join(", "),
        runtime.name().to_camel_case(),
    )
    .unwrap();

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("init")
        .arg("testing")
        .status()
        .unwrap();
    assert!(status.success());

    let runtime_mod_path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .join("../../../serde-generate/runtime/golang");
    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("edit")
        .arg("-replace")
        .arg(format!(
            "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang={}",
            runtime_mod_path.to_str().unwrap()
        ))
        .status()
        .unwrap();
    assert!(status.success());

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("run")
        .arg(&source_path)
        .status()
        .unwrap();
    assert!(status.success());
}

#[test]
fn test_golang_lcs_runtime_on_supported_types() {
    test_golang_runtime_on_supported_types(Runtime::Lcs);
}

#[test]
fn test_golang_bincode_runtime_on_supported_types() {
    test_golang_runtime_on_supported_types(Runtime::Bincode);
}

fn quote_bytes(bytes: &[u8]) -> String {
    format!(
        "{{{}}}",
        bytes
            .iter()
            .map(|x| format!("{}", x))
            .collect::<Vec<_>>()
            .join(", ")
    )
}

fn test_golang_runtime_on_supported_types(runtime: Runtime) {
    let registry = test_utils::get_registry().unwrap();
    let dir = tempdir().unwrap();
    let source_path = dir.path().join("test.go");
    let mut source = File::create(&source_path).unwrap();

    let config = CodeGeneratorConfig::new("main".to_string())
        .with_encodings(vec![runtime.into()])
        .with_external_definitions(
            vec![("github.com/google/go-cmp/cmp".to_string(), vec![])]
                .into_iter()
                .collect(),
        );
    let generator = golang::CodeGenerator::new(&config);
    generator.output(&mut source, &registry).unwrap();

    let positive_encodings = runtime
        .get_positive_samples()
        .iter()
        .map(|bytes| quote_bytes(bytes))
        .collect::<Vec<_>>()
        .join(", ");

    let negative_encodings = runtime
        .get_negative_samples()
        .iter()
        .map(|bytes| quote_bytes(bytes))
        .collect::<Vec<_>>()
        .join(", ");

    writeln!(
        source,
        r#"
func main() {{
	positive_inputs := [][]byte{{{0}}}
	negative_inputs := [][]byte{{{1}}}

	for _, input := range(positive_inputs) {{
		value, err := {2}DeserializeSerdeData(input)
		if err != nil {{ panic(fmt.Sprintf("failed to deserialize input: %v", err)) }}
		output, err := value.{2}Serialize()
		if err != nil {{ panic(fmt.Sprintf("failed to serialize: %v", err)) }}

		if !cmp.Equal(input, output) {{ panic(fmt.Sprintf("input != output:\n  %v\n  %v", input, output)) }}

		for i := 0; i < len(input); i++ {{
			input2 := make([]byte, len(input))
			copy(input2, input)
			input2[i] ^= 0x80
			value2, err := {2}DeserializeSerdeData(input2)
			if err != nil {{ continue }}
			if cmp.Equal(value, value2) {{ panic("Modified input should give a different value.") }}
		}}
	}}

	for _, input := range(negative_inputs) {{
		_, err := {2}DeserializeSerdeData(input)
		if err == nil {{ panic(fmt.Sprintf("Input should fail to deserialize: %v", input)) }}
	}}
}}
"#,
        positive_encodings,
        negative_encodings,
        runtime.name().to_camel_case(),
    )
    .unwrap();

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("init")
        .arg("testing")
        .status()
        .unwrap();
    assert!(status.success());

    let runtime_mod_path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .join("../../../serde-generate/runtime/golang");
    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("edit")
        .arg("-replace")
        .arg(format!(
            "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang={}",
            runtime_mod_path.to_str().unwrap()
        ))
        .status()
        .unwrap();
    assert!(status.success());

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("run")
        .arg(source_path)
        .status()
        .unwrap();
    assert!(status.success());
}
<commit_msg>[golang] use "quick" samples for runtime tests<commit_after>// Copyright (c) Facebook, Inc. and its affiliates
// SPDX-License-Identifier: MIT OR Apache-2.0

use heck::CamelCase;
use serde_generate::{
    golang, test_utils,
    test_utils::{Choice, Runtime, Test},
    CodeGeneratorConfig,
};
use std::fs::File;
use std::io::Write;
use std::process::Command;
use tempfile::tempdir;

#[test]
fn test_golang_runtime_autotests() {
    let runtime_mod_path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .join("../../../serde-generate/runtime/golang");

    let status = Command::new("go")
        .current_dir(runtime_mod_path.to_str().unwrap())
        .arg("test")
        .arg("./...")
        .status()
        .unwrap();
    assert!(status.success());
}

#[test]
fn test_golang_lcs_runtime_on_simple_data() {
    test_golang_runtime_on_simple_data(Runtime::Lcs);
}

#[test]
fn test_golang_bincode_runtime_on_simple_data() {
    test_golang_runtime_on_simple_data(Runtime::Bincode);
}

fn test_golang_runtime_on_simple_data(runtime: Runtime) {
    let registry = test_utils::get_simple_registry().unwrap();
    let dir = tempdir().unwrap();
    let source_path = dir.path().join("test.go");
    let mut source = File::create(&source_path).unwrap();

    let config = CodeGeneratorConfig::new("main".to_string())
        .with_encodings(vec![runtime.into()])
        .with_external_definitions(
            vec![("github.com/google/go-cmp/cmp".to_string(), vec![])]
                .into_iter()
                .collect(),
        );
    let generator = golang::CodeGenerator::new(&config);
    generator.output(&mut source, &registry).unwrap();

    let reference = runtime.serialize(&Test {
        a: vec![4, 6],
        b: (-3, 5),
        c: Choice::C { x: 7 },
    });

    writeln!(
        source,
        r#"
func main() {{
	input := []byte{{{0}}}
	value, err := {1}DeserializeTest(input)
	if err != nil {{ panic("failed to deserialize") }}

        value2 := Test {{
		A: []uint32{{ 4, 6 }},
		B: struct {{ Field0 int64; Field1 uint64 }} {{ -3, 5 }},
		C: &Choice__C {{ X: 7 }},
	}}
	if !cmp.Equal(value, value2) {{ panic("value != value2") }}

	output, err := value2.{1}Serialize()
	if err != nil {{ panic("failed to serialize") }}
	if !cmp.Equal(input, output) {{ panic("input != output") }}

	input2 := []byte{{{0}, 1}}
	value2, err2 := {1}DeserializeTest(input2)
	if err2 == nil {{ panic("was expecting an error") }}
}}
"#,
        reference
            .iter()
            .map(|x| format!("{}", x))
            .collect::<Vec<_>>()
            .join(", "),
        runtime.name().to_camel_case(),
    )
    .unwrap();

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("init")
        .arg("testing")
        .status()
        .unwrap();
    assert!(status.success());

    let runtime_mod_path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .join("../../../serde-generate/runtime/golang");
    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("edit")
        .arg("-replace")
        .arg(format!(
            "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang={}",
            runtime_mod_path.to_str().unwrap()
        ))
        .status()
        .unwrap();
    assert!(status.success());

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("run")
        .arg(&source_path)
        .status()
        .unwrap();
    assert!(status.success());
}

#[test]
fn test_golang_lcs_runtime_on_supported_types() {
    test_golang_runtime_on_supported_types(Runtime::Lcs);
}

#[test]
fn test_golang_bincode_runtime_on_supported_types() {
    test_golang_runtime_on_supported_types(Runtime::Bincode);
}

fn quote_bytes(bytes: &[u8]) -> String {
    format!(
        "{{{}}}",
        bytes
            .iter()
            .map(|x| format!("{}", x))
            .collect::<Vec<_>>()
            .join(", ")
    )
}

fn test_golang_runtime_on_supported_types(runtime: Runtime) {
    let registry = test_utils::get_registry().unwrap();
    let dir = tempdir().unwrap();
    let source_path = dir.path().join("test.go");
    let mut source = File::create(&source_path).unwrap();

    let config = CodeGeneratorConfig::new("main".to_string())
        .with_encodings(vec![runtime.into()])
        .with_external_definitions(
            vec![("github.com/google/go-cmp/cmp".to_string(), vec![])]
                .into_iter()
                .collect(),
        );
    let generator = golang::CodeGenerator::new(&config);
    generator.output(&mut source, &registry).unwrap();

    let positive_encodings = runtime
        .get_positive_samples_quick()
        .iter()
        .map(|bytes| quote_bytes(bytes))
        .collect::<Vec<_>>()
        .join(", ");

    let negative_encodings = runtime
        .get_negative_samples()
        .iter()
        .map(|bytes| quote_bytes(bytes))
        .collect::<Vec<_>>()
        .join(", ");

    writeln!(
        source,
        r#"
func main() {{
	positive_inputs := [][]byte{{{0}}}
	negative_inputs := [][]byte{{{1}}}

	for _, input := range(positive_inputs) {{
		value, err := {2}DeserializeSerdeData(input)
		if err != nil {{ panic(fmt.Sprintf("failed to deserialize input: %v", err)) }}
		output, err := value.{2}Serialize()
		if err != nil {{ panic(fmt.Sprintf("failed to serialize: %v", err)) }}

		if !cmp.Equal(input, output) {{ panic(fmt.Sprintf("input != output:\n  %v\n  %v", input, output)) }}

		for i := 0; i < len(input); i++ {{
			input2 := make([]byte, len(input))
			copy(input2, input)
			input2[i] ^= 0x80
			value2, err := {2}DeserializeSerdeData(input2)
			if err != nil {{ continue }}
			if cmp.Equal(value, value2) {{ panic("Modified input should give a different value.") }}
		}}
	}}

	for _, input := range(negative_inputs) {{
		_, err := {2}DeserializeSerdeData(input)
		if err == nil {{ panic(fmt.Sprintf("Input should fail to deserialize: %v", input)) }}
	}}
}}
"#,
        positive_encodings,
        negative_encodings,
        runtime.name().to_camel_case(),
    )
    .unwrap();

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("init")
        .arg("testing")
        .status()
        .unwrap();
    assert!(status.success());

    let runtime_mod_path = std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .join("../../../serde-generate/runtime/golang");
    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("mod")
        .arg("edit")
        .arg("-replace")
        .arg(format!(
            "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang={}",
            runtime_mod_path.to_str().unwrap()
        ))
        .status()
        .unwrap();
    assert!(status.success());

    let status = Command::new("go")
        .current_dir(dir.path())
        .arg("run")
        .arg(source_path)
        .status()
        .unwrap();
    assert!(status.success());
}
<|endoftext|>


<commit_before>extern crate roboime_next;
extern crate clap;
extern crate env_logger;
extern crate log;

use std::env;
use std::sync::mpsc::channel;
use std::process::{exit, Command};
use std::error::Error;
use log::LogLevelFilter;
use env_logger::LogBuilder;
use roboime_next::prelude::*;
use roboime_next::{game, ai, grsim, Result};
use clap::{Arg, App, AppSettings};

fn main_loop() -> Result<()> {
    let matches = App::new("robime-next-cli")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .setting(AppSettings::TrailingVarArg)
        .about("Connect an AI to a Robocup SSL game through an stdio interface (like codingame).")
        .arg(Arg::with_name("blue")
             .short("b")
             .long("blue")
             .help("Play as the blue team"))
        .arg(Arg::with_name("yellow")
             .conflicts_with("blue")
             .short("y")
             .long("yellow")
             .help("Play as the yellow team (default)"))
        .arg(Arg::with_name("v")
             .short("v")
             .multiple(true)
             .help("Sets the level of verbosity"))
        .arg(Arg::with_name("q")
             .conflicts_with("v")
             .long("quiet")
             .short("q")
             .help("Don't log warnings or errors."))
        .arg(Arg::with_name("grsim_addr")
             .long("grsim")
             .takes_value(true)
             .value_name("ADDRESS:PORT")
             .help("Set the address and port where grSim is listening for commands."))
        .arg(Arg::with_name("grsim_ip")
             .conflicts_with("gsim_addr")
             .long("grsim-addr")
             .takes_value(true)
             .value_name("ADDRESS")
             .help("Set the address where grSim is listening for commands."))
        .arg(Arg::with_name("vision_addr")
             .conflicts_with("vision_port")
             .long("vision")
             .takes_value(true)
             .value_name("ADDRESS:PORT")
             .help("Set the multicast address and port to receive the vision packets."))
        .arg(Arg::with_name("vision_port")
             .conflicts_with("vision_addr")
             .long("vision-port")
             .takes_value(true)
             .value_name("PORT")
             .help("Set the port to receive the vision packets. (Use default multicast address)"))
        .arg(Arg::with_name("AI")
             .required(true)
             .multiple(true)
             .value_name("COMMAND")
             .help("Command to start the AI, accepts arguments."))
        .get_matches();

    let mut builder = LogBuilder::new();
    builder.format(|record| {
        format!("{} [{}] {}", record.level(), record.location().module_path(), record.args())
    });
    let verbosity = matches.occurrences_of("v");
    builder.filter(Some("roboime"), match verbosity {
        0 => LogLevelFilter::Warn,
        1 => LogLevelFilter::Info,
        2 => LogLevelFilter::Debug,
        _ => LogLevelFilter::Trace,
    });
    if matches.is_present("q") {
        builder.filter(Some("roboime"), LogLevelFilter::Off);
    }
    if env::var("RUST_LOG").is_ok() {
       builder.parse(&env::var("RUST_LOG").unwrap());
    }
    try!(builder.init());

    let ai_cmd: Vec<&str> = matches.values_of("AI").unwrap().collect();
    let (ai_program, ai_args) = (ai_cmd[0], &ai_cmd[1..]);
    let mut ai_command = Command::new(ai_program);
    ai_command.args(ai_args);
    let mut ai_cfg = ai::Interface::new(ai_command);
    if matches.is_present("blue") {
        ai_cfg.is_yellow(false);
    }
    if matches.is_present("yellow") {
        ai_cfg.is_yellow(true);
    }

    let mut grsim_cfg = grsim::Interface::new();
    try!(grsim_cfg.grsim_addr(matches.value_of("grsim_addr").unwrap_or("127.0.0.1:20011")));
    try!(grsim_cfg.vision_addr(matches.value_of("vision_addr").unwrap_or("224.5.23.2:10002")));
    if matches.is_present("vision_port") {
        grsim_cfg.vision_port(try!(matches.value_of("vision_port").unwrap().parse()));
    }
    if matches.is_present("grsim_ip") {
        grsim_cfg.grsim_ip(try!(matches.value_of("grsim_ip").unwrap().parse()));
    }

    let state = game::SharedState::new();
    let (tx, rx) = channel();

    let ai = try!(ai_cfg.spawn(state.clone(), tx));
    let grsim = try!(grsim_cfg.spawn(state.clone(), rx));

    (ai, grsim).join()
}

fn dump_error(res: Result<()>) -> bool {
    if let Err(err) = res {
        println!("Error: {}.", err.description());
        while let Some(err) = err.cause() {
            println!("- caused by {}", err.description());
        }
        true
    } else {
        false
    }
}

fn main() {
    if dump_error(main_loop()) {
        exit(1);
    }
}
<commit_msg>Fix recursion bug and avoid unwrap.<commit_after>extern crate roboime_next;
extern crate clap;
extern crate env_logger;
extern crate log;

use std::env;
use std::sync::mpsc::channel;
use std::process::{exit, Command};
use std::error::Error;
use log::LogLevelFilter;
use env_logger::LogBuilder;
use roboime_next::prelude::*;
use roboime_next::{game, ai, grsim, Result};
use clap::{Arg, App, AppSettings};

fn main_loop() -> Result<()> {
    let matches = App::new("robime-next-cli")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .setting(AppSettings::TrailingVarArg)
        .about("Connect an AI to a Robocup SSL game through an stdio interface (like codingame).")
        .arg(Arg::with_name("blue")
             .short("b")
             .long("blue")
             .help("Play as the blue team"))
        .arg(Arg::with_name("yellow")
             .conflicts_with("blue")
             .short("y")
             .long("yellow")
             .help("Play as the yellow team (default)"))
        .arg(Arg::with_name("v")
             .short("v")
             .multiple(true)
             .help("Sets the level of verbosity"))
        .arg(Arg::with_name("q")
             .conflicts_with("v")
             .long("quiet")
             .short("q")
             .help("Don't log warnings or errors."))
        .arg(Arg::with_name("grsim_addr")
             .long("grsim")
             .takes_value(true)
             .value_name("ADDRESS:PORT")
             .help("Set the address and port where grSim is listening for commands."))
        .arg(Arg::with_name("grsim_ip")
             .conflicts_with("gsim_addr")
             .long("grsim-addr")
             .takes_value(true)
             .value_name("ADDRESS")
             .help("Set the address where grSim is listening for commands."))
        .arg(Arg::with_name("vision_addr")
             .conflicts_with("vision_port")
             .long("vision")
             .takes_value(true)
             .value_name("ADDRESS:PORT")
             .help("Set the multicast address and port to receive the vision packets."))
        .arg(Arg::with_name("vision_port")
             .conflicts_with("vision_addr")
             .long("vision-port")
             .takes_value(true)
             .value_name("PORT")
             .help("Set the port to receive the vision packets. (Use default multicast address)"))
        .arg(Arg::with_name("AI")
             .required(true)
             .multiple(true)
             .value_name("COMMAND")
             .help("Command to start the AI, accepts arguments."))
        .get_matches();

    let mut builder = LogBuilder::new();
    builder.format(|record| {
        format!("{} [{}] {}", record.level(), record.location().module_path(), record.args())
    });
    let verbosity = matches.occurrences_of("v");
    builder.filter(Some("roboime"), match verbosity {
        0 => LogLevelFilter::Warn,
        1 => LogLevelFilter::Info,
        2 => LogLevelFilter::Debug,
        _ => LogLevelFilter::Trace,
    });
    if matches.is_present("q") {
        builder.filter(Some("roboime"), LogLevelFilter::Off);
    }
    if let Ok(ref log) = env::var("RUST_LOG") {
       builder.parse(log);
    }
    try!(builder.init());

    let ai_cmd: Vec<&str> = matches.values_of("AI").unwrap().collect();
    let (ai_program, ai_args) = (ai_cmd[0], &ai_cmd[1..]);
    let mut ai_command = Command::new(ai_program);
    ai_command.args(ai_args);
    let mut ai_cfg = ai::Interface::new(ai_command);
    if matches.is_present("blue") {
        ai_cfg.is_yellow(false);
    }
    if matches.is_present("yellow") {
        ai_cfg.is_yellow(true);
    }

    let mut grsim_cfg = grsim::Interface::new();
    try!(grsim_cfg.grsim_addr(matches.value_of("grsim_addr").unwrap_or("127.0.0.1:20011")));
    try!(grsim_cfg.vision_addr(matches.value_of("vision_addr").unwrap_or("224.5.23.2:10002")));
    if matches.is_present("vision_port") {
        grsim_cfg.vision_port(try!(matches.value_of("vision_port").unwrap().parse()));
    }
    if matches.is_present("grsim_ip") {
        grsim_cfg.grsim_ip(try!(matches.value_of("grsim_ip").unwrap().parse()));
    }

    let state = game::SharedState::new();
    let (tx, rx) = channel();

    let ai = try!(ai_cfg.spawn(state.clone(), tx));
    let grsim = try!(grsim_cfg.spawn(state.clone(), rx));

    (ai, grsim).join()
}

fn main() {
    if let Err(err) = main_loop() {
        println!("Error: {}.", err.description());

        let mut err: &Error = &err;
        while let Some(cause) = err.cause() {
            println!("- caused by: {}", cause.description());
            err = cause;
        }

        exit(1);
    }
}
<|endoftext|>


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

#![recursion_limit = "128"]

extern crate phf_generator;
extern crate phf_shared;
extern crate string_cache_shared as shared;
#[macro_use] extern crate quote;

use std::collections::HashSet;
use std::fs::File;
use std::io::{self, Write, BufWriter};
use std::iter;
use std::path::Path;

/// A builder for a static atom set and relevant macros
pub struct AtomType {
    path: String,
    macro_name: String,
    atoms: HashSet<String>,
}

impl AtomType {
    /// Constructs a new static atom set builder
    ///
    /// `path` is a path within a crate of the atom type that will be created.
    /// e.g. `"FooAtom"` at the crate root or `"foo::Atom"` if the generated code
    /// is included in a `foo` module.
    ///
    /// `macro_name` must end with `!`.
    ///
    /// For example, `AtomType::new("foo::FooAtom", "foo_atom!")` will generate:
    ///
    /// ```rust
    /// pub type FooAtom = ::string_cache::Atom<FooAtomStaticSet>;
    /// pub struct FooAtomStaticSet;
    /// impl ::string_cache::StaticAtomSet for FooAtomStaticSet {
    ///     // ...
    /// }
    /// #[macro_export]
    /// macro_rules foo_atom {
    ///    // Expands to: $crate::foo::FooAtom { … }
    /// }
    pub fn new(path: &str, macro_name: &str) -> Self {
        assert!(macro_name.ends_with("!"));
        AtomType {
            path: path.to_owned(),
            macro_name: macro_name[..macro_name.len() - "!".len()].to_owned(),
            atoms: HashSet::new(),
        }
    }

    /// Adds an atom to the builder
    pub fn atom(&mut self, s: &str) -> &mut Self {
        self.atoms.insert(s.to_owned());
        self
    }

    /// Adds multiple atoms to the builder
    pub fn atoms<I>(&mut self, iter: I) -> &mut Self
    where I: IntoIterator, I::Item: AsRef<str> {
        self.atoms.extend(iter.into_iter().map(|s| s.as_ref().to_owned()));
        self
    }

    /// Write generated code to `destination`.
    pub fn write_to<W>(&mut self, mut destination: W) -> io::Result<()> where W: Write {
        destination.write_all(
            self.to_tokens()
            .as_str()
            // Insert some newlines to make the generated code slightly easier to read.
            .replace(" [ \"", "[\n\"")
            .replace("\" , ", "\",\n")
            .replace(" ( \"", "\n( \"")
            .replace("; ", ";\n")
            .as_bytes())
    }

    fn to_tokens(&mut self) -> quote::Tokens {
        // `impl Default for Atom` requires the empty string to be in the static set.
        // This also makes sure the set in non-empty,
        // which would cause divisions by zero in rust-phf.
        self.atoms.insert(String::new());

        let atoms: Vec<&str> = self.atoms.iter().map(|s| &**s).collect();
        let hash_state = phf_generator::generate_hash(&atoms);
        let phf_generator::HashState { key, disps, map } = hash_state;
        let atoms: Vec<&str> = map.iter().map(|&idx| atoms[idx]).collect();
        let empty_string_index = atoms.iter().position(|s| s.is_empty()).unwrap() as u32;
        let data = (0..atoms.len()).map(|i| quote::Hex(shared::pack_static(i as u32)));

        let hashes: Vec<u32> =
            atoms.iter().map(|string| {
                let hash = phf_shared::hash(string, key);
                ((hash >> 32) ^ hash) as u32
            }).collect();

        let type_name = if let Some(position) = self.path.rfind("::") {
            &self.path[position + "::".len() ..]
        } else {
            &self.path
        };
        let static_set_name = quote::Ident::from(format!("{}StaticSet", type_name));
        let type_name = quote::Ident::from(type_name);
        let macro_name = quote::Ident::from(&*self.macro_name);
        let path = iter::repeat(quote::Ident::from(&*self.path));

        quote! {
            pub type #type_name = ::string_cache::Atom<#static_set_name>;
            pub struct #static_set_name;
            impl ::string_cache::StaticAtomSet for #static_set_name {
                fn get() -> &'static ::string_cache::PhfStrSet {
                    static SET: ::string_cache::PhfStrSet = ::string_cache::PhfStrSet {
                        key: #key,
                        disps: &#disps,
                        atoms: &#atoms,
                        hashes: &#hashes
                    };
                    &SET
                }
                fn empty_string_index() -> u32 {
                    #empty_string_index
                }
            }
            #[macro_export]
            macro_rules! #macro_name {
                #(
                    (#atoms) => {
                        $crate::#path {
                            unsafe_data: #data,
                            phantom: ::std::marker::PhantomData,
                        }
                    };
                )*
            }
        }
    }

    /// Create a new file at `path` and write generated code there.
    ///
    /// Typical usage:
    /// `.write_to_file(&Path::new(&env::var("OUT_DIR").unwrap()).join("foo_atom.rs"))`
    pub fn write_to_file(&mut self, path: &Path) -> io::Result<()> {
        self.write_to(BufWriter::new(try!(File::create(path))))
    }
}
<commit_msg>Allow the generated macros to have documentation<commit_after>// Copyright 2016 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A crate to create static string caches at compiletime.
//!
//! # Examples
//!
//! With static atoms:
//!
//! In `Cargo.toml`:
//!
//! ```toml
//! [package]
//! build = "build.rs"
//!
//! [dependencies]
//! string_cache = "0.7"
//!
//! [build-dependencies]
//! string_cache_codegen = "0.4"
//! ```
//!
//! In `build.rs`:
//!
//! ```no_run
//! extern crate string_cache_codegen;
//!
//! use std::env;
//! use std::path::Path;
//!
//! fn main() {
//!     string_cache_codegen::AtomType::new("foo::FooAtom", "foo_atom!")
//!         .atoms(&["foo", "bar"])
//!         .write_to_file(&Path::new(&env::var("OUT_DIR").unwrap()).join("foo_atom.rs"))
//!         .unwrap()
//! }
//! ```
//!
//! In `lib.rs`:
//!
//! ```ignore
//! extern crate string_cache;
//!
//! mod foo {
//!     include!(concat!(env!("OUT_DIR"), "/foo_atom.rs"));
//! }
//! ```
//!
//! The generated code will define a `FooAtom` type and a `foo_atom!` macro.
//! The macro can be used in expression or patterns, with strings listed in `build.rs`.
//! For example:
//!
//! ```ignore
//! fn compute_something(input: &foo::FooAtom) -> u32 {
//!     match *input {
//!         foo_atom!("foo") => 1,
//!         foo_atom!("bar") => 2,
//!         _ => 3,
//!     }
//! }
//! ```
//!

#![recursion_limit = "128"]

extern crate phf_generator;
extern crate phf_shared;
extern crate string_cache_shared as shared;
#[macro_use] extern crate quote;

use std::collections::HashSet;
use std::fs::File;
use std::io::{self, Write, BufWriter};
use std::iter;
use std::path::Path;

/// A builder for a static atom set and relevant macros
pub struct AtomType {
    path: String,
    macro_name: String,
    macro_doc: Option<String>,
    atoms: HashSet<String>,
}

impl AtomType {
    /// Constructs a new static atom set builder
    ///
    /// `path` is a path within a crate of the atom type that will be created.
    /// e.g. `"FooAtom"` at the crate root or `"foo::Atom"` if the generated code
    /// is included in a `foo` module.
    ///
    /// `macro_name` must end with `!`.
    ///
    /// For example, `AtomType::new("foo::FooAtom", "foo_atom!")` will generate:
    ///
    /// ```ignore
    /// pub type FooAtom = ::string_cache::Atom<FooAtomStaticSet>;
    /// pub struct FooAtomStaticSet;
    /// impl ::string_cache::StaticAtomSet for FooAtomStaticSet {
    ///     // ...
    /// }
    /// #[macro_export]
    /// macro_rules foo_atom {
    ///    // Expands to: $crate::foo::FooAtom { … }
    /// }
    pub fn new(path: &str, macro_name: &str) -> Self {
        assert!(macro_name.ends_with("!"));
        AtomType {
            path: path.to_owned(),
            macro_name: macro_name[..macro_name.len() - "!".len()].to_owned(),
            macro_doc: None,
            atoms: HashSet::new(),
        }
    }

    /// Add some documentation to the generated macro.
    ///
    /// Note that `docs` should not contain the `///` at the front of normal docs.
    pub fn with_macro_doc(&mut self, docs: &str) -> &mut Self {
        self.macro_doc = Some(docs.to_owned());
        self
    }

    /// Adds an atom to the builder
    pub fn atom(&mut self, s: &str) -> &mut Self {
        self.atoms.insert(s.to_owned());
        self
    }

    /// Adds multiple atoms to the builder
    pub fn atoms<I>(&mut self, iter: I) -> &mut Self
    where I: IntoIterator, I::Item: AsRef<str> {
        self.atoms.extend(iter.into_iter().map(|s| s.as_ref().to_owned()));
        self
    }

    /// Write generated code to `destination`.
    pub fn write_to<W>(&mut self, mut destination: W) -> io::Result<()> where W: Write {
        destination.write_all(
            self.to_tokens()
            .as_str()
            // Insert some newlines to make the generated code slightly easier to read.
            .replace(" [ \"", "[\n\"")
            .replace("\" , ", "\",\n")
            .replace(" ( \"", "\n( \"")
            .replace("; ", ";\n")
            .as_bytes())
    }

    fn to_tokens(&mut self) -> quote::Tokens {
        // `impl Default for Atom` requires the empty string to be in the static set.
        // This also makes sure the set in non-empty,
        // which would cause divisions by zero in rust-phf.
        self.atoms.insert(String::new());

        let atoms: Vec<&str> = self.atoms.iter().map(|s| &**s).collect();
        let hash_state = phf_generator::generate_hash(&atoms);
        let phf_generator::HashState { key, disps, map } = hash_state;
        let atoms: Vec<&str> = map.iter().map(|&idx| atoms[idx]).collect();
        let empty_string_index = atoms.iter().position(|s| s.is_empty()).unwrap() as u32;
        let data = (0..atoms.len()).map(|i| quote::Hex(shared::pack_static(i as u32)));

        let hashes: Vec<u32> =
            atoms.iter().map(|string| {
                let hash = phf_shared::hash(string, key);
                ((hash >> 32) ^ hash) as u32
            }).collect();

        let type_name = if let Some(position) = self.path.rfind("::") {
            &self.path[position + "::".len() ..]
        } else {
            &self.path
        };
        let macro_doc = match self.macro_doc {
            Some(ref doc) => quote!(#[doc = #doc]),
            None => quote!()
        };
        let static_set_name = quote::Ident::from(format!("{}StaticSet", type_name));
        let type_name = quote::Ident::from(type_name);
        let macro_name = quote::Ident::from(&*self.macro_name);
        let path = iter::repeat(quote::Ident::from(&*self.path));

        quote! {
            pub type #type_name = ::string_cache::Atom<#static_set_name>;
            pub struct #static_set_name;
            impl ::string_cache::StaticAtomSet for #static_set_name {
                fn get() -> &'static ::string_cache::PhfStrSet {
                    static SET: ::string_cache::PhfStrSet = ::string_cache::PhfStrSet {
                        key: #key,
                        disps: &#disps,
                        atoms: &#atoms,
                        hashes: &#hashes
                    };
                    &SET
                }
                fn empty_string_index() -> u32 {
                    #empty_string_index
                }
            }
            #macro_doc
            #[macro_export]
            macro_rules! #macro_name {
                #(
                    (#atoms) => {
                        $crate::#path {
                            unsafe_data: #data,
                            phantom: ::std::marker::PhantomData,
                        }
                    };
                )*
            }
        }
    }

    /// Create a new file at `path` and write generated code there.
    ///
    /// Typical usage:
    /// `.write_to_file(&Path::new(&env::var("OUT_DIR").unwrap()).join("foo_atom.rs"))`
    pub fn write_to_file(&mut self, path: &Path) -> io::Result<()> {
        self.write_to(BufWriter::new(try!(File::create(path))))
    }
}
<|endoftext|>


<commit_before>use std::ffi::CString;
use std::os::raw::{c_void, c_long};
use std::ptr;
use std::rc::Rc;
use winapi;
use winapi::um::d3d11::ID3D11Device;
use winapi::um::d3d11::ID3D11Texture2D;

pub fn get_proc_address(name: &str) -> *const c_void {
    let name = CString::new(name.as_bytes()).unwrap();
    unsafe {
        GetProcAddress(name.as_ptr()) as *const _ as _
    }
}

pub struct SharedEglThings {
    device: EGLDeviceEXT,
    display: types::EGLDisplay,
    config: types::EGLConfig,
}

fn cast_attributes(slice: &[types::EGLenum]) -> &EGLint {
    unsafe {
        &*(slice.as_ptr() as *const EGLint)
    }
}

macro_rules! attributes {
    ($( $key: expr => $value: expr, )*) => {
        cast_attributes(&[
            $( $key, $value, )*
            NONE,
        ])
    }
}

impl SharedEglThings {
    pub unsafe fn new(d3d_device: *mut ID3D11Device) -> Rc<Self> {
        let device = check_mut_ptr(eglCreateDeviceANGLE(
            D3D11_DEVICE_ANGLE,
            d3d_device,
            ptr::null(),
        ));
        let display = check_ptr(GetPlatformDisplayEXT(
            PLATFORM_DEVICE_EXT,
            device,
            attributes! [
                EXPERIMENTAL_PRESENT_PATH_ANGLE => EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE,
            ],
        ));
        check_bool(Initialize(display, ptr::null_mut(), ptr::null_mut()));

        // Adapted from
        // https://searchfox.org/mozilla-central/rev/056a4057/gfx/gl/GLContextProviderEGL.cpp#635
        let mut configs = [ptr::null(); 64];
        let mut num_configs = 0;
        check_bool(ChooseConfig(
            display,
            attributes! [
                SURFACE_TYPE => WINDOW_BIT,
                RENDERABLE_TYPE => OPENGL_ES2_BIT,
                RED_SIZE => 8,
                GREEN_SIZE => 8,
                BLUE_SIZE => 8,
                ALPHA_SIZE => 8,
            ],
            configs.as_mut_ptr(),
            configs.len() as i32,
            &mut num_configs,
        ));
        assert!(num_configs >= 0);
        // FIXME: pick a preferable config?
        let config = configs[0];

        Rc::new(SharedEglThings { device, display, config })
    }
}

impl Drop for SharedEglThings {
    fn drop(&mut self) {
        unsafe {
            // FIXME does EGLDisplay or EGLConfig need clean up? How?
            check_bool(eglReleaseDeviceANGLE(self.device))
        }
    }
}

pub struct PerVisualEglThings {
    shared: Rc<SharedEglThings>,
    context: types::EGLContext,
    surface: types::EGLSurface,
}

impl PerVisualEglThings {
    pub unsafe fn new(shared: Rc<SharedEglThings>, buffer: *const ID3D11Texture2D,
           width: u32, height: u32)
           -> Self {
        let context = check_ptr(CreateContext(
            shared.display,
            shared.config,
            NO_CONTEXT,
            attributes![],
        ));

        let surface = check_ptr(CreatePbufferFromClientBuffer(
            shared.display,
            D3D_TEXTURE_ANGLE,
            buffer as types::EGLClientBuffer,
            shared.config,
            attributes! [
                WIDTH => width,
                HEIGHT => height,
                FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE => TRUE,
            ],
        ));

        PerVisualEglThings { shared, context, surface }
    }

    pub fn make_current(&self) {
        unsafe {
            check_bool(MakeCurrent(self.shared.display, self.surface, self.surface, self.context))
        }
    }
}

impl Drop for PerVisualEglThings {
    fn drop(&mut self) {
        unsafe {
            if GetCurrentContext() == self.context {
                // release the current context without assigning a new one
                check_bool(MakeCurrent(self.shared.display, NO_SURFACE, NO_SURFACE, NO_CONTEXT))
            }
            check_bool(DestroyContext(self.shared.display, self.context));
            check_bool(DestroySurface(self.shared.display, self.surface));
        }
    }
}

fn check_error() {
    unsafe {
        let error = GetError() as types::EGLenum;
        assert_eq!(error, SUCCESS, "0x{:x} != 0x{:x}", error, SUCCESS);
    }
}

fn check_ptr(p: *const c_void) -> *const c_void {
    check_error();
    assert!(!p.is_null());
    p
}

fn check_mut_ptr(p: *mut c_void) -> *mut c_void {
    check_error();
    assert!(!p.is_null());
    p
}

fn check_bool(bool_result: types::EGLBoolean) {
    check_error();
    assert_eq!(bool_result, TRUE);
}

// Adapted from https://github.com/tomaka/glutin/blob/1f3b8360cb/src/api/egl/ffi.rs
#[allow(non_camel_case_types)] pub type khronos_utime_nanoseconds_t = khronos_uint64_t;
#[allow(non_camel_case_types)] pub type khronos_uint64_t = u64;
#[allow(non_camel_case_types)] pub type khronos_ssize_t = c_long;
pub type EGLint = i32;
pub type EGLNativeDisplayType = *const c_void;
pub type EGLNativePixmapType = *const c_void;
pub type EGLNativeWindowType = winapi::shared::windef::HWND;
pub type NativeDisplayType = EGLNativeDisplayType;
pub type NativePixmapType = EGLNativePixmapType;
pub type NativeWindowType = EGLNativeWindowType;

include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs"));

// Adapted from https://chromium.googlesource.com/angle/angle/+/master/include/EGL/eglext_angle.h
type EGLDeviceEXT = *mut c_void;
const EXPERIMENTAL_PRESENT_PATH_ANGLE: types::EGLenum = 0x33A4;
const EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE: types::EGLenum = 0x33A9;
const D3D_TEXTURE_ANGLE: types::EGLenum = 0x33A3;
const FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE: types::EGLenum = 0x33A6;

extern "C" {
    fn eglCreateDeviceANGLE(
        device_type: types::EGLenum,
        device: *mut ID3D11Device,
        attrib_list: *const types::EGLAttrib,
    ) -> EGLDeviceEXT;

    fn eglReleaseDeviceANGLE(device: EGLDeviceEXT) -> types::EGLBoolean;
}
<commit_msg>Add a private Check trait for EGL return values<commit_after>use std::ffi::CString;
use std::os::raw::{c_void, c_long};
use std::ptr;
use std::rc::Rc;
use winapi;
use winapi::um::d3d11::ID3D11Device;
use winapi::um::d3d11::ID3D11Texture2D;

pub fn get_proc_address(name: &str) -> *const c_void {
    let name = CString::new(name.as_bytes()).unwrap();
    unsafe {
        GetProcAddress(name.as_ptr()) as *const _ as _
    }
}

pub struct SharedEglThings {
    device: EGLDeviceEXT,
    display: types::EGLDisplay,
    config: types::EGLConfig,
}

fn cast_attributes(slice: &[types::EGLenum]) -> &EGLint {
    unsafe {
        &*(slice.as_ptr() as *const EGLint)
    }
}

macro_rules! attributes {
    ($( $key: expr => $value: expr, )*) => {
        cast_attributes(&[
            $( $key, $value, )*
            NONE,
        ])
    }
}

impl SharedEglThings {
    pub unsafe fn new(d3d_device: *mut ID3D11Device) -> Rc<Self> {
        let device = eglCreateDeviceANGLE(
            D3D11_DEVICE_ANGLE,
            d3d_device,
            ptr::null(),
        ).check();
        let display = GetPlatformDisplayEXT(
            PLATFORM_DEVICE_EXT,
            device,
            attributes! [
                EXPERIMENTAL_PRESENT_PATH_ANGLE => EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE,
            ],
        ).check();
        Initialize(display, ptr::null_mut(), ptr::null_mut()).check();

        // Adapted from
        // https://searchfox.org/mozilla-central/rev/056a4057/gfx/gl/GLContextProviderEGL.cpp#635
        let mut configs = [ptr::null(); 64];
        let mut num_configs = 0;
        ChooseConfig(
            display,
            attributes! [
                SURFACE_TYPE => WINDOW_BIT,
                RENDERABLE_TYPE => OPENGL_ES2_BIT,
                RED_SIZE => 8,
                GREEN_SIZE => 8,
                BLUE_SIZE => 8,
                ALPHA_SIZE => 8,
            ],
            configs.as_mut_ptr(),
            configs.len() as i32,
            &mut num_configs,
        ).check();
        assert!(num_configs >= 0);
        // FIXME: pick a preferable config?
        let config = configs[0];

        Rc::new(SharedEglThings { device, display, config })
    }
}

impl Drop for SharedEglThings {
    fn drop(&mut self) {
        unsafe {
            // FIXME does EGLDisplay or EGLConfig need clean up? How?
            eglReleaseDeviceANGLE(self.device).check();
        }
    }
}

pub struct PerVisualEglThings {
    shared: Rc<SharedEglThings>,
    context: types::EGLContext,
    surface: types::EGLSurface,
}

impl PerVisualEglThings {
    pub unsafe fn new(shared: Rc<SharedEglThings>, buffer: *const ID3D11Texture2D,
           width: u32, height: u32)
           -> Self {
        let context = CreateContext(
            shared.display,
            shared.config,
            NO_CONTEXT,
            attributes![],
        ).check();

        let surface = CreatePbufferFromClientBuffer(
            shared.display,
            D3D_TEXTURE_ANGLE,
            buffer as types::EGLClientBuffer,
            shared.config,
            attributes! [
                WIDTH => width,
                HEIGHT => height,
                FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE => TRUE,
            ],
        ).check();

        PerVisualEglThings { shared, context, surface }
    }

    pub fn make_current(&self) {
        unsafe {
            MakeCurrent(self.shared.display, self.surface, self.surface, self.context).check();
        }
    }
}

impl Drop for PerVisualEglThings {
    fn drop(&mut self) {
        unsafe {
            if GetCurrentContext() == self.context {
                // release the current context without assigning a new one
                MakeCurrent(self.shared.display, NO_SURFACE, NO_SURFACE, NO_CONTEXT).check();
            }
            DestroyContext(self.shared.display, self.context).check();
            DestroySurface(self.shared.display, self.surface).check();
        }
    }
}

fn check_error() {
    unsafe {
        let error = GetError() as types::EGLenum;
        assert_eq!(error, SUCCESS, "0x{:x} != 0x{:x}", error, SUCCESS);
    }
}

trait Check {
    fn check(self) -> Self;
}

impl Check for *const c_void {
    fn check(self) -> Self {
        check_error();
        assert!(!self.is_null());
        self
    }
}

impl Check for *mut c_void {
    fn check(self) -> Self {
        check_error();
        assert!(!self.is_null());
        self
    }
}

impl Check for types::EGLBoolean {
    fn check(self) -> Self {
        check_error();
        assert_eq!(self, TRUE);
        self
    }
}

// Adapted from https://github.com/tomaka/glutin/blob/1f3b8360cb/src/api/egl/ffi.rs
#[allow(non_camel_case_types)] pub type khronos_utime_nanoseconds_t = khronos_uint64_t;
#[allow(non_camel_case_types)] pub type khronos_uint64_t = u64;
#[allow(non_camel_case_types)] pub type khronos_ssize_t = c_long;
pub type EGLint = i32;
pub type EGLNativeDisplayType = *const c_void;
pub type EGLNativePixmapType = *const c_void;
pub type EGLNativeWindowType = winapi::shared::windef::HWND;
pub type NativeDisplayType = EGLNativeDisplayType;
pub type NativePixmapType = EGLNativePixmapType;
pub type NativeWindowType = EGLNativeWindowType;

include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs"));

// Adapted from https://chromium.googlesource.com/angle/angle/+/master/include/EGL/eglext_angle.h
type EGLDeviceEXT = *mut c_void;
const EXPERIMENTAL_PRESENT_PATH_ANGLE: types::EGLenum = 0x33A4;
const EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE: types::EGLenum = 0x33A9;
const D3D_TEXTURE_ANGLE: types::EGLenum = 0x33A3;
const FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE: types::EGLenum = 0x33A6;

extern "C" {
    fn eglCreateDeviceANGLE(
        device_type: types::EGLenum,
        device: *mut ID3D11Device,
        attrib_list: *const types::EGLAttrib,
    ) -> EGLDeviceEXT;

    fn eglReleaseDeviceANGLE(device: EGLDeviceEXT) -> types::EGLBoolean;
}
<|endoftext|>