<commit_before>use std::io::Result;
use std::io::Error;
use std::io::ErrorKind::Other;
use std::collections::btree_map::BTreeMap;

use uuid::Uuid;
use time::now;
use nix;

use lv::{LV, Segment};
use pv::PV;
use parser::{LvmTextMap, Entry};
use pvlabel::MDA;

#[derive(Debug, PartialEq, Clone)]
pub struct VG {
    pub name: String,
    pub id: String,
    pub seqno: u64,
    pub format: String,
    pub status: Vec<String>,
    pub flags: Vec<String>,
    pub extent_size: u64,
    pub max_lv: u64,
    pub max_pv: u64,
    pub metadata_copies: u64,
    pub pvs: BTreeMap<String, PV>,
    pub lvs: BTreeMap<String, LV>,
}

impl VG {
    pub fn extents_in_use(&self) -> u64 {
        self.lvs
            .values()
            .map(|x| x.used_extents())
            .sum()
    }

    pub fn extents_free(&self) -> u64 {
        self.extents() - self.extents_in_use()
    }

    pub fn extents(&self) -> u64 {
        self.pvs
            .values()
            .map(|x| x.pe_count)
            .sum()
    }

    pub fn new_linear_lv(&mut self, name: &str, extent_size: u64) -> Result<()> {
        if self.lvs.contains_key(name) {
            return Err(Error::new(Other, "LV already exists"));
        }

        let mut contig_area = None;
        for (pvname, areas) in self.free_areas() {
            for (start, len) in areas {
                if len >= extent_size {
                    contig_area = Some((pvname, start));
                    break;
                }
            }
        }

        // we don't support multiple segments yet
        let (pv_with_area, area_start) = match contig_area {
            None => return Err(Error::new(Other, "no contiguous area for new LV")),
            Some(x) => x,
        };

        let segment = Segment {
            name: "segment1".to_string(),
            start_extent: 0,
            extent_count: extent_size,
            ty: "striped".to_string(),
            stripes: vec![(pv_with_area, area_start)],
        };

        let lv = LV {
            name: name.to_string(),
            id: Uuid::new_v4().to_hyphenated_string(),
            status: vec!["READ".to_string(),
                         "WRITE".to_string(),
                         "VISIBLE".to_string()],
            flags: Vec::new(),
            creation_host: nix::sys::utsname::uname().nodename().to_string(),
            creation_time: now().to_timespec().sec,
            segments: vec![segment],
        };

        self.lvs.insert(name.to_string(), lv);

        for (name, pv) in &self.pvs {
            if let Some(path) = pv.device.path() {
                let mut mda = MDA::new(&path).expect("could not open MDA");

                mda.write_metadata(&self.clone().into());
            }
        }

        // tell lvmetad
        // poke dm and tell it about a new device
        // open champagne

        Ok(())
    }

    pub fn used_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut used_map = BTreeMap::new();

        // pretty sure this is only correct for my system...
        for lv in self.lvs.values() {
            for seg in &lv.segments {
                for &(ref pvname, start) in &seg.stripes {
                    used_map.entry(pvname.to_string())
                        .or_insert(BTreeMap::new())
                        .insert(start as u64, seg.extent_count);
                }
            }
        }

        used_map
    }

    pub fn free_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut free_map = BTreeMap::new();

        for (pvname, area_map) in &mut self.used_areas() {

            // Insert an entry to mark the end of the PV so the fold works
            // correctly
            let pv = self.pvs.get(pvname)
                .expect("area map name refers to nonexistent PV");
            area_map.insert(pv.pe_count, 0);

            area_map.iter()
                .fold(0, |prev_end, (start, len)| {
                    if prev_end < *start {
                        free_map.entry(pvname.clone())
                            .or_insert(BTreeMap::new())
                            .insert(prev_end, start-prev_end);
                    }
                    start + len
                });
        }

        free_map
    }
}

