<commit_before>//! Loads a Cargo project into a static instance of analysis, without support
//! for incorporating changes.
use std::{path::Path, sync::Arc};

use anyhow::Result;
use crossbeam_channel::{unbounded, Receiver};
use hir::db::DefDatabase;
use ide::{AnalysisHost, Change};
use ide_db::{base_db::CrateGraph, FxHashMap};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
use vfs::{loader::Handle, AbsPath, AbsPathBuf};

use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};

// Note: Since this type is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub struct LoadCargoConfig {
    pub load_out_dirs_from_check: bool,
    pub with_proc_macro: bool,
    pub prefill_caches: bool,
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub fn load_workspace_at(
    root: &Path,
    cargo_config: &CargoConfig,
    load_config: &LoadCargoConfig,
    progress: &dyn Fn(String),
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
    let root = ProjectManifest::discover_single(&root)?;
    let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;

    if load_config.load_out_dirs_from_check {
        let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
        workspace.set_build_scripts(build_scripts)
    }

    load_workspace(workspace, &cargo_config.extra_env, load_config)
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
//
// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
pub fn load_workspace(
    ws: ProjectWorkspace,
    extra_env: &FxHashMap<String, String>,
    load_config: &LoadCargoConfig,
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let (sender, receiver) = unbounded();
    let mut vfs = vfs::Vfs::default();
    let mut loader = {
        let loader =
            vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
        Box::new(loader)
    };

    let proc_macro_client = if load_config.with_proc_macro {
        let path = AbsPathBuf::assert(std::env::current_exe()?);
        Ok(ProcMacroServer::spawn(path, &["proc-macro"]).unwrap())
    } else {
        Err("proc macro server not started".to_owned())
    };

    let crate_graph = ws.to_crate_graph(
        &mut |_, path: &AbsPath| {
            load_proc_macro(proc_macro_client.as_ref().map_err(|e| &**e), path, &[])
        },
        &mut |path: &AbsPath| {
            let contents = loader.load_sync(path);
            let path = vfs::VfsPath::from(path.to_path_buf());
            vfs.set_file_contents(path.clone(), contents);
            vfs.file_id(&path)
        },
        extra_env,
    );

    let project_folders = ProjectFolders::new(&[ws], &[]);
    loader.set_config(vfs::loader::Config {
        load: project_folders.load,
        watch: vec![],
        version: 0,
    });

    tracing::debug!("crate graph: {:?}", crate_graph);
    let host =
        load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);

    if load_config.prefill_caches {
        host.analysis().parallel_prime_caches(1, |_| {})?;
    }
    Ok((host, vfs, proc_macro_client.ok()))
}

fn load_crate_graph(
    crate_graph: CrateGraph,
    source_root_config: SourceRootConfig,
    vfs: &mut vfs::Vfs,
    receiver: &Receiver<vfs::loader::Message>,
) -> AnalysisHost {
    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
    let mut host = AnalysisHost::new(lru_cap);
    let mut analysis_change = Change::new();

    host.raw_database_mut().set_enable_proc_attr_macros(true);

    // wait until Vfs has loaded all roots
    for task in receiver {
        match task {
            vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
                if n_done == n_total {
                    break;
                }
            }
            vfs::loader::Message::Loaded { files } => {
                for (path, contents) in files {
                    vfs.set_file_contents(path.into(), contents);
                }
            }
        }
    }
    let changes = vfs.take_changes();
    for file in changes {
        if file.exists() {
            let contents = vfs.file_contents(file.file_id).to_vec();
            if let Ok(text) = String::from_utf8(contents) {
                analysis_change.change_file(file.file_id, Some(Arc::new(text)))
            }
        }
    }
    let source_roots = source_root_config.partition(vfs);
    analysis_change.set_roots(source_roots);

    analysis_change.set_crate_graph(crate_graph);

    host.apply_change(analysis_change);
    host
}

#[cfg(test)]
mod tests {
    use super::*;

    use hir::Crate;

    #[test]
    fn test_loading_rust_analyzer() {
        let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
        let cargo_config = CargoConfig::default();
        let load_cargo_config = LoadCargoConfig {
            load_out_dirs_from_check: false,
            with_proc_macro: false,
            prefill_caches: false,
        };
        let (host, _vfs, _proc_macro) =
            load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();

        let n_crates = Crate::all(host.raw_database()).len();
        // RA has quite a few crates, but the exact count doesn't matter
        assert!(n_crates > 20);
    }
}
<commit_msg>Use the sysroot proc-macro server for analysis-stats<commit_after>//! Loads a Cargo project into a static instance of analysis, without support
//! for incorporating changes.
use std::{path::Path, sync::Arc};

use anyhow::Result;
use crossbeam_channel::{unbounded, Receiver};
use hir::db::DefDatabase;
use ide::{AnalysisHost, Change};
use ide_db::{base_db::CrateGraph, FxHashMap};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
use vfs::{loader::Handle, AbsPath, AbsPathBuf};

use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};

// Note: Since this type is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub struct LoadCargoConfig {
    pub load_out_dirs_from_check: bool,
    pub with_proc_macro: bool,
    pub prefill_caches: bool,
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub fn load_workspace_at(
    root: &Path,
    cargo_config: &CargoConfig,
    load_config: &LoadCargoConfig,
    progress: &dyn Fn(String),
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
    let root = ProjectManifest::discover_single(&root)?;
    let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;

    if load_config.load_out_dirs_from_check {
        let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
        workspace.set_build_scripts(build_scripts)
    }

    load_workspace(workspace, &cargo_config.extra_env, load_config)
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
//
// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
pub fn load_workspace(
    ws: ProjectWorkspace,
    extra_env: &FxHashMap<String, String>,
    load_config: &LoadCargoConfig,
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let (sender, receiver) = unbounded();
    let mut vfs = vfs::Vfs::default();
    let mut loader = {
        let loader =
            vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
        Box::new(loader)
    };

    let proc_macro_client = if load_config.with_proc_macro {
        let mut path = AbsPathBuf::assert(std::env::current_exe()?);
        let mut args = vec!["proc-macro"];

        if let ProjectWorkspace::Cargo { sysroot, .. } | ProjectWorkspace::Json { sysroot, .. } =
            &ws
        {
            if let Some(sysroot) = sysroot.as_ref() {
                let standalone_server_name =
                    format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
                let server_path = sysroot.root().join("libexec").join(&standalone_server_name);
                if std::fs::metadata(&server_path).is_ok() {
                    path = server_path;
                    args = vec![];
                }
            }
        }

        ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|e| e.to_string())
    } else {
        Err("proc macro server disabled".to_owned())
    };

    let crate_graph = ws.to_crate_graph(
        &mut |_, path: &AbsPath| {
            load_proc_macro(proc_macro_client.as_ref().map_err(|e| &**e), path, &[])
        },
        &mut |path: &AbsPath| {
            let contents = loader.load_sync(path);
            let path = vfs::VfsPath::from(path.to_path_buf());
            vfs.set_file_contents(path.clone(), contents);
            vfs.file_id(&path)
        },
        extra_env,
    );

    let project_folders = ProjectFolders::new(&[ws], &[]);
    loader.set_config(vfs::loader::Config {
        load: project_folders.load,
        watch: vec![],
        version: 0,
    });

    tracing::debug!("crate graph: {:?}", crate_graph);
    let host =
        load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);

    if load_config.prefill_caches {
        host.analysis().parallel_prime_caches(1, |_| {})?;
    }
    Ok((host, vfs, proc_macro_client.ok()))
}

fn load_crate_graph(
    crate_graph: CrateGraph,
    source_root_config: SourceRootConfig,
    vfs: &mut vfs::Vfs,
    receiver: &Receiver<vfs::loader::Message>,
) -> AnalysisHost {
    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
    let mut host = AnalysisHost::new(lru_cap);
    let mut analysis_change = Change::new();

    host.raw_database_mut().set_enable_proc_attr_macros(true);

    // wait until Vfs has loaded all roots
    for task in receiver {
        match task {
            vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
                if n_done == n_total {
                    break;
                }
            }
            vfs::loader::Message::Loaded { files } => {
                for (path, contents) in files {
                    vfs.set_file_contents(path.into(), contents);
                }
            }
        }
    }
    let changes = vfs.take_changes();
    for file in changes {
        if file.exists() {
            let contents = vfs.file_contents(file.file_id).to_vec();
            if let Ok(text) = String::from_utf8(contents) {
                analysis_change.change_file(file.file_id, Some(Arc::new(text)))
            }
        }
    }
    let source_roots = source_root_config.partition(vfs);
    analysis_change.set_roots(source_roots);

    analysis_change.set_crate_graph(crate_graph);

    host.apply_change(analysis_change);
    host
}

#[cfg(test)]
mod tests {
    use super::*;

    use hir::Crate;

    #[test]
    fn test_loading_rust_analyzer() {
        let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
        let cargo_config = CargoConfig::default();
        let load_cargo_config = LoadCargoConfig {
            load_out_dirs_from_check: false,
            with_proc_macro: false,
            prefill_caches: false,
        };
        let (host, _vfs, _proc_macro) =
            load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();

        let n_crates = Crate::all(host.raw_database()).len();
        // RA has quite a few crates, but the exact count doesn't matter
        assert!(n_crates > 20);
    }
}
<|endoftext|>


<commit_before>use std::{
    fs,
    thread,
    cell::{Cell, RefCell},
    path::PathBuf,
    time::Duration,
    sync::Once,
};

