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

//! Discover or identify devices that may belong to Stratis.
//!
//! This module contains methods for finding all block devices that may belong
//! to Stratis, generally run when Stratis starts up, and other methods that
//! may be used to classify a single unknown block device.
//!
//! The methods rely to a greater or lesser extent on libudev.
//!
//! They have the following invocation heirarchy:
//! find_all*
//!  |
//! find_all_*_devices
//!  |
//! identify_*_device
//!  |
//! process_*_device
//!
//! The primary purpose of the find* methods is to construct a udev
//! enumeration and to properly process each of the udev database entries
//! found.
//!
//! The primary purpose of the identify_* methods is to use udev to identify
//! a single device and take the appropriate action based on that
//! identification.
//!
//! The primary purpose of the process_* methods is to gather up Stratis
//! device identifiers.
//!
//! Each method is expected to be invoked in a particular situation which
//! is guaranteed by the method that invokes it. The methods are not,
//! in general, general purpose methods that can be used in any situation.
//!
//! find_all is public because it is the method that is invoked by the
//! engine on startup. find_all_block_devices_with_stratis_signatures is
//! public for use in testing. identify_block_device is public because it
//! is suitable for identifying a block device associated with a uevent,
//! as the situation in which the uevent is handled is equivalent to that
//! provided by the execution of the
//! find_all_block_devices_with_stratis_signatures method.

use std::{
    collections::HashMap,
    fs::OpenOptions,
    path::{Path, PathBuf},
};

use devicemapper::Device;

use crate::engine::{
    strat_engine::backstore::{
        metadata::{device_identifiers, StratisIdentifiers},
        udev::{block_enumerator, decide_ownership, UdevOwnership},
    },
    types::PoolUuid,
};

// A miscellaneous group of identifiers found when identifiying a Stratis
// device.
pub type StratisInfo = (StratisIdentifiers, Device, PathBuf);

// A wrapper for obtaining the device number as a devicemapper Device
// which interprets absence of the value as an error, which it is in this
// context.
fn device_to_devno_wrapper(device: &libudev::Device) -> Result<Device, String> {
    device
        .devnum()
        .ok_or_else(|| "udev entry did not contain a device number".into())
        .map(Device::from)
}

// A wrapper around the metadata module's device_identifers method
// which also handles failure to open a device for reading.
// Returns an error if the device could not be opened for reading.
// Returns Ok(Err(...)) if there was an error while reading the
// Stratis identifiers from the device.
// Returns Ok(Ok(None)) if the identifers did not appear to be on
// the device.
fn device_identifiers_wrapper(
    devnode: &Path,
) -> Result<Result<Option<StratisIdentifiers>, String>, String> {
    OpenOptions::new()
        .read(true)
        .open(devnode)
        .as_mut()
        .map_err(|err| {
            format!(
                "device {} could not be opened for reading: {}",
                devnode.display(),
                err
            )
        })
        .map(|f| {
            device_identifiers(f).map_err(|err| {
                format!(
                    "encountered an error while reading Stratis header for device {}: {}",
                    devnode.display(),
                    err
                )
            })
        })
}

/// Process a device which udev information indicates is a Stratis device.
fn process_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) | (_, Ok(Err(err))) => {
                    warn!("udev identified device {} as a Stratis device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                (_, Ok(Ok(None))) => {
                    warn!("udev identified device {} as a Stratis device but there appeared to be no Stratis metadata on the device, disregarding the device",
                          devnode.display());
                    None
                }
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a Stratis device, but the udev entry for the device had no device node, disregarding device");
            None
        }
    }
}

/// Process a device which udev information indicates is unowned.
fn process_unowned_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) => {
                    warn!("udev identified device {} as a block device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                // FIXME: Refine error return in StaticHeader::setup(),
                // so it can be used to distinguish between signficant
                // and insignficant errors and then use that ability to
                // distinguish here between different levels of
                // severity.
                (_, Ok(Err(err))) => {
                    debug!("udev identified device {} as a block device but {}, disregarding the device",
                           devnode.display(),
                           err);
                    None
                }
                (_, Ok(Ok(None))) => None,
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a block device, but the udev entry for the device had no device node, disregarding the device");
            None
        }
    }
}

// Use udev to identify all block devices and return the subset of those
// that have Stratis signatures.
#[cfg(test)]
pub fn find_all_block_devices_with_stratis_signatures(
) -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_block_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });

    Ok(pool_map)
}

// Find all devices identified by udev as Stratis devices.
fn find_all_stratis_devices() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_stratis_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });
    Ok(pool_map)
}

// Identify a device that udev enumeration has already picked up as a Stratis
// device. Return None if the device does not, after all, appear to be a Stratis
// device. Log anything unusual at an appropriate level.
fn identify_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        warn!("Found a udev entry for a device identified as a Stratis device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!("Could not determine ownership of a block device identified as a Stratis device by udev, disregarding the device: {}",
                  err);
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::MultipathMember => None,
            _ => {
                warn!("udev enumeration identified this device as a Stratis block device but on further examination udev identifies it as a {}",
                      ownership);
                None
            }
        },
    }
    .map(|(identifiers, device, devnode)| {
        info!("Stratis block device with device number \"{}\", device node \"{}\", pool UUID \"{}\", and device UUID \"{}\" discovered during initial search",
              device,
              devnode.display(),
              identifiers.pool_uuid.to_simple_ref(),
              identifiers.device_uuid.to_simple_ref()
        );
        (identifiers, device, devnode)
    })
}

/// Identify a block device in the context where a udev event has been
/// captured for some block device. Return None if the device does not
/// appear to be a Stratis device. Log at an appropriate level on all errors.
pub fn identify_block_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        debug!("Found a udev entry for a device identified as a block device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!(
                "Could not determine ownership of a udev block device, disregarding the device: {}",
                err
            );
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::Unowned => process_unowned_device(dev),
            _ => None,
        },
    }
    .map(|(identifiers, device, devnode)| {
        info!("Stratis block device with device number \"{}\", device node \"{}\", pool UUID \"{}\", and device UUID \"{}\" discovered during initial search",
              device,
              devnode.display(),
              identifiers.pool_uuid.to_simple_ref(),
              identifiers.device_uuid.to_simple_ref()
        );
        (identifiers, device, devnode)
    })
}

/// Retrieve all block devices that should be made use of by the
/// Stratis engine. This excludes Stratis block devices that appear to be
/// multipath members.
///
/// Includes a fallback path, which is used if no Stratis block devices are
/// found using the obvious udev property- and enumerator-based approach.
/// This fallback path is more expensive, because it must search all block
/// devices via udev rather than just all Stratis block devices.
///
/// Omits any device that appears problematic in some way.
///
/// Return an error only on a failure to construct or scan with a udev
/// enumerator.
///
/// Returns a map of pool uuids to a map of devices to devnodes for each pool.
pub fn find_all() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    info!("Beginning initial search for Stratis block devices");
    find_all_stratis_devices()
}

#[cfg(test)]
mod tests {

    use uuid::Uuid;

    use crate::engine::strat_engine::{
        backstore::{
            devices::{initialize_devices, process_devices},
            metadata::MDADataSize,
            udev::block_device_apply,
        },
        cmd::create_fs,
        tests::{loopbacked, real},
    };

    use super::*;

    /// Test that the process_*_device methods return the expected
    /// pool UUID and device node for initialized paths.
    fn test_process_device_initialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        let pool_uuid = Uuid::new_v4();

        initialize_devices(
            process_devices(paths)
                .unwrap()
                .into_iter()
                .map(|(info, _)| info)
                .collect(),
            pool_uuid,
            MDADataSize::default(),
        )
        .unwrap();