impl From<VG> for LvmTextMap {
    fn from(vg: VG) -> Self {
        let mut map = LvmTextMap::new();

        map.insert("id".to_string(), Entry::String(vg.id));
        map.insert("seqno".to_string(),
                   Entry::Number(vg.seqno as i64 + 1));
        map.insert("format".to_string(), Entry::String(vg.format));

        map.insert("max_pv".to_string(), Entry::Number(0));
        map.insert("max_lv".to_string(), Entry::Number(0));

        map.insert("status".to_string(),
                   Entry::List(
                       Box::new(
                           vg.status
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("flags".to_string(),
                   Entry::List(
                       Box::new(
                           vg.flags
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("extent_size".to_string(),
                   Entry::Number(vg.extent_size as i64));
        map.insert("metadata_copies".to_string(),
                   Entry::Number(vg.metadata_copies as i64));
        map.insert("physical_volumes".to_string(),
                   Entry::TextMap(
                       Box::new(
                           vg.pvs
                               .into_iter()
                               .map(|(k, v)|
                                    (k, Entry::TextMap(Box::new(v.into()))))
                               .collect())));

        map.insert("logical_volumes".to_string(),
                   Entry::TextMap(
                       Box::new(
                           vg.lvs
                               .into_iter()
                               .map(|(k, v)|
                                    (k, Entry::TextMap(Box::new(v.into()))))
                               .collect())));

        let mut outer_map = LvmTextMap::new();

        outer_map.insert(vg.name, Entry::TextMap(Box::new(map)));

        outer_map
    }
}
<commit_msg>vg: reuse map in new_linear_lv()<commit_after>use std::io::Result;
use std::io::Error;
use std::io::ErrorKind::Other;
use std::collections::btree_map::BTreeMap;

use uuid::Uuid;
use time::now;
use nix;

use lv::{LV, Segment};
use pv::PV;
use parser::{LvmTextMap, Entry};
use pvlabel::MDA;
use lvmetad::vg_update_lvmetad;

#[derive(Debug, PartialEq, Clone)]
pub struct VG {
    pub name: String,
    pub id: String,
    pub seqno: u64,
    pub format: String,
    pub status: Vec<String>,
    pub flags: Vec<String>,
    pub extent_size: u64,
    pub max_lv: u64,
    pub max_pv: u64,
    pub metadata_copies: u64,
    pub pvs: BTreeMap<String, PV>,
    pub lvs: BTreeMap<String, LV>,
}

impl VG {
    pub fn extents_in_use(&self) -> u64 {
        self.lvs
            .values()
            .map(|x| x.used_extents())
            .sum()
    }

    pub fn extents_free(&self) -> u64 {
        self.extents() - self.extents_in_use()
    }

    pub fn extents(&self) -> u64 {
        self.pvs
            .values()
            .map(|x| x.pe_count)
            .sum()
    }

    pub fn new_linear_lv(&mut self, name: &str, extent_size: u64) -> Result<()> {
        if self.lvs.contains_key(name) {
            return Err(Error::new(Other, "LV already exists"));
        }

        let mut contig_area = None;
        for (pvname, areas) in self.free_areas() {
            for (start, len) in areas {
                if len >= extent_size {
                    contig_area = Some((pvname, start));
                    break;
                }
            }
        }

        // we don't support multiple segments yet
        let (pv_with_area, area_start) = match contig_area {
            None => return Err(Error::new(Other, "no contiguous area for new LV")),
            Some(x) => x,
        };

        let segment = Segment {
            name: "segment1".to_string(),
            start_extent: 0,
            extent_count: extent_size,
            ty: "striped".to_string(),
            stripes: vec![(pv_with_area, area_start)],
        };

        let lv = LV {
            name: name.to_string(),
            id: Uuid::new_v4().to_hyphenated_string(),
            status: vec!["READ".to_string(),
                         "WRITE".to_string(),
                         "VISIBLE".to_string()],
            flags: Vec::new(),
            creation_host: nix::sys::utsname::uname().nodename().to_string(),
            creation_time: now().to_timespec().sec,
            segments: vec![segment],
        };

        self.lvs.insert(name.to_string(), lv);

        let map = self.clone().into();

        // TODO: atomicity of updating pvs, metad, dm
        for (name, pv) in &self.pvs {
            if let Some(path) = pv.device.path() {
                let mut mda = MDA::new(&path).expect("could not open MDA");

                mda.write_metadata(&map);
            }
        }

        try!(vg_update_lvmetad(&map));

        // tell lvmetad
        // poke dm and tell it about a new device
        // open champagne

        Ok(())
    }

    pub fn used_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut used_map = BTreeMap::new();

        // pretty sure this is only correct for my system...
        for lv in self.lvs.values() {
            for seg in &lv.segments {
                for &(ref pvname, start) in &seg.stripes {
                    used_map.entry(pvname.to_string())
                        .or_insert(BTreeMap::new())
                        .insert(start as u64, seg.extent_count);
                }
            }
        }

        used_map
    }

    pub fn free_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut free_map = BTreeMap::new();

        for (pvname, area_map) in &mut self.used_areas() {

            // Insert an entry to mark the end of the PV so the fold works
            // correctly
            let pv = self.pvs.get(pvname)
                .expect("area map name refers to nonexistent PV");
            area_map.insert(pv.pe_count, 0);

            area_map.iter()
                .fold(0, |prev_end, (start, len)| {
                    if prev_end < *start {
                        free_map.entry(pvname.clone())
                            .or_insert(BTreeMap::new())
                            .insert(prev_end, start-prev_end);
                    }
                    start + len
                });
        }

        free_map
    }
}

impl From<VG> for LvmTextMap {
    fn from(vg: VG) -> Self {
        let mut map = LvmTextMap::new();

        map.insert("id".to_string(), Entry::String(vg.id));
        map.insert("seqno".to_string(),
                   Entry::Number(vg.seqno as i64 + 1));
        map.insert("format".to_string(), Entry::String(vg.format));

        map.insert("max_pv".to_string(), Entry::Number(0));
        map.insert("max_lv".to_string(), Entry::Number(0));

        map.insert("status".to_string(),
                   Entry::List(
                       Box::new(
                           vg.status
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("flags".to_string(),
                   Entry::List(
                       Box::new(
                           vg.flags
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("extent_size".to_string(),
                   Entry::Number(vg.extent_size as i64));
        map.insert("metadata_copies".to_string(),
                   Entry::Number(vg.metadata_copies as i64));
        map.insert("physical_volumes".to_string(),
                   Entry::TextMap(
                       Box::new(
                           vg.pvs
                               .into_iter()
                               .map(|(k, v)|
                                    (k, Entry::TextMap(Box::new(v.into()))))
                               .collect())));

        map.insert("logical_volumes".to_string(),
                   Entry::TextMap(
                       Box::new(
                           vg.lvs
                               .into_iter()
                               .map(|(k, v)|
                                    (k, Entry::TextMap(Box::new(v.into()))))
                               .collect())));

        let mut outer_map = LvmTextMap::new();

        outer_map.insert(vg.name, Entry::TextMap(Box::new(map)));

        outer_map
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::ffi::CString;
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::mpsc::sync_channel;
use std::sync::Once;
use std::thread;
use std::time::Duration;
use std::{env, process::Child};
use std::{fs::File, process::Stdio};

use anyhow::{anyhow, Result};
use base::syslog;
use tempfile::TempDir;

const PREBUILT_URL: &str = "https://storage.googleapis.com/chromeos-localmirror/distfiles";

#[cfg(target_arch = "x86_64")]
const ARCH: &str = "x86_64";
#[cfg(target_arch = "arm")]
const ARCH: &str = "arm";
#[cfg(target_arch = "aarch64")]
const ARCH: &str = "aarch64";

/// Timeout for communicating with the VM. If we do not hear back, panic so we
/// do not block the tests.
const VM_COMMUNICATION_TIMEOUT: Duration = Duration::from_secs(10);

fn prebuilt_version() -> &'static str {
    include_str!("../guest_under_test/PREBUILT_VERSION").trim()
}

fn kernel_prebuilt_url() -> String {
    format!(
        "{}/crosvm-testing-bzimage-{}-{}",
        PREBUILT_URL,
        ARCH,
        prebuilt_version()
    )
}

fn rootfs_prebuilt_url() -> String {
    format!(
        "{}/crosvm-testing-rootfs-{}-{}",
        PREBUILT_URL,
        ARCH,
        prebuilt_version()
    )
}

/// The kernel bzImage is stored next to the test executable, unless overridden by
/// CROSVM_CARGO_TEST_KERNEL_BINARY
fn kernel_path() -> PathBuf {
    match env::var("CROSVM_CARGO_TEST_KERNEL_BINARY") {
        Ok(value) => PathBuf::from(value),
        Err(_) => env::current_exe()
            .unwrap()
            .parent()
            .unwrap()
            .join("bzImage"),
    }
}

/// The rootfs image is stored next to the test executable, unless overridden by
/// CROSVM_CARGO_TEST_ROOTFS_IMAGE
fn rootfs_path() -> PathBuf {
    match env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE") {
        Ok(value) => PathBuf::from(value),
        Err(_) => env::current_exe().unwrap().parent().unwrap().join("rootfs"),
    }
}

/// The crosvm binary is expected to be alongside to the integration tests
/// binary. Alternatively in the parent directory (cargo will put the
/// test binary in target/debug/deps/ but the crosvm binary in target/debug).
fn find_crosvm_binary() -> PathBuf {
    let exe_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf();
    let first = exe_dir.join("crosvm");
    if first.exists() {
        return first;
    }
    let second = exe_dir.parent().unwrap().join("crosvm");
    if second.exists() {
        return second;
    }
    panic!("Cannot find ./crosvm or ../crosvm alongside test binary.");
}

/// Safe wrapper for libc::mkfifo
fn mkfifo(path: &Path) -> io::Result<()> {
    let cpath = CString::new(path.to_str().unwrap()).unwrap();
    let result = unsafe { libc::mkfifo(cpath.as_ptr(), 0o777) };
    if result == 0 {
        Ok(())
    } else {
        Err(io::Error::last_os_error())
    }
}

/// Run the provided closure, but panic if it does not complete until the timeout has passed.
/// We should panic here, as we cannot gracefully stop the closure from running.
fn panic_on_timeout<F, U>(closure: F, timeout: Duration) -> U
where
    F: FnOnce() -> U + Send + 'static,
    U: Send + 'static,
{
    let (tx, rx) = sync_channel::<()>(1);
    let handle = thread::spawn(move || {
        let result = closure();
        tx.send(()).unwrap();
        result
    });
    rx.recv_timeout(timeout)
        .expect("Operation timed out or closure paniced.");
    handle.join().unwrap()
}

fn download_file(url: &str, destination: &Path) -> Result<()> {
    let status = Command::new("curl")
        .arg("--fail")
        .arg("--location")
        .args(&["--output", destination.to_str().unwrap()])
        .arg(url)
        .status();
    match status {
        Ok(exit_code) => {
            if !exit_code.success() {
                Err(anyhow!("Cannot download {}", url))
            } else {
                Ok(())
            }
        }
        Err(error) => Err(anyhow!(error)),
    }
}

fn crosvm_command(command: &str, args: &[&str]) -> Result<()> {
    println!("$ crosvm {} {:?}", command, &args.join(" "));
    let status = Command::new(find_crosvm_binary())
        .arg(command)
        .args(args)
        .status()?;

    if !status.success() {
        Err(anyhow!("Command failed with exit code {}", status))
    } else {
        Ok(())
    }
}

/// Test fixture to spin up a VM running a guest that can be communicated with.
///
/// After creation, commands can be sent via exec_in_guest. The VM is stopped
/// when this instance is dropped.
pub struct TestVm {
    /// Maintain ownership of test_dir until the vm is destroyed.
    #[allow(dead_code)]
    test_dir: TempDir,
    from_guest_reader: BufReader<File>,
    to_guest: File,
    control_socket_path: PathBuf,
    process: Child,
    debug: bool,
}

impl TestVm {
    /// Magic line sent by the delegate binary when the guest is ready.
    const MAGIC_LINE: &'static str = "\x05Ready";

    /// Downloads prebuilts if needed.
    fn initialize_once() {
        syslog::init().unwrap();

        // It's possible the prebuilts downloaded by crosvm-9999.ebuild differ
        // from the version that crosvm was compiled for.
        if let Ok(value) = env::var("CROSVM_CARGO_TEST_PREBUILT_VERSION") {
            if value != prebuilt_version() {
                panic!(
                    "Environment provided prebuilts are version {}, but crosvm was compiled \
                    for prebuilt version {}. Did you update PREBUILT_VERSION everywhere?",
                    value,
                    prebuilt_version()
                );
            }
        }

        let kernel_path = kernel_path();
        if env::var("CROSVM_CARGO_TEST_KERNEL_BINARY").is_err() {
            if !kernel_path.exists() {
                println!("Downloading kernel prebuilt:");
                download_file(&kernel_prebuilt_url(), &kernel_path).unwrap();
            }
        }
        assert!(kernel_path.exists(), "{:?} does not exist", kernel_path);

        let rootfs_path = rootfs_path();
        if env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE").is_err() {
            if !rootfs_path.exists() {
                println!("Downloading rootfs prebuilt:");
                download_file(&rootfs_prebuilt_url(), &rootfs_path).unwrap();
            }
        }
        assert!(rootfs_path.exists(), "{:?} does not exist", rootfs_path);
    }

    // Adds 2 serial devices:
    // - ttyS0: Console device which prints kernel log / debug output of the
    //          delegate binary.
    // - ttyS1: Serial device attached to the named pipes.
    fn configure_serial_devices(
        command: &mut Command,
        from_guest_pipe: &Path,
        to_guest_pipe: &Path,
    ) {
        command.args(&["--serial", "type=syslog"]);

        // Setup channel for communication with the delegate.
        let serial_params = format!(
            "type=file,path={},input={},num=2",
            from_guest_pipe.display(),
            to_guest_pipe.display()
        );
        command.args(&["--serial", &serial_params]);
    }

    /// Configures the VM kernel and rootfs to load from the guest_under_test assets.
    fn configure_kernel(command: &mut Command) {
        command
            .args(&["--root", rootfs_path().to_str().unwrap()])
            .args(&["--params", "init=/bin/delegate"])
            .arg(kernel_path());
    }

    /// Instanciate a new crosvm instance. The first call will trigger the download of prebuilt
    /// files if necessary.
    pub fn new(additional_arguments: &[&str], debug: bool) -> Result<TestVm> {
        static PREP_ONCE: Once = Once::new();
        PREP_ONCE.call_once(|| TestVm::initialize_once());

        // Create two named pipes to communicate with the guest.
        let test_dir = TempDir::new()?;
        let from_guest_pipe = test_dir.path().join("from_guest");
        let to_guest_pipe = test_dir.path().join("to_guest");
        mkfifo(&from_guest_pipe)?;
        mkfifo(&to_guest_pipe)?;

        let control_socket_path = test_dir.path().join("control");

        let mut command = Command::new(find_crosvm_binary());
        command.args(&["run", "--disable-sandbox"]);
        TestVm::configure_serial_devices(&mut command, &from_guest_pipe, &to_guest_pipe);
        command.args(&["--socket", &control_socket_path.to_str().unwrap()]);
        command.args(additional_arguments);

        TestVm::configure_kernel(&mut command);

        println!("$ {:?}", command);
        if !debug {
            command.stdout(Stdio::null());
            command.stderr(Stdio::null());
        }
        let process = command.spawn()?;

        // Open pipes. Panic if we cannot connect after a timeout.
        let (to_guest, from_guest) = panic_on_timeout(
            move || (File::create(to_guest_pipe), File::open(from_guest_pipe)),
            VM_COMMUNICATION_TIMEOUT,
        );

        // Wait for magic line to be received, indicating the delegate is ready.
        let mut from_guest_reader = BufReader::new(from_guest?);
        let mut magic_line = String::new();
        from_guest_reader.read_line(&mut magic_line)?;
        assert_eq!(magic_line.trim(), TestVm::MAGIC_LINE);

        Ok(TestVm {
            test_dir,
            from_guest_reader,
            to_guest: to_guest?,
            control_socket_path,
            process,
            debug,
        })
    }

    /// Executes the shell command `command` and returns the programs stdout.
    pub fn exec_in_guest(&mut self, command: &str) -> Result<String> {
        // Write command to serial port.
        writeln!(&mut self.to_guest, "{}", command)?;

        // We will receive an echo of what we have written on the pipe.
        let mut echo = String::new();
        self.from_guest_reader.read_line(&mut echo)?;
        assert_eq!(echo.trim(), command);

        // Return all remaining lines until we receive the MAGIC_LINE
        let mut output = String::new();
        loop {
            let mut line = String::new();
            self.from_guest_reader.read_line(&mut line)?;
            if line.trim() == TestVm::MAGIC_LINE {
                break;
            }
            output.push_str(&line);
        }
        let trimmed = output.trim();
        if self.debug {
            println!("<- {:?}", trimmed);
        }
        Ok(trimmed.to_string())
    }

    pub fn stop(&self) -> Result<()> {
        crosvm_command("stop", &[self.control_socket_path.to_str().unwrap()])
    }

    pub fn suspend(&self) -> Result<()> {
        crosvm_command("suspend", &[self.control_socket_path.to_str().unwrap()])
    }

    pub fn resume(&self) -> Result<()> {
        crosvm_command("resume", &[self.control_socket_path.to_str().unwrap()])
    }
}

impl Drop for TestVm {
    fn drop(&mut self) {
        self.stop().unwrap();
        self.process.wait().unwrap();
    }
}
<commit_msg>integration_tests: Show error message passed down instead.<commit_after>// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::ffi::CString;
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::mpsc::sync_channel;
use std::sync::Once;
use std::thread;
use std::time::Duration;
use std::{env, process::Child};
use std::{fs::File, process::Stdio};

use anyhow::{anyhow, Result};
use base::syslog;
use tempfile::TempDir;

const PREBUILT_URL: &str = "https://storage.googleapis.com/chromeos-localmirror/distfiles";

#[cfg(target_arch = "x86_64")]
const ARCH: &str = "x86_64";
#[cfg(target_arch = "arm")]
const ARCH: &str = "arm";
#[cfg(target_arch = "aarch64")]
const ARCH: &str = "aarch64";

/// Timeout for communicating with the VM. If we do not hear back, panic so we
/// do not block the tests.
const VM_COMMUNICATION_TIMEOUT: Duration = Duration::from_secs(10);

fn prebuilt_version() -> &'static str {
    include_str!("../guest_under_test/PREBUILT_VERSION").trim()
}

fn kernel_prebuilt_url() -> String {
    format!(
        "{}/crosvm-testing-bzimage-{}-{}",
        PREBUILT_URL,
        ARCH,
        prebuilt_version()
    )
}

fn rootfs_prebuilt_url() -> String {
    format!(
        "{}/crosvm-testing-rootfs-{}-{}",
        PREBUILT_URL,
        ARCH,
        prebuilt_version()
    )
}

/// The kernel bzImage is stored next to the test executable, unless overridden by
/// CROSVM_CARGO_TEST_KERNEL_BINARY
fn kernel_path() -> PathBuf {
    match env::var("CROSVM_CARGO_TEST_KERNEL_BINARY") {
        Ok(value) => PathBuf::from(value),
        Err(_) => env::current_exe()
            .unwrap()
            .parent()
            .unwrap()
            .join("bzImage"),
    }
}

/// The rootfs image is stored next to the test executable, unless overridden by
/// CROSVM_CARGO_TEST_ROOTFS_IMAGE
fn rootfs_path() -> PathBuf {
    match env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE") {
        Ok(value) => PathBuf::from(value),
        Err(_) => env::current_exe().unwrap().parent().unwrap().join("rootfs"),
    }
}

/// The crosvm binary is expected to be alongside to the integration tests
/// binary. Alternatively in the parent directory (cargo will put the
/// test binary in target/debug/deps/ but the crosvm binary in target/debug).
fn find_crosvm_binary() -> PathBuf {
    let exe_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf();
    let first = exe_dir.join("crosvm");
    if first.exists() {
        return first;
    }
    let second = exe_dir.parent().unwrap().join("crosvm");
    if second.exists() {
        return second;
    }
    panic!("Cannot find ./crosvm or ../crosvm alongside test binary.");
}

/// Safe wrapper for libc::mkfifo
fn mkfifo(path: &Path) -> io::Result<()> {
    let cpath = CString::new(path.to_str().unwrap()).unwrap();
    let result = unsafe { libc::mkfifo(cpath.as_ptr(), 0o777) };
    if result == 0 {
        Ok(())
    } else {
        Err(io::Error::last_os_error())
    }
}

/// Run the provided closure, but panic if it does not complete until the timeout has passed.
/// We should panic here, as we cannot gracefully stop the closure from running.
fn panic_on_timeout<F, U>(closure: F, timeout: Duration) -> U
where
    F: FnOnce() -> U + Send + 'static,
    U: Send + 'static,
{
    let (tx, rx) = sync_channel::<()>(1);
    let handle = thread::spawn(move || {
        let result = closure();
        tx.send(()).unwrap();
        result
    });
    rx.recv_timeout(timeout)
        .expect("Operation timed out or closure paniced.");
    handle.join().unwrap()
}

fn download_file(url: &str, destination: &Path) -> Result<()> {
    let status = Command::new("curl")
        .arg("--fail")
        .arg("--location")
        .args(&["--output", destination.to_str().unwrap()])
        .arg(url)
        .status();
    match status {
        Ok(exit_code) => {
            if !exit_code.success() {
                Err(anyhow!("Cannot download {}", url))
            } else {
                Ok(())
            }
        }
        Err(error) => Err(anyhow!(error)),
    }
}

fn crosvm_command(command: &str, args: &[&str]) -> Result<()> {
    println!("$ crosvm {} {:?}", command, &args.join(" "));
    let status = Command::new(find_crosvm_binary())
        .arg(command)
        .args(args)
        .status()?;

    if !status.success() {
        Err(anyhow!("Command failed with exit code {}", status))
    } else {
        Ok(())
    }
}

/// Test fixture to spin up a VM running a guest that can be communicated with.
///
/// After creation, commands can be sent via exec_in_guest. The VM is stopped
/// when this instance is dropped.
pub struct TestVm {
    /// Maintain ownership of test_dir until the vm is destroyed.
    #[allow(dead_code)]
    test_dir: TempDir,
    from_guest_reader: BufReader<File>,
    to_guest: File,
    control_socket_path: PathBuf,
    process: Child,
    debug: bool,
}

impl TestVm {
    /// Magic line sent by the delegate binary when the guest is ready.
    const MAGIC_LINE: &'static str = "\x05Ready";

    /// Downloads prebuilts if needed.
    fn initialize_once() {
        if let Err(e) = syslog::init() {
            panic!("failed to initiailize syslog: {}", e);
        }

        // It's possible the prebuilts downloaded by crosvm-9999.ebuild differ
        // from the version that crosvm was compiled for.
        if let Ok(value) = env::var("CROSVM_CARGO_TEST_PREBUILT_VERSION") {
            if value != prebuilt_version() {
                panic!(
                    "Environment provided prebuilts are version {}, but crosvm was compiled \
                    for prebuilt version {}. Did you update PREBUILT_VERSION everywhere?",
                    value,
                    prebuilt_version()
                );
            }
        }

        let kernel_path = kernel_path();
        if env::var("CROSVM_CARGO_TEST_KERNEL_BINARY").is_err() {
            if !kernel_path.exists() {
                println!("Downloading kernel prebuilt:");
                download_file(&kernel_prebuilt_url(), &kernel_path).unwrap();
            }
        }
        assert!(kernel_path.exists(), "{:?} does not exist", kernel_path);

        let rootfs_path = rootfs_path();
        if env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE").is_err() {
            if !rootfs_path.exists() {
                println!("Downloading rootfs prebuilt:");
                download_file(&rootfs_prebuilt_url(), &rootfs_path).unwrap();
            }
        }
        assert!(rootfs_path.exists(), "{:?} does not exist", rootfs_path);
    }

    // Adds 2 serial devices:
    // - ttyS0: Console device which prints kernel log / debug output of the
    //          delegate binary.
    // - ttyS1: Serial device attached to the named pipes.
    fn configure_serial_devices(
        command: &mut Command,
        from_guest_pipe: &Path,
        to_guest_pipe: &Path,
    ) {
        command.args(&["--serial", "type=syslog"]);

        // Setup channel for communication with the delegate.
        let serial_params = format!(
            "type=file,path={},input={},num=2",
            from_guest_pipe.display(),
            to_guest_pipe.display()
        );
        command.args(&["--serial", &serial_params]);
    }

    /// Configures the VM kernel and rootfs to load from the guest_under_test assets.
    fn configure_kernel(command: &mut Command) {
        command
            .args(&["--root", rootfs_path().to_str().unwrap()])
            .args(&["--params", "init=/bin/delegate"])
            .arg(kernel_path());
    }

    /// Instanciate a new crosvm instance. The first call will trigger the download of prebuilt
    /// files if necessary.
    pub fn new(additional_arguments: &[&str], debug: bool) -> Result<TestVm> {
        static PREP_ONCE: Once = Once::new();
        PREP_ONCE.call_once(|| TestVm::initialize_once());

        // Create two named pipes to communicate with the guest.
        let test_dir = TempDir::new()?;
        let from_guest_pipe = test_dir.path().join("from_guest");
        let to_guest_pipe = test_dir.path().join("to_guest");
        mkfifo(&from_guest_pipe)?;
        mkfifo(&to_guest_pipe)?;

        let control_socket_path = test_dir.path().join("control");

        let mut command = Command::new(find_crosvm_binary());
        command.args(&["run", "--disable-sandbox"]);
        TestVm::configure_serial_devices(&mut command, &from_guest_pipe, &to_guest_pipe);
        command.args(&["--socket", &control_socket_path.to_str().unwrap()]);
        command.args(additional_arguments);

        TestVm::configure_kernel(&mut command);

        println!("$ {:?}", command);
        if !debug {
            command.stdout(Stdio::null());
            command.stderr(Stdio::null());
        }
        let process = command.spawn()?;

        // Open pipes. Panic if we cannot connect after a timeout.
        let (to_guest, from_guest) = panic_on_timeout(
            move || (File::create(to_guest_pipe), File::open(from_guest_pipe)),
            VM_COMMUNICATION_TIMEOUT,
        );

        // Wait for magic line to be received, indicating the delegate is ready.
        let mut from_guest_reader = BufReader::new(from_guest?);
        let mut magic_line = String::new();
        from_guest_reader.read_line(&mut magic_line)?;
        assert_eq!(magic_line.trim(), TestVm::MAGIC_LINE);

        Ok(TestVm {
            test_dir,
            from_guest_reader,
            to_guest: to_guest?,
            control_socket_path,
            process,
            debug,
        })
    }

    /// Executes the shell command `command` and returns the programs stdout.
    pub fn exec_in_guest(&mut self, command: &str) -> Result<String> {
        // Write command to serial port.
        writeln!(&mut self.to_guest, "{}", command)?;

        // We will receive an echo of what we have written on the pipe.
        let mut echo = String::new();
        self.from_guest_reader.read_line(&mut echo)?;
        assert_eq!(echo.trim(), command);

        // Return all remaining lines until we receive the MAGIC_LINE
        let mut output = String::new();
        loop {
            let mut line = String::new();
            self.from_guest_reader.read_line(&mut line)?;
            if line.trim() == TestVm::MAGIC_LINE {
                break;
            }
            output.push_str(&line);
        }
        let trimmed = output.trim();
        if self.debug {
            println!("<- {:?}", trimmed);
        }
        Ok(trimmed.to_string())
    }

    pub fn stop(&self) -> Result<()> {
        crosvm_command("stop", &[self.control_socket_path.to_str().unwrap()])
    }

    pub fn suspend(&self) -> Result<()> {
        crosvm_command("suspend", &[self.control_socket_path.to_str().unwrap()])
    }

    pub fn resume(&self) -> Result<()> {
        crosvm_command("resume", &[self.control_socket_path.to_str().unwrap()])
    }
}

impl Drop for TestVm {
    fn drop(&mut self) {
        self.stop().unwrap();
        self.process.wait().unwrap();
    }
}
<|endoftext|>


<commit_before>#![crate_name = "objc_test_utils"]
#![crate_type = "lib"]

#![allow(unstable)]

#[link(name = "Foundation", kind = "framework")]
extern { }

/// A block that takes no arguments and returns an integer: `int32_t (^)()`.
#[allow(missing_copy_implementations)]
pub enum IntBlock { }

/// A block that takes one integer argument, adds to it, and returns the sum:
/// `int32_t (^)(int32_t)`.
#[allow(missing_copy_implementations)]
pub enum AddBlock { }

#[link(name="block_utils", kind="static")]
extern {
    /// Returns a pointer to a global `IntBlock` that returns 7.
    pub fn get_int_block() -> *mut IntBlock;
    /// Returns a pointer to a copied `IntBlock` that returns `i`.
    pub fn get_int_block_with(i: i32) -> *mut IntBlock;
    /// Returns a pointer to a global `AddBlock` that returns its argument + 7.
    pub fn get_add_block() -> *mut AddBlock;
    /// Returns a pointer to a copied `AddBlock` that returns its argument + `i`.
    pub fn get_add_block_with(i: i32) -> *mut AddBlock;
    /// Invokes an `IntBlock` and returns its result.
    pub fn invoke_int_block(block: *mut IntBlock) -> i32;
    /// Invokes an `AddBlock` with `a` and returns the result.
    pub fn invoke_add_block(block: *mut AddBlock, a: i32) -> i32;
}

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

    #[test]
    fn test_int_block() {
        unsafe {
            assert!(invoke_int_block(get_int_block()) == 7);
            assert!(invoke_int_block(get_int_block_with(13)) == 13);
        }
    }

    #[test]
    fn test_add_block() {
        unsafe {
            assert!(invoke_add_block(get_add_block(), 5) == 12);
            assert!(invoke_add_block(get_add_block_with(3), 5) == 8);
        }
    }
}
<commit_msg>Removed unnecessary allow(unstable) from test_utils.<commit_after>#![crate_name = "objc_test_utils"]
#![crate_type = "lib"]

#[link(name = "Foundation", kind = "framework")]
extern { }

/// A block that takes no arguments and returns an integer: `int32_t (^)()`.
#[allow(missing_copy_implementations)]
pub enum IntBlock { }

/// A block that takes one integer argument, adds to it, and returns the sum:
/// `int32_t (^)(int32_t)`.
#[allow(missing_copy_implementations)]
pub enum AddBlock { }

#[link(name="block_utils", kind="static")]
extern {
    /// Returns a pointer to a global `IntBlock` that returns 7.
    pub fn get_int_block() -> *mut IntBlock;
    /// Returns a pointer to a copied `IntBlock` that returns `i`.
    pub fn get_int_block_with(i: i32) -> *mut IntBlock;
    /// Returns a pointer to a global `AddBlock` that returns its argument + 7.
    pub fn get_add_block() -> *mut AddBlock;
    /// Returns a pointer to a copied `AddBlock` that returns its argument + `i`.
    pub fn get_add_block_with(i: i32) -> *mut AddBlock;
    /// Invokes an `IntBlock` and returns its result.
    pub fn invoke_int_block(block: *mut IntBlock) -> i32;
    /// Invokes an `AddBlock` with `a` and returns the result.
    pub fn invoke_add_block(block: *mut AddBlock, a: i32) -> i32;
}

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

    #[test]
    fn test_int_block() {
        unsafe {
            assert!(invoke_int_block(get_int_block()) == 7);
            assert!(invoke_int_block(get_int_block_with(13)) == 13);
        }
    }

    #[test]
    fn test_add_block() {
        unsafe {
            assert!(invoke_add_block(get_add_block(), 5) == 12);
            assert!(invoke_add_block(get_add_block_with(3), 5) == 8);
        }
    }
}
<|endoftext|>


<commit_before>use std::path::Path;
use std::process::Command;

fn main() {
    if cfg!(feature = "dynamic_linking") {
        println!("cargo:rustc-link-lib=dylib=clingo");
    } else {
        // build clingo for static linking

        if !Path::new("clingo").exists() {
            Command::new("git")
                .args(&["clone", "https://github.com/sthiele/clingo.git"])
                .status()
                .unwrap();

            Command::new("git")
                .args(&["checkout", "mod_v5.3.0"])
                .current_dir("./clingo")
                .status()
                .unwrap();

            Command::new("git")
                .args(&["submodule", "update", "--init", "--recursive"])
                .current_dir("./clingo")
                .status()
                .unwrap();
        }

        //     if !Path::new("bindings.rs").exists() {
        //         let bindings = bindgen::Builder::default()
        //             .header("clingo/libclingo/clingo.h")
        //             .no_copy("clingo_solve_control")
        //             .no_copy("clingo_model")
        //             .no_copy("clingo_solve_handle")
        //             .no_copy("clingo_program_builder")
        //             .no_copy("clingo_control")
        //             .no_copy("clingo_options")
        //             .no_copy("clingo_symbolic_atoms")
        //             .no_copy("clingo_theory_atoms")
        //             .no_copy("clingo_assignment")
        //             .no_copy("clingo_propagate_init")
        //             .no_copy("clingo_propagate_control")
        //             .no_copy("clingo_backend")
        //             .no_copy("clingo_configuration")
        //             .no_copy("clingo_statistic")
        //             .blacklist_type("max_align_t") // https://github.com/rust-lang/rust-bindgen/issues/550
        //             .generate()
        //             .expect("Unable to generate bindings");
        //
        //         // Write the bindings to the bindings.rs file.
        //         bindings
        //             .write_to_file("bindings.rs")
        //             .expect("Couldn't write bindings!");
        //     }

        // libpotassco
        cc::Build::new()
            .cpp(true)
            .flag("-std=c++14")
            .flag("-O3")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .file("clingo/clasp/libpotassco/src/application.cpp")
            .file("clingo/clasp/libpotassco/src/aspif.cpp")
            .file("clingo/clasp/libpotassco/src/aspif_text.cpp")
            .file("clingo/clasp/libpotassco/src/clingo.cpp")
            .file("clingo/clasp/libpotassco/src/convert.cpp")
            .file("clingo/clasp/libpotassco/src/match_basic_types.cpp")
            .file("clingo/clasp/libpotassco/src/program_options.cpp")
            .file("clingo/clasp/libpotassco/src/rule_utils.cpp")
            .file("clingo/clasp/libpotassco/src/smodels.cpp")
            .file("clingo/clasp/libpotassco/src/string_convert.cpp")
            .file("clingo/clasp/libpotassco/src/theory_data.cpp")
            .file("clingo/clasp/libpotassco/src/value_store.cpp")
            .include("clingo/clasp/libpotassco")
            .compile("libpotassco.a");

        // libclasp
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .define("WITH_THREADS", Some("0"))
            .file("clingo/clasp/src/asp_preprocessor.cpp")
            .file("clingo/clasp/src/cb_enumerator.cpp")
            .file("clingo/clasp/src/clasp_facade.cpp")
            .file("clingo/clasp/src/clasp_options.cpp")
            .file("clingo/clasp/src/clasp_output.cpp")
            .file("clingo/clasp/src/clause.cpp")
            .file("clingo/clasp/src/clingo.cpp")
            .file("clingo/clasp/src/constraint.cpp")
            .file("clingo/clasp/src/dependency_graph.cpp")
            .file("clingo/clasp/src/enumerator.cpp")
            .file("clingo/clasp/src/heuristics.cpp")
            .file("clingo/clasp/src/logic_program.cpp")
            .file("clingo/clasp/src/logic_program_types.cpp")
            .file("clingo/clasp/src/lookahead.cpp")
            .file("clingo/clasp/src/minimize_constraint.cpp")
            .file("clingo/clasp/src/model_enumerators.cpp")
            .file("clingo/clasp/src/parser.cpp")
            .file("clingo/clasp/src/program_builder.cpp")
            .file("clingo/clasp/src/satelite.cpp")
            .file("clingo/clasp/src/shared_context.cpp")
            .file("clingo/clasp/src/solve_algorithms.cpp")
            .file("clingo/clasp/src/solver.cpp")
            .file("clingo/clasp/src/solver_strategies.cpp")
            .file("clingo/clasp/src/solver_types.cpp")
            .file("clingo/clasp/src/statistics.cpp")
            .file("clingo/clasp/src/timer.cpp")
            .file("clingo/clasp/src/unfounded_check.cpp")
            .file("clingo/clasp/src/weight_constraint.cpp")
            .file("clingo/clasp/src/parallel_solve.cpp")
            .include("clingo/clasp")
            .include("generated")
            .include("clingo/clasp/libpotassco")
            .compile("libclasp.a");

        // libgringo
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .file("clingo/libgringo/src/backend.cc")
            .file("clingo/libgringo/src/primes.cc")
            .file("clingo/libgringo/src/symbol.cc")
            .file("clingo/libgringo/src/term.cc")
            .file("clingo/libgringo/src/terms.cc")
            .file("clingo/libgringo/src/ground/instantiation.cc")
            .file("clingo/libgringo/src/ground/literals.cc")
            .file("clingo/libgringo/src/ground/program.cc")
            .file("clingo/libgringo/src/ground/statements.cc")
            .file("clingo/libgringo/src/input/aggregate.cc")
            .file("clingo/libgringo/src/input/aggregates.cc")
            .file("clingo/libgringo/src/input/groundtermparser.cc")
            .file("clingo/libgringo/src/input/literal.cc")
            .file("clingo/libgringo/src/input/literals.cc")
            .file("clingo/libgringo/src/input/nongroundparser.cc")
            .file("clingo/libgringo/src/input/program.cc")
            .file("clingo/libgringo/src/input/programbuilder.cc")
            .file("clingo/libgringo/src/input/statement.cc")
            .file("clingo/libgringo/src/input/theory.cc")
            .file("generated/input/groundtermgrammar/grammar.cc")
            .file("generated/input/nongroundgrammar/grammar.cc")
            .file("clingo/libgringo/src/output/aggregates.cc")
            .file("clingo/libgringo/src/output/literal.cc")
            .file("clingo/libgringo/src/output/literals.cc")
            .file("clingo/libgringo/src/output/output.cc")
            .file("clingo/libgringo/src/output/statement.cc")
            .file("clingo/libgringo/src/output/statements.cc")
            .file("clingo/libgringo/src/output/theory.cc")
            .include("clingo/libgringo")
            .include("generated")
            .include("clingo/clasp/libpotassco")
            .include("clingo/libreify")
            .compile("libgringo.a");

        // libclingo
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .define("WITH_THREADS", Some("0"))
            .file("clingo/libclingo/src/ast.cc")
            .file("clingo/libclingo/src/clingo_app.cc")
            .file("clingo/libclingo/src/clingocontrol.cc")
            .file("clingo/libclingo/src/control.cc")
            .file("clingo/libclingo/src/gringo_app.cc")
            .file("clingo/libclingo/src/incmode.cc")
            .file("clingo/libclingo/src/scripts.cc")
            .file("clingo/clasp/app/clasp_app.cpp")
            .include("clingo/libclingo")
            .include("clingo/libgringo")
            .include("clingo/clasp/libpotassco")
            .include("clingo/clasp")
            .include("clingo/clasp/app")
            .include("generated")
            .compile("libclingo.a");

        // libreify
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .file("clingo/libreify/src/program.cc")
            .include("clingo/libreify")
            .include("clingo/libgringo")
            .include("clingo/clasp/libpotassco")
            .compile("libreify.a");

        println!("cargo:rustc-link-lib=static=potassco");
        println!("cargo:rustc-link-lib=static=clasp");
        println!("cargo:rustc-link-lib=static=gringo");
        println!("cargo:rustc-link-lib=static=clingo");
    }
    //     println!("cargo:rustc-link-lib=python3.6m");
    //     -DWITH_PYTHON=1 -I/usr/include/python3.6m
}
<commit_msg>clone clingo to OUT_DIR<commit_after>use std::env;
use std::path::PathBuf;
use std::process::Command;

fn main() {
    if cfg!(feature = "dynamic_linking") {
        println!("cargo:rustc-link-lib=dylib=clingo");
    } else {
        // build clingo for static linking

        let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR was not set"));
        let clingo_dir = out_dir.join("clingo");

        if !clingo_dir.exists() {
            Command::new("git")
                .args(&["clone", "https://github.com/sthiele/clingo.git"])
                .current_dir(out_dir.to_str().unwrap())
                .status()
                .unwrap();

            Command::new("git")
                .args(&["checkout", "mod_v5.3.0"])
                .current_dir(clingo_dir.to_str().unwrap())
                .status()
                .unwrap();

            Command::new("git")
                .args(&["submodule", "update", "--init", "--recursive"])
                .current_dir(clingo_dir.to_str().unwrap())
                .status()
                .unwrap();
        }

        // if !Path::new("bindings.rs").exists() {
        //     let bindings = bindgen::Builder::default()
        //         .header("clingo/libclingo/clingo.h")
        //         .no_copy("clingo_solve_control")
        //         .no_copy("clingo_model")
        //         .no_copy("clingo_solve_handle")
        //         .no_copy("clingo_program_builder")
        //         .no_copy("clingo_control")
        //         .no_copy("clingo_options")
        //         .no_copy("clingo_symbolic_atoms")
        //         .no_copy("clingo_theory_atoms")
        //         .no_copy("clingo_assignment")
        //         .no_copy("clingo_propagate_init")
        //         .no_copy("clingo_propagate_control")
        //         .no_copy("clingo_backend")
        //         .no_copy("clingo_configuration")
        //         .no_copy("clingo_statistic")
        //         .blacklist_type("max_align_t") // https://github.com/rust-lang/rust-bindgen/issues/550
        //         .generate()
        //         .expect("Unable to generate bindings");
        //
        //     // Write the bindings to the bindings.rs file.
        //     bindings
        //         .write_to_file("bindings.rs")
        //         .expect("Couldn't write bindings!");
        // }

        // libpotassco
        cc::Build::new()
            .cpp(true)
            .flag("-std=c++14")
            .flag("-O3")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .file(clingo_dir.join("clasp/libpotassco/src/application.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/aspif.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/aspif_text.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/clingo.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/convert.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/match_basic_types.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/program_options.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/rule_utils.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/smodels.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/string_convert.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/theory_data.cpp"))
            .file(clingo_dir.join("clasp/libpotassco/src/value_store.cpp"))
            .include(clingo_dir.join("clasp/libpotassco"))
            .compile("libpotassco.a");

        // libclasp
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .define("WITH_THREADS", Some("0"))
            .file(clingo_dir.join("clasp/src/asp_preprocessor.cpp"))
            .file(clingo_dir.join("clasp/src/cb_enumerator.cpp"))
            .file(clingo_dir.join("clasp/src/clasp_facade.cpp"))
            .file(clingo_dir.join("clasp/src/clasp_options.cpp"))
            .file(clingo_dir.join("clasp/src/clasp_output.cpp"))
            .file(clingo_dir.join("clasp/src/clause.cpp"))
            .file(clingo_dir.join("clasp/src/clingo.cpp"))
            .file(clingo_dir.join("clasp/src/constraint.cpp"))
            .file(clingo_dir.join("clasp/src/dependency_graph.cpp"))
            .file(clingo_dir.join("clasp/src/enumerator.cpp"))
            .file(clingo_dir.join("clasp/src/heuristics.cpp"))
            .file(clingo_dir.join("clasp/src/logic_program.cpp"))
            .file(clingo_dir.join("clasp/src/logic_program_types.cpp"))
            .file(clingo_dir.join("clasp/src/lookahead.cpp"))
            .file(clingo_dir.join("clasp/src/minimize_constraint.cpp"))
            .file(clingo_dir.join("clasp/src/model_enumerators.cpp"))
            .file(clingo_dir.join("clasp/src/parser.cpp"))
            .file(clingo_dir.join("clasp/src/program_builder.cpp"))
            .file(clingo_dir.join("clasp/src/satelite.cpp"))
            .file(clingo_dir.join("clasp/src/shared_context.cpp"))
            .file(clingo_dir.join("clasp/src/solve_algorithms.cpp"))
            .file(clingo_dir.join("clasp/src/solver.cpp"))
            .file(clingo_dir.join("clasp/src/solver_strategies.cpp"))
            .file(clingo_dir.join("clasp/src/solver_types.cpp"))
            .file(clingo_dir.join("clasp/src/statistics.cpp"))
            .file(clingo_dir.join("clasp/src/timer.cpp"))
            .file(clingo_dir.join("clasp/src/unfounded_check.cpp"))
            .file(clingo_dir.join("clasp/src/weight_constraint.cpp"))
            .file(clingo_dir.join("clasp/src/parallel_solve.cpp"))
            .include(clingo_dir.join("clasp"))
            .include("generated")
            .include(clingo_dir.join("clasp/libpotassco"))
            .compile("libclasp.a");

        // libgringo
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .file(clingo_dir.join("libgringo/src/backend.cc"))
            .file(clingo_dir.join("libgringo/src/primes.cc"))
            .file(clingo_dir.join("libgringo/src/symbol.cc"))
            .file(clingo_dir.join("libgringo/src/term.cc"))
            .file(clingo_dir.join("libgringo/src/terms.cc"))
            .file(clingo_dir.join("libgringo/src/ground/instantiation.cc"))
            .file(clingo_dir.join("libgringo/src/ground/literals.cc"))
            .file(clingo_dir.join("libgringo/src/ground/program.cc"))
            .file(clingo_dir.join("libgringo/src/ground/statements.cc"))
            .file(clingo_dir.join("libgringo/src/input/aggregate.cc"))
            .file(clingo_dir.join("libgringo/src/input/aggregates.cc"))
            .file(clingo_dir.join("libgringo/src/input/groundtermparser.cc"))
            .file(clingo_dir.join("libgringo/src/input/literal.cc"))
            .file(clingo_dir.join("libgringo/src/input/literals.cc"))
            .file(clingo_dir.join("libgringo/src/input/nongroundparser.cc"))
            .file(clingo_dir.join("libgringo/src/input/program.cc"))
            .file(clingo_dir.join("libgringo/src/input/programbuilder.cc"))
            .file(clingo_dir.join("libgringo/src/input/statement.cc"))
            .file(clingo_dir.join("libgringo/src/input/theory.cc"))
            .file("generated/input/groundtermgrammar/grammar.cc")
            .file("generated/input/nongroundgrammar/grammar.cc")
            .file(clingo_dir.join("libgringo/src/output/aggregates.cc"))
            .file(clingo_dir.join("libgringo/src/output/literal.cc"))
            .file(clingo_dir.join("libgringo/src/output/literals.cc"))
            .file(clingo_dir.join("libgringo/src/output/output.cc"))
            .file(clingo_dir.join("libgringo/src/output/statement.cc"))
            .file(clingo_dir.join("libgringo/src/output/statements.cc"))
            .file(clingo_dir.join("libgringo/src/output/theory.cc"))
            .include(clingo_dir.join("libgringo"))
            .include("generated")
            .include(clingo_dir.join("clasp/libpotassco"))
            .include(clingo_dir.join("libreify"))
            .compile("libgringo.a");

        // libclingo
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .define("WITH_THREADS", Some("0"))
            .file(clingo_dir.join("libclingo/src/ast.cc"))
            .file(clingo_dir.join("libclingo/src/clingo_app.cc"))
            .file(clingo_dir.join("libclingo/src/clingocontrol.cc"))
            .file(clingo_dir.join("libclingo/src/control.cc"))
            .file(clingo_dir.join("libclingo/src/gringo_app.cc"))
            .file(clingo_dir.join("libclingo/src/incmode.cc"))
            .file(clingo_dir.join("libclingo/src/scripts.cc"))
            .file(clingo_dir.join("clasp/app/clasp_app.cpp"))
            .include(clingo_dir.join("libclingo"))
            .include(clingo_dir.join("libgringo"))
            .include(clingo_dir.join("clasp/libpotassco"))
            .include(clingo_dir.join("clasp"))
            .include(clingo_dir.join("clasp/app"))
            .include("generated")
            .compile("libclingo.a");

        // libreify
        cc::Build::new()
            .cpp(true)
            .flag("-O3")
            .flag("-std=c++14")
            .warnings(false)
            .define("NDEBUG", Some("1"))
            .file(clingo_dir.join("libreify/src/program.cc"))
            .include(clingo_dir.join("libreify"))
            .include(clingo_dir.join("libgringo"))
            .include(clingo_dir.join("clasp/libpotassco"))
            .compile("libreify.a");

        println!("cargo:rustc-link-lib=static=potassco");
        println!("cargo:rustc-link-lib=static=clasp");
        println!("cargo:rustc-link-lib=static=gringo");
        println!("cargo:rustc-link-lib=static=clingo");
    }
    //     println!("cargo:rustc-link-lib=python3.6m");
    //     -DWITH_PYTHON=1 -I/usr/include/python3.6m
}
<|endoftext|>


<commit_before>//! Data types for addresses to nodes in DICOM networks.

use std::{
    net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
    str::FromStr,
};

use snafu::{ensure, AsErrorSource, ResultExt, Snafu};

/// A specification for a full address to the target SCP:
/// an application entity title, plus a generic  address,
/// typically a socket address.
///
/// These addresses can be serialized and parsed
/// with the syntax `{ae_title}@{address}`.
///
/// For the version of the struct without a mandatory AE title,
/// see [`AeAddr`].
///
/// # Example
///
/// ```
/// # use dicom_ul::FullAeAddr;
/// # use std::net::SocketAddr;
/// #
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # // socket address can be a string
/// let addr: FullAeAddr<String> = "SCP-STORAGE@127.0.0.1:104".parse()?;
/// assert_eq!(addr.ae_title(), "SCP-STORAGE");
/// assert_eq!(addr.socket_addr(), "127.0.0.1:104");
/// # // or anything else which can be parsed into a socket address
/// let addr: FullAeAddr<SocketAddr> = "SCP-STORAGE@127.0.0.1:104".parse()?;
/// assert_eq!(addr.ae_title(), "SCP-STORAGE");
/// assert_eq!(addr.socket_addr(), &SocketAddr::from(([127, 0, 0, 1], 104)));
/// assert_eq!(&addr.to_string(), "SCP-STORAGE@127.0.0.1:104");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct FullAeAddr<T> {
    ae_title: String,
    socket_addr: T,
}

impl<T> FullAeAddr<T> {
    /// Create an AE address from its bare constituent parts.
    pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
        FullAeAddr {
            ae_title: ae_title.into(),
            socket_addr,
        }
    }

    /// Retrieve the application entity title portion.
    pub fn ae_title(&self) -> &str {
        &self.ae_title
    }

    /// Retrieve the network address portion.
    pub fn socket_addr(&self) -> &T {
        &self.socket_addr
    }

    /// Convert the full address into its constituent parts.
    pub fn into_parts(self) -> (String, T) {
        (self.ae_title, self.socket_addr)
    }
}

impl<T> From<(String, T)> for FullAeAddr<T> {
    fn from((ae_title, socket_addr): (String, T)) -> Self {
        Self::new(ae_title, socket_addr)
    }
}

/// A error which occurred when parsing an AE address.
#[derive(Debug, Clone, Eq, PartialEq, Snafu)]
pub enum ParseAeAddressError<E>
where
    E: std::fmt::Debug + AsErrorSource,
{
    /// Missing `@` in full AE address
    MissingPart,

    /// Could not parse network socket address
    ParseSocketAddress { source: E },
}

impl<T> FromStr for FullAeAddr<T>
where
    T: FromStr,
    T::Err: std::fmt::Debug + AsErrorSource,
{
    type Err = ParseAeAddressError<<T as FromStr>::Err>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // !!! there should be a way to escape the `@`
        if let Some((ae_title, addr)) = s.split_once('@') {
            ensure!(!ae_title.is_empty(), MissingPartSnafu);
            Ok(FullAeAddr {
                ae_title: ae_title.to_string(),
                socket_addr: addr.parse().context(ParseSocketAddressSnafu)?,
            })
        } else {
            Err(ParseAeAddressError::MissingPart)
        }
    }
}

impl<T> ToSocketAddrs for FullAeAddr<T>
where
    T: ToSocketAddrs,
{
    type Iter = T::Iter;

    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
        self.socket_addr.to_socket_addrs()
    }
}

