<commit_before>use {Errno, Error, Result, NixPath};
use fcntl::OFlag;
use libc::{self, c_void, size_t, off_t, mode_t};
use sys::stat::Mode;
use std::os::unix::io::RawFd;

pub use self::consts::*;

bitflags!{
    flags ProtFlags : libc::c_int {
        const PROT_NONE      = libc::PROT_NONE,
        const PROT_READ      = libc::PROT_READ,
        const PROT_WRITE     = libc::PROT_WRITE,
        const PROT_EXEC      = libc::PROT_EXEC,
        #[cfg(any(target_os = "linux", target_os = "android"))]
        const PROT_GROWSDOWN = libc::PROT_GROWSDOWN,
        #[cfg(any(target_os = "linux", target_os = "android"))]
        const PROT_GROWSUP   = libc::PROT_GROWSUP,
    }
}

#[cfg(any(target_os = "linux", target_os = "android"))]
mod consts {
    use libc::{self, c_int};

    bitflags!{
        flags MmapFlags: c_int {
            const MAP_FILE       = libc::MAP_FILE,
            const MAP_SHARED     = libc::MAP_SHARED,
            const MAP_PRIVATE    = libc::MAP_PRIVATE,
            const MAP_FIXED      = libc::MAP_FIXED,
            const MAP_ANON       = libc::MAP_ANON,
            const MAP_ANONYMOUS  = libc::MAP_ANON,
            const MAP_32BIT      = libc::MAP_32BIT,
            const MAP_GROWSDOWN  = libc::MAP_GROWSDOWN,
            const MAP_DENYWRITE  = libc::MAP_DENYWRITE,
            const MAP_EXECUTABLE = libc::MAP_EXECUTABLE,
            const MAP_LOCKED     = libc::MAP_LOCKED,
            const MAP_NORESERVE  = libc::MAP_NORESERVE,
            const MAP_POPULATE   = libc::MAP_POPULATE,
            const MAP_NONBLOCK   = libc::MAP_NONBLOCK,
            const MAP_STACK      = libc::MAP_STACK,
            const MAP_HUGETLB    = libc::MAP_HUGETLB,
        }
    }

    pub type MmapAdvise = c_int;

    pub const MADV_NORMAL     : MmapAdvise  = 0; /* No further special treatment.  */
    pub const MADV_RANDOM     : MmapAdvise  = 1; /* Expect random page references.  */
    pub const MADV_SEQUENTIAL : MmapAdvise  = 2; /* Expect sequential page references.  */
    pub const MADV_WILLNEED   : MmapAdvise  = 3; /* Will need these pages.  */
    pub const MADV_DONTNEED   : MmapAdvise  = 4; /* Don't need these pages.  */
    pub const MADV_REMOVE     : MmapAdvise  = 9; /* Remove these pages and resources.  */
    pub const MADV_DONTFORK   : MmapAdvise  = 10; /* Do not inherit across fork.  */
    pub const MADV_DOFORK     : MmapAdvise  = 11; /* Do inherit across fork.  */
    pub const MADV_MERGEABLE  : MmapAdvise  = 12; /* KSM may merge identical pages.  */
    pub const MADV_UNMERGEABLE: MmapAdvise  = 13; /* KSM may not merge identical pages.  */
    pub const MADV_HUGEPAGE   : MmapAdvise  = 14; /* Worth backing with hugepages.  */
    pub const MADV_NOHUGEPAGE : MmapAdvise  = 15; /* Not worth backing with hugepages.  */
    pub const MADV_DONTDUMP   : MmapAdvise  = 16; /* Explicity exclude from the core dump, overrides the coredump filter bits.  */
    pub const MADV_DODUMP     : MmapAdvise  = 17; /* Clear the MADV_DONTDUMP flag.  */
    pub const MADV_HWPOISON   : MmapAdvise  = 100; /* Poison a page for testing.  */

    bitflags!{
        flags SyncFlags: c_int {
            const MS_ASYNC      = libc::MS_ASYNC,
            const MS_INVALIDATE = libc::MS_INVALIDATE,
            const MS_SYNC       = libc::MS_SYNC,
        }
    }

    pub const MAP_FAILED: isize               = -1;
}

#[cfg(any(target_os = "macos",
          target_os = "ios"))]
mod consts {
    use libc::{self, c_int};

    bitflags!{
        flags MmapFlags: c_int {
            const MAP_FILE    = libc::MAP_FILE,
            const MAP_SHARED  = libc::MAP_SHARED,
            const MAP_PRIVATE = libc::MAP_PRIVATE,
            const MAP_FIXED   = libc::MAP_FIXED,
            const MAP_ANON    = libc::MAP_ANON,
            const MAP_NOCACHE = libc::MAP_NOCACHE,
            const MAP_JIT     = libc::MAP_JIT,
        }
    }

    pub type MmapAdvise = c_int;

    pub const MADV_NORMAL     : MmapAdvise      = 0; /* No further special treatment.  */
    pub const MADV_RANDOM     : MmapAdvise      = 1; /* Expect random page references.  */
    pub const MADV_SEQUENTIAL : MmapAdvise      = 2; /* Expect sequential page references.  */
    pub const MADV_WILLNEED   : MmapAdvise      = 3; /* Will need these pages.  */
    pub const MADV_DONTNEED   : MmapAdvise      = 4; /* Don't need these pages.  */
    pub const MADV_FREE       : MmapAdvise      = 5; /* pages unneeded, discard contents */
    pub const MADV_ZERO_WIRED_PAGES: MmapAdvise = 6; /* zero the wired pages that have not been unwired before the entry is deleted */
    pub const MADV_FREE_REUSABLE : MmapAdvise   = 7; /* pages can be reused (by anyone) */
    pub const MADV_FREE_REUSE : MmapAdvise      = 8; /* caller wants to reuse those pages */
    pub const MADV_CAN_REUSE : MmapAdvise       = 9;

    bitflags!{
        flags SyncFlags: c_int {
            const MS_ASYNC      = libc::MS_ASYNC, /* [MF|SIO] return immediately */
            const MS_INVALIDATE = libc::MS_INVALIDATE, /* [MF|SIO] invalidate all cached data */
            const MS_KILLPAGES  = libc::MS_KILLPAGES, /* invalidate pages, leave mapped */
            const MS_DEACTIVATE = libc::MS_DEACTIVATE, /* deactivate pages, leave mapped */
            const MS_SYNC       = libc::MS_SYNC, /* [MF|SIO] msync synchronously */
        }
    }

    pub const MAP_FAILED: isize               = -1;
}

#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))]
mod consts {
    use libc::c_int;

    pub type MmapFlags = c_int;

    bitflags!{
        flags MmapFlags: c_int {
            const MAP_FILE         = libc::MAP_FILE,
            const MAP_SHARED       = libc::MAP_SHARED,
            const MAP_PRIVATE      = libc::MAP_PRIVATE,
            const MAP_FIXED        = libc::MAP_FIXED,
            const MAP_RENAME       = libc::MAP_RENAME,
            const MAP_NORESERVE    = libc::MAP_NORESERVE,
            const MAP_HASSEMAPHORE = libc::MAP_HASSEMAPHORE,
            const MAP_STACK        = libc::MAP_STACK,
            #[cfg(target_os = "netbsd")]
            const MAP_WIRED        = libc::MAP_WIRED,
            const MAP_NOSYNC       = libc::MAP_NOSYNC,
            const MAP_ANON         = libc::MAP_ANON,
        }
    }

    pub type MmapAdvise = c_int;

    pub const MADV_NORMAL     : MmapAdvise      = 0; /* No further special treatment.  */
    pub const MADV_RANDOM     : MmapAdvise      = 1; /* Expect random page references.  */
    pub const MADV_SEQUENTIAL : MmapAdvise      = 2; /* Expect sequential page references.  */
    pub const MADV_WILLNEED   : MmapAdvise      = 3; /* Will need these pages.  */
    pub const MADV_DONTNEED   : MmapAdvise      = 4; /* Don't need these pages.  */
    pub const MADV_FREE       : MmapAdvise      = 5; /* pages unneeded, discard contents */
    pub const MADV_NOSYNC     : MmapAdvise      = 6; /* try to avoid flushes to physical media*/
    pub const MADV_AUTOSYNC   : MmapAdvise      = 7; /* refert to default flushing strategy */
    pub const MADV_NOCORE     : MmapAdvise      = 8; /* do not include these pages in a core file */
    pub const MADV_CORE       : MmapAdvise      = 9; /* revert to including pages in a core file */
    #[cfg(not(target_os = "dragonfly"))]
    pub const MADV_PROTECT    : MmapAdvise      = 10; /* protect process from pageout kill */
    #[cfg(target_os = "dragonfly")]
    pub const MADV_INVAL      : MmapAdvise      = 10; /* virt page tables have changed, inval pmap */
    #[cfg(target_os = "dragonfly")]
    pub const MADV_SETMAP     : MmapAdvise      = 11; /* set page table directory page for map */

    bitflags!{
        flags SyncFlags: c_int {
            const MS_ASYNC      = libc::MS_ASYNC, /* [MF|SIO] return immediately */
            const MS_INVALIDATE = libc::MS_INVALIDATE, /* [MF|SIO] invalidate all cached data */
            #[cfg(not(target_os = "dragonfly"))]
            const MS_KILLPAGES  = 0x0004, /* invalidate pages, leave mapped */
            #[cfg(not(target_os = "dragonfly"))]
            const MS_DEACTIVATE = 0x0004, /* deactivate pages, leave mapped */
            const MS_SYNC       = libc::MS_SYNC, /* [MF|SIO] msync synchronously */
        }
    }