        for path in paths {
            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);

            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);
        }
    }

    #[test]
    fn loop_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn real_test_process_device_initialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn travis_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    /// Test that the process_*_device methods return None if the device is
    /// not a Stratis device. Strictly speaking, the methods are only supposed
    /// to be called in particular contexts, the situation where the device
    /// is claimed by a filesystem should be excluded by udev, which should
    /// identify the device as Theirs. But the methods should return the
    /// correct result in this situation, regardless, although their log
    /// messages will not precisely match their actual situation, but rather
    /// their expected context.
    fn test_process_device_uninitialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        for path in paths {
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }

        for path in paths {
            create_fs(path, None).unwrap();
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }
    }

    #[test]
    fn loop_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn real_test_process_device_uninitialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn travis_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }
}
<commit_msg>Change log entry level and message in identify_block_device<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Discover or identify devices that may belong to Stratis.
//!
//! This module contains methods for finding all block devices that may belong
//! to Stratis, generally run when Stratis starts up, and other methods that
//! may be used to classify a single unknown block device.
//!
//! The methods rely to a greater or lesser extent on libudev.
//!
//! They have the following invocation heirarchy:
//! find_all*
//!  |
//! find_all_*_devices
//!  |
//! identify_*_device
//!  |
//! process_*_device
//!
//! The primary purpose of the find* methods is to construct a udev
//! enumeration and to properly process each of the udev database entries
//! found.
//!
//! The primary purpose of the identify_* methods is to use udev to identify
//! a single device and take the appropriate action based on that
//! identification.
//!
//! The primary purpose of the process_* methods is to gather up Stratis
//! device identifiers.
//!
//! Each method is expected to be invoked in a particular situation which
//! is guaranteed by the method that invokes it. The methods are not,
//! in general, general purpose methods that can be used in any situation.
//!
//! find_all is public because it is the method that is invoked by the
//! engine on startup. find_all_block_devices_with_stratis_signatures is
//! public for use in testing. identify_block_device is public because it
//! is suitable for identifying a block device associated with a uevent,
//! as the situation in which the uevent is handled is equivalent to that
//! provided by the execution of the
//! find_all_block_devices_with_stratis_signatures method.

use std::{
    collections::HashMap,
    fs::OpenOptions,
    path::{Path, PathBuf},
};

use devicemapper::Device;

use crate::engine::{
    strat_engine::backstore::{
        metadata::{device_identifiers, StratisIdentifiers},
        udev::{block_enumerator, decide_ownership, UdevOwnership},
    },
    types::PoolUuid,
};

// A miscellaneous group of identifiers found when identifiying a Stratis
// device.
pub type StratisInfo = (StratisIdentifiers, Device, PathBuf);

// A wrapper for obtaining the device number as a devicemapper Device
// which interprets absence of the value as an error, which it is in this
// context.
fn device_to_devno_wrapper(device: &libudev::Device) -> Result<Device, String> {
    device
        .devnum()
        .ok_or_else(|| "udev entry did not contain a device number".into())
        .map(Device::from)
}

// A wrapper around the metadata module's device_identifers method
// which also handles failure to open a device for reading.
// Returns an error if the device could not be opened for reading.
// Returns Ok(Err(...)) if there was an error while reading the
// Stratis identifiers from the device.
// Returns Ok(Ok(None)) if the identifers did not appear to be on
// the device.
fn device_identifiers_wrapper(
    devnode: &Path,
) -> Result<Result<Option<StratisIdentifiers>, String>, String> {
    OpenOptions::new()
        .read(true)
        .open(devnode)
        .as_mut()
        .map_err(|err| {
            format!(
                "device {} could not be opened for reading: {}",
                devnode.display(),
                err
            )
        })
        .map(|f| {
            device_identifiers(f).map_err(|err| {
                format!(
                    "encountered an error while reading Stratis header for device {}: {}",
                    devnode.display(),
                    err
                )
            })
        })
}

/// Process a device which udev information indicates is a Stratis device.
fn process_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) | (_, Ok(Err(err))) => {
                    warn!("udev identified device {} as a Stratis device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                (_, Ok(Ok(None))) => {
                    warn!("udev identified device {} as a Stratis device but there appeared to be no Stratis metadata on the device, disregarding the device",
                          devnode.display());
                    None
                }
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a Stratis device, but the udev entry for the device had no device node, disregarding device");
            None
        }
    }
}

/// Process a device which udev information indicates is unowned.
fn process_unowned_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) => {
                    warn!("udev identified device {} as a block device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                // FIXME: Refine error return in StaticHeader::setup(),
                // so it can be used to distinguish between signficant
                // and insignficant errors and then use that ability to
                // distinguish here between different levels of
                // severity.
                (_, Ok(Err(err))) => {
                    debug!("udev identified device {} as a block device but {}, disregarding the device",
                           devnode.display(),
                           err);
                    None
                }
                (_, Ok(Ok(None))) => None,
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a block device, but the udev entry for the device had no device node, disregarding the device");
            None
        }
    }
}

// Use udev to identify all block devices and return the subset of those
// that have Stratis signatures.
#[cfg(test)]
pub fn find_all_block_devices_with_stratis_signatures(
) -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_block_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });

    Ok(pool_map)
}

// Find all devices identified by udev as Stratis devices.
fn find_all_stratis_devices() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_stratis_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });
    Ok(pool_map)
}

// Identify a device that udev enumeration has already picked up as a Stratis
// device. Return None if the device does not, after all, appear to be a Stratis
// device. Log anything unusual at an appropriate level.
fn identify_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        warn!("Found a udev entry for a device identified as a Stratis device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!("Could not determine ownership of a block device identified as a Stratis device by udev, disregarding the device: {}",
                  err);
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::MultipathMember => None,
            _ => {
                warn!("udev enumeration identified this device as a Stratis block device but on further examination udev identifies it as a {}",
                      ownership);
                None
            }
        },
    }
    .map(|(identifiers, device, devnode)| {
        info!("Stratis block device with device number \"{}\", device node \"{}\", pool UUID \"{}\", and device UUID \"{}\" discovered during initial search",
              device,
              devnode.display(),
              identifiers.pool_uuid.to_simple_ref(),
              identifiers.device_uuid.to_simple_ref()
        );
        (identifiers, device, devnode)
    })
}

/// Identify a block device in the context where a udev event has been
/// captured for some block device. Return None if the device does not
/// appear to be a Stratis device. Log at an appropriate level on all errors.
pub fn identify_block_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        debug!("Found a udev entry for a device identified as a block device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!(
                "Could not determine ownership of a udev block device, disregarding the device: {}",
                err
            );
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::Unowned => process_unowned_device(dev),
            _ => None,
        },
    }
    .map(|(identifiers, device, devnode)| {
        debug!("Stratis block device with device number \"{}\", device node \"{}\", pool UUID \"{}\", and device UUID \"{}\" identified",
              device,
              devnode.display(),
              identifiers.pool_uuid.to_simple_ref(),
              identifiers.device_uuid.to_simple_ref()
        );
        (identifiers, device, devnode)
    })
}

/// Retrieve all block devices that should be made use of by the
/// Stratis engine. This excludes Stratis block devices that appear to be
/// multipath members.
///
/// Includes a fallback path, which is used if no Stratis block devices are
/// found using the obvious udev property- and enumerator-based approach.
/// This fallback path is more expensive, because it must search all block
/// devices via udev rather than just all Stratis block devices.
///
/// Omits any device that appears problematic in some way.
///
/// Return an error only on a failure to construct or scan with a udev
/// enumerator.
///
/// Returns a map of pool uuids to a map of devices to devnodes for each pool.
pub fn find_all() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    info!("Beginning initial search for Stratis block devices");
    find_all_stratis_devices()
}