use tempdir::TempDir;
use crossbeam_channel::{bounded, after, Sender, Receiver};
use flexi_logger::Logger;
use languageserver_types::{
    Url,
    TextDocumentIdentifier,
    request::{Request, Shutdown},
    notification::DidOpenTextDocument,
    DidOpenTextDocumentParams,
    TextDocumentItem,
};
use serde::Serialize;
use serde_json::{Value, from_str, to_string_pretty};
use gen_lsp_server::{RawMessage, RawRequest, RawNotification};

use m::{Result, main_loop, req};

pub fn project(fixture: &str) -> Server {
    static INIT: Once = Once::new();
    INIT.call_once(|| Logger::with_env_or_str(::LOG).start().unwrap());

    let tmp_dir = TempDir::new("test-project")
        .unwrap();
    let mut buf = String::new();
    let mut file_name = None;
    let mut paths = vec![];
    macro_rules! flush {
        () => {
            if let Some(file_name) = file_name {
                let path = tmp_dir.path().join(file_name);
                fs::create_dir_all(path.parent().unwrap()).unwrap();
                fs::write(path.as_path(), buf.as_bytes()).unwrap();
                paths.push((path, buf.clone()));
            }
        }
    };
    for line in fixture.lines() {
        if line.starts_with("//-") {
            flush!();
            buf.clear();
            file_name = Some(line["//-".len()..].trim());
            continue;
        }
        buf.push_str(line);
        buf.push('\n');
    }
    flush!();

    Server::new(tmp_dir, paths)
}

pub struct Server {
    req_id: Cell<u64>,
    messages: RefCell<Vec<RawMessage>>,
    dir: TempDir,
    sender: Option<Sender<RawMessage>>,
    receiver: Receiver<RawMessage>,
    server: Option<thread::JoinHandle<Result<()>>>,
}

impl Server {
    fn new(dir: TempDir, files: Vec<(PathBuf, String)>) -> Server {
        let path = dir.path().to_path_buf();
        let (client_sender, mut server_receiver) = bounded(1);
        let (mut server_sender, client_receiver) = bounded(1);
        let server = thread::spawn(move || main_loop(true, path, &mut server_receiver, &mut server_sender));
        let res = Server {
            req_id: Cell::new(1),
            dir,
            messages: Default::default(),
            sender: Some(client_sender),
            receiver: client_receiver,
            server: Some(server),
        };
        for (path, text) in files {
            res.send_notification(RawNotification::new::<DidOpenTextDocument>(
                &DidOpenTextDocumentParams {
                    text_document: TextDocumentItem {
                        uri: Url::from_file_path(path).unwrap(),
                        language_id: "rust".to_string(),
                        version: 0,
                        text,
                    }
                }
            ))
        }
        res
    }

    pub fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
        let path = self.dir.path().join(rel_path);
        TextDocumentIdentifier {
            uri: Url::from_file_path(path).unwrap(),
        }
    }

    pub fn request<R>(
        &self,
        params: R::Params,
        expected_resp: &str,
    )
    where
        R: Request,
        R::Params: Serialize,
    {
        let id = self.req_id.get();
        self.req_id.set(id + 1);
        let expected_resp: Value = from_str(expected_resp).unwrap();
        let actual = self.send_request::<R>(id, params);
        assert_eq!(
            expected_resp, actual,
            "Expected:\n{}\n\
             Actual:\n{}\n",
            to_string_pretty(&expected_resp).unwrap(),
            to_string_pretty(&actual).unwrap(),
        );
    }

    fn send_request<R>(&self, id: u64, params: R::Params) -> Value
    where
        R: Request,
        R::Params: Serialize,
    {
        let r = RawRequest::new::<R>(id, &params);
        self.send_request_(r)
    }
    fn send_request_(&self, r: RawRequest) -> Value
    {
        let id = r.id;
        self.sender.as_ref()
            .unwrap()
            .send(RawMessage::Request(r));
        while let Some(msg) = self.recv() {
            match msg {
                RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
                RawMessage::Notification(_) => (),
                RawMessage::Response(res) => {
                    assert_eq!(res.id, id);
                    if let Some(err) = res.error {
                        panic!("error response: {:#?}", err);
                    }
                    return res.result.unwrap();
                }
            }
        }
        panic!("no response");
    }
    pub fn wait_for_feedback(&self, feedback: &str) {
        self.wait_for_feedback_n(feedback, 1)
    }
    pub fn wait_for_feedback_n(&self, feedback: &str, n: usize) {
        let f = |msg: &RawMessage| match msg {
                RawMessage::Notification(n) if n.method == "internalFeedback" => {
                    return n.clone().cast::<req::InternalFeedback>()
                        .unwrap() == feedback
                }
                _ => false,
        };
        let mut total = 0;
        for msg in self.messages.borrow().iter() {
            if f(msg) {
                total += 1
            }
        }
        while total < n {
            let msg = self.recv().expect("no response");
            if f(&msg) {
                total += 1;
            }
        }
    }
    fn recv(&self) -> Option<RawMessage> {
        let timeout = Duration::from_secs(5);
        let msg = select! {
            recv(&self.receiver, msg) => msg,
            recv(after(timeout)) => panic!("timed out"),
        };
        msg.map(|msg| {
            self.messages.borrow_mut().push(msg.clone());
            msg
        })
    }
    fn send_notification(&self, not: RawNotification) {
        self.sender.as_ref()
            .unwrap()
            .send(RawMessage::Notification(not));
    }
}

impl Drop for Server {
    fn drop(&mut self) {
        {
            self.send_request::<Shutdown>(666, ());
            drop(self.sender.take().unwrap());
            while let Some(msg) = self.receiver.recv() {
                drop(msg);
            }
        }
        self.server.take()
            .unwrap()
            .join().unwrap().unwrap();
    }
}
<commit_msg>fix deadlock<commit_after>use std::{
    fs,
    thread,
    cell::{Cell, RefCell},
    path::PathBuf,
    time::Duration,
    sync::Once,
};

use tempdir::TempDir;
use crossbeam_channel::{unbounded, after, Sender, Receiver};
use flexi_logger::Logger;
use languageserver_types::{
    Url,
    TextDocumentIdentifier,
    request::{Request, Shutdown},
    notification::DidOpenTextDocument,
    DidOpenTextDocumentParams,
    TextDocumentItem,
};
use serde::Serialize;
use serde_json::{Value, from_str, to_string_pretty};
use gen_lsp_server::{RawMessage, RawRequest, RawNotification};

use m::{Result, main_loop, req};

pub fn project(fixture: &str) -> Server {
    static INIT: Once = Once::new();
    INIT.call_once(|| Logger::with_env_or_str(::LOG).start().unwrap());

    let tmp_dir = TempDir::new("test-project")
        .unwrap();
    let mut buf = String::new();
    let mut file_name = None;
    let mut paths = vec![];
    macro_rules! flush {
        () => {
            if let Some(file_name) = file_name {
                let path = tmp_dir.path().join(file_name);
                fs::create_dir_all(path.parent().unwrap()).unwrap();
                fs::write(path.as_path(), buf.as_bytes()).unwrap();
                paths.push((path, buf.clone()));
            }
        }
    };
    for line in fixture.lines() {
        if line.starts_with("//-") {
            flush!();
            buf.clear();
            file_name = Some(line["//-".len()..].trim());
            continue;
        }
        buf.push_str(line);
        buf.push('\n');
    }
    flush!();
    Server::new(tmp_dir, paths)
}

pub struct Server {
    req_id: Cell<u64>,
    messages: RefCell<Vec<RawMessage>>,
    dir: TempDir,
    sender: Option<Sender<RawMessage>>,
    receiver: Receiver<RawMessage>,
    server: Option<thread::JoinHandle<Result<()>>>,
}

impl Server {
    fn new(dir: TempDir, files: Vec<(PathBuf, String)>) -> Server {
        let path = dir.path().to_path_buf();
        let (client_sender, mut server_receiver) = unbounded();
        let (mut server_sender, client_receiver) = unbounded();
        let server = thread::spawn(move || main_loop(true, path, &mut server_receiver, &mut server_sender));
        let res = Server {
            req_id: Cell::new(1),
            dir,
            messages: Default::default(),
            sender: Some(client_sender),
            receiver: client_receiver,
            server: Some(server),
        };

        for (path, text) in files {
            res.send_notification(RawNotification::new::<DidOpenTextDocument>(
                &DidOpenTextDocumentParams {
                    text_document: TextDocumentItem {
                        uri: Url::from_file_path(path).unwrap(),
                        language_id: "rust".to_string(),
                        version: 0,
                        text,
                    }
                }
            ))
        }
        res
    }