    pub const MAP_FAILED: isize                 = -1;
}

mod ffi {
    use libc::{c_void, size_t, c_int, c_char, mode_t};

    pub use libc::{mmap, munmap};

    #[allow(improper_ctypes)]
    extern {
        pub fn shm_open(name: *const c_char, oflag: c_int, mode: mode_t) -> c_int;
        pub fn shm_unlink(name: *const c_char) -> c_int;
        pub fn mlock(addr: *const c_void, len: size_t) -> c_int;
        pub fn munlock(addr: *const c_void, len: size_t) -> c_int;
        pub fn madvise (addr: *const c_void, len: size_t, advice: c_int) -> c_int;
        pub fn msync (addr: *const c_void, len: size_t, flags: c_int) -> c_int;
    }
}

pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> {
    Errno::result(ffi::mlock(addr, length)).map(drop)
}

pub fn munlock(addr: *const c_void, length: size_t) -> Result<()> {
    Errno::result(unsafe { ffi::munlock(addr, length) }).map(drop)
}

/// Calls to mmap are inherently unsafe, so they must be made in an unsafe block. Typically
/// a higher-level abstraction will hide the unsafe interactions with the mmap'd region.
pub fn mmap(addr: *mut c_void, length: size_t, prot: ProtFlags, flags: MmapFlags, fd: RawFd, offset: off_t) -> Result<*mut c_void> {
    let ret = unsafe { ffi::mmap(addr, length, prot.bits(), flags.bits(), fd, offset) };

    if ret as isize == MAP_FAILED  {
        Err(Error::Sys(Errno::last()))
    } else {
        Ok(ret)
    }
}

pub fn munmap(addr: *mut c_void, len: size_t) -> Result<()> {
    Errno::result(unsafe { ffi::munmap(addr, len) }).map(drop)
}

pub fn madvise(addr: *const c_void, length: size_t, advise: MmapAdvise) -> Result<()> {
    Errno::result(unsafe { ffi::madvise(addr, length, advise) }).map(drop)
}

pub fn msync(addr: *const c_void, length: size_t, flags: SyncFlags) -> Result<()> {
    Errno::result(unsafe { ffi::msync(addr, length, flags.bits()) }).map(drop)
}

pub fn shm_open<P: ?Sized + NixPath>(name: &P, flag: OFlag, mode: Mode) -> Result<RawFd> {
    let ret = try!(name.with_nix_path(|cstr| {
        unsafe {
            ffi::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as mode_t)
        }
    }));

    Errno::result(ret)
}

pub fn shm_unlink<P: ?Sized + NixPath>(name: &P) -> Result<()> {
    let ret = try!(name.with_nix_path(|cstr| {
        unsafe { ffi::shm_unlink(cstr.as_ptr()) }
    }));

    Errno::result(ret).map(drop)
}
<commit_msg>Rename to MapFlags and MsFlags.<commit_after>use {Errno, Error, Result, NixPath};
use fcntl::OFlag;
use libc::{self, c_void, size_t, off_t, mode_t};
use sys::stat::Mode;
use std::os::unix::io::RawFd;

pub use self::consts::*;

bitflags!{
    flags ProtFlags : libc::c_int {
        const PROT_NONE      = libc::PROT_NONE,
        const PROT_READ      = libc::PROT_READ,
        const PROT_WRITE     = libc::PROT_WRITE,
        const PROT_EXEC      = libc::PROT_EXEC,
        #[cfg(any(target_os = "linux", target_os = "android"))]
        const PROT_GROWSDOWN = libc::PROT_GROWSDOWN,
        #[cfg(any(target_os = "linux", target_os = "android"))]
        const PROT_GROWSUP   = libc::PROT_GROWSUP,
    }
}

#[cfg(any(target_os = "linux", target_os = "android"))]
mod consts {
    use libc::{self, c_int};

    bitflags!{
        flags MapFlags: c_int {
            const MAP_FILE       = libc::MAP_FILE,
            const MAP_SHARED     = libc::MAP_SHARED,
            const MAP_PRIVATE    = libc::MAP_PRIVATE,
            const MAP_FIXED      = libc::MAP_FIXED,
            const MAP_ANON       = libc::MAP_ANON,
            const MAP_ANONYMOUS  = libc::MAP_ANON,
            const MAP_32BIT      = libc::MAP_32BIT,
            const MAP_GROWSDOWN  = libc::MAP_GROWSDOWN,
            const MAP_DENYWRITE  = libc::MAP_DENYWRITE,
            const MAP_EXECUTABLE = libc::MAP_EXECUTABLE,
            const MAP_LOCKED     = libc::MAP_LOCKED,
            const MAP_NORESERVE  = libc::MAP_NORESERVE,
            const MAP_POPULATE   = libc::MAP_POPULATE,
            const MAP_NONBLOCK   = libc::MAP_NONBLOCK,
            const MAP_STACK      = libc::MAP_STACK,
            const MAP_HUGETLB    = libc::MAP_HUGETLB,
        }
    }

    pub type MmapAdvise = c_int;

    pub const MADV_NORMAL     : MmapAdvise  = 0; /* No further special treatment.  */
    pub const MADV_RANDOM     : MmapAdvise  = 1; /* Expect random page references.  */
    pub const MADV_SEQUENTIAL : MmapAdvise  = 2; /* Expect sequential page references.  */
    pub const MADV_WILLNEED   : MmapAdvise  = 3; /* Will need these pages.  */
    pub const MADV_DONTNEED   : MmapAdvise  = 4; /* Don't need these pages.  */
    pub const MADV_REMOVE     : MmapAdvise  = 9; /* Remove these pages and resources.  */
    pub const MADV_DONTFORK   : MmapAdvise  = 10; /* Do not inherit across fork.  */
    pub const MADV_DOFORK     : MmapAdvise  = 11; /* Do inherit across fork.  */
    pub const MADV_MERGEABLE  : MmapAdvise  = 12; /* KSM may merge identical pages.  */
    pub const MADV_UNMERGEABLE: MmapAdvise  = 13; /* KSM may not merge identical pages.  */
    pub const MADV_HUGEPAGE   : MmapAdvise  = 14; /* Worth backing with hugepages.  */
    pub const MADV_NOHUGEPAGE : MmapAdvise  = 15; /* Not worth backing with hugepages.  */
    pub const MADV_DONTDUMP   : MmapAdvise  = 16; /* Explicity exclude from the core dump, overrides the coredump filter bits.  */
    pub const MADV_DODUMP     : MmapAdvise  = 17; /* Clear the MADV_DONTDUMP flag.  */
    pub const MADV_HWPOISON   : MmapAdvise  = 100; /* Poison a page for testing.  */

    bitflags!{
        flags MsFlags: c_int {
            const MS_ASYNC      = libc::MS_ASYNC,
            const MS_INVALIDATE = libc::MS_INVALIDATE,
            const MS_SYNC       = libc::MS_SYNC,
        }
    }

    pub const MAP_FAILED: isize               = -1;
}

#[cfg(any(target_os = "macos",
          target_os = "ios"))]
mod consts {
    use libc::{self, c_int};

    bitflags!{
        flags MapFlags: c_int {
            const MAP_FILE    = libc::MAP_FILE,
            const MAP_SHARED  = libc::MAP_SHARED,
            const MAP_PRIVATE = libc::MAP_PRIVATE,
            const MAP_FIXED   = libc::MAP_FIXED,
            const MAP_ANON    = libc::MAP_ANON,
            const MAP_NOCACHE = libc::MAP_NOCACHE,
            const MAP_JIT     = libc::MAP_JIT,
        }
    }

    pub type MmapAdvise = c_int;

    pub const MADV_NORMAL     : MmapAdvise      = 0; /* No further special treatment.  */
    pub const MADV_RANDOM     : MmapAdvise      = 1; /* Expect random page references.  */
    pub const MADV_SEQUENTIAL : MmapAdvise      = 2; /* Expect sequential page references.  */
    pub const MADV_WILLNEED   : MmapAdvise      = 3; /* Will need these pages.  */
    pub const MADV_DONTNEED   : MmapAdvise      = 4; /* Don't need these pages.  */
    pub const MADV_FREE       : MmapAdvise      = 5; /* pages unneeded, discard contents */
    pub const MADV_ZERO_WIRED_PAGES: MmapAdvise = 6; /* zero the wired pages that have not been unwired before the entry is deleted */
    pub const MADV_FREE_REUSABLE : MmapAdvise   = 7; /* pages can be reused (by anyone) */
    pub const MADV_FREE_REUSE : MmapAdvise      = 8; /* caller wants to reuse those pages */
    pub const MADV_CAN_REUSE : MmapAdvise       = 9;

    bitflags!{
        flags MsFlags: c_int {
            const MS_ASYNC      = libc::MS_ASYNC, /* [MF|SIO] return immediately */
            const MS_INVALIDATE = libc::MS_INVALIDATE, /* [MF|SIO] invalidate all cached data */
            const MS_KILLPAGES  = libc::MS_KILLPAGES, /* invalidate pages, leave mapped */
            const MS_DEACTIVATE = libc::MS_DEACTIVATE, /* deactivate pages, leave mapped */
            const MS_SYNC       = libc::MS_SYNC, /* [MF|SIO] msync synchronously */
        }
    }

    pub const MAP_FAILED: isize               = -1;
}