#[cfg(test)]
mod tests {

    use uuid::Uuid;

    use crate::engine::strat_engine::{
        backstore::{
            devices::{initialize_devices, process_devices},
            metadata::MDADataSize,
            udev::block_device_apply,
        },
        cmd::create_fs,
        tests::{loopbacked, real},
    };

    use super::*;

    /// Test that the process_*_device methods return the expected
    /// pool UUID and device node for initialized paths.
    fn test_process_device_initialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        let pool_uuid = Uuid::new_v4();

        initialize_devices(
            process_devices(paths)
                .unwrap()
                .into_iter()
                .map(|(info, _)| info)
                .collect(),
            pool_uuid,
            MDADataSize::default(),
        )
        .unwrap();

        for path in paths {
            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);

            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);
        }
    }

    #[test]
    fn loop_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn real_test_process_device_initialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn travis_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    /// Test that the process_*_device methods return None if the device is
    /// not a Stratis device. Strictly speaking, the methods are only supposed
    /// to be called in particular contexts, the situation where the device
    /// is claimed by a filesystem should be excluded by udev, which should
    /// identify the device as Theirs. But the methods should return the
    /// correct result in this situation, regardless, although their log
    /// messages will not precisely match their actual situation, but rather
    /// their expected context.
    fn test_process_device_uninitialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        for path in paths {
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }

        for path in paths {
            create_fs(path, None).unwrap();
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }
    }

    #[test]
    fn loop_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn real_test_process_device_uninitialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn travis_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }
}
<|endoftext|>


<commit_before>extern crate byteorder;

use super::{NativeSqlType, FromSql, Nullable, Array};
use Queriable;
use row::Row;

use self::byteorder::{ReadBytesExt, BigEndian};
use std::error::Error;
use std::fmt;

macro_rules! primitive_impls {
    ($($Source:ident -> $Target:ty),+,) => {
        $(
            impl NativeSqlType for super::$Source {}

            impl Queriable<super::$Source> for $Target {
                type Row = Self;

                fn build(row: Self::Row) -> Self {
                    row
                }
            }
        )+
    }
}

primitive_impls! {
    Bool -> bool,

    SmallSerial -> i16,
    Serial -> i32,
    BigSerial -> i64,

    SmallInt -> i16,
    Integer -> i32,
    BigInt -> i64,

    Float -> f32,
    Double -> f64,

    VarChar -> String,

    Binary -> Vec<u8>,
}

impl FromSql<super::Bool> for bool {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(bytes) => Ok(bytes[0] != 0),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::SmallInt> for i16 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(mut bytes) => bytes.read_i16::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::SmallSerial> for i16 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        <Self as FromSql<super::SmallInt>>::from_sql(bytes)
    }
}

impl FromSql<super::Integer> for i32 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(mut bytes) => bytes.read_i32::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::Serial> for i32 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        <Self as FromSql<super::Integer>>::from_sql(bytes)
    }
}

impl FromSql<super::BigInt> for i64 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(mut bytes) => bytes.read_i64::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::BigSerial> for i64 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        <Self as FromSql<super::BigInt>>::from_sql(bytes)
    }
}

impl FromSql<super::Float> for f32 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(mut bytes) => bytes.read_f32::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::Double> for f64 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(mut bytes) => bytes.read_f64::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::VarChar> for String {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(bytes) => String::from_utf8(bytes.into()).map_err(|e| Box::new(e) as Box<Error>),
            None => unexpected_null(),
        }
    }
}

impl FromSql<super::Binary> for Vec<u8> {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(bytes) => Ok(bytes.into()),
            None => unexpected_null(),
        }
    }
}

impl<T: NativeSqlType> NativeSqlType for Nullable<T> {}
impl<T, ST> FromSql<Nullable<ST>> for Option<T> where
    T: FromSql<ST>,
    ST: NativeSqlType,
{
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(_) => T::from_sql(bytes).map(Some),
            None => Ok(None)
        }
    }
}

impl<T, ST> Queriable<Nullable<ST>> for Option<T> where
    T: FromSql<ST> + Queriable<ST>,
    ST: NativeSqlType,
{
    type Row = Self;
    fn build(row: Self) -> Self {
        row
    }
}

impl<T: NativeSqlType> NativeSqlType for Array<T> {}

impl<T, ST> FromSql<Array<ST>> for Vec<T> where
    T: FromSql<ST>,
    ST: NativeSqlType,
{
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(mut bytes) => {
                let num_dimensions = try!(bytes.read_i32::<BigEndian>());
                let has_null = try!(bytes.read_i32::<BigEndian>()) != 0;
                let _oid = try!(bytes.read_i32::<BigEndian>());
                let num_elements = try!(bytes.read_i32::<BigEndian>());
                let lower_bound = try!(bytes.read_i32::<BigEndian>());

                assert!(num_dimensions == 1, "multi-dimensional arrays are not supported");
                assert!(lower_bound == 1, "lower bound must be 1");

                (0..num_elements).map(|_| {
                    let elem_size = try!(bytes.read_i32::<BigEndian>());
                    if has_null && elem_size == -1 {
                        T::from_sql(None)
                    } else {
                        let (elem_bytes, new_bytes) = bytes.split_at(elem_size as usize);
                        bytes = new_bytes;
                        T::from_sql(Some(&elem_bytes))
                    }
                }).collect()
            },
            None => unexpected_null(),
        }
    }
}

impl<T, ST> Queriable<Array<ST>> for Vec<T> where
    T: FromSql<ST> + Queriable<ST>,
    ST: NativeSqlType,
{
    type Row = Self;
    fn build(row: Self) -> Self {
        row
    }
}

#[derive(Debug)]
struct UnexpectedNullError {
    msg: String,
}

impl fmt::Display for UnexpectedNullError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.msg)
    }
}

impl Error for UnexpectedNullError {
    fn description(&self) -> &str {
        &self.msg
    }
}

fn unexpected_null<T>() -> Result<T, Box<Error>> {
    Err(Box::new(UnexpectedNullError {
        msg: "Unexpected null for non-null column".to_string(),
    }))
}
<commit_msg>Decrease the indentation level of `FromSql` impls<commit_after>extern crate byteorder;

use super::{NativeSqlType, FromSql, Nullable, Array};
use Queriable;
use row::Row;

use self::byteorder::{ReadBytesExt, BigEndian};
use std::error::Error;
use std::fmt;

macro_rules! primitive_impls {
    ($($Source:ident -> $Target:ty),+,) => {
        $(
            impl NativeSqlType for super::$Source {}

            impl Queriable<super::$Source> for $Target {
                type Row = Self;

                fn build(row: Self::Row) -> Self {
                    row
                }
            }
        )+
    }
}

primitive_impls! {
    Bool -> bool,

    SmallSerial -> i16,
    Serial -> i32,
    BigSerial -> i64,

    SmallInt -> i16,
    Integer -> i32,
    BigInt -> i64,

    Float -> f32,
    Double -> f64,

    VarChar -> String,

    Binary -> Vec<u8>,
}

macro_rules! not_none {
    ($bytes:expr) => {
        match $bytes {
            Some(bytes) => bytes,
            None => return Err(Box::new(UnexpectedNullError {
                msg: "Unexpected null for non-null column".to_string(),
            })),
        }
    }
}

impl FromSql<super::Bool> for bool {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let bytes = not_none!(bytes);
        Ok(bytes[0] != 0)
    }
}