impl<T> std::fmt::Display for FullAeAddr<T>
where
    T: std::fmt::Display,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.ae_title.replace("@", "\\@"))?;
        f.write_str("@")?;
        std::fmt::Display::fmt(&self.socket_addr, f)
    }
}

/// A specification for an address to the target SCP:
/// a generic network socket address
/// which may also include an application entity title.
///
/// These addresses can be serialized and parsed
/// with the syntax `{ae_title}@{socket_address}`,
/// where the
///
/// For the version of the struct in which the AE title part is mandatory,
/// see [`FullAeAddr`].
///
/// # Example
///
/// ```
/// # use dicom_ul::{AeAddr, FullAeAddr};
/// # use std::net::{SocketAddr, SocketAddrV4};
/// #
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let addr: AeAddr<SocketAddrV4> = "SCP-STORAGE@127.0.0.1:104".parse()?;
/// assert_eq!(addr.ae_title(), Some("SCP-STORAGE"));
/// assert_eq!(addr.socket_addr(), &SocketAddrV4::new([127, 0, 0, 1].into(), 104));
/// assert_eq!(&addr.to_string(), "SCP-STORAGE@127.0.0.1:104");
///
/// // AE title can be missing
/// let addr: AeAddr<String> = "192.168.1.99:1045".parse()?;
/// assert_eq!(addr.ae_title(), None);
/// // but can be provided later
/// let full_addr: FullAeAddr<_> = addr.with_ae_title("SCP-QUERY");
/// assert_eq!(full_addr.ae_title(), "SCP-QUERY");
/// assert_eq!(&full_addr.to_string(), "SCP-QUERY@192.168.1.99:1045");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct AeAddr<T> {
    ae_title: Option<String>,
    socket_addr: T,
}