#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))]
mod consts {
    use libc::c_int;

    bitflags!{
        flags MapFlags: c_int {
            const MAP_FILE         = libc::MAP_FILE,
            const MAP_SHARED       = libc::MAP_SHARED,
            const MAP_PRIVATE      = libc::MAP_PRIVATE,
            const MAP_FIXED        = libc::MAP_FIXED,
            const MAP_RENAME       = libc::MAP_RENAME,
            const MAP_NORESERVE    = libc::MAP_NORESERVE,
            const MAP_HASSEMAPHORE = libc::MAP_HASSEMAPHORE,
            const MAP_STACK        = libc::MAP_STACK,
            #[cfg(target_os = "netbsd")]
            const MAP_WIRED        = libc::MAP_WIRED,
            const MAP_NOSYNC       = libc::MAP_NOSYNC,
            const MAP_ANON         = libc::MAP_ANON,
        }
    }

    pub type MmapAdvise = c_int;

    pub const MADV_NORMAL     : MmapAdvise      = 0; /* No further special treatment.  */
    pub const MADV_RANDOM     : MmapAdvise      = 1; /* Expect random page references.  */
    pub const MADV_SEQUENTIAL : MmapAdvise      = 2; /* Expect sequential page references.  */
    pub const MADV_WILLNEED   : MmapAdvise      = 3; /* Will need these pages.  */
    pub const MADV_DONTNEED   : MmapAdvise      = 4; /* Don't need these pages.  */
    pub const MADV_FREE       : MmapAdvise      = 5; /* pages unneeded, discard contents */
    pub const MADV_NOSYNC     : MmapAdvise      = 6; /* try to avoid flushes to physical media*/
    pub const MADV_AUTOSYNC   : MmapAdvise      = 7; /* refert to default flushing strategy */
    pub const MADV_NOCORE     : MmapAdvise      = 8; /* do not include these pages in a core file */
    pub const MADV_CORE       : MmapAdvise      = 9; /* revert to including pages in a core file */
    #[cfg(not(target_os = "dragonfly"))]
    pub const MADV_PROTECT    : MmapAdvise      = 10; /* protect process from pageout kill */
    #[cfg(target_os = "dragonfly")]
    pub const MADV_INVAL      : MmapAdvise      = 10; /* virt page tables have changed, inval pmap */
    #[cfg(target_os = "dragonfly")]
    pub const MADV_SETMAP     : MmapAdvise      = 11; /* set page table directory page for map */

    bitflags!{
        flags MsFlags: c_int {
            const MS_ASYNC      = libc::MS_ASYNC, /* [MF|SIO] return immediately */
            const MS_INVALIDATE = libc::MS_INVALIDATE, /* [MF|SIO] invalidate all cached data */
            #[cfg(not(target_os = "dragonfly"))]
            const MS_KILLPAGES  = 0x0004, /* invalidate pages, leave mapped */
            #[cfg(not(target_os = "dragonfly"))]
            const MS_DEACTIVATE = 0x0004, /* deactivate pages, leave mapped */
            const MS_SYNC       = libc::MS_SYNC, /* [MF|SIO] msync synchronously */
        }
    }

    pub const MAP_FAILED: isize                 = -1;
}

mod ffi {
    use libc::{c_void, size_t, c_int, c_char, mode_t};

    pub use libc::{mmap, munmap};

    #[allow(improper_ctypes)]
    extern {
        pub fn shm_open(name: *const c_char, oflag: c_int, mode: mode_t) -> c_int;
        pub fn shm_unlink(name: *const c_char) -> c_int;
        pub fn mlock(addr: *const c_void, len: size_t) -> c_int;
        pub fn munlock(addr: *const c_void, len: size_t) -> c_int;
        pub fn madvise (addr: *const c_void, len: size_t, advice: c_int) -> c_int;
        pub fn msync (addr: *const c_void, len: size_t, flags: c_int) -> c_int;
    }
}

pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> {
    Errno::result(ffi::mlock(addr, length)).map(drop)
}

pub fn munlock(addr: *const c_void, length: size_t) -> Result<()> {
    Errno::result(unsafe { ffi::munlock(addr, length) }).map(drop)
}

/// Calls to mmap are inherently unsafe, so they must be made in an unsafe block. Typically
/// a higher-level abstraction will hide the unsafe interactions with the mmap'd region.
pub fn mmap(addr: *mut c_void, length: size_t, prot: ProtFlags, flags: MapFlags, fd: RawFd, offset: off_t) -> Result<*mut c_void> {
    let ret = unsafe { ffi::mmap(addr, length, prot.bits(), flags.bits(), fd, offset) };

    if ret as isize == MAP_FAILED  {
        Err(Error::Sys(Errno::last()))
    } else {
        Ok(ret)
    }
}

pub fn munmap(addr: *mut c_void, len: size_t) -> Result<()> {
    Errno::result(unsafe { ffi::munmap(addr, len) }).map(drop)
}

pub fn madvise(addr: *const c_void, length: size_t, advise: MmapAdvise) -> Result<()> {
    Errno::result(unsafe { ffi::madvise(addr, length, advise) }).map(drop)
}

pub fn msync(addr: *const c_void, length: size_t, flags: MsFlags) -> Result<()> {
    Errno::result(unsafe { ffi::msync(addr, length, flags.bits()) }).map(drop)
}

pub fn shm_open<P: ?Sized + NixPath>(name: &P, flag: OFlag, mode: Mode) -> Result<RawFd> {
    let ret = try!(name.with_nix_path(|cstr| {
        unsafe {
            ffi::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as mode_t)
        }
    }));

    Errno::result(ret)
}

pub fn shm_unlink<P: ?Sized + NixPath>(name: &P) -> Result<()> {
    let ret = try!(name.with_nix_path(|cstr| {
        unsafe { ffi::shm_unlink(cstr.as_ptr()) }
    }));

    Errno::result(ret).map(drop)
}
<|endoftext|>


<commit_before>use cfg_if::cfg_if;
use libc::{self, c_int};
use crate::Result;
use crate::errno::Errno;
use crate::unistd::Pid;
use crate::sys::signal::Signal;
use std::convert::TryFrom;

libc_bitflags!(
    pub struct WaitPidFlag: c_int {
        WNOHANG;
        WUNTRACED;
        #[cfg(any(target_os = "android",
                  target_os = "freebsd",
                  target_os = "haiku",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "redox",
                  target_os = "macos",
                  target_os = "netbsd"))]
        WEXITED;
        WCONTINUED;
        #[cfg(any(target_os = "android",
                  target_os = "freebsd",
                  target_os = "haiku",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "redox",
                  target_os = "macos",
                  target_os = "netbsd"))]
        WSTOPPED;
        /// Don't reap, just poll status.
        #[cfg(any(target_os = "android",
                  target_os = "freebsd",
                  target_os = "haiku",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "redox",
                  target_os = "macos",
                  target_os = "netbsd"))]
        WNOWAIT;
        /// Don't wait on children of other threads in this group
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        __WNOTHREAD;
        /// Wait on all children, regardless of type
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        __WALL;
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        __WCLONE;
    }
);

/// Possible return values from `wait()` or `waitpid()`.
///
/// Each status (other than `StillAlive`) describes a state transition
/// in a child process `Pid`, such as the process exiting or stopping,
/// plus additional data about the transition if any.
///
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
/// and `PtraceSyscall`. Portable code should avoid exhaustively
/// matching on `WaitStatus`.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WaitStatus {
    /// The process exited normally (as with `exit()` or returning from
    /// `main`) with the given exit code. This case matches the C macro
    /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
    Exited(Pid, i32),
    /// The process was killed by the given signal. The third field
    /// indicates whether the signal generated a core dump. This case
    /// matches the C macro `WIFSIGNALED(status)`; the last two fields
    /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
    Signaled(Pid, Signal, bool),
    /// The process is alive, but was stopped by the given signal. This
    /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
    /// case matches the C macro `WIFSTOPPED(status)`; the second field
    /// is `WSTOPSIG(status)`.
    Stopped(Pid, Signal),
    /// The traced process was stopped by a `PTRACE_EVENT_*` event. See
    /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
    /// currently-defined events use `SIGTRAP` as the signal; the third
    /// field is the `PTRACE_EVENT_*` value of the event.
    ///
    /// [`nix::sys::ptrace`]: ../ptrace/index.html
    /// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceEvent(Pid, Signal, c_int),
    /// The traced process was stopped by execution of a system call,
    /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
    /// more information.
    ///
    /// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceSyscall(Pid),
    /// The process was previously stopped but has resumed execution
    /// after receiving a `SIGCONT` signal. This is only reported if
    /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
    /// macro `WIFCONTINUED(status)`.
    Continued(Pid),
    /// There are currently no state changes to report in any awaited
    /// child process. This is only returned if `WaitPidFlag::WNOHANG`
    /// was used (otherwise `wait()` or `waitpid()` would block until
    /// there was something to report).
    StillAlive,
}

impl WaitStatus {
    /// Extracts the PID from the WaitStatus unless it equals StillAlive.
    pub fn pid(&self) -> Option<Pid> {
        use self::WaitStatus::*;
        match *self {
            Exited(p, _)  | Signaled(p, _, _) |
                Stopped(p, _) | Continued(p) => Some(p),
            StillAlive => None,
            #[cfg(any(target_os = "android", target_os = "linux"))]
            PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
        }
    }
}