impl FromSql<super::SmallInt> for i16 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let mut bytes = not_none!(bytes);
        bytes.read_i16::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>)
    }
}

impl FromSql<super::SmallSerial> for i16 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        <Self as FromSql<super::SmallInt>>::from_sql(bytes)
    }
}

impl FromSql<super::Integer> for i32 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let mut bytes = not_none!(bytes);
        bytes.read_i32::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>)
    }
}

impl FromSql<super::Serial> for i32 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        <Self as FromSql<super::Integer>>::from_sql(bytes)
    }
}

impl FromSql<super::BigInt> for i64 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let mut bytes = not_none!(bytes);
        bytes.read_i64::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>)
    }
}

impl FromSql<super::BigSerial> for i64 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        <Self as FromSql<super::BigInt>>::from_sql(bytes)
    }
}

impl FromSql<super::Float> for f32 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let mut bytes = not_none!(bytes);
        bytes.read_f32::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>)
    }
}

impl FromSql<super::Double> for f64 {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let mut bytes = not_none!(bytes);
        bytes.read_f64::<BigEndian>().map_err(|e| Box::new(e) as Box<Error>)
    }
}

impl FromSql<super::VarChar> for String {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let bytes = not_none!(bytes);
        String::from_utf8(bytes.into()).map_err(|e| Box::new(e) as Box<Error>)
    }
}

impl FromSql<super::Binary> for Vec<u8> {
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        Ok(not_none!(bytes).into())
    }
}

impl<T: NativeSqlType> NativeSqlType for Nullable<T> {}
impl<T, ST> FromSql<Nullable<ST>> for Option<T> where
    T: FromSql<ST>,
    ST: NativeSqlType,
{
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        match bytes {
            Some(_) => T::from_sql(bytes).map(Some),
            None => Ok(None)
        }
    }
}

impl<T, ST> Queriable<Nullable<ST>> for Option<T> where
    T: FromSql<ST> + Queriable<ST>,
    ST: NativeSqlType,
{
    type Row = Self;
    fn build(row: Self) -> Self {
        row
    }
}

impl<T: NativeSqlType> NativeSqlType for Array<T> {}

impl<T, ST> FromSql<Array<ST>> for Vec<T> where
    T: FromSql<ST>,
    ST: NativeSqlType,
{
    fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error>> {
        let mut bytes = not_none!(bytes);
        let num_dimensions = try!(bytes.read_i32::<BigEndian>());
        let has_null = try!(bytes.read_i32::<BigEndian>()) != 0;
        let _oid = try!(bytes.read_i32::<BigEndian>());
        let num_elements = try!(bytes.read_i32::<BigEndian>());
        let lower_bound = try!(bytes.read_i32::<BigEndian>());

        assert!(num_dimensions == 1, "multi-dimensional arrays are not supported");
        assert!(lower_bound == 1, "lower bound must be 1");

        (0..num_elements).map(|_| {
            let elem_size = try!(bytes.read_i32::<BigEndian>());
            if has_null && elem_size == -1 {
                T::from_sql(None)
            } else {
                let (elem_bytes, new_bytes) = bytes.split_at(elem_size as usize);
                bytes = new_bytes;
                T::from_sql(Some(&elem_bytes))
            }
        }).collect()
    }
}

impl<T, ST> Queriable<Array<ST>> for Vec<T> where
    T: FromSql<ST> + Queriable<ST>,
    ST: NativeSqlType,
{
    type Row = Self;
    fn build(row: Self) -> Self {
        row
    }
}

#[derive(Debug)]
struct UnexpectedNullError {
    msg: String,
}

impl fmt::Display for UnexpectedNullError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.msg)
    }
}

impl Error for UnexpectedNullError {
    fn description(&self) -> &str {
        &self.msg
    }
}
<|endoftext|>


<commit_before>// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.

// Code that generates a test runner to run all the tests in a crate

use driver::session;
use front::config;

use syntax::ast_util::*;
use syntax::attr;
use syntax::codemap::{dummy_sp, span, ExpandedFrom, CallInfo, NameAndSpan};
use syntax::codemap;
use syntax::ext::base::{mk_ctxt, ext_ctxt};
use syntax::fold;
use syntax::print::pprust;
use syntax::{ast, ast_util};

type node_id_gen = @fn() -> ast::node_id;

struct Test {
    span: span,
    path: ~[ast::ident],
    bench: bool,
    ignore: bool,
    should_fail: bool
}

struct TestCtxt {
    sess: session::Session,
    crate: @ast::crate,
    path: ~[ast::ident],
    ext_cx: @ext_ctxt,
    testfns: ~[Test]
}

// Traverse the crate, collecting all the test functions, eliding any
// existing main functions, and synthesizing a main test harness
pub fn modify_for_testing(sess: session::Session,
                          crate: @ast::crate)
                       -> @ast::crate {
    // We generate the test harness when building in the 'test'
    // configuration, either with the '--test' or '--cfg test'
    // command line options.
    let should_test = attr::contains(crate.node.config,
                                     attr::mk_word_item(@~"test"));

    if should_test {
        generate_test_harness(sess, crate)
    } else {
        strip_test_functions(crate)
    }
}

fn generate_test_harness(sess: session::Session,
                         crate: @ast::crate)
                      -> @ast::crate {
    let cx: @mut TestCtxt = @mut TestCtxt {
        sess: sess,
        crate: crate,
        ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
        path: ~[],
        testfns: ~[]
    };

    cx.ext_cx.bt_push(ExpandedFrom(CallInfo {
        call_site: dummy_sp(),
        callee: NameAndSpan {
            name: ~"test",
            span: None
        }
    }));

    let precursor = @fold::AstFoldFns {
        fold_crate: fold::wrap(|a,b| fold_crate(cx, a, b) ),
        fold_item: |a,b| fold_item(cx, a, b),
        fold_mod: |a,b| fold_mod(cx, a, b),.. *fold::default_ast_fold()};

    let fold = fold::make_fold(precursor);
    let res = @fold.fold_crate(&*crate);
    cx.ext_cx.bt_pop();
    return res;
}

fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
    // When not compiling with --test we should not compile the
    // #[test] functions
    do config::strip_items(crate) |attrs| {
        !attr::contains_name(attr::attr_metas(attrs), ~"test") &&
        !attr::contains_name(attr::attr_metas(attrs), ~"bench")
    }
}

fn fold_mod(cx: @mut TestCtxt,
            m: &ast::_mod,
            fld: @fold::ast_fold)
         -> ast::_mod {
    // Remove any #[main] from the AST so it doesn't clash with
    // the one we're going to add. Only if compiling an executable.

    fn nomain(cx: @mut TestCtxt, item: @ast::item) -> @ast::item {
        if !*cx.sess.building_library {
            @ast::item{attrs: item.attrs.filtered(|attr| {
                               *attr::get_attr_name(attr) != ~"main"
                           }),.. copy *item}
        } else { item }
    }

    let mod_nomain = ast::_mod {
        view_items: /*bad*/copy m.view_items,
        items: vec::map(m.items, |i| nomain(cx, *i)),
    };

    fold::noop_fold_mod(&mod_nomain, fld)
}

fn fold_crate(cx: @mut TestCtxt,
              c: &ast::crate_,
              fld: @fold::ast_fold)
           -> ast::crate_ {
    let folded = fold::noop_fold_crate(c, fld);

    // Add a special __test module to the crate that will contain code
    // generated for the test harness
    ast::crate_ {
        module: add_test_module(cx, &folded.module),
        .. folded
    }
}