impl<T> AeAddr<T> {
    /// Create an AE address from its bare constituent parts.
    pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
        AeAddr {
            ae_title: Some(ae_title.into()),
            socket_addr,
        }
    }

    /// Retrieve the application entity title portion, if present.
    pub fn ae_title(&self) -> Option<&str> {
        self.ae_title.as_deref()
    }

    /// Retrieve the socket address portion.
    pub fn socket_addr(&self) -> &T {
        &self.socket_addr
    }

    /// Create a new address with the full application entity target,
    /// discarding any potentially existing AE title.
    pub fn with_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
        FullAeAddr {
            ae_title: ae_title.into(),
            socket_addr: self.socket_addr,
        }
    }

    /// Create a new address with the full application entity target,
    /// using the given AE title if it is missing.
    pub fn with_default_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
        FullAeAddr {
            ae_title: self.ae_title.unwrap_or_else(|| ae_title.into()),
            socket_addr: self.socket_addr,
        }
    }

    /// Convert the address into its constituent parts.
    pub fn into_parts(self) -> (Option<String>, T) {
        (self.ae_title, self.socket_addr)
    }
}

/// This conversion provides a socket address without an AE title.
impl From<SocketAddr> for AeAddr<SocketAddr> {
    fn from(socket_addr: SocketAddr) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }
}