#[allow(unused_unsafe)]
fn exited(status: i32) -> bool {
    unsafe { libc::WIFEXITED(status) }
}

#[allow(unused_unsafe)]
fn exit_status(status: i32) -> i32 {
    unsafe { libc::WEXITSTATUS(status) }
}

#[allow(unused_unsafe)]
fn signaled(status: i32) -> bool {
    unsafe { libc::WIFSIGNALED(status) }
}

#[allow(unused_unsafe)]
fn term_signal(status: i32) -> Result<Signal> {
    Signal::try_from(unsafe { libc::WTERMSIG(status) })
}

#[allow(unused_unsafe)]
fn dumped_core(status: i32) -> bool {
    unsafe { libc::WCOREDUMP(status) }
}

#[allow(unused_unsafe)]
fn stopped(status: i32) -> bool {
    unsafe { libc::WIFSTOPPED(status) }
}

#[allow(unused_unsafe)]
fn stop_signal(status: i32) -> Result<Signal> {
    Signal::try_from(unsafe { libc::WSTOPSIG(status) })
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(unused_unsafe)]
fn syscall_stop(status: i32) -> bool {
    // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
    // of delivering SIGTRAP | 0x80 as the signal number for syscall
    // stops. This allows easily distinguishing syscall stops from
    // genuine SIGTRAP signals.
    unsafe { libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 }
}

#[cfg(any(target_os = "android", target_os = "linux"))]
fn stop_additional(status: i32) -> c_int {
    (status >> 16) as c_int
}

#[allow(unused_unsafe)]
fn continued(status: i32) -> bool {
    unsafe { libc::WIFCONTINUED(status) }
}

impl WaitStatus {
    /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus`
    ///
    /// # Errors
    ///
    /// Returns an `Error` corresponding to `EINVAL` for invalid status values.
    ///
    /// # Examples
    ///
    /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`:
    ///
    /// ```
    /// use nix::sys::wait::WaitStatus;
    /// use nix::sys::signal::Signal;
    /// let pid = nix::unistd::Pid::from_raw(1);
    /// let status = WaitStatus::from_raw(pid, 0x0002);
    /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
    /// ```
    pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
        Ok(if exited(status) {
            WaitStatus::Exited(pid, exit_status(status))
        } else if signaled(status) {
            WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
        } else if stopped(status) {
            cfg_if! {
                if #[cfg(any(target_os = "android", target_os = "linux"))] {
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
                        let status_additional = stop_additional(status);
                        Ok(if syscall_stop(status) {
                            WaitStatus::PtraceSyscall(pid)
                        } else if status_additional == 0 {
                            WaitStatus::Stopped(pid, stop_signal(status)?)
                        } else {
                            WaitStatus::PtraceEvent(pid, stop_signal(status)?,
                                                    stop_additional(status))
                        })
                    }
                } else {
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
                        Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
                    }
                }
            }
            return decode_stopped(pid, status);
        } else {
            assert!(continued(status));
            WaitStatus::Continued(pid)
        })
    }
}

pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
    use self::WaitStatus::*;

    let mut status: i32 = 0;

    let option_bits = match options {
        Some(bits) => bits.bits(),
        None => 0,
    };

    let res = unsafe {
        libc::waitpid(
            pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
            &mut status as *mut c_int,
            option_bits,
        )
    };

    match Errno::result(res)? {
        0 => Ok(StillAlive),
        res => WaitStatus::from_raw(Pid::from_raw(res), status),
    }
}

pub fn wait() -> Result<WaitStatus> {
    waitpid(None, None)
}
<commit_msg>cargo format<commit_after>use crate::errno::Errno;
use crate::sys::signal::Signal;
use crate::unistd::Pid;
use crate::Result;
use cfg_if::cfg_if;
use libc::{self, c_int};
use std::convert::TryFrom;

libc_bitflags!(
    pub struct WaitPidFlag: c_int {
        WNOHANG;
        WUNTRACED;
        #[cfg(any(target_os = "android",
                  target_os = "freebsd",
                  target_os = "haiku",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "redox",
                  target_os = "macos",
                  target_os = "netbsd"))]
        WEXITED;
        WCONTINUED;
        #[cfg(any(target_os = "android",
                  target_os = "freebsd",
                  target_os = "haiku",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "redox",
                  target_os = "macos",
                  target_os = "netbsd"))]
        WSTOPPED;
        /// Don't reap, just poll status.
        #[cfg(any(target_os = "android",
                  target_os = "freebsd",
                  target_os = "haiku",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "redox",
                  target_os = "macos",
                  target_os = "netbsd"))]
        WNOWAIT;
        /// Don't wait on children of other threads in this group
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        __WNOTHREAD;
        /// Wait on all children, regardless of type
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        __WALL;
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        __WCLONE;
    }
);

/// Possible return values from `wait()` or `waitpid()`.
///
/// Each status (other than `StillAlive`) describes a state transition
/// in a child process `Pid`, such as the process exiting or stopping,
/// plus additional data about the transition if any.
///
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
/// and `PtraceSyscall`. Portable code should avoid exhaustively
/// matching on `WaitStatus`.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WaitStatus {
    /// The process exited normally (as with `exit()` or returning from
    /// `main`) with the given exit code. This case matches the C macro
    /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
    Exited(Pid, i32),
    /// The process was killed by the given signal. The third field
    /// indicates whether the signal generated a core dump. This case
    /// matches the C macro `WIFSIGNALED(status)`; the last two fields
    /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
    Signaled(Pid, Signal, bool),
    /// The process is alive, but was stopped by the given signal. This
    /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
    /// case matches the C macro `WIFSTOPPED(status)`; the second field
    /// is `WSTOPSIG(status)`.
    Stopped(Pid, Signal),
    /// The traced process was stopped by a `PTRACE_EVENT_*` event. See
    /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
    /// currently-defined events use `SIGTRAP` as the signal; the third
    /// field is the `PTRACE_EVENT_*` value of the event.
    ///
    /// [`nix::sys::ptrace`]: ../ptrace/index.html
    /// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceEvent(Pid, Signal, c_int),
    /// The traced process was stopped by execution of a system call,
    /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
    /// more information.
    ///
    /// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceSyscall(Pid),
    /// The process was previously stopped but has resumed execution
    /// after receiving a `SIGCONT` signal. This is only reported if
    /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
    /// macro `WIFCONTINUED(status)`.
    Continued(Pid),
    /// There are currently no state changes to report in any awaited
    /// child process. This is only returned if `WaitPidFlag::WNOHANG`
    /// was used (otherwise `wait()` or `waitpid()` would block until
    /// there was something to report).
    StillAlive,
}

impl WaitStatus {
    /// Extracts the PID from the WaitStatus unless it equals StillAlive.
    pub fn pid(&self) -> Option<Pid> {
        use self::WaitStatus::*;
        match *self {
            Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => Some(p),
            StillAlive => None,
            #[cfg(any(target_os = "android", target_os = "linux"))]
            PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
        }
    }
}

#[allow(unused_unsafe)]
fn exited(status: i32) -> bool {
    unsafe { libc::WIFEXITED(status) }
}

#[allow(unused_unsafe)]
fn exit_status(status: i32) -> i32 {
    unsafe { libc::WEXITSTATUS(status) }
}

#[allow(unused_unsafe)]
fn signaled(status: i32) -> bool {
    unsafe { libc::WIFSIGNALED(status) }
}

#[allow(unused_unsafe)]
fn term_signal(status: i32) -> Result<Signal> {
    Signal::try_from(unsafe { libc::WTERMSIG(status) })
}

#[allow(unused_unsafe)]
fn dumped_core(status: i32) -> bool {
    unsafe { libc::WCOREDUMP(status) }
}

#[allow(unused_unsafe)]
fn stopped(status: i32) -> bool {
    unsafe { libc::WIFSTOPPED(status) }
}

#[allow(unused_unsafe)]
fn stop_signal(status: i32) -> Result<Signal> {
    Signal::try_from(unsafe { libc::WSTOPSIG(status) })
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(unused_unsafe)]
fn syscall_stop(status: i32) -> bool {
    // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
    // of delivering SIGTRAP | 0x80 as the signal number for syscall
    // stops. This allows easily distinguishing syscall stops from
    // genuine SIGTRAP signals.
    unsafe { libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 }
}

#[cfg(any(target_os = "android", target_os = "linux"))]
fn stop_additional(status: i32) -> c_int {
    (status >> 16) as c_int
}

#[allow(unused_unsafe)]
fn continued(status: i32) -> bool {
    unsafe { libc::WIFCONTINUED(status) }
}

impl WaitStatus {
    /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus`
    ///
    /// # Errors
    ///
    /// Returns an `Error` corresponding to `EINVAL` for invalid status values.
    ///
    /// # Examples
    ///
    /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`:
    ///
    /// ```
    /// use nix::sys::wait::WaitStatus;
    /// use nix::sys::signal::Signal;
    /// let pid = nix::unistd::Pid::from_raw(1);
    /// let status = WaitStatus::from_raw(pid, 0x0002);
    /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
    /// ```
    pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
        Ok(if exited(status) {
            WaitStatus::Exited(pid, exit_status(status))
        } else if signaled(status) {
            WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
        } else if stopped(status) {
            cfg_if! {
                if #[cfg(any(target_os = "android", target_os = "linux"))] {
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
                        let status_additional = stop_additional(status);
                        Ok(if syscall_stop(status) {
                            WaitStatus::PtraceSyscall(pid)
                        } else if status_additional == 0 {
                            WaitStatus::Stopped(pid, stop_signal(status)?)
                        } else {
                            WaitStatus::PtraceEvent(pid, stop_signal(status)?,
                                                    stop_additional(status))
                        })
                    }
                } else {
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
                        Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
                    }
                }
            }
            return decode_stopped(pid, status);
        } else {
            assert!(continued(status));
            WaitStatus::Continued(pid)
        })
    }
}

pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
    use self::WaitStatus::*;

    let mut status: i32 = 0;

    let option_bits = match options {
        Some(bits) => bits.bits(),
        None => 0,
    };

    let res = unsafe {
        libc::waitpid(
            pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
            &mut status as *mut c_int,
            option_bits,
        )
    };

    match Errno::result(res)? {
        0 => Ok(StillAlive),
        res => WaitStatus::from_raw(Pid::from_raw(res), status),
    }
}

pub fn wait() -> Result<WaitStatus> {
    waitpid(None, None)
}
<|endoftext|>


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

// ----------------

// This program exercises the Rust PNG decoder at
// https://github.com/image-rs/image-png
// which is the top result for https://crates.io/search?q=png&sort=downloads
//
// Wuffs' C code doesn't depend on Rust per se, but this program gives some
// performance data for specific Rust PNG implementations. The equivalent Wuffs
// benchmarks (on the same test images) are run via:
//
// wuffs bench std/png
//
// To run this program, do "cargo run --release" from the parent directory (the
// directory containing the Cargo.toml file).

extern crate png;
extern crate rustc_version_runtime;

use std::time::Instant;

const ITERSCALE: u64 = 50;
const REPS: u64 = 5;

fn main() {
    let version = rustc_version_runtime::version();
    print!(
        "# Rust {}.{}.{}\n",
        version.major, version.minor, version.patch,
    );
    print!("#\n");
    print!("# The output format, including the \"Benchmark\" prefixes, is compatible with the\n");
    print!("# https://godoc.org/golang.org/x/perf/cmd/benchstat tool. To install it, first\n");
    print!("# install Go, then run \"go get golang.org/x/perf/cmd/benchstat\".\n");

    let mut dst0 = vec![0u8; 64 * 1024 * 1024];
    let mut dst1 = vec![0u8; 64 * 1024 * 1024];

    // The various magic constants below are copied from test/c/std/png.c
    for i in 0..(1 + REPS) {
        bench(
            "19k_8bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/bricks-gray.no-ancillary.png"),
            i == 0,        // warm_up
            160 * 120 * 1, // want_num_bytes = 19_200
            50,            // iters_unscaled
        );

        bench(
            "40k_24bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/hat.png"),
            i == 0,       // warm_up
            90 * 112 * 4, // want_num_bytes = 40_320
            30,           // iters_unscaled
        );

        bench(
            "77k_8bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/bricks-dither.png"),
            i == 0,        // warm_up
            160 * 120 * 4, // want_num_bytes = 76_800
            30,            // iters_unscaled
        );

        bench(
            "552k_32bpp_verify_checksum",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/hibiscus.primitive.png"),
            i == 0,        // warm_up
            312 * 442 * 4, // want_num_bytes = 551_616
            4,             // iters_unscaled
        );

        bench(
            "4002k_24bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/harvesters.png"),
            i == 0,         // warm_up
            1165 * 859 * 4, // want_num_bytes = 4_002_940
            1,              // iters_unscaled
        );
    }
}

fn bench(
    name: &str,          // Benchmark name.
    dst0: &mut [u8],     // Destination buffer #0.
    dst1: &mut [u8],     // Destination buffer #1.
    src: &[u8],          // Source data.
    warm_up: bool,       // Whether this is a warm up rep.
    want_num_bytes: u64, // Expected num_bytes per iteration.
    iters_unscaled: u64, // Base number of iterations.
) {
    let iters = iters_unscaled * ITERSCALE;
    let mut total_num_bytes = 0u64;

    let start = Instant::now();
    for _ in 0..iters {
        let n = decode(&mut dst0[..], &mut dst1[..], src);
        if n != want_num_bytes {
            panic!("num_bytes: got {}, want {}", n, want_num_bytes);
        }
        total_num_bytes += n;
    }
    let elapsed = start.elapsed();

    let elapsed_nanos = (elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64);
    let kb_per_s: u64 = total_num_bytes * 1_000_000 / elapsed_nanos;

    if warm_up {
        return;
    }

    print!(
        "Benchmarkrust_png_decode_image_{:16}   {:8}   {:12} ns/op   {:3}.{:03} MB/s\n",
        name,
        iters,
        elapsed_nanos / iters,
        kb_per_s / 1_000,
        kb_per_s % 1_000
    );
}

// decode returns the number of bytes processed.
fn decode(dst0: &mut [u8], dst1: &mut [u8], src: &[u8]) -> u64 {
    let decoder = png::Decoder::new(src);
    let (info, mut reader) = decoder.read_info().unwrap();
    let num_bytes = info.buffer_size() as u64;
    reader.next_frame(dst0).unwrap();
    if info.color_type == png::ColorType::Grayscale {
        // No conversion necessary.
        return num_bytes;
    } else if info.color_type == png::ColorType::RGB {
        // Convert RGB => BGRA.
        for i in 0..((num_bytes / 3) as usize) {
            dst1[(4 * i) + 0] = dst0[(3 * i) + 2];
            dst1[(4 * i) + 1] = dst0[(3 * i) + 1];
            dst1[(4 * i) + 2] = dst0[(3 * i) + 0];
            dst1[(4 * i) + 3] = 0xFF;
        }
        return (num_bytes / 3) * 4;
    } else if info.color_type == png::ColorType::RGBA {
        // Convert RGBA => BGRA.
        for i in 0..((num_bytes / 4) as usize) {
            let d = dst0[(4 * i) + 0];
            dst0[(4 * i) + 0] = dst0[(4 * i) + 2];
            dst0[(4 * i) + 2] = d;
        }
        return num_bytes;
    }
    // Returning 0 should lead to a panic (when want_num_bytes != 0).
    0
}
<commit_msg>Use iterators for converting RGB to BGRA in the Rust PNG benchmark<commit_after>// Copyright 2021 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// ----------------

// This program exercises the Rust PNG decoder at
// https://github.com/image-rs/image-png
// which is the top result for https://crates.io/search?q=png&sort=downloads
//
// Wuffs' C code doesn't depend on Rust per se, but this program gives some
// performance data for specific Rust PNG implementations. The equivalent Wuffs
// benchmarks (on the same test images) are run via:
//
// wuffs bench std/png
//
// To run this program, do "cargo run --release" from the parent directory (the
// directory containing the Cargo.toml file).

extern crate png;
extern crate rustc_version_runtime;

use std::time::Instant;
use std::convert::TryInto;

const ITERSCALE: u64 = 50;
const REPS: u64 = 5;

fn main() {
    let version = rustc_version_runtime::version();
    print!(
        "# Rust {}.{}.{}\n",
        version.major, version.minor, version.patch,
    );
    print!("#\n");
    print!("# The output format, including the \"Benchmark\" prefixes, is compatible with the\n");
    print!("# https://godoc.org/golang.org/x/perf/cmd/benchstat tool. To install it, first\n");
    print!("# install Go, then run \"go get golang.org/x/perf/cmd/benchstat\".\n");

    let mut dst0 = vec![0u8; 64 * 1024 * 1024];
    let mut dst1 = vec![0u8; 64 * 1024 * 1024];

    // The various magic constants below are copied from test/c/std/png.c
    for i in 0..(1 + REPS) {
        bench(
            "19k_8bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/bricks-gray.no-ancillary.png"),
            i == 0,        // warm_up
            160 * 120 * 1, // want_num_bytes = 19_200
            50,            // iters_unscaled
        );

        bench(
            "40k_24bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/hat.png"),
            i == 0,       // warm_up
            90 * 112 * 4, // want_num_bytes = 40_320
            30,           // iters_unscaled
        );

        bench(
            "77k_8bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/bricks-dither.png"),
            i == 0,        // warm_up
            160 * 120 * 4, // want_num_bytes = 76_800
            30,            // iters_unscaled
        );

        bench(
            "552k_32bpp_verify_checksum",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/hibiscus.primitive.png"),
            i == 0,        // warm_up
            312 * 442 * 4, // want_num_bytes = 551_616
            4,             // iters_unscaled
        );

        bench(
            "4002k_24bpp",
            &mut dst0[..],
            &mut dst1[..],
            include_bytes!("../../../test/data/harvesters.png"),
            i == 0,         // warm_up
            1165 * 859 * 4, // want_num_bytes = 4_002_940
            1,              // iters_unscaled
        );
    }
}

fn bench(
    name: &str,          // Benchmark name.
    dst0: &mut [u8],     // Destination buffer #0.
    dst1: &mut [u8],     // Destination buffer #1.
    src: &[u8],          // Source data.
    warm_up: bool,       // Whether this is a warm up rep.
    want_num_bytes: u64, // Expected num_bytes per iteration.
    iters_unscaled: u64, // Base number of iterations.
) {
    let iters = iters_unscaled * ITERSCALE;
    let mut total_num_bytes = 0u64;

    let start = Instant::now();
    for _ in 0..iters {
        let n = decode(&mut dst0[..], &mut dst1[..], src);
        if n != want_num_bytes {
            panic!("num_bytes: got {}, want {}", n, want_num_bytes);
        }
        total_num_bytes += n;
    }
    let elapsed = start.elapsed();

    let elapsed_nanos = (elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64);
    let kb_per_s: u64 = total_num_bytes * 1_000_000 / elapsed_nanos;

    if warm_up {
        return;
    }

    print!(
        "Benchmarkrust_png_decode_image_{:16}   {:8}   {:12} ns/op   {:3}.{:03} MB/s\n",
        name,
        iters,
        elapsed_nanos / iters,
        kb_per_s / 1_000,
        kb_per_s % 1_000
    );
}