    pub fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
        let path = self.dir.path().join(rel_path);
        TextDocumentIdentifier {
            uri: Url::from_file_path(path).unwrap(),
        }
    }

    pub fn request<R>(
        &self,
        params: R::Params,
        expected_resp: &str,
    )
    where
        R: Request,
        R::Params: Serialize,
    {
        let id = self.req_id.get();
        self.req_id.set(id + 1);
        let expected_resp: Value = from_str(expected_resp).unwrap();
        let actual = self.send_request::<R>(id, params);
        assert_eq!(
            expected_resp, actual,
            "Expected:\n{}\n\
             Actual:\n{}\n",
            to_string_pretty(&expected_resp).unwrap(),
            to_string_pretty(&actual).unwrap(),
        );
    }

    fn send_request<R>(&self, id: u64, params: R::Params) -> Value
    where
        R: Request,
        R::Params: Serialize,
    {
        let r = RawRequest::new::<R>(id, &params);
        self.send_request_(r)
    }
    fn send_request_(&self, r: RawRequest) -> Value
    {
        let id = r.id;
        self.sender.as_ref()
            .unwrap()
            .send(RawMessage::Request(r));
        while let Some(msg) = self.recv() {
            match msg {
                RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
                RawMessage::Notification(_) => (),
                RawMessage::Response(res) => {
                    assert_eq!(res.id, id);
                    if let Some(err) = res.error {
                        panic!("error response: {:#?}", err);
                    }
                    return res.result.unwrap();
                }
            }
        }
        panic!("no response");
    }
    pub fn wait_for_feedback(&self, feedback: &str) {
        self.wait_for_feedback_n(feedback, 1)
    }
    pub fn wait_for_feedback_n(&self, feedback: &str, n: usize) {
        let f = |msg: &RawMessage| match msg {
                RawMessage::Notification(n) if n.method == "internalFeedback" => {
                    return n.clone().cast::<req::InternalFeedback>()
                        .unwrap() == feedback
                }
                _ => false,
        };
        let mut total = 0;
        for msg in self.messages.borrow().iter() {
            if f(msg) {
                total += 1
            }
        }
        while total < n {
            let msg = self.recv().expect("no response");
            if f(&msg) {
                total += 1;
            }
        }
    }
    fn recv(&self) -> Option<RawMessage> {
        recv_timeout(&self.receiver)
            .map(|msg| {
                self.messages.borrow_mut().push(msg.clone());
                msg
            })
    }
    fn send_notification(&self, not: RawNotification) {
        self.sender.as_ref()
            .unwrap()
            .send(RawMessage::Notification(not));
    }
}

impl Drop for Server {
    fn drop(&mut self) {
        {
            self.send_request::<Shutdown>(666, ());
            drop(self.sender.take().unwrap());
            while let Some(msg) = recv_timeout(&self.receiver) {
                drop(msg);
            }
        }
        self.server.take()
            .unwrap()
            .join().unwrap().unwrap();
    }
}

fn recv_timeout(receiver: &Receiver<RawMessage>) -> Option<RawMessage> {
    let timeout = Duration::from_secs(5);
    select! {
        recv(receiver, msg) => msg,
        recv(after(timeout)) => panic!("timed out"),
    }
}
<|endoftext|>


<commit_before>use super::{GossipError, GossipResult};
use std::collections::HashMap;
use uuid::Uuid;
use bincode::{serialize, deserialize, Infinite};

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct NodeState {
    is_up: bool,
    last_update: u64,
}

impl NodeState {
    fn new() -> NodeState {
        NodeState{is_up: false,
            last_update: 0}
    }
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ClusterState {
    nodes: HashMap<String, NodeState>,
}

impl ClusterState {
    fn new() -> ClusterState {
        ClusterState{nodes:HashMap::new()}
    }
    fn update(&mut self, node: Uuid, state: NodeState) -> GossipResult<()> {
        Err(GossipError::OldState)
    }
}<commit_msg>adding application state<commit_after>use super::{GossipError, GossipResult};
use std::collections::HashMap;
use uuid::Uuid;
use bincode::{serialize, deserialize, Infinite};

#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, Eq, PartialOrd)]
enum ApplicationState {
    Status,
    Load,
    Schema,
    Datacenter,
    Rack,
    Version,
    InternalIp,
    HostId,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct NodeState {
    is_up: bool,
    version: u64,
    state: HashMap<ApplicationState, String>,
}

impl NodeState {
    fn new() -> NodeState {
        NodeState{is_up: false,
            version: 0,
            state: HashMap::new()}
    }
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ClusterState {
    nodes: HashMap<String, NodeState>,
}

impl ClusterState {
    fn new() -> ClusterState {
        ClusterState{nodes:HashMap::new()}
    }
    fn update(&mut self, node: Uuid, state: NodeState) -> GossipResult<()> {
        Err(GossipError::OldState)
    }
}<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/// Code to handle management of a pool's thinpool device.

use std::process::Command;

use uuid::Uuid;

use devicemapper as dm;
use devicemapper::{DM, DataBlocks, DmError, LinearDev, MetaBlocks, Sectors, Segment, ThinDev,
                   ThinDevId, ThinPoolDev};
use devicemapper::ErrorEnum::CheckFailed;

use super::super::consts::IEC;
use super::super::engine::HasName;
use super::super::errors::{EngineError, EngineResult, ErrorEnum};
use super::super::structures::Table;
use super::super::types::{PoolUuid, FilesystemUuid, RenameAction};

use super::dmdevice::{FlexRole, ThinDevIdPool, ThinPoolRole, ThinRole, format_flex_name,
                      format_thinpool_name, format_thin_name};
use super::filesystem::{StratFilesystem, FilesystemStatus};
use super::mdv::MetadataVol;
use super::serde_structs::{FilesystemSave, Recordable, ThinPoolDevSave};

pub const DATA_BLOCK_SIZE: Sectors = Sectors(2048);
pub const DATA_LOWATER: DataBlocks = DataBlocks(512);
pub const META_LOWATER: MetaBlocks = MetaBlocks(512);

const DEFAULT_THIN_DEV_SIZE: Sectors = Sectors(2 * IEC::Gi); // 1 TiB

/// A ThinPool struct contains the thinpool itself, but also the spare
/// segments for its metadata device.
#[derive(Debug)]
pub struct ThinPool {
    thin_pool: ThinPoolDev,
    meta_spare: Vec<Segment>,
    id_gen: ThinDevIdPool,
    filesystems: Table<StratFilesystem>,
    mdv: MetadataVol,
}

/// A struct returning the status of the distinct parts of the
/// thinpool.
pub struct ThinPoolStatus {
    /// The status of the thinpool itself.
    pub thinpool: dm::ThinPoolStatus,
    /// The status of the filesystems within the thinpool.
    pub filesystems: Vec<FilesystemStatus>,
}

impl ThinPool {
    /// Make a new thin pool.
    #[allow(too_many_arguments)]
    pub fn new(pool_uuid: PoolUuid,
               dm: &DM,
               data_block_size: Sectors,
               low_water_mark: DataBlocks,
               spare_segments: Vec<Segment>,
               meta_dev: LinearDev,
               data_dev: LinearDev,
               mdv: MetadataVol)
               -> EngineResult<ThinPool> {
        let name = format_thinpool_name(&pool_uuid, ThinPoolRole::Pool);
        let thinpool_dev = try!(ThinPoolDev::new(&name,
                                                 dm,
                                                 try!(data_dev.size()),
                                                 data_block_size,
                                                 low_water_mark,
                                                 meta_dev,
                                                 data_dev));
        Ok(ThinPool {
               thin_pool: thinpool_dev,
               meta_spare: spare_segments,
               id_gen: ThinDevIdPool::new_from_ids(&[]),
               filesystems: Table::default(),
               mdv: mdv,
           })
    }

    /// Set up an "existing" thin pool.
    /// A thin pool must store the metadata for its thin devices, regardless of
    /// whether it has an existing device node. An existing thin pool device
    /// is a device where the metadata is already stored on its meta device.
    /// If initial setup fails due to a thin_check failure, attempt to fix
    /// the problem by running thin_repair. If failure recurs, return an
    /// error.
    #[allow(too_many_arguments)]
    pub fn setup(pool_uuid: PoolUuid,
                 dm: &DM,
                 data_block_size: Sectors,
                 low_water_mark: DataBlocks,
                 thin_ids: &[ThinDevId],
                 spare_segments: Vec<Segment>,
                 meta_dev: LinearDev,
                 data_dev: LinearDev,
                 mdv: MetadataVol,
                 fs_save: Vec<FilesystemSave>)
                 -> EngineResult<ThinPool> {
        let name = format_thinpool_name(&pool_uuid, ThinPoolRole::Pool);
        let size = try!(data_dev.size());

        let res = match ThinPoolDev::setup(&name,
                                           dm,
                                           size,
                                           data_block_size,
                                           low_water_mark,
                                           meta_dev,
                                           data_dev) {
            Ok(dev) => Ok((dev, spare_segments)),
            Err(DmError::Dm(CheckFailed(meta_dev, data_dev), _)) => {
                attempt_thin_repair(pool_uuid, dm, meta_dev, spare_segments)
                    .and_then(|(new_meta_dev, new_spare_segments)| {
                        ThinPoolDev::setup(&name,
                                           dm,
                                           size,
                                           data_block_size,
                                           low_water_mark,
                                           new_meta_dev,
                                           data_dev)
                                .map(|dev| (dev, new_spare_segments))
                                .map_err(|e| e.into())
                    })
            }
            Err(e) => Err(e.into()),
        };
        let (thinpool_dev, spare_segments) = try!(res);

        // TODO: not fail completely if one filesystem setup fails?
        let filesystems: Vec<StratFilesystem> = {
            // Set up a filesystem from its metadata.
            let get_filesystem = |fssave: &FilesystemSave| -> EngineResult<StratFilesystem> {
                let device_name = format_thin_name(&pool_uuid, ThinRole::Filesystem(fssave.uuid));
                let thin_dev = try!(ThinDev::setup(&device_name,
                                                   dm,
                                                   &thinpool_dev,
                                                   fssave.thin_id,
                                                   fssave.size));
                Ok(StratFilesystem::setup(fssave.uuid, &fssave.name, thin_dev))
            };

            try!(fs_save.iter().map(get_filesystem).collect())
        };

        let mut fs_table = Table::default();
        for fs in filesystems {
            let evicted = fs_table.insert(fs);
            if !evicted.is_empty() {
                let err_msg = "filesystems with duplicate UUID or name specified in metadata";
                return Err(EngineError::Engine(ErrorEnum::Invalid, err_msg.into()));
            }
        }

        Ok(ThinPool {
               thin_pool: thinpool_dev,
               meta_spare: spare_segments,
               id_gen: ThinDevIdPool::new_from_ids(thin_ids),
               filesystems: fs_table,
               mdv: mdv,
           })
    }