fn fold_item(cx: @mut TestCtxt, i: @ast::item, fld: @fold::ast_fold)
          -> Option<@ast::item> {
    cx.path.push(i.ident);
    debug!("current path: %s",
           ast_util::path_name_i(copy cx.path, cx.sess.parse_sess.interner));

    if is_test_fn(i) || is_bench_fn(i) {
        match i.node {
          ast::item_fn(_, purity, _, _, _) if purity == ast::unsafe_fn => {
            let sess = cx.sess;
            sess.span_fatal(
                i.span,
                ~"unsafe functions cannot be used for tests");
          }
          _ => {
            debug!("this is a test function");
            let test = Test {
                span: i.span,
                path: /*bad*/copy cx.path,
                bench: is_bench_fn(i),
                ignore: is_ignored(cx, i),
                should_fail: should_fail(i)
            };
            cx.testfns.push(test);
            // debug!("have %u test/bench functions", cx.testfns.len());
          }
        }
    }

    let res = fold::noop_fold_item(i, fld);
    cx.path.pop();
    return res;
}

fn is_test_fn(i: @ast::item) -> bool {
    let has_test_attr = !attr::find_attrs_by_name(i.attrs,
                                                  ~"test").is_empty();

    fn has_test_signature(i: @ast::item) -> bool {
        match &i.node {
          &ast::item_fn(ref decl, _, _, ref generics, _) => {
            let no_output = match decl.output.node {
                ast::ty_nil => true,
                _ => false
            };
            decl.inputs.is_empty()
                && no_output
                && !generics.is_parameterized()
          }
          _ => false
        }
    }

    return has_test_attr && has_test_signature(i);
}

fn is_bench_fn(i: @ast::item) -> bool {
    let has_bench_attr =
        vec::len(attr::find_attrs_by_name(i.attrs, ~"bench")) > 0u;

    fn has_test_signature(i: @ast::item) -> bool {
        match i.node {
            ast::item_fn(ref decl, _, _, ref generics, _) => {
                let input_cnt = vec::len(decl.inputs);
                let no_output = match decl.output.node {
                    ast::ty_nil => true,
                    _ => false
                };
                let tparm_cnt = generics.ty_params.len();
                // NB: inadequate check, but we're running
                // well before resolve, can't get too deep.
                input_cnt == 1u
                    && no_output && tparm_cnt == 0u
            }
          _ => false
        }
    }

    return has_bench_attr && has_test_signature(i);
}

fn is_ignored(cx: @mut TestCtxt, i: @ast::item) -> bool {
    let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
    let ignoreitems = attr::attr_metas(ignoreattrs);
    return if !ignoreitems.is_empty() {
        let cfg_metas =
            vec::concat(
                vec::filter_map(ignoreitems,
                                |i| attr::get_meta_item_list(i)));
        config::metas_in_cfg(/*bad*/copy cx.crate.node.config, cfg_metas)
    } else {
        false
    }
}

fn should_fail(i: @ast::item) -> bool {
    vec::len(attr::find_attrs_by_name(i.attrs, ~"should_fail")) > 0u
}

fn add_test_module(cx: &TestCtxt, m: &ast::_mod) -> ast::_mod {
    let testmod = mk_test_module(cx);
    ast::_mod {
        items: vec::append_one(/*bad*/copy m.items, testmod),
        .. /*bad*/ copy *m
    }
}

/*

We're going to be building a module that looks more or less like:

mod __test {
  #[!resolve_unexported]
  extern mod std (name = "std", vers = "...");
  fn main() {
    #[main];
    std::test::test_main_static(::os::args(), tests)
  }

  static tests : &'static [std::test::TestDescAndFn] = &[
    ... the list of tests in the crate ...
  ];
}

*/

fn mk_std(cx: &TestCtxt) -> @ast::view_item {
    let vers = ast::lit_str(@~"0.7-pre");
    let vers = nospan(vers);
    let mi = ast::meta_name_value(@~"vers", vers);
    let mi = nospan(mi);
    let id_std = cx.sess.ident_of(~"std");
    let vi = if is_std(cx) {
        ast::view_item_use(
            ~[@nospan(ast::view_path_simple(id_std,
                                            path_node(~[id_std]),
                                            cx.sess.next_node_id()))])
    } else {
        ast::view_item_extern_mod(id_std, ~[@mi],
                           cx.sess.next_node_id())
    };
    let vi = ast::view_item {
        node: vi,
        attrs: ~[],
        vis: ast::public,
        span: dummy_sp()
    };
    return @vi;
}

fn mk_test_module(cx: &TestCtxt) -> @ast::item {

    // Link to std
    let view_items = ~[mk_std(cx)];

    // A constant vector of test descriptors.
    let tests = mk_tests(cx);

    // The synthesized main function which will call the console test runner
    // with our list of tests
    let ext_cx = cx.ext_cx;
    let mainfn = (quote_item!(
        pub fn main() {
            #[main];
            std::test::test_main_static(::os::args(), tests);
        }
    )).get();

    let testmod = ast::_mod {
        view_items: view_items,
        items: ~[mainfn, tests],
    };
    let item_ = ast::item_mod(testmod);

    // This attribute tells resolve to let us call unexported functions
    let resolve_unexported_attr =
        attr::mk_attr(attr::mk_word_item(@~"!resolve_unexported"));

    let item = ast::item {
        ident: cx.sess.ident_of(~"__test"),
        attrs: ~[resolve_unexported_attr],
        id: cx.sess.next_node_id(),
        node: item_,
        vis: ast::public,
        span: dummy_sp(),
     };

    debug!("Synthetic test module:\n%s\n",
           pprust::item_to_str(@copy item, cx.sess.intr()));

    return @item;
}

fn nospan<T:Copy>(t: T) -> codemap::spanned<T> {
    codemap::spanned { node: t, span: dummy_sp() }
}

fn path_node(ids: ~[ast::ident]) -> @ast::Path {
    @ast::Path { span: dummy_sp(),
                global: false,
                idents: ids,
                rp: None,
                types: ~[] }
}

fn path_node_global(ids: ~[ast::ident]) -> @ast::Path {
    @ast::Path { span: dummy_sp(),
                 global: true,
                 idents: ids,
                 rp: None,
                 types: ~[] }
}

fn mk_tests(cx: &TestCtxt) -> @ast::item {

    let ext_cx = cx.ext_cx;

    // The vector of test_descs for this crate
    let test_descs = mk_test_descs(cx);

    (quote_item!(
        pub static tests : &'static [self::std::test::TestDescAndFn] =
            $test_descs
        ;
    )).get()
}

fn is_std(cx: &TestCtxt) -> bool {
    let is_std = {
        let items = attr::find_linkage_metas(cx.crate.node.attrs);
        match attr::last_meta_item_value_str_by_name(items, ~"name") {
          Some(@~"std") => true,
          _ => false
        }
    };
    return is_std;
}

fn mk_test_descs(cx: &TestCtxt) -> @ast::expr {
    debug!("building test vector from %u tests", cx.testfns.len());
    let mut descs = ~[];
    for cx.testfns.each |test| {
        descs.push(mk_test_desc_and_fn_rec(cx, test));
    }

    let sess = cx.sess;
    let inner_expr = @ast::expr {
        id: sess.next_node_id(),
        callee_id: sess.next_node_id(),
        node: ast::expr_vec(descs, ast::m_imm),
        span: dummy_sp(),
    };

    @ast::expr {
        id: sess.next_node_id(),
        callee_id: sess.next_node_id(),
        node: ast::expr_vstore(inner_expr, ast::expr_vstore_slice),
        span: dummy_sp(),
    }
}

fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> @ast::expr {
    let span = test.span;
    let path = /*bad*/copy test.path;

    let ext_cx = cx.ext_cx;

    debug!("encoding %s", ast_util::path_name_i(path,
                                                cx.sess.parse_sess.interner));

    let name_lit: ast::lit =
        nospan(ast::lit_str(@ast_util::path_name_i(
            path,
            cx.sess.parse_sess.interner)));

    let name_expr = @ast::expr {
          id: cx.sess.next_node_id(),
          callee_id: cx.sess.next_node_id(),
          node: ast::expr_lit(@name_lit),
          span: span
    };

    let fn_path = path_node_global(path);

    let fn_expr = @ast::expr {
        id: cx.sess.next_node_id(),
        callee_id: cx.sess.next_node_id(),
        node: ast::expr_path(fn_path),
        span: span,
    };

    let t_expr = if test.bench {
        quote_expr!( self::std::test::StaticBenchFn($fn_expr) )
    } else {
        quote_expr!( self::std::test::StaticTestFn($fn_expr) )
    };

    let ignore_expr = if test.ignore {
        quote_expr!( true )
    } else {
        quote_expr!( false )
    };

    let fail_expr = if test.should_fail {
        quote_expr!( true )
    } else {
        quote_expr!( false )
    };

    let e = quote_expr!(
        self::std::test::TestDescAndFn {
            desc: self::std::test::TestDesc {
                name: self::std::test::StaticTestName($name_expr),
                ignore: $ignore_expr,
                should_fail: $fail_expr
            },
            testfn: $t_expr,
        }
    );
    e
}
<commit_msg>Issue 4391: rustc should not silently skip tests with erroneous signature.<commit_after>// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.

// Code that generates a test runner to run all the tests in a crate

use driver::session;
use front::config;

use syntax::ast_util::*;
use syntax::attr;
use syntax::codemap::{dummy_sp, span, ExpandedFrom, CallInfo, NameAndSpan};
use syntax::codemap;
use syntax::ext::base::{mk_ctxt, ext_ctxt};
use syntax::fold;
use syntax::print::pprust;
use syntax::{ast, ast_util};

type node_id_gen = @fn() -> ast::node_id;

struct Test {
    span: span,
    path: ~[ast::ident],
    bench: bool,
    ignore: bool,
    should_fail: bool
}

struct TestCtxt {
    sess: session::Session,
    crate: @ast::crate,
    path: ~[ast::ident],
    ext_cx: @ext_ctxt,
    testfns: ~[Test]
}

// Traverse the crate, collecting all the test functions, eliding any
// existing main functions, and synthesizing a main test harness
pub fn modify_for_testing(sess: session::Session,
                          crate: @ast::crate)
                       -> @ast::crate {
    // We generate the test harness when building in the 'test'
    // configuration, either with the '--test' or '--cfg test'
    // command line options.
    let should_test = attr::contains(crate.node.config,
                                     attr::mk_word_item(@~"test"));

    if should_test {
        generate_test_harness(sess, crate)
    } else {
        strip_test_functions(crate)
    }
}

fn generate_test_harness(sess: session::Session,
                         crate: @ast::crate)
                      -> @ast::crate {
    let cx: @mut TestCtxt = @mut TestCtxt {
        sess: sess,
        crate: crate,
        ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
        path: ~[],
        testfns: ~[]
    };

    cx.ext_cx.bt_push(ExpandedFrom(CallInfo {
        call_site: dummy_sp(),
        callee: NameAndSpan {
            name: ~"test",
            span: None
        }
    }));

    let precursor = @fold::AstFoldFns {
        fold_crate: fold::wrap(|a,b| fold_crate(cx, a, b) ),
        fold_item: |a,b| fold_item(cx, a, b),
        fold_mod: |a,b| fold_mod(cx, a, b),.. *fold::default_ast_fold()};

    let fold = fold::make_fold(precursor);
    let res = @fold.fold_crate(&*crate);
    cx.ext_cx.bt_pop();
    return res;
}

fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
    // When not compiling with --test we should not compile the
    // #[test] functions
    do config::strip_items(crate) |attrs| {
        !attr::contains_name(attr::attr_metas(attrs), ~"test") &&
        !attr::contains_name(attr::attr_metas(attrs), ~"bench")
    }
}

fn fold_mod(cx: @mut TestCtxt,
            m: &ast::_mod,
            fld: @fold::ast_fold)
         -> ast::_mod {
    // Remove any #[main] from the AST so it doesn't clash with
    // the one we're going to add. Only if compiling an executable.

    fn nomain(cx: @mut TestCtxt, item: @ast::item) -> @ast::item {
        if !*cx.sess.building_library {
            @ast::item{attrs: item.attrs.filtered(|attr| {
                               *attr::get_attr_name(attr) != ~"main"
                           }),.. copy *item}
        } else { item }
    }

    let mod_nomain = ast::_mod {
        view_items: /*bad*/copy m.view_items,
        items: vec::map(m.items, |i| nomain(cx, *i)),
    };

    fold::noop_fold_mod(&mod_nomain, fld)
}

fn fold_crate(cx: @mut TestCtxt,
              c: &ast::crate_,
              fld: @fold::ast_fold)
           -> ast::crate_ {
    let folded = fold::noop_fold_crate(c, fld);

    // Add a special __test module to the crate that will contain code
    // generated for the test harness
    ast::crate_ {
        module: add_test_module(cx, &folded.module),
        .. folded
    }
}

fn fold_item(cx: @mut TestCtxt, i: @ast::item, fld: @fold::ast_fold)
          -> Option<@ast::item> {
    cx.path.push(i.ident);
    debug!("current path: %s",
           ast_util::path_name_i(copy cx.path, cx.sess.parse_sess.interner));

    if is_test_fn(cx, i) || is_bench_fn(i) {
        match i.node {
          ast::item_fn(_, purity, _, _, _) if purity == ast::unsafe_fn => {
            let sess = cx.sess;
            sess.span_fatal(
                i.span,
                ~"unsafe functions cannot be used for tests");
          }
          _ => {
            debug!("this is a test function");
            let test = Test {
                span: i.span,
                path: /*bad*/copy cx.path,
                bench: is_bench_fn(i),
                ignore: is_ignored(cx, i),
                should_fail: should_fail(i)
            };
            cx.testfns.push(test);
            // debug!("have %u test/bench functions", cx.testfns.len());
          }
        }
    }

    let res = fold::noop_fold_item(i, fld);
    cx.path.pop();
    return res;
}

fn is_test_fn(cx: @mut TestCtxt, i: @ast::item) -> bool {
    let has_test_attr = !attr::find_attrs_by_name(i.attrs,
                                                  ~"test").is_empty();

    fn has_test_signature(i: @ast::item) -> bool {
        match &i.node {
          &ast::item_fn(ref decl, _, _, ref generics, _) => {
            let no_output = match decl.output.node {
                ast::ty_nil => true,
                _ => false
            };
            decl.inputs.is_empty()
                && no_output
                && !generics.is_parameterized()
          }
          _ => false
        }
    }

    if has_test_attr && !has_test_signature(i) {
        let sess = cx.sess;
        sess.span_fatal(
            i.span,
            ~"functions used as tests must have signature fn() -> ()."
        );
    }
    return has_test_attr && has_test_signature(i);
}

fn is_bench_fn(i: @ast::item) -> bool {
    let has_bench_attr =
        vec::len(attr::find_attrs_by_name(i.attrs, ~"bench")) > 0u;

    fn has_test_signature(i: @ast::item) -> bool {
        match i.node {
            ast::item_fn(ref decl, _, _, ref generics, _) => {
                let input_cnt = vec::len(decl.inputs);
                let no_output = match decl.output.node {
                    ast::ty_nil => true,
                    _ => false
                };
                let tparm_cnt = generics.ty_params.len();
                // NB: inadequate check, but we're running
                // well before resolve, can't get too deep.
                input_cnt == 1u
                    && no_output && tparm_cnt == 0u
            }
          _ => false
        }
    }

    return has_bench_attr && has_test_signature(i);
}