/// This conversion provides an IPv4 socket address without an AE title.
impl From<SocketAddrV4> for AeAddr<SocketAddrV4> {
    fn from(socket_addr: SocketAddrV4) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }
}

/// This conversion provides an IPv6 socket address without an AE title.
impl From<SocketAddrV6> for AeAddr<SocketAddrV6> {
    fn from(socket_addr: SocketAddrV6) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }
}

impl<T> From<FullAeAddr<T>> for AeAddr<T> {
    fn from(full: FullAeAddr<T>) -> Self {
        AeAddr {
            ae_title: Some(full.ae_title),
            socket_addr: full.socket_addr,
        }
    }
}

impl<T> FromStr for AeAddr<T>
where
    T: FromStr,
{
    type Err = <T as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // !!! there should be a way to escape the `@`
        if let Some((ae_title, address)) = s.split_once('@') {
            Ok(AeAddr {
                ae_title: Some(ae_title)
                    .filter(|s| !s.is_empty())
                    .map(|s| s.to_string()),
                socket_addr: address.parse()?,
            })
        } else {
            Ok(AeAddr {
                ae_title: None,
                socket_addr: s.parse()?,
            })
        }
    }
}

impl<T> ToSocketAddrs for AeAddr<T>
where
    T: ToSocketAddrs,
{
    type Iter = T::Iter;

    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
        self.socket_addr.to_socket_addrs()
    }
}