    /// The status of the thin pool as calculated by DM.
    pub fn check(&self, dm: &DM) -> EngineResult<ThinPoolStatus> {
        let thinpool = try!(self.thin_pool.status(dm));
        try!(self.mdv.check());

        let filesystems: Vec<_> = try!(self.filesystems
                                           .into_iter()
                                           .map(|fs| fs.check(dm))
                                           .collect());

        Ok(ThinPoolStatus {
               thinpool,
               filesystems,
           })
    }

    /// Tear down the components managed here: filesystems, the MDV,
    /// and the actual thinpool device itself.
    pub fn teardown(self, dm: &DM) -> EngineResult<()> {
        // Must succeed in tearing down all filesystems before the
        // thinpool..
        for fs in self.filesystems.empty() {
            try!(fs.teardown(dm));
        }
        try!(self.thin_pool.teardown(dm));

        // ..but MDV has no DM dependencies with the above
        try!(self.mdv.teardown(dm));

        Ok(())
    }

    /// Get an immutable reference to the sparse segments of the ThinPool.
    pub fn spare_segments(&self) -> &[Segment] {
        &self.meta_spare
    }

    /// The segments belonging to the thin pool meta device.
    pub fn thin_pool_meta_segments(&self) -> &[Segment] {
        self.thin_pool.meta_dev().segments()
    }

    /// The segments belonging to the thin pool data device.
    pub fn thin_pool_data_segments(&self) -> &[Segment] {
        self.thin_pool.data_dev().segments()
    }

    /// The segments belonging to the MDV.
    pub fn thin_pool_mdv_segments(&self) -> &[Segment] {
        self.mdv.segments()
    }

    /// Get the devicemapper::ThinPoolDev for this pool. Used for testing.
    pub fn thinpooldev(&self) -> &ThinPoolDev {
        &self.thin_pool
    }

    /// Extend the thinpool with new data regions.
    pub fn extend_data(&mut self, dm: &DM, segs: Vec<Segment>) -> EngineResult<()> {
        Ok(try!(self.thin_pool.extend_data(dm, segs)))
    }

    /// The number of physical sectors in use, that is, unavailable for storage
    /// of additional user data, by this pool.
    // This includes all the sectors being held as spares for the meta device,
    // all the sectors allocated to the meta data device, and all the sectors
    // in use on the data device.
    pub fn total_physical_used(&self) -> EngineResult<Sectors> {
        let data_dev_used = match try!(self.thin_pool.status(&try!(DM::new()))) {
            dm::ThinPoolStatus::Good(_, usage) => *usage.used_data * DATA_BLOCK_SIZE,
            _ => {
                let err_msg = "thin pool failed, could not obtain usage";
                return Err(EngineError::Engine(ErrorEnum::Invalid, err_msg.into()));
            }
        };

        let spare_total = self.spare_segments().iter().map(|s| s.length).sum();
        let meta_dev_total = self.thin_pool
            .meta_dev()
            .segments()
            .iter()
            .map(|s| s.length)
            .sum();

        let mdv_total = self.mdv.segments().iter().map(|s| s.length).sum();

        Ok(data_dev_used + spare_total + meta_dev_total + mdv_total)
    }

    pub fn get_mut_filesystem_by_uuid(&mut self,
                                      uuid: &FilesystemUuid)
                                      -> Option<&mut StratFilesystem> {
        self.filesystems.get_mut_by_uuid(uuid)
    }

    pub fn get_mut_filesystem_by_name(&mut self, name: &str) -> Option<&mut StratFilesystem> {
        self.filesystems.get_mut_by_name(name)
    }

    pub fn has_filesystems(&self) -> bool {
        !self.filesystems.is_empty()
    }

    /// Create a filesystem within the thin pool. Given name must not
    /// already be in use.
    pub fn create_filesystem(&mut self,
                             pool_uuid: &Uuid,
                             name: &str,
                             dm: &DM,
                             size: Option<Sectors>)
                             -> EngineResult<FilesystemUuid> {
        let fs_uuid = Uuid::new_v4();
        let device_name = format_thin_name(pool_uuid, ThinRole::Filesystem(fs_uuid));
        let thin_dev = try!(ThinDev::new(&device_name,
                                         dm,
                                         &self.thin_pool,
                                         try!(self.id_gen.new_id()),
                                         size.unwrap_or(DEFAULT_THIN_DEV_SIZE)));

        let new_filesystem = try!(StratFilesystem::initialize(fs_uuid, name, thin_dev));
        try!(self.mdv.save_fs(&new_filesystem));
        self.filesystems.insert(new_filesystem);

        Ok(fs_uuid)
    }

    /// Destroy a filesystem within the thin pool.
    pub fn destroy_filesystem(&mut self, dm: &DM, uuid: &FilesystemUuid) -> EngineResult<()> {
        if let Some(fs) = self.filesystems.remove_by_uuid(uuid) {
            try!(fs.destroy(dm, &self.thin_pool));
            try!(self.mdv.rm_fs(uuid));
        }
        Ok(())
    }

    /// Rename a filesystem within the thin pool.
    pub fn rename_filesystem(&mut self,
                             uuid: &FilesystemUuid,
                             new_name: &str)
                             -> EngineResult<RenameAction> {

        let old_name = rename_filesystem_pre!(self; uuid; new_name);

        let mut filesystem =
            self.filesystems
                .remove_by_uuid(uuid)
                .expect("Must succeed since self.filesystems.get_by_uuid() returned a value");

        filesystem.rename(new_name);
        if let Err(err) = self.mdv.save_fs(&filesystem) {
            filesystem.rename(&old_name);
            self.filesystems.insert(filesystem);
            Err(err)
        } else {
            self.filesystems.insert(filesystem);
            Ok(RenameAction::Renamed)
        }
    }
}

impl Recordable<ThinPoolDevSave> for ThinPool {
    fn record(&self) -> EngineResult<ThinPoolDevSave> {
        Ok(ThinPoolDevSave { data_block_size: self.thin_pool.data_block_size() })
    }
}

/// Attempt a thin repair operation on the meta device.
/// If the operation succeeds, teardown the old meta device,
/// and return the new meta device and the new spare segments.
fn attempt_thin_repair(pool_uuid: PoolUuid,
                       dm: &DM,
                       meta_dev: LinearDev,
                       mut spare_segments: Vec<Segment>)
                       -> EngineResult<(LinearDev, Vec<Segment>)> {
    let mut new_meta_dev = try!(LinearDev::new(&format_flex_name(&pool_uuid,
                                                                 FlexRole::ThinMetaSpare),
                                               dm,
                                               spare_segments.drain(..).collect()));

    if !try!(Command::new("thin_repair")
                 .arg("-i")
                 .arg(&try!(meta_dev.devnode()))
                 .arg("-o")
                 .arg(&try!(new_meta_dev.devnode()))
                 .status())
                .success() {
        return Err(EngineError::Engine(ErrorEnum::Error,
                                       "thin_repair failed, pool unusable".into()));
    }

    let name = meta_dev.name().to_owned();
    let new_spare_segments = meta_dev
        .segments()
        .iter()
        .map(|x| {
                 Segment {
                     start: x.start,
                     length: x.length,
                     device: x.device,
                 }
             })
        .collect();
    try!(meta_dev.teardown(dm));
    try!(new_meta_dev.set_name(dm, &name));

    Ok((new_meta_dev, new_spare_segments))
}
<commit_msg>Borrow a mutable filesystem to map to check()<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/// Code to handle management of a pool's thinpool device.

use std::borrow::BorrowMut;
use std::process::Command;

use uuid::Uuid;

use devicemapper as dm;
use devicemapper::{DM, DataBlocks, DmError, LinearDev, MetaBlocks, Sectors, Segment, ThinDev,
                   ThinDevId, ThinPoolDev};
use devicemapper::ErrorEnum::CheckFailed;

use super::super::consts::IEC;
use super::super::engine::HasName;
use super::super::errors::{EngineError, EngineResult, ErrorEnum};
use super::super::structures::Table;
use super::super::types::{PoolUuid, FilesystemUuid, RenameAction};

use super::dmdevice::{FlexRole, ThinDevIdPool, ThinPoolRole, ThinRole, format_flex_name,
                      format_thinpool_name, format_thin_name};
use super::filesystem::{StratFilesystem, FilesystemStatus};
use super::mdv::MetadataVol;
use super::serde_structs::{FilesystemSave, Recordable, ThinPoolDevSave};

pub const DATA_BLOCK_SIZE: Sectors = Sectors(2048);
pub const DATA_LOWATER: DataBlocks = DataBlocks(512);
pub const META_LOWATER: MetaBlocks = MetaBlocks(512);

const DEFAULT_THIN_DEV_SIZE: Sectors = Sectors(2 * IEC::Gi); // 1 TiB

/// A ThinPool struct contains the thinpool itself, but also the spare
/// segments for its metadata device.
#[derive(Debug)]
pub struct ThinPool {
    thin_pool: ThinPoolDev,
    meta_spare: Vec<Segment>,
    id_gen: ThinDevIdPool,
    filesystems: Table<StratFilesystem>,
    mdv: MetadataVol,
}

/// A struct returning the status of the distinct parts of the
/// thinpool.
pub struct ThinPoolStatus {
    /// The status of the thinpool itself.
    pub thinpool: dm::ThinPoolStatus,
    /// The status of the filesystems within the thinpool.
    pub filesystems: Vec<FilesystemStatus>,
}

impl ThinPool {
    /// Make a new thin pool.
    #[allow(too_many_arguments)]
    pub fn new(pool_uuid: PoolUuid,
               dm: &DM,
               data_block_size: Sectors,
               low_water_mark: DataBlocks,
               spare_segments: Vec<Segment>,
               meta_dev: LinearDev,
               data_dev: LinearDev,
               mdv: MetadataVol)
               -> EngineResult<ThinPool> {
        let name = format_thinpool_name(&pool_uuid, ThinPoolRole::Pool);
        let thinpool_dev = try!(ThinPoolDev::new(&name,
                                                 dm,
                                                 try!(data_dev.size()),
                                                 data_block_size,
                                                 low_water_mark,
                                                 meta_dev,
                                                 data_dev));
        Ok(ThinPool {
               thin_pool: thinpool_dev,
               meta_spare: spare_segments,
               id_gen: ThinDevIdPool::new_from_ids(&[]),
               filesystems: Table::default(),
               mdv: mdv,
           })
    }