// decode returns the number of bytes processed.
fn decode(dst0: &mut [u8], dst1: &mut [u8], src: &[u8]) -> u64 {
    let decoder = png::Decoder::new(src);
    let (info, mut reader) = decoder.read_info().unwrap();
    let num_bytes = info.buffer_size() as u64;
    reader.next_frame(dst0).unwrap();
    if info.color_type == png::ColorType::Grayscale {
        // No conversion necessary.
        return num_bytes;
    } else if info.color_type == png::ColorType::RGB {
        // Convert RGB => BGRA.
        let new_size = ((num_bytes / 3) * 4) as usize;
        rgb_to_bgra(&dst0[..num_bytes as usize], &mut dst1[..new_size]);
        return new_size as u64;
    } else if info.color_type == png::ColorType::RGBA {
        // Convert RGBA => BGRA.
        for i in 0..((num_bytes / 4) as usize) {
            let d = dst0[(4 * i) + 0];
            dst0[(4 * i) + 0] = dst0[(4 * i) + 2];
            dst0[(4 * i) + 2] = d;
        }
        return num_bytes;
    }
    // Returning 0 should lead to a panic (when want_num_bytes != 0).
    0
}

/// Copy `src` (treated as 3-byte chunks) into `dst`
/// (treated as 4-byte chunks), filling out `dst` by adding
/// a `0xff` "alpha value" at the end of each entry.
///
/// # Panics:
///
/// Will panic if
///
/// * The length of `src` is not a multiple of 3.
/// * The length of `dst` is not a multiple of 4.
/// * `src` and `dst` do not have the same length in chunks.
#[inline]
pub fn rgb_to_bgra(src: &[u8], dst: &mut [u8]) {
    let nsrc = src.len();
    let ndst = dst.len();
    assert_eq!(0, nsrc % 3);
    assert_eq!(0, ndst % 4);
    assert_eq!(nsrc, (ndst / 4) * 3);
    for (s, d) in src.chunks_exact(3).zip(dst.chunks_exact_mut(4)) {
        let s: &[u8; 3] = s.try_into().unwrap();
        let d: &mut [u8; 4] = d.try_into().unwrap();
        // R
        d[0] = s[2];
        // G
        d[1] = s[1];
        // B
        d[2] = s[0];
        // A
        d[3] = 0xff;
    }
}
<|endoftext|>


<commit_before>use fortanix_sgx_abi::Tcs;

use super::abi::thread;

use super::waitqueue::{try_lock_or_false, NotifiedTcs, SpinMutex, WaitQueue, WaitVariable};

pub struct Mutex {
    inner: SpinMutex<WaitVariable<bool>>,
}

pub type MovableMutex = Mutex;

// Implementation according to “Operating Systems: Three Easy Pieces”, chapter 28
impl Mutex {
    pub const fn new() -> Mutex {
        Mutex { inner: SpinMutex::new(WaitVariable::new(false)) }
    }

    #[inline]
    pub unsafe fn init(&mut self) {}

    #[inline]
    pub unsafe fn lock(&self) {
        let mut guard = self.inner.lock();
        if *guard.lock_var() {
            // Another thread has the lock, wait
            WaitQueue::wait(guard, || {})
        // Another thread has passed the lock to us
        } else {
            // We are just now obtaining the lock
            *guard.lock_var_mut() = true;
        }
    }

    #[inline]
    pub unsafe fn unlock(&self) {
        let guard = self.inner.lock();
        if let Err(mut guard) = WaitQueue::notify_one(guard) {
            // No other waiters, unlock
            *guard.lock_var_mut() = false;
        } else {
            // There was a thread waiting, just pass the lock
        }
    }

    #[inline]
    pub unsafe fn try_lock(&self) -> bool {
        let mut guard = try_lock_or_false!(self.inner);
        if *guard.lock_var() {
            // Another thread has the lock
            false
        } else {
            // We are just now obtaining the lock
            *guard.lock_var_mut() = true;
            true
        }
    }

    #[inline]
    pub unsafe fn destroy(&self) {}
}

struct ReentrantLock {
    owner: Option<Tcs>,
    count: usize,
}

pub struct ReentrantMutex {
    inner: SpinMutex<WaitVariable<ReentrantLock>>,
}

impl ReentrantMutex {
    pub const fn uninitialized() -> ReentrantMutex {
        ReentrantMutex {
            inner: SpinMutex::new(WaitVariable::new(ReentrantLock { owner: None, count: 0 })),
        }
    }

    #[inline]
    pub unsafe fn init(&self) {}

    #[inline]
    pub unsafe fn lock(&self) {
        let mut guard = self.inner.lock();
        match guard.lock_var().owner {
            Some(tcs) if tcs != thread::current() => {
                // Another thread has the lock, wait
                WaitQueue::wait(guard, || {});
                // Another thread has passed the lock to us
            }
            _ => {
                // We are just now obtaining the lock
                guard.lock_var_mut().owner = Some(thread::current());
                guard.lock_var_mut().count += 1;
            }
        }
    }

    #[inline]
    pub unsafe fn unlock(&self) {
        let mut guard = self.inner.lock();
        if guard.lock_var().count > 1 {
            guard.lock_var_mut().count -= 1;
        } else {
            match WaitQueue::notify_one(guard) {
                Err(mut guard) => {
                    // No other waiters, unlock
                    guard.lock_var_mut().count = 0;
                    guard.lock_var_mut().owner = None;
                }
                Ok(mut guard) => {
                    // There was a thread waiting, just pass the lock
                    if let NotifiedTcs::Single(tcs) = guard.notified_tcs() {
                        guard.lock_var_mut().owner = Some(tcs)
                    } else {
                        unreachable!() // called notify_one
                    }
                }
            }
        }
    }

    #[inline]
    pub unsafe fn try_lock(&self) -> bool {
        let mut guard = try_lock_or_false!(self.inner);
        match guard.lock_var().owner {
            Some(tcs) if tcs != thread::current() => {
                // Another thread has the lock
                false
            }
            _ => {
                // We are just now obtaining the lock
                guard.lock_var_mut().owner = Some(thread::current());
                guard.lock_var_mut().count += 1;
                true
            }
        }
    }

    #[inline]
    pub unsafe fn destroy(&self) {}
}
<commit_msg>Revert "SGX mutex is movable"<commit_after>use fortanix_sgx_abi::Tcs;

use super::abi::thread;

use super::waitqueue::{try_lock_or_false, NotifiedTcs, SpinMutex, WaitQueue, WaitVariable};

pub struct Mutex {
    inner: SpinMutex<WaitVariable<bool>>,
}

pub type MovableMutex = Box<Mutex>;

// Implementation according to “Operating Systems: Three Easy Pieces”, chapter 28
impl Mutex {
    pub const fn new() -> Mutex {
        Mutex { inner: SpinMutex::new(WaitVariable::new(false)) }
    }

    #[inline]
    pub unsafe fn init(&mut self) {}

    #[inline]
    pub unsafe fn lock(&self) {
        let mut guard = self.inner.lock();
        if *guard.lock_var() {
            // Another thread has the lock, wait
            WaitQueue::wait(guard, || {})
        // Another thread has passed the lock to us
        } else {
            // We are just now obtaining the lock
            *guard.lock_var_mut() = true;
        }
    }

    #[inline]
    pub unsafe fn unlock(&self) {
        let guard = self.inner.lock();
        if let Err(mut guard) = WaitQueue::notify_one(guard) {
            // No other waiters, unlock
            *guard.lock_var_mut() = false;
        } else {
            // There was a thread waiting, just pass the lock
        }
    }

    #[inline]
    pub unsafe fn try_lock(&self) -> bool {
        let mut guard = try_lock_or_false!(self.inner);
        if *guard.lock_var() {
            // Another thread has the lock
            false
        } else {
            // We are just now obtaining the lock
            *guard.lock_var_mut() = true;
            true
        }
    }

    #[inline]
    pub unsafe fn destroy(&self) {}
}

struct ReentrantLock {
    owner: Option<Tcs>,
    count: usize,
}

pub struct ReentrantMutex {
    inner: SpinMutex<WaitVariable<ReentrantLock>>,
}

impl ReentrantMutex {
    pub const fn uninitialized() -> ReentrantMutex {
        ReentrantMutex {
            inner: SpinMutex::new(WaitVariable::new(ReentrantLock { owner: None, count: 0 })),
        }
    }

    #[inline]
    pub unsafe fn init(&self) {}

    #[inline]
    pub unsafe fn lock(&self) {
        let mut guard = self.inner.lock();
        match guard.lock_var().owner {
            Some(tcs) if tcs != thread::current() => {
                // Another thread has the lock, wait
                WaitQueue::wait(guard, || {});
                // Another thread has passed the lock to us
            }
            _ => {
                // We are just now obtaining the lock
                guard.lock_var_mut().owner = Some(thread::current());
                guard.lock_var_mut().count += 1;
            }
        }
    }