impl<T> std::fmt::Display for AeAddr<T>
where
    T: std::fmt::Display,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let socket_addr = self.socket_addr.to_string();
        if let Some(ae_title) = &self.ae_title {
            f.write_str(&ae_title.replace("@", "\\@"))?;
            f.write_str("@")?;
        } else if socket_addr.contains("@") {
            // if formatted socket address contains a `@`,
            // we need to start the output with `@`
            // so that the start of the socket address
            // is not interpreted as an AE title
            f.write_str("@")?;
        }

        std::fmt::Display::fmt(&socket_addr, f)
    }
}

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

    #[test]
    fn ae_addr_parse() {
        // socket address can be a string
        let addr: FullAeAddr<String> = "SCP-STORAGE@127.0.0.1:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "SCP-STORAGE");
        assert_eq!(addr.socket_addr(), "127.0.0.1:104");

        // or anything else which can be parsed into a socket address
        let addr: FullAeAddr<SocketAddr> = "SCP_STORAGE@127.0.0.1:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "SCP_STORAGE");
        assert_eq!(addr.socket_addr(), &SocketAddr::from(([127, 0, 0, 1], 104)));
        assert_eq!(&addr.to_string(), "SCP_STORAGE@127.0.0.1:104");

        // IPv4 socket address
        let addr: FullAeAddr<SocketAddrV4> = "MAMMOSTORE@10.0.0.11:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "MAMMOSTORE");
        assert_eq!(
            addr.socket_addr(),
            &SocketAddrV4::new([10, 0, 0, 11].into(), 104)
        );
        assert_eq!(&addr.to_string(), "MAMMOSTORE@10.0.0.11:104");
    }

    /// test addresses without an AE title
    #[test]
    fn ae_addr_parse_no_ae() {
        // should fail
        let res = FullAeAddr::<String>::from_str("pacs.hospital.example.com:104");
        assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));
        // should also fail (AE title can't be empty)
        let res = FullAeAddr::<String>::from_str("@pacs.hospital.example.com:104");
        assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));

        // should return an ae addr with no AE title
        let addr: AeAddr<String> = "pacs.hospital.example.com:104".parse().unwrap();
        assert_eq!(addr.ae_title(), None);
        assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
        // should also return an ae addr with no AE title
        let addr: AeAddr<String> = "@pacs.hospital.example.com:104".parse().unwrap();
        assert_eq!(addr.ae_title(), None);
        assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
    }

    #[test]
    fn ae_addr_parse_weird_scenarios() {
        // can parse addresses with multiple @'s
        let addr: FullAeAddr<String> = "ABC@DICOM@pacs.archive.example.com:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "ABC");
        assert_eq!(addr.socket_addr(), "DICOM@pacs.archive.example.com:104");
        assert_eq!(&addr.to_string(), "ABC@DICOM@pacs.archive.example.com:104");
    }
}
<commit_msg>[ul] Add `AeAddr::new_socket_addr`<commit_after>//! Data types for addresses to nodes in DICOM networks.

use std::{
    net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
    str::FromStr,
};

use snafu::{ensure, AsErrorSource, ResultExt, Snafu};

/// A specification for a full address to the target SCP:
/// an application entity title, plus a generic  address,
/// typically a socket address.
///
/// These addresses can be serialized and parsed
/// with the syntax `{ae_title}@{address}`.
///
/// For the version of the struct without a mandatory AE title,
/// see [`AeAddr`].
///
/// # Example
///
/// ```
/// # use dicom_ul::FullAeAddr;
/// # use std::net::SocketAddr;
/// #
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # // socket address can be a string
/// let addr: FullAeAddr<String> = "SCP-STORAGE@127.0.0.1:104".parse()?;
/// assert_eq!(addr.ae_title(), "SCP-STORAGE");
/// assert_eq!(addr.socket_addr(), "127.0.0.1:104");
/// # // or anything else which can be parsed into a socket address
/// let addr: FullAeAddr<SocketAddr> = "SCP-STORAGE@127.0.0.1:104".parse()?;
/// assert_eq!(addr.ae_title(), "SCP-STORAGE");
/// assert_eq!(addr.socket_addr(), &SocketAddr::from(([127, 0, 0, 1], 104)));
/// assert_eq!(&addr.to_string(), "SCP-STORAGE@127.0.0.1:104");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct FullAeAddr<T> {
    ae_title: String,
    socket_addr: T,
}

impl<T> FullAeAddr<T> {
    /// Create an AE address from its bare constituent parts.
    pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
        FullAeAddr {
            ae_title: ae_title.into(),
            socket_addr,
        }
    }

    /// Retrieve the application entity title portion.
    pub fn ae_title(&self) -> &str {
        &self.ae_title
    }

    /// Retrieve the network address portion.
    pub fn socket_addr(&self) -> &T {
        &self.socket_addr
    }

    /// Convert the full address into its constituent parts.
    pub fn into_parts(self) -> (String, T) {
        (self.ae_title, self.socket_addr)
    }
}

impl<T> From<(String, T)> for FullAeAddr<T> {
    fn from((ae_title, socket_addr): (String, T)) -> Self {
        Self::new(ae_title, socket_addr)
    }
}

/// A error which occurred when parsing an AE address.
#[derive(Debug, Clone, Eq, PartialEq, Snafu)]
pub enum ParseAeAddressError<E>
where
    E: std::fmt::Debug + AsErrorSource,
{
    /// Missing `@` in full AE address
    MissingPart,

    /// Could not parse network socket address
    ParseSocketAddress { source: E },
}

impl<T> FromStr for FullAeAddr<T>
where
    T: FromStr,
    T::Err: std::fmt::Debug + AsErrorSource,
{
    type Err = ParseAeAddressError<<T as FromStr>::Err>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // !!! there should be a way to escape the `@`
        if let Some((ae_title, addr)) = s.split_once('@') {
            ensure!(!ae_title.is_empty(), MissingPartSnafu);
            Ok(FullAeAddr {
                ae_title: ae_title.to_string(),
                socket_addr: addr.parse().context(ParseSocketAddressSnafu)?,
            })
        } else {
            Err(ParseAeAddressError::MissingPart)
        }
    }
}

impl<T> ToSocketAddrs for FullAeAddr<T>
where
    T: ToSocketAddrs,
{
    type Iter = T::Iter;

    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
        self.socket_addr.to_socket_addrs()
    }
}

impl<T> std::fmt::Display for FullAeAddr<T>
where
    T: std::fmt::Display,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.ae_title.replace("@", "\\@"))?;
        f.write_str("@")?;
        std::fmt::Display::fmt(&self.socket_addr, f)
    }
}