    /// Set up an "existing" thin pool.
    /// A thin pool must store the metadata for its thin devices, regardless of
    /// whether it has an existing device node. An existing thin pool device
    /// is a device where the metadata is already stored on its meta device.
    /// If initial setup fails due to a thin_check failure, attempt to fix
    /// the problem by running thin_repair. If failure recurs, return an
    /// error.
    #[allow(too_many_arguments)]
    pub fn setup(pool_uuid: PoolUuid,
                 dm: &DM,
                 data_block_size: Sectors,
                 low_water_mark: DataBlocks,
                 thin_ids: &[ThinDevId],
                 spare_segments: Vec<Segment>,
                 meta_dev: LinearDev,
                 data_dev: LinearDev,
                 mdv: MetadataVol,
                 fs_save: Vec<FilesystemSave>)
                 -> EngineResult<ThinPool> {
        let name = format_thinpool_name(&pool_uuid, ThinPoolRole::Pool);
        let size = try!(data_dev.size());

        let res = match ThinPoolDev::setup(&name,
                                           dm,
                                           size,
                                           data_block_size,
                                           low_water_mark,
                                           meta_dev,
                                           data_dev) {
            Ok(dev) => Ok((dev, spare_segments)),
            Err(DmError::Dm(CheckFailed(meta_dev, data_dev), _)) => {
                attempt_thin_repair(pool_uuid, dm, meta_dev, spare_segments)
                    .and_then(|(new_meta_dev, new_spare_segments)| {
                        ThinPoolDev::setup(&name,
                                           dm,
                                           size,
                                           data_block_size,
                                           low_water_mark,
                                           new_meta_dev,
                                           data_dev)
                                .map(|dev| (dev, new_spare_segments))
                                .map_err(|e| e.into())
                    })
            }
            Err(e) => Err(e.into()),
        };
        let (thinpool_dev, spare_segments) = try!(res);

        // TODO: not fail completely if one filesystem setup fails?
        let filesystems: Vec<StratFilesystem> = {
            // Set up a filesystem from its metadata.
            let get_filesystem = |fssave: &FilesystemSave| -> EngineResult<StratFilesystem> {
                let device_name = format_thin_name(&pool_uuid, ThinRole::Filesystem(fssave.uuid));
                let thin_dev = try!(ThinDev::setup(&device_name,
                                                   dm,
                                                   &thinpool_dev,
                                                   fssave.thin_id,
                                                   fssave.size));
                Ok(StratFilesystem::setup(fssave.uuid, &fssave.name, thin_dev))
            };

            try!(fs_save.iter().map(get_filesystem).collect())
        };

        let mut fs_table = Table::default();
        for fs in filesystems {
            let evicted = fs_table.insert(fs);
            if !evicted.is_empty() {
                let err_msg = "filesystems with duplicate UUID or name specified in metadata";
                return Err(EngineError::Engine(ErrorEnum::Invalid, err_msg.into()));
            }
        }

        Ok(ThinPool {
               thin_pool: thinpool_dev,
               meta_spare: spare_segments,
               id_gen: ThinDevIdPool::new_from_ids(thin_ids),
               filesystems: fs_table,
               mdv: mdv,
           })
    }

    /// The status of the thin pool as calculated by DM.
    pub fn check(&mut self, dm: &DM) -> EngineResult<ThinPoolStatus> {
        let thinpool = try!(self.thin_pool.status(dm));
        try!(self.mdv.check());

        let filesystems: Vec<_> = try!(self.filesystems
                                           .borrow_mut()
                                           .into_iter()
                                           .map(|fs| fs.check(dm))
                                           .collect());

        Ok(ThinPoolStatus {
               thinpool,
               filesystems,
           })
    }

    /// Tear down the components managed here: filesystems, the MDV,
    /// and the actual thinpool device itself.
    pub fn teardown(self, dm: &DM) -> EngineResult<()> {
        // Must succeed in tearing down all filesystems before the
        // thinpool..
        for fs in self.filesystems.empty() {
            try!(fs.teardown(dm));
        }
        try!(self.thin_pool.teardown(dm));

        // ..but MDV has no DM dependencies with the above
        try!(self.mdv.teardown(dm));

        Ok(())
    }

    /// Get an immutable reference to the sparse segments of the ThinPool.
    pub fn spare_segments(&self) -> &[Segment] {
        &self.meta_spare
    }

    /// The segments belonging to the thin pool meta device.
    pub fn thin_pool_meta_segments(&self) -> &[Segment] {
        self.thin_pool.meta_dev().segments()
    }

    /// The segments belonging to the thin pool data device.
    pub fn thin_pool_data_segments(&self) -> &[Segment] {
        self.thin_pool.data_dev().segments()
    }

    /// The segments belonging to the MDV.
    pub fn thin_pool_mdv_segments(&self) -> &[Segment] {
        self.mdv.segments()
    }

    /// Get the devicemapper::ThinPoolDev for this pool. Used for testing.
    pub fn thinpooldev(&self) -> &ThinPoolDev {
        &self.thin_pool
    }

    /// Extend the thinpool with new data regions.
    pub fn extend_data(&mut self, dm: &DM, segs: Vec<Segment>) -> EngineResult<()> {
        Ok(try!(self.thin_pool.extend_data(dm, segs)))
    }

    /// The number of physical sectors in use, that is, unavailable for storage
    /// of additional user data, by this pool.
    // This includes all the sectors being held as spares for the meta device,
    // all the sectors allocated to the meta data device, and all the sectors
    // in use on the data device.
    pub fn total_physical_used(&self) -> EngineResult<Sectors> {
        let data_dev_used = match try!(self.thin_pool.status(&try!(DM::new()))) {
            dm::ThinPoolStatus::Good(_, usage) => *usage.used_data * DATA_BLOCK_SIZE,
            _ => {
                let err_msg = "thin pool failed, could not obtain usage";
                return Err(EngineError::Engine(ErrorEnum::Invalid, err_msg.into()));
            }
        };

        let spare_total = self.spare_segments().iter().map(|s| s.length).sum();
        let meta_dev_total = self.thin_pool
            .meta_dev()
            .segments()
            .iter()
            .map(|s| s.length)
            .sum();

        let mdv_total = self.mdv.segments().iter().map(|s| s.length).sum();

        Ok(data_dev_used + spare_total + meta_dev_total + mdv_total)
    }

    pub fn get_mut_filesystem_by_uuid(&mut self,
                                      uuid: &FilesystemUuid)
                                      -> Option<&mut StratFilesystem> {
        self.filesystems.get_mut_by_uuid(uuid)
    }

    pub fn get_mut_filesystem_by_name(&mut self, name: &str) -> Option<&mut StratFilesystem> {
        self.filesystems.get_mut_by_name(name)
    }

    pub fn has_filesystems(&self) -> bool {
        !self.filesystems.is_empty()
    }

    /// Create a filesystem within the thin pool. Given name must not
    /// already be in use.
    pub fn create_filesystem(&mut self,
                             pool_uuid: &Uuid,
                             name: &str,
                             dm: &DM,
                             size: Option<Sectors>)
                             -> EngineResult<FilesystemUuid> {
        let fs_uuid = Uuid::new_v4();
        let device_name = format_thin_name(pool_uuid, ThinRole::Filesystem(fs_uuid));
        let thin_dev = try!(ThinDev::new(&device_name,
                                         dm,
                                         &self.thin_pool,
                                         try!(self.id_gen.new_id()),
                                         size.unwrap_or(DEFAULT_THIN_DEV_SIZE)));

        let new_filesystem = try!(StratFilesystem::initialize(fs_uuid, name, thin_dev));
        try!(self.mdv.save_fs(&new_filesystem));
        self.filesystems.insert(new_filesystem);

        Ok(fs_uuid)
    }

    /// Destroy a filesystem within the thin pool.
    pub fn destroy_filesystem(&mut self, dm: &DM, uuid: &FilesystemUuid) -> EngineResult<()> {
        if let Some(fs) = self.filesystems.remove_by_uuid(uuid) {
            try!(fs.destroy(dm, &self.thin_pool));
            try!(self.mdv.rm_fs(uuid));
        }
        Ok(())
    }