    #[inline]
    pub unsafe fn unlock(&self) {
        let mut guard = self.inner.lock();
        if guard.lock_var().count > 1 {
            guard.lock_var_mut().count -= 1;
        } else {
            match WaitQueue::notify_one(guard) {
                Err(mut guard) => {
                    // No other waiters, unlock
                    guard.lock_var_mut().count = 0;
                    guard.lock_var_mut().owner = None;
                }
                Ok(mut guard) => {
                    // There was a thread waiting, just pass the lock
                    if let NotifiedTcs::Single(tcs) = guard.notified_tcs() {
                        guard.lock_var_mut().owner = Some(tcs)
                    } else {
                        unreachable!() // called notify_one
                    }
                }
            }
        }
    }

    #[inline]
    pub unsafe fn try_lock(&self) -> bool {
        let mut guard = try_lock_or_false!(self.inner);
        match guard.lock_var().owner {
            Some(tcs) if tcs != thread::current() => {
                // Another thread has the lock
                false
            }
            _ => {
                // We are just now obtaining the lock
                guard.lock_var_mut().owner = Some(thread::current());
                guard.lock_var_mut().count += 1;
                true
            }
        }
    }

    #[inline]
    pub unsafe fn destroy(&self) {}
}
<|endoftext|>


<commit_before>use crate::runtime::Builder;
use crate::task::JoinSet;

#[test]
fn test_join_set() {
    loom::model(|| {
        let rt = Builder::new_multi_thread()
            .worker_threads(1)
            .build()
            .unwrap();
        let mut set = JoinSet::new();

        rt.block_on(async {
            assert_eq!(set.len(), 0);
            set.spawn(async { () });
            assert_eq!(set.len(), 1);
            set.spawn(async { () });
            assert_eq!(set.len(), 2);
            let () = set.join_next().await.unwrap().unwrap();
            assert_eq!(set.len(), 1);
            set.spawn(async { () });
            assert_eq!(set.len(), 2);
            let () = set.join_next().await.unwrap().unwrap();
            assert_eq!(set.len(), 1);
            let () = set.join_next().await.unwrap().unwrap();
            assert_eq!(set.len(), 0);
            set.spawn(async { () });
            assert_eq!(set.len(), 1);
        });

        drop(set);
        drop(rt);
    });
}