fn is_ignored(cx: @mut TestCtxt, i: @ast::item) -> bool {
    let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
    let ignoreitems = attr::attr_metas(ignoreattrs);
    return if !ignoreitems.is_empty() {
        let cfg_metas =
            vec::concat(
                vec::filter_map(ignoreitems,
                                |i| attr::get_meta_item_list(i)));
        config::metas_in_cfg(/*bad*/copy cx.crate.node.config, cfg_metas)
    } else {
        false
    }
}

fn should_fail(i: @ast::item) -> bool {
    vec::len(attr::find_attrs_by_name(i.attrs, ~"should_fail")) > 0u
}

fn add_test_module(cx: &TestCtxt, m: &ast::_mod) -> ast::_mod {
    let testmod = mk_test_module(cx);
    ast::_mod {
        items: vec::append_one(/*bad*/copy m.items, testmod),
        .. /*bad*/ copy *m
    }
}

/*

We're going to be building a module that looks more or less like:

mod __test {
  #[!resolve_unexported]
  extern mod std (name = "std", vers = "...");
  fn main() {
    #[main];
    std::test::test_main_static(::os::args(), tests)
  }

  static tests : &'static [std::test::TestDescAndFn] = &[
    ... the list of tests in the crate ...
  ];
}

*/

fn mk_std(cx: &TestCtxt) -> @ast::view_item {
    let vers = ast::lit_str(@~"0.7-pre");
    let vers = nospan(vers);
    let mi = ast::meta_name_value(@~"vers", vers);
    let mi = nospan(mi);
    let id_std = cx.sess.ident_of(~"std");
    let vi = if is_std(cx) {
        ast::view_item_use(
            ~[@nospan(ast::view_path_simple(id_std,
                                            path_node(~[id_std]),
                                            cx.sess.next_node_id()))])
    } else {
        ast::view_item_extern_mod(id_std, ~[@mi],
                           cx.sess.next_node_id())
    };
    let vi = ast::view_item {
        node: vi,
        attrs: ~[],
        vis: ast::public,
        span: dummy_sp()
    };
    return @vi;
}

fn mk_test_module(cx: &TestCtxt) -> @ast::item {

    // Link to std
    let view_items = ~[mk_std(cx)];

    // A constant vector of test descriptors.
    let tests = mk_tests(cx);

    // The synthesized main function which will call the console test runner
    // with our list of tests
    let ext_cx = cx.ext_cx;
    let mainfn = (quote_item!(
        pub fn main() {
            #[main];
            std::test::test_main_static(::os::args(), tests);
        }
    )).get();

    let testmod = ast::_mod {
        view_items: view_items,
        items: ~[mainfn, tests],
    };
    let item_ = ast::item_mod(testmod);

    // This attribute tells resolve to let us call unexported functions
    let resolve_unexported_attr =
        attr::mk_attr(attr::mk_word_item(@~"!resolve_unexported"));

    let item = ast::item {
        ident: cx.sess.ident_of(~"__test"),
        attrs: ~[resolve_unexported_attr],
        id: cx.sess.next_node_id(),
        node: item_,
        vis: ast::public,
        span: dummy_sp(),
     };

    debug!("Synthetic test module:\n%s\n",
           pprust::item_to_str(@copy item, cx.sess.intr()));

    return @item;
}

fn nospan<T:Copy>(t: T) -> codemap::spanned<T> {
    codemap::spanned { node: t, span: dummy_sp() }
}

fn path_node(ids: ~[ast::ident]) -> @ast::Path {
    @ast::Path { span: dummy_sp(),
                global: false,
                idents: ids,
                rp: None,
                types: ~[] }
}

fn path_node_global(ids: ~[ast::ident]) -> @ast::Path {
    @ast::Path { span: dummy_sp(),
                 global: true,
                 idents: ids,
                 rp: None,
                 types: ~[] }
}

fn mk_tests(cx: &TestCtxt) -> @ast::item {

    let ext_cx = cx.ext_cx;

    // The vector of test_descs for this crate
    let test_descs = mk_test_descs(cx);

    (quote_item!(
        pub static tests : &'static [self::std::test::TestDescAndFn] =
            $test_descs
        ;
    )).get()
}

fn is_std(cx: &TestCtxt) -> bool {
    let is_std = {
        let items = attr::find_linkage_metas(cx.crate.node.attrs);
        match attr::last_meta_item_value_str_by_name(items, ~"name") {
          Some(@~"std") => true,
          _ => false
        }
    };
    return is_std;
}

fn mk_test_descs(cx: &TestCtxt) -> @ast::expr {
    debug!("building test vector from %u tests", cx.testfns.len());
    let mut descs = ~[];
    for cx.testfns.each |test| {
        descs.push(mk_test_desc_and_fn_rec(cx, test));
    }

    let sess = cx.sess;
    let inner_expr = @ast::expr {
        id: sess.next_node_id(),
        callee_id: sess.next_node_id(),
        node: ast::expr_vec(descs, ast::m_imm),
        span: dummy_sp(),
    };

    @ast::expr {
        id: sess.next_node_id(),
        callee_id: sess.next_node_id(),
        node: ast::expr_vstore(inner_expr, ast::expr_vstore_slice),
        span: dummy_sp(),
    }
}

fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> @ast::expr {
    let span = test.span;
    let path = /*bad*/copy test.path;

    let ext_cx = cx.ext_cx;

    debug!("encoding %s", ast_util::path_name_i(path,
                                                cx.sess.parse_sess.interner));

    let name_lit: ast::lit =
        nospan(ast::lit_str(@ast_util::path_name_i(
            path,
            cx.sess.parse_sess.interner)));

    let name_expr = @ast::expr {
          id: cx.sess.next_node_id(),
          callee_id: cx.sess.next_node_id(),
          node: ast::expr_lit(@name_lit),
          span: span
    };

    let fn_path = path_node_global(path);

    let fn_expr = @ast::expr {
        id: cx.sess.next_node_id(),
        callee_id: cx.sess.next_node_id(),
        node: ast::expr_path(fn_path),
        span: span,
    };

    let t_expr = if test.bench {
        quote_expr!( self::std::test::StaticBenchFn($fn_expr) )
    } else {
        quote_expr!( self::std::test::StaticTestFn($fn_expr) )
    };

    let ignore_expr = if test.ignore {
        quote_expr!( true )
    } else {
        quote_expr!( false )
    };

    let fail_expr = if test.should_fail {
        quote_expr!( true )
    } else {
        quote_expr!( false )
    };

    let e = quote_expr!(
        self::std::test::TestDescAndFn {
            desc: self::std::test::TestDesc {
                name: self::std::test::StaticTestName($name_expr),
                ignore: $ignore_expr,
                should_fail: $fail_expr
            },
            testfn: $t_expr,
        }
    );
    e
}
<|endoftext|>


<commit_before>//! System bindings for the wasm/web platform
//!
//! This module contains the facade (aka platform-specific) implementations of
//! OS level functionality for wasm. Note that this wasm is *not* the emscripten
//! wasm, so we have no runtime here.
//!
//! This is all super highly experimental and not actually intended for
//! wide/production use yet, it's still all in the experimental category. This
//! will likely change over time.
//!
//! Currently all functions here are basically stubs that immediately return
//! errors. The hope is that with a portability lint we can turn actually just
//! remove all this and just omit parts of the standard library if we're
//! compiling for wasm. That way it's a compile time error for something that's
//! guaranteed to be a runtime error!