    /// Rename a filesystem within the thin pool.
    pub fn rename_filesystem(&mut self,
                             uuid: &FilesystemUuid,
                             new_name: &str)
                             -> EngineResult<RenameAction> {

        let old_name = rename_filesystem_pre!(self; uuid; new_name);

        let mut filesystem =
            self.filesystems
                .remove_by_uuid(uuid)
                .expect("Must succeed since self.filesystems.get_by_uuid() returned a value");

        filesystem.rename(new_name);
        if let Err(err) = self.mdv.save_fs(&filesystem) {
            filesystem.rename(&old_name);
            self.filesystems.insert(filesystem);
            Err(err)
        } else {
            self.filesystems.insert(filesystem);
            Ok(RenameAction::Renamed)
        }
    }
}

impl Recordable<ThinPoolDevSave> for ThinPool {
    fn record(&self) -> EngineResult<ThinPoolDevSave> {
        Ok(ThinPoolDevSave { data_block_size: self.thin_pool.data_block_size() })
    }
}

/// Attempt a thin repair operation on the meta device.
/// If the operation succeeds, teardown the old meta device,
/// and return the new meta device and the new spare segments.
fn attempt_thin_repair(pool_uuid: PoolUuid,
                       dm: &DM,
                       meta_dev: LinearDev,
                       mut spare_segments: Vec<Segment>)
                       -> EngineResult<(LinearDev, Vec<Segment>)> {
    let mut new_meta_dev = try!(LinearDev::new(&format_flex_name(&pool_uuid,
                                                                 FlexRole::ThinMetaSpare),
                                               dm,
                                               spare_segments.drain(..).collect()));

    if !try!(Command::new("thin_repair")
                 .arg("-i")
                 .arg(&try!(meta_dev.devnode()))
                 .arg("-o")
                 .arg(&try!(new_meta_dev.devnode()))
                 .status())
                .success() {
        return Err(EngineError::Engine(ErrorEnum::Error,
                                       "thin_repair failed, pool unusable".into()));
    }

    let name = meta_dev.name().to_owned();
    let new_spare_segments = meta_dev
        .segments()
        .iter()
        .map(|x| {
                 Segment {
                     start: x.start,
                     length: x.length,
                     device: x.device,
                 }
             })
        .collect();
    try!(meta_dev.teardown(dm));
    try!(new_meta_dev.set_name(dm, &name));

    Ok((new_meta_dev, new_spare_segments))
}
<|endoftext|>


<commit_before>// pest. The Elegant Parser
// Copyright (C) 2017  Dragoș Tiselice
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::Rc;

use super::input::Input;
use super::position;

/// A `struct` of a span over an `Input`. It is created from either
/// [two `Position`s](struct.Position.html#method.span) or from a
/// [`Pair`](../iterators/struct.Pair.html#method.span).
pub struct Span<I: Input> {
    input: Rc<I>,
    start: usize,
    end: usize,
}

#[inline]
pub fn new<I: Input>(input: Rc<I>, start: usize, end: usize) -> Span<I> {
    Span { input, start, end }
}

impl<'a, I: Input> Span<I> {
    /// Returns the `Span`'s start position as a `usize`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.span(end);
    ///
    /// assert_eq!(span.start(), 0);
    /// ```
    #[inline]
    pub fn start(&self) -> usize {
        self.start
    }

    /// Returns the `Span`'s end position as a `usize`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.span(end);
    ///
    /// assert_eq!(span.end(), 2);
    /// ```
    #[inline]
    pub fn end(&self) -> usize {
        self.end
    }

    /// Returns the `Span`'s start `Position`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.clone().span(end);
    ///
    /// assert_eq!(span.start_pos(), start);
    /// ```
    #[inline]
    pub fn start_pos(&self) -> position::Position<I> {
        // Span start position is a UTF-8 border and is safe.
        unsafe { position::new(self.input.clone(), self.start) }
    }

    /// Returns the `Span`'s end `Position`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.span(end.clone());
    ///
    /// assert_eq!(span.end_pos(), end);
    /// ```
    #[inline]
    pub fn end_pos(&self) -> position::Position<I> {
        // Span end position is a UTF-8 border and is safe.
        unsafe { position::new(self.input.clone(), self.end) }
    }

    /// Splits the `Span` into a pair of `Position`s.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.clone().span(end.clone());
    ///
    /// assert_eq!(span.split(), (start, end));
    /// ```
    #[inline]
    pub fn split(self) -> (position::Position<I>, position::Position<I>) {
        // Span start and end positions are UTF-8 borders and safe.
        let pos1 = unsafe { position::new(self.input.clone(), self.start) };
        let pos2 = unsafe { position::new(self.input, self.end) };

        (pos1, pos2)
    }

    /// Captures a `&str` slice from the `Input` defined by the `Span`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("abc".to_owned()));
    /// let start = Position::from_start(input).skip(1).unwrap();
    /// let end = start.clone().match_string("b").unwrap();
    /// let span = start.span(end);
    ///
    /// assert_eq!(span.as_str(), "b");
    /// ```
    #[inline]
    pub fn as_str(&'a self) -> &'a str {
        unsafe { self.input.slice(self.start, self.end) }
    }
}

// We don't want to enforce derivable traits on the Input which forces to implement them manually.

impl<I: Input> fmt::Debug for Span<I> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Span {{ start: {}, end: {} }}", self.start, self.end)
    }
}

impl<I: Input> Clone for Span<I> {
    fn clone(&self) -> Span<I> {
        new(self.input.clone(), self.start, self.end)
    }
}

impl<I: Input> PartialEq for Span<I> {
    fn eq(&self, other: &Span<I>) -> bool {
        Rc::ptr_eq(&self.input, &other.input) && self.start == other.start && self.end == other.end
    }
}

impl<I: Input> Eq for Span<I> {}

impl<I: Input> PartialOrd for Span<I> {
    fn partial_cmp(&self, other: &Span<I>) -> Option<Ordering> {
        if Rc::ptr_eq(&self.input, &other.input) {
            match self.start.partial_cmp(&other.start) {
                Some(Ordering::Equal) => self.end.partial_cmp(&other.end),
                ordering => ordering,
            }
        } else {
            None
        }
    }
}

impl<I: Input> Ord for Span<I> {
    fn cmp(&self, other: &Span<I>) -> Ordering {
        self.partial_cmp(other).expect(
            "cannot compare spans from different inputs",
        )
    }
}

impl<'a, I: Input> Hash for Span<I> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (&*self.input as *const I).hash(state);
        self.start.hash(state);
        self.end.hash(state);
    }
}
<commit_msg>(fix) remove unneeded lifetime in as_str<commit_after>// pest. The Elegant Parser
// Copyright (C) 2017  Dragoș Tiselice
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::Rc;

use super::input::Input;
use super::position;

/// A `struct` of a span over an `Input`. It is created from either
/// [two `Position`s](struct.Position.html#method.span) or from a
/// [`Pair`](../iterators/struct.Pair.html#method.span).
pub struct Span<I: Input> {
    input: Rc<I>,
    start: usize,
    end: usize,
}

#[inline]
pub fn new<I: Input>(input: Rc<I>, start: usize, end: usize) -> Span<I> {
    Span { input, start, end }
}

impl<'a, I: Input> Span<I> {
    /// Returns the `Span`'s start position as a `usize`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.span(end);
    ///
    /// assert_eq!(span.start(), 0);
    /// ```
    #[inline]
    pub fn start(&self) -> usize {
        self.start
    }

    /// Returns the `Span`'s end position as a `usize`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.span(end);
    ///
    /// assert_eq!(span.end(), 2);
    /// ```
    #[inline]
    pub fn end(&self) -> usize {
        self.end
    }

    /// Returns the `Span`'s start `Position`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.clone().span(end);
    ///
    /// assert_eq!(span.start_pos(), start);
    /// ```
    #[inline]
    pub fn start_pos(&self) -> position::Position<I> {
        // Span start position is a UTF-8 border and is safe.
        unsafe { position::new(self.input.clone(), self.start) }
    }

    /// Returns the `Span`'s end `Position`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.span(end.clone());
    ///
    /// assert_eq!(span.end_pos(), end);
    /// ```
    #[inline]
    pub fn end_pos(&self) -> position::Position<I> {
        // Span end position is a UTF-8 border and is safe.
        unsafe { position::new(self.input.clone(), self.end) }
    }

    /// Splits the `Span` into a pair of `Position`s.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("ab".to_owned()));
    /// let start = Position::from_start(input);
    /// let end = start.clone().match_string("ab").unwrap();
    /// let span = start.clone().span(end.clone());
    ///
    /// assert_eq!(span.split(), (start, end));
    /// ```
    #[inline]
    pub fn split(self) -> (position::Position<I>, position::Position<I>) {
        // Span start and end positions are UTF-8 borders and safe.
        let pos1 = unsafe { position::new(self.input.clone(), self.start) };
        let pos2 = unsafe { position::new(self.input, self.end) };

        (pos1, pos2)
    }

    /// Captures a `&str` slice from the `Input` defined by the `Span`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest::inputs::{Position, StringInput};
    /// let input = Rc::new(StringInput::new("abc".to_owned()));
    /// let start = Position::from_start(input).skip(1).unwrap();
    /// let end = start.clone().match_string("b").unwrap();
    /// let span = start.span(end);
    ///
    /// assert_eq!(span.as_str(), "b");
    /// ```
    #[inline]
    pub fn as_str(&self) -> &str {
        unsafe { self.input.slice(self.start, self.end) }
    }
}

// We don't want to enforce derivable traits on the Input which forces to implement them manually.

impl<I: Input> fmt::Debug for Span<I> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Span {{ start: {}, end: {} }}", self.start, self.end)
    }
}

impl<I: Input> Clone for Span<I> {
    fn clone(&self) -> Span<I> {
        new(self.input.clone(), self.start, self.end)
    }
}