#[test]
fn abort_all_during_completion() {
    use std::sync::{
        atomic::{AtomicBool, Ordering::SeqCst},
        Arc,
    };

    // These booleans assert that at least one execution had the task complete first, and that at
    // least one execution had the task be cancelled before it completed.
    let complete_happened = Arc::new(AtomicBool::new(false));
    let cancel_happened = Arc::new(AtomicBool::new(false));

    {
        let complete_happened = complete_happened.clone();
        let cancel_happened = cancel_happened.clone();
        loom::model(move || {
            let rt = Builder::new_multi_thread()
                .worker_threads(1)
                .build()
                .unwrap();

            let mut set = JoinSet::new();

            rt.block_on(async {
                set.spawn(async { () });
                set.abort_all();

                match set.join_next().await {
                    Some(Ok(())) => complete_happened.store(true, SeqCst),
                    Some(Err(err)) if err.is_cancelled() => cancel_happened.store(true, SeqCst),
                    Some(Err(err)) => panic!("fail: {}", err),
                    None => {
                        unreachable!("Aborting the task does not remove it from the JoinSet.")
                    }
                }

                assert!(matches!(set.join_next().await, None));
            });

            drop(set);
            drop(rt);
        });
    }

    assert!(complete_happened.load(SeqCst));
    assert!(cancel_happened.load(SeqCst));
}
<commit_msg>joinset: fix loom test without tokio_unstable (#4783)<commit_after>#![cfg(tokio_unstable)]
use crate::runtime::Builder;
use crate::task::JoinSet;

#[test]
fn test_join_set() {
    loom::model(|| {
        let rt = Builder::new_multi_thread()
            .worker_threads(1)
            .build()
            .unwrap();
        let mut set = JoinSet::new();

        rt.block_on(async {
            assert_eq!(set.len(), 0);
            set.spawn(async { () });
            assert_eq!(set.len(), 1);
            set.spawn(async { () });
            assert_eq!(set.len(), 2);
            let () = set.join_next().await.unwrap().unwrap();
            assert_eq!(set.len(), 1);
            set.spawn(async { () });
            assert_eq!(set.len(), 2);
            let () = set.join_next().await.unwrap().unwrap();
            assert_eq!(set.len(), 1);
            let () = set.join_next().await.unwrap().unwrap();
            assert_eq!(set.len(), 0);
            set.spawn(async { () });
            assert_eq!(set.len(), 1);
        });

        drop(set);
        drop(rt);
    });
}

#[test]
fn abort_all_during_completion() {
    use std::sync::{
        atomic::{AtomicBool, Ordering::SeqCst},
        Arc,
    };

    // These booleans assert that at least one execution had the task complete first, and that at
    // least one execution had the task be cancelled before it completed.
    let complete_happened = Arc::new(AtomicBool::new(false));
    let cancel_happened = Arc::new(AtomicBool::new(false));

    {
        let complete_happened = complete_happened.clone();
        let cancel_happened = cancel_happened.clone();
        loom::model(move || {
            let rt = Builder::new_multi_thread()
                .worker_threads(1)
                .build()
                .unwrap();

            let mut set = JoinSet::new();

            rt.block_on(async {
                set.spawn(async { () });
                set.abort_all();

                match set.join_next().await {
                    Some(Ok(())) => complete_happened.store(true, SeqCst),
                    Some(Err(err)) if err.is_cancelled() => cancel_happened.store(true, SeqCst),
                    Some(Err(err)) => panic!("fail: {}", err),
                    None => {
                        unreachable!("Aborting the task does not remove it from the JoinSet.")
                    }
                }

                assert!(matches!(set.join_next().await, None));
            });

            drop(set);
            drop(rt);
        });
    }

    assert!(complete_happened.load(SeqCst));
    assert!(cancel_happened.load(SeqCst));
}
<|endoftext|>


<commit_before>mod fakes;

use crate::web::Server;
use failure::Error;
use once_cell::unsync::OnceCell;
use postgres::Connection;
use reqwest::{Client, Method, RequestBuilder};
use std::sync::{Arc, Mutex, MutexGuard};
use std::panic;

pub(crate) fn wrapper(f: impl FnOnce(&TestEnvironment) -> Result<(), Error>) {
    let env = TestEnvironment::new();
    // if we didn't catch the panic, the server would hang forever
    let maybe_panic = panic::catch_unwind(panic::AssertUnwindSafe(|| f(&env)));
    env.cleanup();
    let result = match maybe_panic {
        Ok(r) => r,
        Err(payload) => panic::resume_unwind(payload),
    };

    if let Err(err) = result {
        eprintln!("the test failed: {}", err);
        for cause in err.iter_causes() {
            eprintln!("  caused by: {}", cause);
        }

        eprintln!("{}", err.backtrace());

        panic!("the test failed");
    }
}

/// Make sure that a URL returns a status code between 200-299
pub(crate) fn assert_success(path: &str, web: &TestFrontend) -> Result<(), Error> {
    let status = web.get(path).send()?.status();
    assert!(status.is_success(), "failed to GET {}: {}", path, status);
    Ok(())
}

/// Make sure that a URL redirects to a specific page
pub(crate) fn assert_redirect(path: &str, expected_target: &str, web: &TestFrontend) -> Result<(), Error> {
    let response = web.get(path).send()?;
    let status = response.status();

    // Reqwest follows redirects automatically
    let redirect_target = if expected_target.starts_with("https://") {
        response.url().as_str()
    } else {
        response.url().path()
    };
    if redirect_target != expected_target {
        if redirect_target != path {
            panic!("{}: expected redirect to {}, got redirect to {}",
                   path, expected_target, redirect_target);
        } else {
            panic!("{}: expected redirect to {}, got {}", path, expected_target, status);
        }
    }
    assert!(status.is_success(), "failed to GET {}: {}", expected_target, status);
    Ok(())
}

pub(crate) struct TestEnvironment {
    db: OnceCell<TestDatabase>,
    frontend: OnceCell<TestFrontend>,
}

impl TestEnvironment {
    fn new() -> Self {
        Self {
            db: OnceCell::new(),
            frontend: OnceCell::new(),
        }
    }

    fn cleanup(self) {
        if let Some(frontend) = self.frontend.into_inner() {
            frontend.server.leak();
        }
    }

    pub(crate) fn db(&self) -> &TestDatabase {
        self.db
            .get_or_init(|| TestDatabase::new().expect("failed to initialize the db"))
    }

    pub(crate) fn frontend(&self) -> &TestFrontend {
        self.frontend.get_or_init(|| TestFrontend::new(self.db()))
    }
}

pub(crate) struct TestDatabase {
    conn: Arc<Mutex<Connection>>,
}

impl TestDatabase {
    fn new() -> Result<Self, Error> {
        // The temporary migration uses CREATE TEMPORARY TABLE instead of CREATE TABLE, creating
        // fresh temporary copies of the database on top of the real one. The temporary tables are
        // only visible to this connection, and will be deleted when it exits.
        let conn = crate::db::connect_db()?;
        crate::db::migrate_temporary(None, &conn)?;

        Ok(TestDatabase {
            conn: Arc::new(Mutex::new(conn)),
        })
    }

    pub(crate) fn conn(&self) -> MutexGuard<Connection> {
        self.conn.lock().expect("failed to lock the connection")
    }

    pub(crate) fn fake_release(&self) -> fakes::FakeRelease {
        fakes::FakeRelease::new(self)
    }
}

pub(crate) struct TestFrontend {
    server: Server,
    client: Client,
}

impl TestFrontend {
    fn new(db: &TestDatabase) -> Self {
        Self {
            server: Server::start_test(db.conn.clone()),
            client: Client::new(),
        }
    }

    fn build_request(&self, method: Method, url: &str) -> RequestBuilder {
        self.client
            .request(method, &format!("http://{}{}", self.server.addr(), url))
    }

    pub(crate) fn get(&self, url: &str) -> RequestBuilder {
        self.build_request(Method::GET, url)
    }
}
<commit_msg>make it clearer what assert_redirect() is doing<commit_after>mod fakes;

use crate::web::Server;
use failure::Error;
use once_cell::unsync::OnceCell;
use postgres::Connection;
use reqwest::{Client, Method, RequestBuilder};
use std::sync::{Arc, Mutex, MutexGuard};
use std::panic;

pub(crate) fn wrapper(f: impl FnOnce(&TestEnvironment) -> Result<(), Error>) {
    let env = TestEnvironment::new();
    // if we didn't catch the panic, the server would hang forever
    let maybe_panic = panic::catch_unwind(panic::AssertUnwindSafe(|| f(&env)));
    env.cleanup();
    let result = match maybe_panic {
        Ok(r) => r,
        Err(payload) => panic::resume_unwind(payload),
    };

    if let Err(err) = result {
        eprintln!("the test failed: {}", err);
        for cause in err.iter_causes() {
            eprintln!("  caused by: {}", cause);
        }

        eprintln!("{}", err.backtrace());

        panic!("the test failed");
    }
}

/// Make sure that a URL returns a status code between 200-299
pub(crate) fn assert_success(path: &str, web: &TestFrontend) -> Result<(), Error> {
    let status = web.get(path).send()?.status();
    assert!(status.is_success(), "failed to GET {}: {}", path, status);
    Ok(())
}

/// Make sure that a URL redirects to a specific page
pub(crate) fn assert_redirect(path: &str, expected_target: &str, web: &TestFrontend) -> Result<(), Error> {
    // Reqwest follows redirects automatically
    let response = web.get(path).send()?;
    let status = response.status();

    let redirect_target = if expected_target.starts_with("https://") {
        response.url().as_str()
    } else {
        response.url().path()
    };
    // Either we followed a redirect to the wrong place, or there was no redirect
    if redirect_target != expected_target {
        // wrong place
        if redirect_target != path {
            panic!("{}: expected redirect to {}, got redirect to {}",
                   path, expected_target, redirect_target);
        } else {
            // no redirect
            panic!("{}: expected redirect to {}, got {}", path, expected_target, status);
        }
    }
    assert!(status.is_success(), "failed to GET {}: {}", expected_target, status);
    Ok(())
}

pub(crate) struct TestEnvironment {
    db: OnceCell<TestDatabase>,
    frontend: OnceCell<TestFrontend>,
}

impl TestEnvironment {
    fn new() -> Self {
        Self {
            db: OnceCell::new(),
            frontend: OnceCell::new(),
        }
    }

    fn cleanup(self) {
        if let Some(frontend) = self.frontend.into_inner() {
            frontend.server.leak();
        }
    }

    pub(crate) fn db(&self) -> &TestDatabase {
        self.db
            .get_or_init(|| TestDatabase::new().expect("failed to initialize the db"))
    }

    pub(crate) fn frontend(&self) -> &TestFrontend {
        self.frontend.get_or_init(|| TestFrontend::new(self.db()))
    }
}

pub(crate) struct TestDatabase {
    conn: Arc<Mutex<Connection>>,
}

impl TestDatabase {
    fn new() -> Result<Self, Error> {
        // The temporary migration uses CREATE TEMPORARY TABLE instead of CREATE TABLE, creating
        // fresh temporary copies of the database on top of the real one. The temporary tables are
        // only visible to this connection, and will be deleted when it exits.
        let conn = crate::db::connect_db()?;
        crate::db::migrate_temporary(None, &conn)?;

        Ok(TestDatabase {
            conn: Arc::new(Mutex::new(conn)),
        })
    }

    pub(crate) fn conn(&self) -> MutexGuard<Connection> {
        self.conn.lock().expect("failed to lock the connection")
    }

    pub(crate) fn fake_release(&self) -> fakes::FakeRelease {
        fakes::FakeRelease::new(self)
    }
}

pub(crate) struct TestFrontend {
    server: Server,
    client: Client,
}

impl TestFrontend {
    fn new(db: &TestDatabase) -> Self {
        Self {
            server: Server::start_test(db.conn.clone()),
            client: Client::new(),
        }
    }

    fn build_request(&self, method: Method, url: &str) -> RequestBuilder {
        self.client
            .request(method, &format!("http://{}{}", self.server.addr(), url))
    }

    pub(crate) fn get(&self, url: &str) -> RequestBuilder {
        self.build_request(Method::GET, url)
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use {Docopt, ArgvMap};
use Value::{self, Switch, Plain};

fn get_args(doc: &str, argv: &[&'static str]) -> ArgvMap {
    let dopt = match Docopt::new(doc) {
        Err(err) => panic!("Invalid usage: {}", err),
        Ok(dopt) => dopt,
    };
    match dopt.argv(vec!["cmd"].iter().chain(argv.iter()).cloned()).parse() {
        Err(err) => panic!("{}", err),
        Ok(vals) => vals,
    }
}

fn map_from_alist(alist: Vec<(&'static str, Value)>) -> HashMap<String, Value> {
    alist.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
}

fn same_args(expected: &HashMap<String, Value>, got: &ArgvMap) {
    for (k, ve) in expected.iter() {
        match got.map.find(k) {
            None => panic!("EXPECTED has '{}' but GOT does not.", k),
            Some(vg) => assert!(ve == vg,
                                "{}: EXPECTED = '{:?}' != '{:?}' = GOT", k, ve, vg),
        }
    }
    for (k, vg) in got.map.iter() {
        match got.map.find(k) {
            None => panic!("GOT has '{}' but EXPECTED does not.", k),
            Some(ve) => assert!(vg == ve,
                                "{}: GOT = '{:?}' != '{:?}' = EXPECTED", k, vg, ve),
        }
    }
}

macro_rules! test_expect(
    ($name:ident, $doc:expr, $args:expr, $expected:expr) => (
        #[test]
        fn $name() {
            let vals = get_args($doc, $args);
            let expected = map_from_alist($expected);
            same_args(&expected, &vals);
        }
    );
);

macro_rules! test_user_error(
    ($name:ident, $doc:expr, $args:expr) => (
        #[test]
        #[should_panic]
        fn $name() { get_args($doc, $args); }
    );
);

test_expect!(test_issue_13, "Usage: prog file <file>", &["file", "file"],
             vec![("file", Switch(true)),
                  ("<file>", Plain(Some("file".to_string())))]);

test_expect!(test_issue_129, "Usage: prog [options]

Options:
    --foo ARG   Foo foo.",
             &["--foo=a b"],
             vec![("--foo", Plain(Some("a b".into())))]);

mod testcases;
mod suggestions;
<commit_msg>80 cols<commit_after>use std::collections::HashMap;
use {Docopt, ArgvMap};
use Value::{self, Switch, Plain};

fn get_args(doc: &str, argv: &[&'static str]) -> ArgvMap {
    let dopt = match Docopt::new(doc) {
        Err(err) => panic!("Invalid usage: {}", err),
        Ok(dopt) => dopt,
    };
    match dopt.argv(vec!["cmd"].iter().chain(argv.iter())).parse() {
        Err(err) => panic!("{}", err),
        Ok(vals) => vals,
    }
}

fn map_from_alist(alist: Vec<(&'static str, Value)>)
                 -> HashMap<String, Value> {
    alist.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
}

fn same_args(expected: &HashMap<String, Value>, got: &ArgvMap) {
    for (k, ve) in expected.iter() {
        match got.map.find(k) {
            None => panic!("EXPECTED has '{}' but GOT does not.", k),
            Some(vg) => {
                assert!(ve == vg,
                        "{}: EXPECTED = '{:?}' != '{:?}' = GOT", k, ve, vg)
            }
        }
    }
    for (k, vg) in got.map.iter() {
        match got.map.find(k) {
            None => panic!("GOT has '{}' but EXPECTED does not.", k),
            Some(ve) => {
                assert!(vg == ve,
                        "{}: GOT = '{:?}' != '{:?}' = EXPECTED", k, vg, ve)
        }
    }
}

macro_rules! test_expect(
    ($name:ident, $doc:expr, $args:expr, $expected:expr) => (
        #[test]
        fn $name() {
            let vals = get_args($doc, $args);
            let expected = map_from_alist($expected);
            same_args(&expected, &vals);
        }
    );
);

macro_rules! test_user_error(
    ($name:ident, $doc:expr, $args:expr) => (
        #[test]
        #[should_panic]
        fn $name() { get_args($doc, $args); }
    );
);

test_expect!(test_issue_13, "Usage: prog file <file>", &["file", "file"],
             vec![("file", Switch(true)),
                  ("<file>", Plain(Some("file".to_string())))]);

test_expect!(test_issue_129, "Usage: prog [options]

Options:
    --foo ARG   Foo foo.",
             &["--foo=a b"],
             vec![("--foo", Plain(Some("a b".into())))]);

mod testcases;
mod suggestions;
<|endoftext|>