/// A specification for an address to the target SCP:
/// a generic network socket address
/// which may also include an application entity title.
///
/// These addresses can be serialized and parsed
/// with the syntax `{ae_title}@{socket_address}`,
/// where the
///
/// For the version of the struct in which the AE title part is mandatory,
/// see [`FullAeAddr`].
///
/// # Example
///
/// ```
/// # use dicom_ul::{AeAddr, FullAeAddr};
/// # use std::net::{SocketAddr, SocketAddrV4};
/// #
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let addr: AeAddr<SocketAddrV4> = "SCP-STORAGE@127.0.0.1:104".parse()?;
/// assert_eq!(addr.ae_title(), Some("SCP-STORAGE"));
/// assert_eq!(addr.socket_addr(), &SocketAddrV4::new([127, 0, 0, 1].into(), 104));
/// assert_eq!(&addr.to_string(), "SCP-STORAGE@127.0.0.1:104");
///
/// // AE title can be missing
/// let addr: AeAddr<String> = "192.168.1.99:1045".parse()?;
/// assert_eq!(addr.ae_title(), None);
/// // but can be provided later
/// let full_addr: FullAeAddr<_> = addr.with_ae_title("SCP-QUERY");
/// assert_eq!(full_addr.ae_title(), "SCP-QUERY");
/// assert_eq!(&full_addr.to_string(), "SCP-QUERY@192.168.1.99:1045");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct AeAddr<T> {
    ae_title: Option<String>,
    socket_addr: T,
}

impl<T> AeAddr<T> {
    /// Create an AE address from its bare constituent parts.
    pub fn new(ae_title: impl Into<String>, socket_addr: T) -> Self {
        AeAddr {
            ae_title: Some(ae_title.into()),
            socket_addr,
        }
    }

    /// Create an address with a missing AE title.
    pub fn new_socket_addr(socket_addr: T) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }

    /// Retrieve the application entity title portion, if present.
    pub fn ae_title(&self) -> Option<&str> {
        self.ae_title.as_deref()
    }

    /// Retrieve the socket address portion.
    pub fn socket_addr(&self) -> &T {
        &self.socket_addr
    }

    /// Create a new address with the full application entity target,
    /// discarding any potentially existing AE title.
    pub fn with_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
        FullAeAddr {
            ae_title: ae_title.into(),
            socket_addr: self.socket_addr,
        }
    }

    /// Create a new address with the full application entity target,
    /// using the given AE title if it is missing.
    pub fn with_default_ae_title(self, ae_title: impl Into<String>) -> FullAeAddr<T> {
        FullAeAddr {
            ae_title: self.ae_title.unwrap_or_else(|| ae_title.into()),
            socket_addr: self.socket_addr,
        }
    }

    /// Convert the address into its constituent parts.
    pub fn into_parts(self) -> (Option<String>, T) {
        (self.ae_title, self.socket_addr)
    }
}

/// This conversion provides a socket address without an AE title.
impl From<SocketAddr> for AeAddr<SocketAddr> {
    fn from(socket_addr: SocketAddr) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }
}

/// This conversion provides an IPv4 socket address without an AE title.
impl From<SocketAddrV4> for AeAddr<SocketAddrV4> {
    fn from(socket_addr: SocketAddrV4) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }
}

/// This conversion provides an IPv6 socket address without an AE title.
impl From<SocketAddrV6> for AeAddr<SocketAddrV6> {
    fn from(socket_addr: SocketAddrV6) -> Self {
        AeAddr {
            ae_title: None,
            socket_addr,
        }
    }
}

impl<T> From<FullAeAddr<T>> for AeAddr<T> {
    fn from(full: FullAeAddr<T>) -> Self {
        AeAddr {
            ae_title: Some(full.ae_title),
            socket_addr: full.socket_addr,
        }
    }
}

impl<T> FromStr for AeAddr<T>
where
    T: FromStr,
{
    type Err = <T as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // !!! there should be a way to escape the `@`
        if let Some((ae_title, address)) = s.split_once('@') {
            Ok(AeAddr {
                ae_title: Some(ae_title)
                    .filter(|s| !s.is_empty())
                    .map(|s| s.to_string()),
                socket_addr: address.parse()?,
            })
        } else {
            Ok(AeAddr {
                ae_title: None,
                socket_addr: s.parse()?,
            })
        }
    }
}

impl<T> ToSocketAddrs for AeAddr<T>
where
    T: ToSocketAddrs,
{
    type Iter = T::Iter;

    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
        self.socket_addr.to_socket_addrs()
    }
}

impl<T> std::fmt::Display for AeAddr<T>
where
    T: std::fmt::Display,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let socket_addr = self.socket_addr.to_string();
        if let Some(ae_title) = &self.ae_title {
            f.write_str(&ae_title.replace("@", "\\@"))?;
            f.write_str("@")?;
        } else if socket_addr.contains("@") {
            // if formatted socket address contains a `@`,
            // we need to start the output with `@`
            // so that the start of the socket address
            // is not interpreted as an AE title
            f.write_str("@")?;
        }

        std::fmt::Display::fmt(&socket_addr, f)
    }
}

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

    #[test]
    fn ae_addr_parse() {
        // socket address can be a string
        let addr: FullAeAddr<String> = "SCP-STORAGE@127.0.0.1:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "SCP-STORAGE");
        assert_eq!(addr.socket_addr(), "127.0.0.1:104");

        // or anything else which can be parsed into a socket address
        let addr: FullAeAddr<SocketAddr> = "SCP_STORAGE@127.0.0.1:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "SCP_STORAGE");
        assert_eq!(addr.socket_addr(), &SocketAddr::from(([127, 0, 0, 1], 104)));
        assert_eq!(&addr.to_string(), "SCP_STORAGE@127.0.0.1:104");

        // IPv4 socket address
        let addr: FullAeAddr<SocketAddrV4> = "MAMMOSTORE@10.0.0.11:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "MAMMOSTORE");
        assert_eq!(
            addr.socket_addr(),
            &SocketAddrV4::new([10, 0, 0, 11].into(), 104)
        );
        assert_eq!(&addr.to_string(), "MAMMOSTORE@10.0.0.11:104");
    }

    /// test addresses without an AE title
    #[test]
    fn ae_addr_parse_no_ae() {
        // should fail
        let res = FullAeAddr::<String>::from_str("pacs.hospital.example.com:104");
        assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));
        // should also fail (AE title can't be empty)
        let res = FullAeAddr::<String>::from_str("@pacs.hospital.example.com:104");
        assert!(matches!(res, Err(ParseAeAddressError::MissingPart)));

        // should return an ae addr with no AE title
        let addr: AeAddr<String> = "pacs.hospital.example.com:104".parse().unwrap();
        assert_eq!(addr.ae_title(), None);
        assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
        // should also return an ae addr with no AE title
        let addr: AeAddr<String> = "@pacs.hospital.example.com:104".parse().unwrap();
        assert_eq!(addr.ae_title(), None);
        assert_eq!(addr.socket_addr(), "pacs.hospital.example.com:104");
    }

    #[test]
    fn ae_addr_parse_weird_scenarios() {
        // can parse addresses with multiple @'s
        let addr: FullAeAddr<String> = "ABC@DICOM@pacs.archive.example.com:104".parse().unwrap();
        assert_eq!(addr.ae_title(), "ABC");
        assert_eq!(addr.socket_addr(), "DICOM@pacs.archive.example.com:104");
        assert_eq!(&addr.to_string(), "ABC@DICOM@pacs.archive.example.com:104");
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate duct;

use std::io::{BufRead, BufReader};
use std::fs::File;

#[test]
fn examples() {
    let mut error = false;
    for file in std::fs::read_dir("clippy_tests/examples").unwrap() {
        let file = file.unwrap().path();
        // only test *.rs files
        if file.extension().map_or(true, |file| file != "rs") {
            continue;
        }
        print!("testing {}... ", file.file_stem().unwrap().to_str().unwrap());
        let skip = BufReader::new(File::open(&file).unwrap()).lines().any(|line| {
            let line = line.as_ref().unwrap().trim();
            line == "// ignore-x86" && cfg!(target_pointer_width = "32") ||
            line == "// ignore-x86_64" && cfg!(target_pointer_width = "64")
        });
        if skip {
            println!("skipping");
            continue;
        }
        cmd!("touch", &file).run().unwrap();
        let output = file.with_extension("stderr");
        cmd!("cargo", "rustc", "-q", "--example", file.file_stem().unwrap(), "--", "-Dwarnings",
             "-Zremap-path-prefix-from=examples/", "-Zremap-path-prefix-to=",
             "-Zremap-path-prefix-from=examples\\", "-Zremap-path-prefix-to="
             )
            .unchecked()
            .stderr(&output)
            .env("CLIPPY_DISABLE_WIKI_LINKS", "true")
            .dir("clippy_tests")
            .run()
            .unwrap();
        if cmd!("git", "diff", "--exit-code", output).run().is_err() {
            error = true;
            println!("ERROR");
        } else {
            println!("ok");
        }
    }
    assert!(!error, "A test failed");
}
<commit_msg>Rustc doesn't generate expansion info for `cfg!`<commit_after>#[macro_use]
extern crate duct;

use std::io::{BufRead, BufReader};
use std::fs::File;

// FIXME: rustc doesn't generate expansion info for `cfg!` anymore
#![allow(logic-bug)]

#[test]
fn examples() {
    let mut error = false;
    for file in std::fs::read_dir("clippy_tests/examples").unwrap() {
        let file = file.unwrap().path();
        // only test *.rs files
        if file.extension().map_or(true, |file| file != "rs") {
            continue;
        }
        print!("testing {}... ", file.file_stem().unwrap().to_str().unwrap());
        let skip = BufReader::new(File::open(&file).unwrap()).lines().any(|line| {
            let line = line.as_ref().unwrap().trim();
            line == "// ignore-x86" && cfg!(target_pointer_width = "32") ||
            line == "// ignore-x86_64" && cfg!(target_pointer_width = "64")
        });
        if skip {
            println!("skipping");
            continue;
        }
        cmd!("touch", &file).run().unwrap();
        let output = file.with_extension("stderr");
        cmd!("cargo", "rustc", "-q", "--example", file.file_stem().unwrap(), "--", "-Dwarnings",
             "-Zremap-path-prefix-from=examples/", "-Zremap-path-prefix-to=",
             "-Zremap-path-prefix-from=examples\\", "-Zremap-path-prefix-to="
             )
            .unchecked()
            .stderr(&output)
            .env("CLIPPY_DISABLE_WIKI_LINKS", "true")
            .dir("clippy_tests")
            .run()
            .unwrap();
        if cmd!("git", "diff", "--exit-code", output).run().is_err() {
            error = true;
            println!("ERROR");
        } else {
            println!("ok");
        }
    }
    assert!(!error, "A test failed");
}
<|endoftext|>


<commit_before><commit_msg>Only sign SREP once per batch<commit_after><|endoftext|>


<commit_before><commit_msg>Remove feature-gated `box expr`<commit_after><|endoftext|>


<commit_before>/*
 * Copyright 2017-2018 Ben Ashford
 *
 * 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.
 */

//! The client API itself.
//!
//! This contains three main functions that return three specific types of client:
//!
//! * `connect` returns a pair of `Stream` and `Sink`, clients can write RESP messages to the
//! `Sink` and read RESP messages from the `Stream`. Pairing requests to responses is up to the
//! client.  This is intended to be a low-level interface from which more user-friendly interfaces
//! can be built.
//! * `paired_connect` is used for most of the standard Redis commands, where one request results
//! in one response.
//! * `pubsub_connect` is used for Redis's PUBSUB functionality.

pub mod connect;
#[macro_use]
pub mod paired;
pub mod pubsub;

pub use self::{connect::connect, paired::{paired_connect, PairedConnection},
               pubsub::{pubsub_connect, PubsubConnection}};

#[cfg(test)]
mod test {
    use std::io;

    use futures::sync::oneshot;
    use futures::{stream, Future, Sink, Stream};

    use tokio;

    use error;
    use resp;

    fn run_and_wait<R, E, F>(f: F) -> Result<R, E>
    where
        F: Future<Item = R, Error = E> + Send + 'static,
        R: Send + 'static,
        E: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();
        tokio::run(f.then(|r| tx.send(r).map_err(|_| panic!("Cannot send Result"))));
        rx.wait().expect("Cannot wait for a result")
    }

    #[test]
    fn can_connect() {
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connection = super::connect(&addr)
            .map_err(|e| e.into())
            .and_then(|connection| {
                connection
                    .send(resp_array!["PING", "TEST"])
                    .map_err(|e| e.into())
            })
            .and_then(|connection| connection.take(1).collect());

        let values = run_and_wait(connection).unwrap();

        assert_eq!(values.len(), 1);
        assert_eq!(values[0], "TEST".into());
    }

    #[test]
    fn complex_test() {
        let addr = "127.0.0.1:6379".parse().unwrap();
        let connection = super::connect(&addr)
            .map_err(|e| e.into())
            .and_then(|connection| {
                let mut ops = Vec::<resp::RespValue>::new();
                ops.push(resp_array!["FLUSH"]);
                ops.extend(
                    (0..1000).map(|i| resp_array!["SADD", "test_set", format!("VALUE: {}", i)]),
                );
                ops.push(resp_array!["SMEMBERS", "test_set"]);
                let send_f = connection
                    .send_all(stream::iter_ok::<_, io::Error>(ops))
                    .map(|(sender, _)| sender)
                    .map_err(|e| e.into());
                send_f.and_then(|connection| connection.skip(1001).take(1).collect())
            });
        let values = run_and_wait(connection).unwrap();
        assert_eq!(values.len(), 1);
        let values = match &values[0] {
            &resp::RespValue::Array(ref values) => values.clone(),
            _ => panic!("Not an array"),
        };
        assert_eq!(values.len(), 1000);
    }

    #[test]
    fn can_paired_connect() {
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connect_f = super::paired_connect(&addr).and_then(|connection| {
            let res_f = connection.send(resp_array!["PING", "TEST"]);
            faf!(connection.send(resp_array!["SET", "X", "123"]));
            let wait_f = connection.send(resp_array!["GET", "X"]);
            res_f.join(wait_f)
        });
        let (result_1, result_2): (String, String) = run_and_wait(connect_f).unwrap();
        assert_eq!(result_1, "TEST");
        assert_eq!(result_2, "123");
    }

    #[test]
    fn complex_paired_connect() {
        // let addr = "127.0.0.1:6379".parse().unwrap();

        // let connect_f = super::paired_connect(&addr).and_then(|connection| {
        //     connection
        //         .send(resp_array!["INCR", "CTR"])
        //         .and_then(move |value: String| {
        //             connection.send(resp_array!["SET", "LASTCTR", value])
        //         })
        // });
        // let result: String = run_and_wait(connect_f).unwrap();
        // assert_eq!(result, "OK");
    }

    #[test]
    fn sending_a_lot_of_data_test() {
        // let addr = "127.0.0.1:6379".parse().unwrap();

        // let test_f = super::paired_connect(&addr);
        // let send_data = test_f.and_then(|connection| {
        //     let mut futures = Vec::with_capacity(1000);
        //     for i in 0..1000 {
        //         let key = format!("X_{}", i);
        //         faf!(connection.send(resp_array!["SET", &key, i.to_string()]));
        //         futures.push(connection.send(resp_array!["GET", key]));
        //     }
        //     futures.remove(999)
        // });
        // let result: String = run_and_wait(send_data).unwrap();
        // assert_eq!(result, "999");
    }

    #[test]
    fn pubsub_test() {
        // let addr = "127.0.0.1:6379".parse().unwrap();
        // let paired_c = super::paired_connect(&addr);
        // let pubsub_c = super::pubsub_connect(&addr);
        // let msgs = paired_c.join(pubsub_c).and_then(|(paired, pubsub)| {
        //     let subscribe = pubsub.subscribe("test-topic");
        //     subscribe.and_then(move |msgs| {
        //         faf!(paired.send(resp_array!["PUBLISH", "test-topic", "test-message"]));
        //         faf!(paired.send(resp_array!["PUBLISH", "test-not-topic", "test-message-1.5"]));
        //         paired
        //             .send(resp_array!["PUBLISH", "test-topic", "test-message2"])
        //             .map(|_: resp::RespValue| msgs)
        //     })
        // });
        // let tst = msgs.and_then(|msgs| {
        //     msgs.take(2)
        //         .collect()
        //         .map_err(|_| error::internal("unreachable"))
        // });
        // let result = run_and_wait(tst).unwrap();
        // assert_eq!(result.len(), 2);
        // assert_eq!(result[0], "test-message".into());
        // assert_eq!(result[1], "test-message2".into());
    }
}
<commit_msg>Streamlining<commit_after>/*
 * Copyright 2017-2018 Ben Ashford
 *
 * 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.
 */