impl<I: Input> PartialEq for Span<I> {
    fn eq(&self, other: &Span<I>) -> bool {
        Rc::ptr_eq(&self.input, &other.input) && self.start == other.start && self.end == other.end
    }
}

impl<I: Input> Eq for Span<I> {}

impl<I: Input> PartialOrd for Span<I> {
    fn partial_cmp(&self, other: &Span<I>) -> Option<Ordering> {
        if Rc::ptr_eq(&self.input, &other.input) {
            match self.start.partial_cmp(&other.start) {
                Some(Ordering::Equal) => self.end.partial_cmp(&other.end),
                ordering => ordering,
            }
        } else {
            None
        }
    }
}

impl<I: Input> Ord for Span<I> {
    fn cmp(&self, other: &Span<I>) -> Ordering {
        self.partial_cmp(other).expect(
            "cannot compare spans from different inputs",
        )
    }
}

impl<'a, I: Input> Hash for Span<I> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (&*self.input as *const I).hash(state);
        self.start.hash(state);
        self.end.hash(state);
    }
}
<|endoftext|>


<commit_before>//! Functions and structures for interacting with cubic shells.

use range_abs::range_abs;
use std::cmp::{min, max};
use std::iter::range_inclusive;
use block_position::BlockPosition;

#[cfg(test)]
use std::collections::HashSet;
#[cfg(test)]
use nalgebra::Vec3;
#[cfg(test)]
use test::Bencher;

#[inline]
// TODO: This should return an iterator.
/// Generate the set of points corresponding to the surface of a cube made of voxels.
pub fn cube_shell(center: &BlockPosition, radius: i32) -> Vec<BlockPosition> {
  let mut shell = Vec::new();

  if radius == 0 {
    shell.push(*center);
    return shell;
  }

  macro_rules! add_square(
    ($dxs: expr, $dys: expr, $dzs: expr) => (
      for dx in $dxs {
        for dy in $dys {
          for dz in $dzs {
            shell.push(BlockPosition::new(
              center.as_pnt().x + dx,
              center.as_pnt().y + dy,
              center.as_pnt().z + dz,
            ));
          }
        }
      }
    );
  );

  add_square!(
    [-radius, radius].iter().cloned(),
    range_abs(radius),
    range_abs(radius)
  );
  add_square!(
    range_abs(radius - 1),
    [-radius, radius].iter().cloned(),
    range_abs(radius)
  );
  add_square!(
    range_abs(radius - 1),
    range_abs(radius - 1),
    [-radius, radius].iter().cloned()
  );

  shell
}

// TODO: This should return an iterator.
// TODO: This should diff cube_shells instead of whole cubes.
/// Return the blocks that are present in a cube of radius `radius` centered at `from`,
/// but not in one centered at `to`.
pub fn cube_diff(
  from: &BlockPosition,
  to: &BlockPosition,
  radius: i32,
) -> Vec<BlockPosition> {
  let mut ret = Vec::new();

  macro_rules! add_square(
    ($xs: expr, $ys: expr, $zs: expr) => (
      for x in $xs {
        for y in $ys {
          for z in $zs {
            ret.push(BlockPosition::new(x, y, z));
          }
        }
      }
    );
  );

  let from = *from.as_pnt();
  let to = *to.as_pnt();

  add_square!(
    if from.x < to.x {
      range_inclusive(from.x - radius, min(from.x + radius, to.x - radius - 1))
    } else {
      range_inclusive(max(from.x - radius, to.x + radius + 1), from.x + radius)
    },
    range_inclusive(from.y - radius, from.y + radius),
    range_inclusive(from.z - radius, from.z + radius)
  );

  add_square!(
    if from.x < to.x {
      range_inclusive(to.x - radius, from.x + radius)
    } else {
      range_inclusive(from.x - radius, to.x + radius)
    },
    if from.y < to.y {
      range_inclusive(from.y - radius, min(from.y + radius, to.y - radius - 1))
    } else {
      range_inclusive(max(from.y - radius, to.y + radius + 1), from.y + radius)
    },
    range_inclusive(from.z - radius, from.z + radius)
  );

  add_square!(
    if from.x < to.x {
      range_inclusive(to.x - radius, from.x + radius)
    } else {
      range_inclusive(from.x - radius, to.x + radius)
    },
    if from.y < to.y {
      range_inclusive(to.y - radius, from.y + radius)
    } else {
      range_inclusive(from.y - radius, to.y + radius)
    },
    if from.z < to.z {
      range_inclusive(from.z - radius, min(from.z + radius, to.z - radius - 1))
    } else {
      range_inclusive(max(from.z - radius, to.z + radius + 1), from.z + radius)
    }
  );

  ret
}

#[cfg(test)]
/// the number of elements in a cube shell
pub fn cube_shell_area(radius: i32) -> u32 {
  assert!(radius >= 0);
  if radius == 0 {
    return 1;
  }

  let width = 2 * radius + 1;
  // volume of the cube
  let outer = width * width * width;
  // volume of the cube of radius r - 1.
  let inner = (width - 2) * (width - 2) * (width - 2);
  (outer - inner) as u32
}

#[test]
// Check that the surface area is correct for a few different shells.
fn test_surface_area() {
  let centers = [
    BlockPosition::new(0, 0, 0),
    BlockPosition::new(2, 1, -4),
  ];
  for center in centers.iter() {
    for radius in range(1, 5) {
      assert_eq!(
        cube_shell(center, radius as i32).len() as u32,
        cube_shell_area(radius)
      );
    }
  }
}

#[test]
fn test_simple_shell() {
  let center = BlockPosition::new(2, 0, -3);
  let radius = 1;

  let expected: HashSet<BlockPosition> = [
      BlockPosition::new( 0,  0,  1),
      BlockPosition::new( 0,  0, -1),
      BlockPosition::new( 0,  1,  0),
      BlockPosition::new( 0,  1,  1),
      BlockPosition::new( 0,  1, -1),
      BlockPosition::new( 0, -1,  0),
      BlockPosition::new( 0, -1,  1),
      BlockPosition::new( 0, -1, -1),
      BlockPosition::new( 1,  0,  0),
      BlockPosition::new( 1,  0,  1),
      BlockPosition::new( 1,  0, -1),
      BlockPosition::new( 1,  1,  0),
      BlockPosition::new( 1,  1,  1),
      BlockPosition::new( 1,  1, -1),
      BlockPosition::new( 1, -1,  0),
      BlockPosition::new( 1, -1,  1),
      BlockPosition::new( 1, -1, -1),
      BlockPosition::new(-1,  0,  0),
      BlockPosition::new(-1,  0,  1),
      BlockPosition::new(-1,  0, -1),
      BlockPosition::new(-1,  1,  0),
      BlockPosition::new(-1,  1,  1),
      BlockPosition::new(-1,  1, -1),
      BlockPosition::new(-1, -1,  0),
      BlockPosition::new(-1, -1,  1),
      BlockPosition::new(-1, -1, -1),
    ]
    .iter()
    .map(|p| p.clone() + center.as_pnt().to_vec())
    .collect();

  let actual = cube_shell(&center, radius);
  assert_eq!(expected, actual.into_iter().collect());
}

#[test]
fn test_shell_no_dups() {
  let center = BlockPosition::new(2, 0, -3);
  let radius = 2;
  let expected = cube_shell(&center, radius);
  let actual: HashSet<BlockPosition> = expected.iter().map(|&x| x).collect();
  assert_eq!(expected.len(), actual.len());
}

#[bench]
fn simple_bench(_: &mut Bencher) {
  let _ = cube_shell(&BlockPosition::new(0, 0, 0), 100);
}

#[test]
fn test_simple_diff() {
  let from = BlockPosition::new(2, 0, -3);
  let to = from + Vec3::new(-1, 2, 0);
  let radius = 1;

  let expected: HashSet<BlockPosition> = [
      BlockPosition::new( 1,  0,  0),
      BlockPosition::new( 1,  1,  0),
      BlockPosition::new( 1, -1,  0),
      BlockPosition::new( 1,  0,  1),
      BlockPosition::new( 1,  1,  1),
      BlockPosition::new( 1, -1,  1),
      BlockPosition::new( 1,  0, -1),
      BlockPosition::new( 1,  1, -1),
      BlockPosition::new( 1, -1, -1),

      BlockPosition::new( 0, -1,  0),
      BlockPosition::new( 0,  0,  0),
      BlockPosition::new( 1, -1,  0),
      BlockPosition::new( 1,  0,  0),
      BlockPosition::new(-1, -1,  0),
      BlockPosition::new(-1,  0,  0),
      BlockPosition::new( 0, -1, -1),
      BlockPosition::new( 0,  0, -1),
      BlockPosition::new( 1, -1, -1),
      BlockPosition::new( 1,  0, -1),
      BlockPosition::new(-1, -1, -1),
      BlockPosition::new(-1,  0, -1),
      BlockPosition::new( 0, -1,  1),
      BlockPosition::new( 0,  0,  1),
      BlockPosition::new( 1, -1,  1),
      BlockPosition::new( 1,  0,  1),
      BlockPosition::new(-1, -1,  1),
      BlockPosition::new(-1,  0,  1),
    ]
    .iter()
    .map(|p| p.clone() + from.as_pnt().to_vec())
    .collect();

  let actual = cube_diff(&from, &to, radius);
  assert_eq!(expected, actual.into_iter().collect());
}