use libc;
use crate::io::{Error, ErrorKind};
use crate::mem;
use crate::os::raw::c_char;

pub mod alloc;
pub mod args;
#[path = "../wasm/cmath.rs"]
pub mod cmath;
#[path = "../wasm/condvar.rs"]
pub mod condvar;
pub mod env;
pub mod fd;
pub mod fs;
#[path = "../wasm/memchr.rs"]
pub mod memchr;
#[path = "../wasm/mutex.rs"]
pub mod mutex;
pub mod net;
pub mod io;
pub mod os;
pub use crate::sys_common::os_str_bytes as os_str;
pub mod path;
pub mod pipe;
pub mod process;
#[path = "../wasm/rwlock.rs"]
pub mod rwlock;
#[path = "../wasm/stack_overflow.rs"]
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
#[path = "../wasm/thread_local.rs"]
pub mod thread_local;
#[path = "../wasm/fast_thread_local.rs"]
pub mod fast_thread_local;
pub mod time;
pub mod ext;

#[cfg(not(test))]
pub fn init() {
}

pub fn unsupported<T>() -> crate::io::Result<T> {
    Err(unsupported_err())
}

pub fn unsupported_err() -> Error {
    Error::new(ErrorKind::Other, "operation not supported on wasm yet")
}

pub fn decode_error_kind(_code: i32) -> ErrorKind {
    ErrorKind::Other
}

// This enum is used as the storage for a bunch of types which can't actually
// exist.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum Void {}

pub unsafe fn strlen(mut s: *const c_char) -> usize {
    let mut n = 0;
    while *s != 0 {
        n += 1;
        s = s.offset(1);
    }
    return n
}

pub unsafe fn abort_internal() -> ! {
    libc::abort()
}

pub fn hashmap_random_keys() -> (u64, u64) {
    let mut ret = (0u64, 0u64);
    unsafe {
        let base = &mut ret as *mut (u64, u64) as *mut libc::c_void;
        let len = mem::size_of_val(&ret);
        cvt_wasi(libc::__wasi_random_get(base, len)).unwrap();
    }
    return ret
}

#[doc(hidden)]
pub trait IsMinusOne {
    fn is_minus_one(&self) -> bool;
}

macro_rules! impl_is_minus_one {
    ($($t:ident)*) => ($(impl IsMinusOne for $t {
        fn is_minus_one(&self) -> bool {
            *self == -1
        }
    })*)
}

impl_is_minus_one! { i8 i16 i32 i64 isize }

pub fn cvt<T: IsMinusOne>(t: T) -> crate::io::Result<T> {
    if t.is_minus_one() {
        Err(Error::last_os_error())
    } else {
        Ok(t)
    }
}

pub fn cvt_wasi(r: u16) -> crate::io::Result<()> {
    if r != libc::__WASI_ESUCCESS {
        Err(Error::from_raw_os_error(r as i32))
    } else {
        Ok(())
    }
}
<commit_msg>Implement decode_error_kind for wasi<commit_after>//! System bindings for the wasm/web platform
//!
//! This module contains the facade (aka platform-specific) implementations of
//! OS level functionality for wasm. Note that this wasm is *not* the emscripten
//! wasm, so we have no runtime here.
//!
//! This is all super highly experimental and not actually intended for
//! wide/production use yet, it's still all in the experimental category. This
//! will likely change over time.
//!
//! Currently all functions here are basically stubs that immediately return
//! errors. The hope is that with a portability lint we can turn actually just
//! remove all this and just omit parts of the standard library if we're
//! compiling for wasm. That way it's a compile time error for something that's
//! guaranteed to be a runtime error!

use libc;
use crate::io::{Error, ErrorKind};
use crate::mem;
use crate::os::raw::c_char;

pub mod alloc;
pub mod args;
#[path = "../wasm/cmath.rs"]
pub mod cmath;
#[path = "../wasm/condvar.rs"]
pub mod condvar;
pub mod env;
pub mod fd;
pub mod fs;
#[path = "../wasm/memchr.rs"]
pub mod memchr;
#[path = "../wasm/mutex.rs"]
pub mod mutex;
pub mod net;
pub mod io;
pub mod os;
pub use crate::sys_common::os_str_bytes as os_str;
pub mod path;
pub mod pipe;
pub mod process;
#[path = "../wasm/rwlock.rs"]
pub mod rwlock;
#[path = "../wasm/stack_overflow.rs"]
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
#[path = "../wasm/thread_local.rs"]
pub mod thread_local;
#[path = "../wasm/fast_thread_local.rs"]
pub mod fast_thread_local;
pub mod time;
pub mod ext;

#[cfg(not(test))]
pub fn init() {
}

pub fn unsupported<T>() -> crate::io::Result<T> {
    Err(unsupported_err())
}

pub fn unsupported_err() -> Error {
    Error::new(ErrorKind::Other, "operation not supported on wasm yet")
}

pub fn decode_error_kind(errno: i32) -> ErrorKind {
    match errno as libc::c_int {
        libc::ECONNREFUSED => ErrorKind::ConnectionRefused,
        libc::ECONNRESET => ErrorKind::ConnectionReset,
        libc::EPERM | libc::EACCES => ErrorKind::PermissionDenied,
        libc::EPIPE => ErrorKind::BrokenPipe,
        libc::ENOTCONN => ErrorKind::NotConnected,
        libc::ECONNABORTED => ErrorKind::ConnectionAborted,
        libc::EADDRNOTAVAIL => ErrorKind::AddrNotAvailable,
        libc::EADDRINUSE => ErrorKind::AddrInUse,
        libc::ENOENT => ErrorKind::NotFound,
        libc::EINTR => ErrorKind::Interrupted,
        libc::EINVAL => ErrorKind::InvalidInput,
        libc::ETIMEDOUT => ErrorKind::TimedOut,
        libc::EEXIST => ErrorKind::AlreadyExists,
        libc::EAGAIN => ErrorKind::WouldBlock,
        _ => ErrorKind::Other,
    }
}

// This enum is used as the storage for a bunch of types which can't actually
// exist.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum Void {}

pub unsafe fn strlen(mut s: *const c_char) -> usize {
    let mut n = 0;
    while *s != 0 {
        n += 1;
        s = s.offset(1);
    }
    return n
}

pub unsafe fn abort_internal() -> ! {
    libc::abort()
}

pub fn hashmap_random_keys() -> (u64, u64) {
    let mut ret = (0u64, 0u64);
    unsafe {
        let base = &mut ret as *mut (u64, u64) as *mut libc::c_void;
        let len = mem::size_of_val(&ret);
        cvt_wasi(libc::__wasi_random_get(base, len)).unwrap();
    }
    return ret
}

#[doc(hidden)]
pub trait IsMinusOne {
    fn is_minus_one(&self) -> bool;
}

macro_rules! impl_is_minus_one {
    ($($t:ident)*) => ($(impl IsMinusOne for $t {
        fn is_minus_one(&self) -> bool {
            *self == -1
        }
    })*)
}

impl_is_minus_one! { i8 i16 i32 i64 isize }

pub fn cvt<T: IsMinusOne>(t: T) -> crate::io::Result<T> {
    if t.is_minus_one() {
        Err(Error::last_os_error())
    } else {
        Ok(t)
    }
}

pub fn cvt_wasi(r: u16) -> crate::io::Result<()> {
    if r != libc::__WASI_ESUCCESS {
        Err(Error::from_raw_os_error(r as i32))
    } else {
        Ok(())
    }
}
<|endoftext|>