//! The client API itself.
//!
//! This contains three main functions that return three specific types of client:
//!
//! * `connect` returns a pair of `Stream` and `Sink`, clients can write RESP messages to the
//! `Sink` and read RESP messages from the `Stream`. Pairing requests to responses is up to the
//! client.  This is intended to be a low-level interface from which more user-friendly interfaces
//! can be built.
//! * `paired_connect` is used for most of the standard Redis commands, where one request results
//! in one response.
//! * `pubsub_connect` is used for Redis's PUBSUB functionality.

pub mod connect;
#[macro_use]
pub mod paired;
pub mod pubsub;

pub use self::{connect::connect, paired::{paired_connect, PairedConnection},
               pubsub::{pubsub_connect, PubsubConnection}};

#[cfg(test)]
mod test {
    use std::io;

    use futures::sync::oneshot;
    use futures::{stream, Future, Sink, Stream};

    use tokio;

    use error;
    use resp;

    fn run_and_wait<R, E, F>(f: F) -> Result<R, E>
    where
        F: Future<Item = R, Error = E> + Send + 'static,
        R: Send + 'static,
        E: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();
        tokio::run(f.then(|r| tx.send(r).map_err(|_| panic!("Cannot send Result"))));
        rx.wait().expect("Cannot wait for a result")
    }

    #[test]
    fn can_connect() {
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connection = super::connect(&addr)
            .map_err(|e| e.into())
            .and_then(|connection| {
                connection
                    .send(resp_array!["PING", "TEST"])
                    .map_err(|e| e.into())
            })
            .and_then(|connection| connection.take(1).collect());

        let values = run_and_wait(connection).unwrap();

        assert_eq!(values.len(), 1);
        assert_eq!(values[0], "TEST".into());
    }

    #[test]
    fn complex_test() {
        let addr = "127.0.0.1:6379".parse().unwrap();
        let connection = super::connect(&addr)
            .map_err(|e| e.into())
            .and_then(|connection| {
                let mut ops = Vec::<resp::RespValue>::new();
                ops.push(resp_array!["FLUSH"]);
                ops.extend(
                    (0..1000).map(|i| resp_array!["SADD", "test_set", format!("VALUE: {}", i)]),
                );
                ops.push(resp_array!["SMEMBERS", "test_set"]);
                connection
                    .send_all(stream::iter_ok::<_, io::Error>(ops))
                    .map(|(sender, _)| sender)
                    .map_err(|e| e.into())
            })
            .and_then(|connection| connection.skip(1001).take(1).collect());
        let values = run_and_wait(connection).unwrap();
        assert_eq!(values.len(), 1);
        let values = match &values[0] {
            &resp::RespValue::Array(ref values) => values.clone(),
            _ => panic!("Not an array"),
        };
        assert_eq!(values.len(), 1000);
    }

    #[test]
    fn can_paired_connect() {
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connect_f = super::paired_connect(&addr).and_then(|connection| {
            let res_f = connection.send(resp_array!["PING", "TEST"]);
            faf!(connection.send(resp_array!["SET", "X", "123"]));
            let wait_f = connection.send(resp_array!["GET", "X"]);
            res_f.join(wait_f)
        });
        let (result_1, result_2): (String, String) = run_and_wait(connect_f).unwrap();
        assert_eq!(result_1, "TEST");
        assert_eq!(result_2, "123");
    }

    #[test]
    fn complex_paired_connect() {
        // let addr = "127.0.0.1:6379".parse().unwrap();

        // let connect_f = super::paired_connect(&addr).and_then(|connection| {
        //     connection
        //         .send(resp_array!["INCR", "CTR"])
        //         .and_then(move |value: String| {
        //             connection.send(resp_array!["SET", "LASTCTR", value])
        //         })
        // });
        // let result: String = run_and_wait(connect_f).unwrap();
        // assert_eq!(result, "OK");
    }

    #[test]
    fn sending_a_lot_of_data_test() {
        // let addr = "127.0.0.1:6379".parse().unwrap();

        // let test_f = super::paired_connect(&addr);
        // let send_data = test_f.and_then(|connection| {
        //     let mut futures = Vec::with_capacity(1000);
        //     for i in 0..1000 {
        //         let key = format!("X_{}", i);
        //         faf!(connection.send(resp_array!["SET", &key, i.to_string()]));
        //         futures.push(connection.send(resp_array!["GET", key]));
        //     }
        //     futures.remove(999)
        // });
        // let result: String = run_and_wait(send_data).unwrap();
        // assert_eq!(result, "999");
    }

    #[test]
    fn pubsub_test() {
        // let addr = "127.0.0.1:6379".parse().unwrap();
        // let paired_c = super::paired_connect(&addr);
        // let pubsub_c = super::pubsub_connect(&addr);
        // let msgs = paired_c.join(pubsub_c).and_then(|(paired, pubsub)| {
        //     let subscribe = pubsub.subscribe("test-topic");
        //     subscribe.and_then(move |msgs| {
        //         faf!(paired.send(resp_array!["PUBLISH", "test-topic", "test-message"]));
        //         faf!(paired.send(resp_array!["PUBLISH", "test-not-topic", "test-message-1.5"]));
        //         paired
        //             .send(resp_array!["PUBLISH", "test-topic", "test-message2"])
        //             .map(|_: resp::RespValue| msgs)
        //     })
        // });
        // let tst = msgs.and_then(|msgs| {
        //     msgs.take(2)
        //         .collect()
        //         .map_err(|_| error::internal("unreachable"))
        // });
        // let result = run_and_wait(tst).unwrap();
        // assert_eq!(result.len(), 2);
        // assert_eq!(result[0], "test-message".into());
        // assert_eq!(result[1], "test-message2".into());
    }
}
<|endoftext|>