#[test]
fn test_diff_no_dups() {
  let from = BlockPosition::new(2, 0, -3);
  let to = from + Vec3::new(-1, 2, 0);
  let radius = 2;

  let expected = cube_diff(&from, &to, radius);
  let actual: HashSet<BlockPosition> = expected.iter().map(|&x| x).collect();
  assert_eq!(expected.len(), actual.len());
}

#[test]
fn test_no_diff() {
  let center = BlockPosition::new(-5, 1, -7);
  let radius = 3;
  assert!(cube_diff(&center, &center, radius).is_empty());
}
<commit_msg>Fix `common` tests and add a bench for `cube_diff`.<commit_after>//! Functions and structures for interacting with cubic shells.

use range_abs::range_abs;
use std::cmp::{min, max};
use std::iter::range_inclusive;
use block_position::BlockPosition;

#[cfg(test)]
use std::collections::HashSet;
#[cfg(test)]
use nalgebra::Vec3;
#[cfg(test)]
use test::{Bencher, black_box};

#[inline]
// TODO: This should return an iterator.
/// Generate the set of points corresponding to the surface of a cube made of voxels.
pub fn cube_shell(center: &BlockPosition, radius: i32) -> Vec<BlockPosition> {
  let mut shell = Vec::new();

  if radius == 0 {
    shell.push(*center);
    return shell;
  }

  macro_rules! add_square(
    ($dxs: expr, $dys: expr, $dzs: expr) => (
      for dx in $dxs {
        for dy in $dys {
          for dz in $dzs {
            shell.push(BlockPosition::new(
              center.as_pnt().x + dx,
              center.as_pnt().y + dy,
              center.as_pnt().z + dz,
            ));
          }
        }
      }
    );
  );

  add_square!(
    [-radius, radius].iter().cloned(),
    range_abs(radius),
    range_abs(radius)
  );
  add_square!(
    range_abs(radius - 1),
    [-radius, radius].iter().cloned(),
    range_abs(radius)
  );
  add_square!(
    range_abs(radius - 1),
    range_abs(radius - 1),
    [-radius, radius].iter().cloned()
  );

  shell
}

// TODO: This should return an iterator.
// TODO: This should diff cube_shells instead of whole cubes.
/// Return the blocks that are present in a cube of radius `radius` centered at `from`,
/// but not in one centered at `to`.
pub fn cube_diff(
  from: &BlockPosition,
  to: &BlockPosition,
  radius: i32,
) -> Vec<BlockPosition> {
  let mut ret = Vec::new();

  macro_rules! add_square(
    ($xs: expr, $ys: expr, $zs: expr) => (
      for x in $xs {
        for y in $ys {
          for z in $zs {
            ret.push(BlockPosition::new(x, y, z));
          }
        }
      }
    );
  );

  let from = *from.as_pnt();
  let to = *to.as_pnt();

  add_square!(
    if from.x < to.x {
      range_inclusive(from.x - radius, min(from.x + radius, to.x - radius - 1))
    } else {
      range_inclusive(max(from.x - radius, to.x + radius + 1), from.x + radius)
    },
    range_inclusive(from.y - radius, from.y + radius),
    range_inclusive(from.z - radius, from.z + radius)
  );

  add_square!(
    if from.x < to.x {
      range_inclusive(to.x - radius, from.x + radius)
    } else {
      range_inclusive(from.x - radius, to.x + radius)
    },
    if from.y < to.y {
      range_inclusive(from.y - radius, min(from.y + radius, to.y - radius - 1))
    } else {
      range_inclusive(max(from.y - radius, to.y + radius + 1), from.y + radius)
    },
    range_inclusive(from.z - radius, from.z + radius)
  );

  add_square!(
    if from.x < to.x {
      range_inclusive(to.x - radius, from.x + radius)
    } else {
      range_inclusive(from.x - radius, to.x + radius)
    },
    if from.y < to.y {
      range_inclusive(to.y - radius, from.y + radius)
    } else {
      range_inclusive(from.y - radius, to.y + radius)
    },
    if from.z < to.z {
      range_inclusive(from.z - radius, min(from.z + radius, to.z - radius - 1))
    } else {
      range_inclusive(max(from.z - radius, to.z + radius + 1), from.z + radius)
    }
  );

  ret
}

#[cfg(test)]
/// the number of elements in a cube shell
pub fn cube_shell_area(radius: i32) -> u32 {
  assert!(radius >= 0);
  if radius == 0 {
    return 1;
  }

  let width = 2 * radius + 1;
  // volume of the cube
  let outer = width * width * width;
  // volume of the cube of radius r - 1.
  let inner = (width - 2) * (width - 2) * (width - 2);
  (outer - inner) as u32
}

#[test]
// Check that the surface area is correct for a few different shells.
fn test_surface_area() {
  let centers = [
    BlockPosition::new(0, 0, 0),
    BlockPosition::new(2, 1, -4),
  ];
  for center in centers.iter() {
    for radius in range(1, 5) {
      assert_eq!(
        cube_shell(center, radius as i32).len() as u32,
        cube_shell_area(radius)
      );
    }
  }
}

#[test]
fn test_simple_shell() {
  let center = BlockPosition::new(2, 0, -3);
  let radius = 1;

  let expected: HashSet<BlockPosition> = [
      BlockPosition::new( 0,  0,  1),
      BlockPosition::new( 0,  0, -1),
      BlockPosition::new( 0,  1,  0),
      BlockPosition::new( 0,  1,  1),
      BlockPosition::new( 0,  1, -1),
      BlockPosition::new( 0, -1,  0),
      BlockPosition::new( 0, -1,  1),
      BlockPosition::new( 0, -1, -1),
      BlockPosition::new( 1,  0,  0),
      BlockPosition::new( 1,  0,  1),
      BlockPosition::new( 1,  0, -1),
      BlockPosition::new( 1,  1,  0),
      BlockPosition::new( 1,  1,  1),
      BlockPosition::new( 1,  1, -1),
      BlockPosition::new( 1, -1,  0),
      BlockPosition::new( 1, -1,  1),
      BlockPosition::new( 1, -1, -1),
      BlockPosition::new(-1,  0,  0),
      BlockPosition::new(-1,  0,  1),
      BlockPosition::new(-1,  0, -1),
      BlockPosition::new(-1,  1,  0),
      BlockPosition::new(-1,  1,  1),
      BlockPosition::new(-1,  1, -1),
      BlockPosition::new(-1, -1,  0),
      BlockPosition::new(-1, -1,  1),
      BlockPosition::new(-1, -1, -1),
    ]
    .iter()
    .map(|p| p.clone() + center.as_pnt().to_vec())
    .collect();

  let actual = cube_shell(&center, radius);
  assert_eq!(expected, actual.into_iter().collect());
}

#[test]
fn test_shell_no_dups() {
  let center = BlockPosition::new(2, 0, -3);
  let radius = 2;
  let expected = cube_shell(&center, radius);
  let actual: HashSet<BlockPosition> = expected.iter().map(|&x| x).collect();
  assert_eq!(expected.len(), actual.len());
}

#[test]
fn test_simple_diff() {
  let from = BlockPosition::new(2, 0, -3);
  let to = from + Vec3::new(-1, 2, 0);
  let radius = 1;

  let expected: HashSet<BlockPosition> = [
      BlockPosition::new( 1,  0,  0),
      BlockPosition::new( 1,  1,  0),
      BlockPosition::new( 1, -1,  0),
      BlockPosition::new( 1,  0,  1),
      BlockPosition::new( 1,  1,  1),
      BlockPosition::new( 1, -1,  1),
      BlockPosition::new( 1,  0, -1),
      BlockPosition::new( 1,  1, -1),
      BlockPosition::new( 1, -1, -1),

      BlockPosition::new( 0, -1,  0),
      BlockPosition::new( 0,  0,  0),
      BlockPosition::new( 1, -1,  0),
      BlockPosition::new( 1,  0,  0),
      BlockPosition::new(-1, -1,  0),
      BlockPosition::new(-1,  0,  0),
      BlockPosition::new( 0, -1, -1),
      BlockPosition::new( 0,  0, -1),
      BlockPosition::new( 1, -1, -1),
      BlockPosition::new( 1,  0, -1),
      BlockPosition::new(-1, -1, -1),
      BlockPosition::new(-1,  0, -1),
      BlockPosition::new( 0, -1,  1),
      BlockPosition::new( 0,  0,  1),
      BlockPosition::new( 1, -1,  1),
      BlockPosition::new( 1,  0,  1),
      BlockPosition::new(-1, -1,  1),
      BlockPosition::new(-1,  0,  1),
    ]
    .iter()
    .map(|p| p.clone() + from.as_pnt().to_vec())
    .collect();

  let actual = cube_diff(&from, &to, radius);
  assert_eq!(expected, actual.into_iter().collect());
}

#[test]
fn test_diff_no_dups() {
  let from = BlockPosition::new(2, 0, -3);
  let to = from + Vec3::new(-1, 2, 0);
  let radius = 2;

  let expected = cube_diff(&from, &to, radius);
  let actual: HashSet<BlockPosition> = expected.iter().map(|&x| x).collect();
  assert_eq!(expected.len(), actual.len());
}

#[test]
fn test_no_diff() {
  let center = BlockPosition::new(-5, 1, -7);
  let radius = 3;
  assert!(cube_diff(&center, &center, radius).is_empty());
}

#[bench]
fn simple_shell_bench(_: &mut Bencher) {
  black_box(cube_shell(&BlockPosition::new(0, 0, 0), 400));
}

#[bench]
fn simple_diff_bench(_: &mut Bencher) {
  black_box(cube_diff(&BlockPosition::new(-1, 1, -1), &BlockPosition::new(0, 0, 0), 400));
}
<|endoftext|>