<commit_before>use core::prelude::*;
use core::default::Default;
use boxed::Box;
use fn_box::FnBox;
use collections::RingBuf;
use global::global;
use alloc::heap::allocate;

pub fn spawn<F, R>(f:F) -> Future<R> where F: FnOnce()->R, F:Send {
    global().scheduler.lock().spawn(f)
}

pub unsafe fn reschedule(current_rsp: uint) -> ! {

    // switch stack so we can park current thread
    const SCHEDULER_STACK_SIZE: uint = 4096;
    static SCHEDULER_STACK: [u8; SCHEDULER_STACK_SIZE] = [0; SCHEDULER_STACK_SIZE];

    let scheduler_stack_top = &SCHEDULER_STACK as *const [u8; 4096] as uint
        + SCHEDULER_STACK_SIZE;
    call_on_stack(inner, current_rsp, scheduler_stack_top);

    fn new_stack() -> uint {
        const NEW_STACK_SIZE: uint = 4096*2;
        let stack_bottom = unsafe{allocate(NEW_STACK_SIZE, 4096)};
        let stack_top = (stack_bottom as uint + NEW_STACK_SIZE);
        stack_top
    }

    fn call_on_stack<Arg>(function: fn(Arg) -> !, arg: Arg, stack_top: uint) -> ! {
        unsafe{asm!("call $0;" :: "r"(function), "{rdi}"(arg), "{rsp}"(stack_top) :
            : "intel", "volatile")}
        panic!("diverging fn returned");
    }

    fn inner(current_rsp: uint) -> ! {
        extern {
            fn pop_registers_and_iret(rsp: uint) -> !;
        }

        // park current thread
        let current = Thread::from_rsp(current_rsp);
        global().scheduler.lock().park(current);

        // schedule new thread
        loop {
            print!("_");
            let new_thread = unsafe{global().scheduler.lock().schedule().unwrap_or_default()};
            match new_thread {
                Thread{state: ThreadState::Active{rsp}} => {
                    unsafe{pop_registers_and_iret(rsp)}
                },
                Thread{state: ThreadState::New{function}} => {
                    println!("new");
                    let new_stack_top = new_stack();
                    let invoke_fn : fn(Box<FnBox() + Send>) -> ! = invoke;
                    call_on_stack(invoke_fn, function, new_stack_top)
                },
            }
        }
    }

    fn invoke(function: Box<FnBox() + Send>) -> ! {
        function.call_once(());
        unsafe{asm!("int $$66" :::: "volatile")};
        unreachable!();
    }

}

pub type GlobalScheduler = Scheduler;
impl GlobalScheduler {
    pub fn new() -> GlobalScheduler {
        Scheduler::new()
    }
}

pub struct Future<T>;

pub struct Thread {
    state: ThreadState,
}

impl Thread {
    fn new<F>(f: F) -> Thread where F : FnOnce(), F: Send {
        Thread {
            state: ThreadState::New {
                function: box f,
            }
        }
    }

    pub fn from_rsp(rsp: uint) -> Thread {
        Thread {
            state: ThreadState::Active {
                rsp: rsp,
            }
        }
    }
}

impl Default for Thread {
    fn default() -> Thread {
        Thread::new(|| print!("."))
    }
}

enum ThreadState {
    New {
        function: Box<FnBox() + Send>,
    },
    Active {
        rsp: uint,
    }
}

pub struct Scheduler {
    threads: RingBuf<Thread>,
}

impl Scheduler {
    pub fn new() -> Scheduler {
        Scheduler{
            threads: RingBuf::new(),
        }
    }

    fn spawn<F, R>(&mut self, f:F) -> Future<R> where F: FnOnce()->R, F: Send {

        //let (tx, rx) = channel();

        self.threads.push_back(Thread::new(move |:| {
            /*tx.send_opt(*/ f();
        }));

        Future/*::from_receiver(rx)*/
    }

    fn park(&mut self, thread: Thread) {
        self.threads.push_back(thread)
    }

    unsafe fn schedule(&mut self) -> Option<Thread> {
        self.threads.pop_front()
    }
}<commit_msg>add schedule; reschedule and schedule both call schedule_inner<commit_after>use core::prelude::*;
use core::default::Default;
use boxed::Box;
use fn_box::FnBox;
use collections::RingBuf;
use global::global;
use alloc::heap::allocate;

pub fn spawn<F, R>(f:F) -> Future<R> where F: FnOnce()->R, F:Send {
    global().scheduler.lock().spawn(f)
}

pub unsafe fn reschedule(current_rsp: uint) -> ! {
    // switch stack so we can park current thread

    call_on_stack(inner, current_rsp, scheduler_stack_top());

    fn inner(current_rsp: uint) -> ! {
        // park current thread
        let current = Thread::from_rsp(current_rsp);
        global().scheduler.lock().park(current);

        schedule_inner();
    }
}

pub unsafe fn schedule() -> ! {
    call_on_stack(inner, (), scheduler_stack_top());

    fn inner(_:()) -> ! {
        schedule_inner();
    }
}

fn schedule_inner() -> ! {
    #[allow(improper_ctypes)]
    extern {
        fn pop_registers_and_iret(rsp: uint) -> !;
    }

    fn new_stack() -> uint {
        const NEW_STACK_SIZE: uint = 4096*2;
        let stack_bottom = unsafe{allocate(NEW_STACK_SIZE, 4096)};
        let stack_top = stack_bottom as uint + NEW_STACK_SIZE;
        stack_top
    }

    fn invoke(function: Box<FnBox() + Send>) -> ! {
        function.call_once(());
        unsafe{asm!("int $$66" :::: "volatile")};
        unreachable!();
    }

    // schedule new thread
    print!("_");
    let new_thread = unsafe{global().scheduler.lock().schedule().unwrap_or_default()};

    match new_thread {
        Thread{state: ThreadState::Active{rsp}} => {
            unsafe{pop_registers_and_iret(rsp)}
        },
        Thread{state: ThreadState::New{function}} => {
            println!("new");
            let new_stack_top = new_stack();
            let invoke_fn : fn(Box<FnBox() + Send>) -> ! = invoke;
            unsafe{call_on_stack(invoke_fn, function, new_stack_top)}
        },
    }
}

fn scheduler_stack_top() -> uint {
    const SCHEDULER_STACK_SIZE: uint = 4096;
    static SCHEDULER_STACK: [u8; SCHEDULER_STACK_SIZE] = [0; SCHEDULER_STACK_SIZE];

    &SCHEDULER_STACK as *const [u8; 4096] as uint + SCHEDULER_STACK_SIZE
}

unsafe fn call_on_stack<Arg>(function: fn(Arg) -> !, arg: Arg, stack_top: uint) -> ! {
    asm!("call $0;" :: "r"(function), "{rdi}"(arg), "{rsp}"(stack_top) :
        : "intel", "volatile");
    panic!("diverging fn returned");
}

pub type GlobalScheduler = Scheduler;
impl GlobalScheduler {
    pub fn new() -> GlobalScheduler {
        Scheduler::new()
    }
}

pub struct Future<T>;

pub struct Thread {
    state: ThreadState,
}

impl Thread {
    fn new<F>(f: F) -> Thread where F : FnOnce(), F: Send {
        Thread {
            state: ThreadState::New {
                function: box f,
            }
        }
    }

    pub fn from_rsp(rsp: uint) -> Thread {
        Thread {
            state: ThreadState::Active {
                rsp: rsp,
            }
        }
    }
}

impl Default for Thread {
    fn default() -> Thread {
        Thread::new(|| print!("."))
    }
}

enum ThreadState {
    New {
        function: Box<FnBox() + Send>,
    },
    Active {
        rsp: uint,
    }
}

pub struct Scheduler {
    threads: RingBuf<Thread>,
}

impl Scheduler {
    pub fn new() -> Scheduler {
        Scheduler{
            threads: RingBuf::new(),
        }
    }

    fn spawn<F, R>(&mut self, f:F) -> Future<R> where F: FnOnce()->R, F: Send {

        //let (tx, rx) = channel();

        self.threads.push_back(Thread::new(move |:| {
            /*tx.send_opt(*/ f();
        }));

        Future/*::from_receiver(rx)*/
    }

    fn park(&mut self, thread: Thread) {
        self.threads.push_back(thread)
    }

    unsafe fn schedule(&mut self) -> Option<Thread> {
        self.threads.pop_front()
    }
}<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::collections::hash_map::{Entry, HashMap};
use std::collections::hash_set::HashSet;
use std::mem;
use std::rc::Rc;

use alloc::AllocBox;
use gc_error::{GcError, Result};
use js_types::js_var::{JsPtrEnum, JsType, JsVar};
use js_types::binding::Binding;
use js_types::allocator::Allocator;

// Tunable GC parameter. Probably should not be a constant, but good enough for now.
const GC_THRESHOLD: usize = 64;

/// A logical scope in the AST. Represents any scoped block of Javascript code.
/// roots: A set of all root references into the heap
/// parent: An optional parent scope, e.g. the caller of this function scope,
///         or the function that owns an `if` statement
/// heap: A shared reference to the heap allocator.
/// stack: The stack of the current scope, containing all variables allocated
///        by this scope.
pub struct Scope {
    roots: HashSet<Binding>,
    pub parent: Option<Box<Scope>>,
    heap: Rc<RefCell<AllocBox>>,
    stack: HashMap<Binding, JsVar>,
}

impl Scope {
    /// Create a new, parentless scope node.
    pub fn new(heap: &Rc<RefCell<AllocBox>>) -> Scope {
        Scope {
            roots: HashSet::new(),
            parent: None,
            heap: heap.clone(),
            stack: HashMap::new(),
        }
    }

    /// Sets the parent of a scope, and clones and unions its root bindings.
    pub fn set_parent(&mut self, parent: Scope) {
        self.roots = self.roots.union(&parent.roots).cloned().collect();
        self.parent = Some(box parent);
    }

    /// Push a new JsVar onto the stack, and maybe allocate a pointer in the heap.
    pub fn push(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> Result<()> {
        let res = match var.t {
            JsType::JsPtr =>
                if let Some(ptr) = ptr {
                    // Creating a new pointer creates a new root
                    self.roots.insert(var.binding.clone());
                    self.heap.borrow_mut().alloc(var.binding.clone(), ptr)
                } else {
                    return Err(GcError::PtrError);
                },
            _ => if let Some(_) = ptr { Err(GcError::PtrError) } else { Ok(()) },
        };
        self.stack.insert(var.binding.clone(), var);
        res
    }

    /// Take ownership of a variable (usually from another scope).
    pub fn own(&mut self, var: JsVar) {
        self.stack.insert(var.binding.clone(), var);
    }

    /// Return an optional copy of a variable and an optional pointer into the heap.
    pub fn get_var_copy(&self, bnd: &Binding) -> (Option<JsVar>, Option<JsPtrEnum>) {
        if let Some(var) = self.stack.get(bnd) {
            match var.t {
                JsType::JsPtr => {
                    if let Some(alloc) = self.heap.borrow().find_id(bnd) {
                        (Some(var.clone()), Some(alloc.borrow().clone()))
                    } else {
                        // This case should be impossible unless you have an
                        // invalid ptr, which should also be impossible.
                        (None, None)
                    }
                },
                _ => (Some(var.clone()), None),
            }
        } else if let Some(ref parent) = self.parent {
            // FIXME? This is slow.
            parent.get_var_copy(bnd)
        } else { (None, None) }
    }

    /// Try to update a variable that's been allocated.
    pub fn update_var(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> Result<()> {
        match var.t {
            JsType::JsPtr =>
                if let Some(ptr) = ptr {
                    // A new root was potentially created
                    self.roots.insert(var.binding.clone());
                    self.heap.borrow_mut().update_ptr(&var.binding, ptr)
                } else {
                    Err(GcError::PtrError)
                },
            _ => {
                if let Some(_) = ptr { return Err(GcError::PtrError); }
                if let Entry::Occupied(mut view) = self.stack.entry(var.binding.clone()) {
                    // A root was potentially removed
                    self.roots.remove(&var.binding);
                    *view.get_mut() = var;
                    return Ok(());
                }
                // Hack to skirt mutable borrow of self lasting longer than it should
                if let Entry::Vacant(_) = self.stack.entry(var.binding.clone()) {
                    // TODO Need to call `push` on the root scope?
                    // Push into this scope and just give to parent when done?
                    return self.push(var, ptr);
                }
                unreachable!();
            },
        }
    }

    /// Called when a scope exits. Transfers the stack of this scope to its parent,
    /// and returns the parent scope, which may be `None`.
    pub fn transfer_stack(&mut self, gc_yield: bool) -> Option<Box<Scope>> {
        if gc_yield {
            // The interpreter says we can GC now
            self.heap.borrow_mut().mark_roots(&self.roots);
            self.heap.borrow_mut().mark_ptrs();
            self.heap.borrow_mut().sweep_ptrs();
            // Pop all of the roots we just deleted
            for bnd in &self.roots {
                if let None = self.heap.borrow().find_id(bnd) {
                    self.stack.remove(bnd);
                }
            }
        }
        if let Some(ref mut parent) = self.parent {
            for (_, var) in self.stack.drain() {
                if let JsType::JsPtr = var.t {
                        // Mangle each binding before giving it to the parent
                        // scope. This avoids binding collisions, and helps
                        // identify to a human observer which bindings are
                        // not from the current scope.
                        let mut mangled_var = var.clone();
                        mangled_var.binding = Binding::mangle(var.binding);
                        parent.own(mangled_var);
                }
            }
            parent.roots = parent.roots.union(&self.roots).cloned().collect();
        }
        mem::replace(&mut self.parent, None)
    }
}

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

    use std::cell::RefCell;
    use std::collections::hash_map::HashMap;
    use std::rc::Rc;

    use alloc::AllocBox;
    use gc_error::GcError;
    use js_types::js_var::{JsPtrEnum, JsKey, JsKeyEnum, JsType};
    use js_types::binding::Binding;
    use js_types::js_str::JsStrStruct;
    use test_utils;

    fn new_scope_as_child(parent: Scope, heap: &Rc<RefCell<AllocBox>>) -> Scope {
        Scope {
            roots: parent.roots.clone(),
            parent: Some(box parent),
            heap: heap.clone(),
            stack: HashMap::new(),
        }
    }

    #[test]
    fn test_new_scope() {
        let heap = test_utils::make_alloc_box();
        let test_scope = Scope::new(&heap);
        assert!(test_scope.parent.is_none());
    }

    #[test]
    fn test_as_child_scope() {
        let heap = test_utils::make_alloc_box();
        let parent_scope = Scope::new(&heap);
        let test_scope = new_scope_as_child(parent_scope, &heap);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_set_parent() {
        let heap = test_utils::make_alloc_box();
        let parent_scope = Scope::new(&heap);
        let mut test_scope = Scope::new(&heap);
        assert!(test_scope.parent.is_none());
        test_scope.set_parent(parent_scope);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_push() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (var, ptr, _) = test_utils::make_str("test");
        assert!(test_scope.push(var, Some(ptr)).is_ok());
        assert_eq!(test_scope.heap.borrow().len(), 1);
        let var = test_utils::make_num(1.);
        assert!(test_scope.push(var, None).is_ok());
        assert_eq!(test_scope.heap.borrow().len(), 1);
    }

    #[test]
    fn test_push_fail() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (var, ptr, _) = test_utils::make_str("test");
        let res = test_scope.push(var, None);
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));
        assert!(test_scope.heap.borrow().is_empty());
        let var = test_utils::make_num(1.);
        let res = test_scope.push(var, Some(ptr));
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));
        assert!(test_scope.heap.borrow().is_empty());
    }

    #[test]
    fn test_get_var_copy() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        test_scope.push(x, Some(x_ptr)).unwrap();

        let (var_copy, ptr_copy) = test_scope.get_var_copy(&x_bnd);
        assert!(var_copy.is_some());
        assert!(ptr_copy.is_some());
    }

    #[test]
    fn test_get_var_copy_fail() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (bad_copy, ptr_copy) = test_scope.get_var_copy(&Binding::anon());
        assert!(bad_copy.is_none());
        assert!(ptr_copy.is_none());
    }

    #[test]
    fn test_get_var_copy_from_parent_scope() {
        let heap = test_utils::make_alloc_box();
        let mut parent_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        parent_scope.push(x, Some(x_ptr)).unwrap();

        let child_scope = new_scope_as_child(parent_scope, &heap);

        let (var_copy, ptr_copy) = child_scope.get_var_copy(&x_bnd);
        assert!(var_copy.is_some());
        assert!(ptr_copy.is_some());
    }

    #[test]
    fn test_update_var() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        assert!(test_scope.push(x, Some(x_ptr)).is_ok());
        let (update, _) = test_scope.get_var_copy(&x_bnd);
        let update_ptr = Some(JsPtrEnum::JsStr(JsStrStruct::new("test")));
        assert!(test_scope.update_var(update.unwrap(), update_ptr).is_ok());

        let (update, update_ptr) = test_scope.get_var_copy(&x_bnd);
        match update_ptr.unwrap() {
            JsPtrEnum::JsStr(JsStrStruct{text: ref s}) => assert_eq!(s, "test"),
            _ => unreachable!(),
        }
        assert_eq!(update.unwrap().binding, x_bnd);
    }

    #[test]
    fn test_update_var_fail() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        assert!(test_scope.push(x, Some(x_ptr)).is_ok());
        let (mut update, update_ptr) = test_scope.get_var_copy(&x_bnd);
        let res = test_scope.update_var(update.clone().unwrap(), None);
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));

        let mut update = update.unwrap();
        update.t = JsType::JsNum(1.);
        let res = test_scope.update_var(update, update_ptr);
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));
    }

    #[test]
    fn test_transfer_stack() {
        let heap = test_utils::make_alloc_box();
        let mut parent_scope = Scope::new(&heap);
        {
            let mut test_scope = new_scope_as_child(parent_scope, &heap);
            test_scope.push(test_utils::make_num(0.), None).unwrap();
            test_scope.push(test_utils::make_num(1.), None).unwrap();
            test_scope.push(test_utils::make_num(2.), None).unwrap();
            let kvs = vec![(JsKey::new(JsKeyEnum::JsBool(true)),
                            test_utils::make_num(1.), None)];
            let (var, ptr, _) = test_utils::make_obj(kvs, heap.clone());
            test_scope.push(var, Some(ptr)).unwrap();
            parent_scope = *test_scope.transfer_stack(false).unwrap();
        }
        assert_eq!(parent_scope.stack.len(), 1);
    }

    #[test]
    fn test_transfer_stack_with_yield() {
        let heap = test_utils::make_alloc_box();
        // Make a scope
        let mut parent_scope = Scope::new(&heap);
        {
            // Push a child scope
            let mut test_scope = new_scope_as_child(parent_scope, &heap);
            // Allocate some non-root variables (numbers)
            test_scope.push(test_utils::make_num(0.), None).unwrap();
            test_scope.push(test_utils::make_num(1.), None).unwrap();
            test_scope.push(test_utils::make_num(2.), None).unwrap();

            // Make a string to put into an object
            // (so it's heap-allocated and we can lose its ref from the object)
            let (var, ptr, _) = test_utils::make_str("test");

            // Create an obj of { true: 1.0, false: heap("test") }
            let kvs = vec![(JsKey::new(JsKeyEnum::JsBool(true)),
                            test_utils::make_num(1.), None),
                           (JsKey::new(JsKeyEnum::JsBool(false)),
                            var, Some(ptr))];
            let key_bnd = kvs[1].0.binding.clone();
            let (var, ptr, bnd) = test_utils::make_obj(kvs, heap.clone());

            // Push the obj into the current scope
            test_scope.push(var, Some(ptr)).unwrap();

            // Replace the string in the object with something else so it's no longer live
            let copy = test_scope.get_var_copy(&bnd);
            let (var_cp, mut ptr_cp) = (copy.0.unwrap(), copy.1.unwrap());
            let key = JsKey { binding: key_bnd, k: JsKeyEnum::JsBool(false) };
            match ptr_cp {
                JsPtrEnum::JsObj(ref mut obj) => { obj.dict.insert(key, test_utils::make_num(-1.)); }
                _ => unreachable!()
            }
            test_scope.update_var(var_cp, Some(ptr_cp)).unwrap();

            // Kill the current scope & give its refs to the parent,
            // allowing the GC to kick in beforehand.
            parent_scope = *test_scope.transfer_stack(true).unwrap();
        }
        // The object we created above should still exist
        assert_eq!(parent_scope.stack.len(), 1);
        assert_eq!(heap.borrow().len(), 1);
    }

}
<commit_msg>Scopes now keep all of their bindings alive when they die if they return a closure<commit_after>use std::cell::RefCell;
use std::collections::hash_map::{Entry, HashMap};
use std::collections::hash_set::HashSet;
use std::mem;
use std::rc::Rc;

use alloc::AllocBox;
use gc_error::{GcError, Result};
use js_types::js_var::{JsPtrEnum, JsPtrTag, JsType, JsVar};
use js_types::binding::Binding;
use js_types::allocator::Allocator;

// Tunable GC parameter. Probably should not be a constant, but good enough for now.
const GC_THRESHOLD: usize = 64;

/// A logical scope in the AST. Represents any scoped block of Javascript code.
/// roots: A set of all root references into the heap
/// parent: An optional parent scope, e.g. the caller of this function scope,
///         or the function that owns an `if` statement
/// heap: A shared reference to the heap allocator.
/// stack: The stack of the current scope, containing all variables allocated
///        by this scope.
pub struct Scope {
    roots: HashSet<Binding>,
    pub parent: Option<Box<Scope>>,
    heap: Rc<RefCell<AllocBox>>,
    stack: HashMap<Binding, JsVar>,
}

impl Scope {
    /// Create a new, parentless scope node.
    pub fn new(heap: &Rc<RefCell<AllocBox>>) -> Scope {
        Scope {
            roots: HashSet::new(),
            parent: None,
            heap: heap.clone(),
            stack: HashMap::new(),
        }
    }

    /// Sets the parent of a scope, and clones and unions its root bindings.
    pub fn set_parent(&mut self, parent: Scope) {
        self.roots = self.roots.union(&parent.roots).cloned().collect();
        self.parent = Some(box parent);
    }

    /// Push a new JsVar onto the stack, and maybe allocate a pointer in the heap.
    pub fn push(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> Result<()> {
        let res = match var.t {
            JsType::JsPtr(_) =>
                if let Some(ptr) = ptr {
                    // Creating a new pointer creates a new root
                    self.roots.insert(var.binding.clone());
                    self.heap.borrow_mut().alloc(var.binding.clone(), ptr)
                } else {
                    return Err(GcError::PtrError);
                },
            _ => if let Some(_) = ptr { Err(GcError::PtrError) } else { Ok(()) },
        };
        self.stack.insert(var.binding.clone(), var);
        res
    }

    /// Take ownership of a variable (usually from another scope).
    pub fn own(&mut self, var: JsVar) {
        self.stack.insert(var.binding.clone(), var);
    }

    /// Return an optional copy of a variable and an optional pointer into the heap.
    pub fn get_var_copy(&self, bnd: &Binding) -> (Option<JsVar>, Option<JsPtrEnum>) {
        if let Some(var) = self.stack.get(bnd) {
            match var.t {
                JsType::JsPtr(_) => {
                    if let Some(alloc) = self.heap.borrow().find_id(bnd) {
                        (Some(var.clone()), Some(alloc.borrow().clone()))
                    } else {
                        // This case should be impossible unless you have an
                        // invalid ptr, which should also be impossible.
                        (None, None)
                    }
                },
                _ => (Some(var.clone()), None),
            }
        } else if let Some(ref parent) = self.parent {
            // FIXME? This is slow.
            parent.get_var_copy(bnd)
        } else { (None, None) }
    }

    /// Try to update a variable that's been allocated.
    pub fn update_var(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> Result<()> {
        match var.t {
            JsType::JsPtr(tag) =>
                if let Some(ptr) = ptr {
                    // A new root was potentially created
                    // If the pointer and its underlying type are not equal, return an error.
                    if !tag.eq_ptr_type(&ptr) { return Err(GcError::PtrError); }
                    self.roots.insert(var.binding.clone());
                    self.heap.borrow_mut().update_ptr(&var.binding, ptr)
                } else {
                    Err(GcError::PtrError)
                },
            _ => {
                if let Some(_) = ptr { return Err(GcError::PtrError); }
                if let Entry::Occupied(mut view) = self.stack.entry(var.binding.clone()) {
                    // A root was potentially removed
                    self.roots.remove(&var.binding);
                    *view.get_mut() = var;
                    return Ok(());
                }
                // Hack to skirt mutable borrow of self lasting longer than it should
                if let Entry::Vacant(_) = self.stack.entry(var.binding.clone()) {
                    // TODO Need to call `push` on the root scope?
                    // Push into this scope and just give to parent when done?
                    return self.push(var, ptr);
                }
                unreachable!();
            },
        }
    }

    /// Called when a scope exits. Transfers the stack of this scope to its parent,
    /// and returns the parent scope, which may be `None`.
    pub fn transfer_stack(&mut self, gc_yield: bool) -> Option<Box<Scope>> {
        if gc_yield {
            // The interpreter says we can GC now
            self.heap.borrow_mut().mark_roots(&self.roots);
            self.heap.borrow_mut().mark_ptrs();
            self.heap.borrow_mut().sweep_ptrs();
            // Pop all of the roots we just deleted
            for bnd in &self.roots {
                if let None = self.heap.borrow().find_id(bnd) {
                    self.stack.remove(bnd);
                }
            }
        }
        if let Some(ref mut parent) = self.parent {
            let returning_closure = self.stack.iter()
                                              .any(|(_, v)|
                                                   matches!(v.t, JsType::JsPtr(JsPtrTag::JsFn)));
            // If we're returning a closure, conservatively assume the closure takes ownership of
            // every binding defined in this scope, so it must all live into the parent scope.
            if returning_closure {
                for (_, var) in self.stack.drain() {
                    let mut mangled_var = var.clone();
                    mangled_var.binding = Binding::mangle(var.binding);
                    parent.own(mangled_var);
                }
            } else {
                for (_, var) in self.stack.drain() {
                    if let JsType::JsPtr(_) = var.t {
                            // Mangle each binding before giving it to the parent
                            // scope. This avoids binding collisions, and helps
                            // identify to a human observer which bindings are
                            // not from the current scope.
                            let mut mangled_var = var.clone();
                            mangled_var.binding = Binding::mangle(var.binding);
                            parent.own(mangled_var);
                    }
                }
            }
            parent.roots = parent.roots.union(&self.roots).cloned().collect();
        }
        mem::replace(&mut self.parent, None)
    }
}

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

    use std::cell::RefCell;
    use std::collections::hash_map::HashMap;
    use std::rc::Rc;

    use alloc::AllocBox;
    use gc_error::GcError;
    use js_types::js_var::{JsPtrEnum, JsKey, JsKeyEnum, JsType};
    use js_types::binding::Binding;
    use js_types::js_str::JsStrStruct;
    use test_utils;

    fn new_scope_as_child(parent: Scope, heap: &Rc<RefCell<AllocBox>>) -> Scope {
        Scope {
            roots: parent.roots.clone(),
            parent: Some(box parent),
            heap: heap.clone(),
            stack: HashMap::new(),
        }
    }

    #[test]
    fn test_new_scope() {
        let heap = test_utils::make_alloc_box();
        let test_scope = Scope::new(&heap);
        assert!(test_scope.parent.is_none());
    }

    #[test]
    fn test_as_child_scope() {
        let heap = test_utils::make_alloc_box();
        let parent_scope = Scope::new(&heap);
        let test_scope = new_scope_as_child(parent_scope, &heap);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_set_parent() {
        let heap = test_utils::make_alloc_box();
        let parent_scope = Scope::new(&heap);
        let mut test_scope = Scope::new(&heap);
        assert!(test_scope.parent.is_none());
        test_scope.set_parent(parent_scope);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_push() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (var, ptr, _) = test_utils::make_str("test");
        assert!(test_scope.push(var, Some(ptr)).is_ok());
        assert_eq!(test_scope.heap.borrow().len(), 1);
        let var = test_utils::make_num(1.);
        assert!(test_scope.push(var, None).is_ok());
        assert_eq!(test_scope.heap.borrow().len(), 1);
    }

    #[test]
    fn test_push_fail() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (var, ptr, _) = test_utils::make_str("test");
        let res = test_scope.push(var, None);
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));
        assert!(test_scope.heap.borrow().is_empty());
        let var = test_utils::make_num(1.);
        let res = test_scope.push(var, Some(ptr));
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));
        assert!(test_scope.heap.borrow().is_empty());
    }

    #[test]
    fn test_get_var_copy() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        test_scope.push(x, Some(x_ptr)).unwrap();

        let (var_copy, ptr_copy) = test_scope.get_var_copy(&x_bnd);
        assert!(var_copy.is_some());
        assert!(ptr_copy.is_some());
    }

    #[test]
    fn test_get_var_copy_fail() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (bad_copy, ptr_copy) = test_scope.get_var_copy(&Binding::anon());
        assert!(bad_copy.is_none());
        assert!(ptr_copy.is_none());
    }

    #[test]
    fn test_get_var_copy_from_parent_scope() {
        let heap = test_utils::make_alloc_box();
        let mut parent_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        parent_scope.push(x, Some(x_ptr)).unwrap();

        let child_scope = new_scope_as_child(parent_scope, &heap);

        let (var_copy, ptr_copy) = child_scope.get_var_copy(&x_bnd);
        assert!(var_copy.is_some());
        assert!(ptr_copy.is_some());
    }

    #[test]
    fn test_update_var() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        assert!(test_scope.push(x, Some(x_ptr)).is_ok());
        let (update, _) = test_scope.get_var_copy(&x_bnd);
        let update_ptr = Some(JsPtrEnum::JsStr(JsStrStruct::new("test")));
        assert!(test_scope.update_var(update.unwrap(), update_ptr).is_ok());

        let (update, update_ptr) = test_scope.get_var_copy(&x_bnd);
        match update_ptr.unwrap() {
            JsPtrEnum::JsStr(JsStrStruct{text: ref s}) => assert_eq!(s, "test"),
            _ => unreachable!(),
        }
        assert_eq!(update.unwrap().binding, x_bnd);
    }

    #[test]
    fn test_update_var_fail() {
        let heap = test_utils::make_alloc_box();
        let mut test_scope = Scope::new(&heap);
        let (x, x_ptr, x_bnd) = test_utils::make_str("x");
        assert!(test_scope.push(x, Some(x_ptr)).is_ok());
        let (mut update, update_ptr) = test_scope.get_var_copy(&x_bnd);
        let res = test_scope.update_var(update.clone().unwrap(), None);
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));

        let mut update = update.unwrap();
        update.t = JsType::JsNum(1.);
        let res = test_scope.update_var(update, update_ptr);
        assert!(res.is_err());
        assert!(matches!(res, Err(GcError::PtrError)));
    }

    #[test]
    fn test_transfer_stack() {
        let heap = test_utils::make_alloc_box();
        let mut parent_scope = Scope::new(&heap);
        {
            let mut test_scope = new_scope_as_child(parent_scope, &heap);
            test_scope.push(test_utils::make_num(0.), None).unwrap();
            test_scope.push(test_utils::make_num(1.), None).unwrap();
            test_scope.push(test_utils::make_num(2.), None).unwrap();
            let kvs = vec![(JsKey::new(JsKeyEnum::JsBool(true)),
                            test_utils::make_num(1.), None)];
            let (var, ptr, _) = test_utils::make_obj(kvs, heap.clone());
            test_scope.push(var, Some(ptr)).unwrap();
            parent_scope = *test_scope.transfer_stack(false).unwrap();
        }
        assert_eq!(parent_scope.stack.len(), 1);
    }

    #[test]
    fn test_transfer_stack_with_yield() {
        let heap = test_utils::make_alloc_box();
        // Make a scope
        let mut parent_scope = Scope::new(&heap);
        {
            // Push a child scope
            let mut test_scope = new_scope_as_child(parent_scope, &heap);
            // Allocate some non-root variables (numbers)
            test_scope.push(test_utils::make_num(0.), None).unwrap();
            test_scope.push(test_utils::make_num(1.), None).unwrap();
            test_scope.push(test_utils::make_num(2.), None).unwrap();

            // Make a string to put into an object
            // (so it's heap-allocated and we can lose its ref from the object)
            let (var, ptr, _) = test_utils::make_str("test");

            // Create an obj of { true: 1.0, false: heap("test") }
            let kvs = vec![(JsKey::new(JsKeyEnum::JsBool(true)),
                            test_utils::make_num(1.), None),
                           (JsKey::new(JsKeyEnum::JsBool(false)),
                            var, Some(ptr))];
            let key_bnd = kvs[1].0.binding.clone();
            let (var, ptr, bnd) = test_utils::make_obj(kvs, heap.clone());

            // Push the obj into the current scope
            test_scope.push(var, Some(ptr)).unwrap();

            // Replace the string in the object with something else so it's no longer live
            let copy = test_scope.get_var_copy(&bnd);
            let (var_cp, mut ptr_cp) = (copy.0.unwrap(), copy.1.unwrap());
            let key = JsKey { binding: key_bnd, k: JsKeyEnum::JsBool(false) };
            match ptr_cp {
                JsPtrEnum::JsObj(ref mut obj) => { obj.dict.insert(key, test_utils::make_num(-1.)); }
                _ => unreachable!()
            }
            test_scope.update_var(var_cp, Some(ptr_cp)).unwrap();

            // Kill the current scope & give its refs to the parent,
            // allowing the GC to kick in beforehand.
            parent_scope = *test_scope.transfer_stack(true).unwrap();
        }
        // The object we created above should still exist
        assert_eq!(parent_scope.stack.len(), 1);
        assert_eq!(heap.borrow().len(), 1);
    }

}
<|endoftext|>


<commit_before>use crate::i18n::LocalText;
use crate::*;

use colored::Colorize;
use git2::{Repository, Status};
use std::io::prelude::*;
use std::sync::{Arc, RwLock};
use std::{fs, io, path};
use subprocess::{Exec, NullFile, Redirection};

// FTL: help-subcommand-setup
/// Setup a publishing project for use with CaSILE
pub fn run() -> Result<()> {
    show_header("setup-header");
    let path = &CONF.get_string("path")?;
    let metadata = fs::metadata(&path)?;
    match metadata.is_dir() {
        true => match Repository::open(path) {
            Ok(repo) => {
                regen_gitignore(repo)?;
                configure_short_shas(Repository::open(path)?)?;
                Ok(())
            }
            Err(_error) => Err(Box::new(io::Error::new(
                io::ErrorKind::InvalidInput,
                LocalText::new("setup-error-not-git").fmt(),
            ))),
        },
        false => Err(Box::new(io::Error::new(
            io::ErrorKind::InvalidInput,
            LocalText::new("setup-error-not-dir").fmt(),
        ))),
    }
}

/// Evaluate whether this project is properly configured
pub fn is_setup() -> Result<bool> {
    let results = Arc::new(RwLock::new(Vec::new()));

    // First round of tests, entirely independent
    rayon::scope(|s| {
        s.spawn(|_| {
            let ret = is_repo().unwrap();
            results.write().unwrap().push(ret);
        });
        s.spawn(|_| {
            let ret = is_make_exectuable().unwrap();
            results.write().unwrap().push(ret);
        });
    });

    // Second round of tests, dependent on first set
    if results.read().unwrap().iter().all(|&v| v) {
        rayon::scope(|s| {
            s.spawn(|_| {
                let ret = is_not_casile_source().unwrap();
                results.write().unwrap().push(ret);
            });
            s.spawn(|_| {
                let ret = is_writable().unwrap();
                results.write().unwrap().push(ret);
            });
            s.spawn(|_| {
                let ret = is_make_gnu().unwrap();
                results.write().unwrap().push(ret);
            });
        });
    }

    let ret = results.read().unwrap().iter().all(|&v| v);
    let msg = LocalText::new(if ret { "setup-good" } else { "setup-bad" }).fmt();
    eprintln!(
        "{} {}",
        "┠─".cyan(),
        if ret { msg.green() } else { msg.red() }
    );
    Ok(ret)
}

/// Are we in a git repo?
pub fn is_repo() -> Result<bool> {
    let ret = get_repo().is_ok();
    display_check("setup-is-repo", ret);
    Ok(ret)
}

/// Are we not in the CaSILE source repo?
pub fn is_not_casile_source() -> Result<bool> {
    let repo = get_repo()?;
    let workdir = repo.workdir().unwrap();
    let testfile = workdir.join("make-shell.zsh.in");
    let ret = fs::File::open(&testfile).is_err();
    display_check("setup-is-not-casile", ret);
    Ok(ret)
}

/// Is the git repo we are in writable?
pub fn is_writable() -> Result<bool> {
    let repo = get_repo()?;
    let workdir = repo.workdir().unwrap();
    let testfile = workdir.join(".casile-write-test");
    let mut file = fs::File::create(&testfile)?;
    file.write_all(b"test")?;
    fs::remove_file(&testfile)?;
    let ret = true;
    display_check("setup-is-writable", ret);
    Ok(ret)
}

/// Check if we can execute the system's `make` utility
pub fn is_make_exectuable() -> Result<bool> {
    let ret = Exec::cmd("make")
        .arg("-v")
        .stdout(NullFile)
        .stderr(NullFile)
        .join()
        .is_ok();
    display_check("setup-is-make-executable", ret);
    Ok(ret)
}

/// Check that the system's `make` utility is GNU Make
pub fn is_make_gnu() -> Result<bool> {
    let out = Exec::cmd("make")
        .arg("-v")
        .stdout(Redirection::Pipe)
        .stderr(NullFile)
        .capture()?
        .stdout_str();
    let ret = out.starts_with("GNU Make 4.");
    display_check("setup-is-make-gnu", ret);
    Ok(ret)
}

fn regen_gitignore(repo: Repository) -> Result<()> {
    let target = vec![String::from(".gitignore")];
    make::run(target)?;
    let path = path::Path::new(".gitignore");
    let mut index = repo.index()?;
    index.add_path(path)?;
    let oid = index.write_tree()?;
    match repo.status_file(path) {
        Ok(Status::CURRENT) => {
            let text = LocalText::new("setup-gitignore-fresh").fmt();
            eprintln!("{} {}", "┠┄".cyan(), text);
            Ok(())
        }
        _ => {
            let text = LocalText::new("setup-gitignore-committing").fmt();
            eprintln!("{} {}", "┠┄".cyan(), text);
            match commit(repo, oid, "Update .gitignore") {
                Ok(_) => {
                    index.write()?;
                    Ok(())
                }
                Err(error) => Err(Box::new(error)),
            }
        }
    }
}

fn configure_short_shas(repo: Repository) -> Result<()> {
    let text = LocalText::new("setup-short-shas").fmt();
    eprintln!("{} {}", "┠┄".cyan(), text);
    let mut conf = repo.config()?;
    Ok(conf.set_i32("core.abbrev", 7)?)
}
<commit_msg>refactor(cli): Consistently use internal repo finder<commit_after>use crate::i18n::LocalText;
use crate::*;

use colored::Colorize;
use git2::{Repository, Status};
use std::io::prelude::*;
use std::sync::{Arc, RwLock};
use std::{fs, io, path};
use subprocess::{Exec, NullFile, Redirection};

// FTL: help-subcommand-setup
/// Setup a publishing project for use with CaSILE
pub fn run() -> Result<()> {
    show_header("setup-header");
    let path = &CONF.get_string("path")?;
    let metadata = fs::metadata(&path)?;
    match metadata.is_dir() {
        true => match is_repo()? {
            true => {
                regen_gitignore(get_repo()?)?;
                configure_short_shas(get_repo()?)?;
                Ok(())
            }
            false => Err(Box::new(io::Error::new(
                io::ErrorKind::InvalidInput,
                LocalText::new("setup-error-not-git").fmt(),
            ))),
        },
        false => Err(Box::new(io::Error::new(
            io::ErrorKind::InvalidInput,
            LocalText::new("setup-error-not-dir").fmt(),
        ))),
    }
}

/// Evaluate whether this project is properly configured
pub fn is_setup() -> Result<bool> {
    let results = Arc::new(RwLock::new(Vec::new()));

    // First round of tests, entirely independent
    rayon::scope(|s| {
        s.spawn(|_| {
            let ret = is_repo().unwrap();
            results.write().unwrap().push(ret);
        });
        s.spawn(|_| {
            let ret = is_make_exectuable().unwrap();
            results.write().unwrap().push(ret);
        });
    });

    // Second round of tests, dependent on first set
    if results.read().unwrap().iter().all(|&v| v) {
        rayon::scope(|s| {
            s.spawn(|_| {
                let ret = is_not_casile_source().unwrap();
                results.write().unwrap().push(ret);
            });
            s.spawn(|_| {
                let ret = is_writable().unwrap();
                results.write().unwrap().push(ret);
            });
            s.spawn(|_| {
                let ret = is_make_gnu().unwrap();
                results.write().unwrap().push(ret);
            });
        });
    }

    let ret = results.read().unwrap().iter().all(|&v| v);
    let msg = LocalText::new(if ret { "setup-good" } else { "setup-bad" }).fmt();
    eprintln!(
        "{} {}",
        "┠─".cyan(),
        if ret { msg.green() } else { msg.red() }
    );
    Ok(ret)
}

/// Are we in a git repo?
pub fn is_repo() -> Result<bool> {
    let ret = get_repo().is_ok();
    display_check("setup-is-repo", ret);
    Ok(ret)
}

/// Are we not in the CaSILE source repo?
pub fn is_not_casile_source() -> Result<bool> {
    let repo = get_repo()?;
    let workdir = repo.workdir().unwrap();
    let testfile = workdir.join("make-shell.zsh.in");
    let ret = fs::File::open(&testfile).is_err();
    display_check("setup-is-not-casile", ret);
    Ok(ret)
}

/// Is the git repo we are in writable?
pub fn is_writable() -> Result<bool> {
    let repo = get_repo()?;
    let workdir = repo.workdir().unwrap();
    let testfile = workdir.join(".casile-write-test");
    let mut file = fs::File::create(&testfile)?;
    file.write_all(b"test")?;
    fs::remove_file(&testfile)?;
    let ret = true;
    display_check("setup-is-writable", ret);
    Ok(ret)
}

/// Check if we can execute the system's `make` utility
pub fn is_make_exectuable() -> Result<bool> {
    let ret = Exec::cmd("make")
        .arg("-v")
        .stdout(NullFile)
        .stderr(NullFile)
        .join()
        .is_ok();
    display_check("setup-is-make-executable", ret);
    Ok(ret)
}

/// Check that the system's `make` utility is GNU Make
pub fn is_make_gnu() -> Result<bool> {
    let out = Exec::cmd("make")
        .arg("-v")
        .stdout(Redirection::Pipe)
        .stderr(NullFile)
        .capture()?
        .stdout_str();
    let ret = out.starts_with("GNU Make 4.");
    display_check("setup-is-make-gnu", ret);
    Ok(ret)
}

fn regen_gitignore(repo: Repository) -> Result<()> {
    let target = vec![String::from(".gitignore")];
    make::run(target)?;
    let path = path::Path::new(".gitignore");
    let mut index = repo.index()?;
    index.add_path(path)?;
    let oid = index.write_tree()?;
    match repo.status_file(path) {
        Ok(Status::CURRENT) => {
            let text = LocalText::new("setup-gitignore-fresh").fmt();
            eprintln!("{} {}", "┠┄".cyan(), text);
            Ok(())
        }
        _ => {
            let text = LocalText::new("setup-gitignore-committing").fmt();
            eprintln!("{} {}", "┠┄".cyan(), text);
            match commit(repo, oid, "Update .gitignore") {
                Ok(_) => {
                    index.write()?;
                    Ok(())
                }
                Err(error) => Err(Box::new(error)),
            }
        }
    }
}

fn configure_short_shas(repo: Repository) -> Result<()> {
    let text = LocalText::new("setup-short-shas").fmt();
    eprintln!("{} {}", "┠┄".cyan(), text);
    let mut conf = repo.config()?;
    Ok(conf.set_i32("core.abbrev", 7)?)
}
<|endoftext|>


<commit_before>#![crate_name = "test"]
#![feature(convert)]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) mahkoh (ju.orth [at] gmail [dot] com)
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

extern crate libc;

use std::collections::HashMap;
use std::ffi::{CString, OsString};
use std::env::{args_os};
use std::str::{from_utf8};

static NAME: &'static str = "test";

// TODO: decide how to handle non-UTF8 input for all the utils
// Definitely don't use [u8], try keeping it as OsStr or OsString instead
pub fn uumain(_: Vec<String>) -> i32 {
    let args = args_os().collect::<Vec<OsString>>();
    // This is completely disregarding valid windows paths that aren't valid unicode
    let args = args.iter().map(|a| a.to_bytes().unwrap()).collect::<Vec<&[u8]>>();
    if args.len() == 0 {
        return 2;
    }
    let args =
        if !args[0].ends_with(NAME.as_bytes()) {
            &args[1..]
        } else {
            &args[..]
        };
    let args = match args[0] {
        b"[" => match args[args.len() - 1] {
            b"]" => &args[1..args.len() - 1],
            _ => return 2,
        },
        _ => &args[1..args.len()],
    };
    let mut error = false;
    let retval = 1 - parse_expr(args, &mut error) as i32;
    if error {
        2
    } else {
        retval
    }
}

fn one(args: &[&[u8]]) -> bool {
    args[0].len() > 0
}

fn two(args: &[&[u8]], error: &mut bool) -> bool {
    match args[0] {
        b"!" => !one(&args[1..]),
        b"-b" => path(args[1], PathCondition::BlockSpecial),
        b"-c" => path(args[1], PathCondition::CharacterSpecial),
        b"-d" => path(args[1], PathCondition::Directory),
        b"-e" => path(args[1], PathCondition::Exists),
        b"-f" => path(args[1], PathCondition::Regular),
        b"-g" => path(args[1], PathCondition::GroupIDFlag),
        b"-h" => path(args[1], PathCondition::SymLink),
        b"-L" => path(args[1], PathCondition::SymLink),
        b"-n" => one(&args[1..]),
        b"-p" => path(args[1], PathCondition::FIFO),
        b"-r" => path(args[1], PathCondition::Readable),
        b"-S" => path(args[1], PathCondition::Socket),
        b"-s" => path(args[1], PathCondition::NonEmpty),
        b"-t" => isatty(args[1]),
        b"-u" => path(args[1], PathCondition::UserIDFlag),
        b"-w" => path(args[1], PathCondition::Writable),
        b"-x" => path(args[1], PathCondition::Executable),
        b"-z" => !one(&args[1..]),
        _ => {
            *error = true;
            false
        }
    }
}

fn three(args: &[&[u8]], error: &mut bool) -> bool {
    match args[1] {
        b"=" => args[0] == args[2],
        b"==" => args[0] == args[2],
        b"!=" => args[0] != args[2],
        b"-eq" => integers(args[0], args[2], IntegerCondition::Equal),
        b"-ne" => integers(args[0], args[2], IntegerCondition::Unequal),
        b"-gt" => integers(args[0], args[2], IntegerCondition::Greater),
        b"-ge" => integers(args[0], args[2], IntegerCondition::GreaterEqual),
        b"-lt" => integers(args[0], args[2], IntegerCondition::Less),
        b"-le" => integers(args[0], args[2], IntegerCondition::LessEqual),
        _ => match args[0] {
            b"!" => !two(&args[1..], error),
            _ => {
                *error = true;
                false
            }
        }
    }
}

fn four(args: &[&[u8]], error: &mut bool) -> bool {
    match args[0] {
        b"!" => {
            !three(&args[1..], error)
        }
        _ => {
            *error = true;
            false
        }
    }
}

enum IntegerCondition {
    Equal,
    Unequal,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
}

fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool {
    let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) {
        (Ok(a), Ok(b)) => (a, b),
        _ => return false,
    };
    let (a, b): (i64, i64) = match (a.parse(), b.parse()) {
        (Ok(a), Ok(b)) => (a, b),
        _ => return false,
    };
    match cond {
        IntegerCondition::Equal        => a == b,
        IntegerCondition::Unequal      => a != b,
        IntegerCondition::Greater      => a >  b,
        IntegerCondition::GreaterEqual => a >= b,
        IntegerCondition::Less         => a <  b,
        IntegerCondition::LessEqual    => a <= b,
    }
}

fn isatty(fd: &[u8]) -> bool {
    use libc::{isatty};
    from_utf8(fd).ok().and_then(|s| s.parse().ok())
            .map(|i| unsafe { isatty(i) == 1 }).unwrap_or(false)
}

fn dispatch(args: &mut &[&[u8]], error: &mut bool) -> bool {
    let (val, idx) = match args.len() {
        0 => {
            *error = true;
            (false, 0)
        }
        1 => (one(*args), 1),
        2 => dispatch_two(args, error),
        3 => dispatch_three(args, error),
        _ => dispatch_four(args, error)
    };
    *args = &(*args)[idx..];
    val
}

fn dispatch_two(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
    let val = two(*args, error);
    if *error {
        *error = false;
        (one(*args), 1)
    } else {
        (val, 2)
    }
}

fn dispatch_three(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
    let val = three(*args, error);
    if *error {
        *error = false;
        dispatch_two(args, error)
    } else {
        (val, 3)
    }
}

fn dispatch_four(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
    let val = four(*args, error);
    if *error {
        *error = false;
        dispatch_three(args, error)
    } else {
        (val, 4)
    }
}

#[derive(Clone, Copy)]
enum Precedence {
    Unknown = 0,
    Paren,     // FIXME: this is useless (parentheses have not been implemented)
    Or,
    And,
    BUnOp,
    BinOp,
    UnOp
}

fn parse_expr(mut args: &[&[u8]], error: &mut bool) -> bool {
    if args.len() == 0 {
        false
    } else {
        let hashmap = setup_hashmap();
        let lhs = dispatch(&mut args, error);

        if args.len() > 0 {
            parse_expr_helper(&hashmap, &mut args, lhs, Precedence::Unknown, error)
        } else {
            lhs
        }
    }
}

fn parse_expr_helper<'a>(hashmap: &HashMap<&'a [u8], Precedence>,
                         args: &mut &[&'a [u8]],
                         mut lhs: bool,
                         min_prec: Precedence,
                         error: &mut bool) -> bool {
    let mut prec = *hashmap.get(&args[0]).unwrap_or_else(|| {
        *error = true;
        &min_prec
    });
    while !*error && args.len() > 0 && prec as usize >= min_prec as usize {
        let op = args[0];
        *args = &(*args)[1..];
        let mut rhs = dispatch(args, error);
        while args.len() > 0 {
            let subprec = *hashmap.get(&args[0]).unwrap_or_else(|| {
                *error = true;
                &min_prec
            });
            if subprec as usize <= prec as usize || *error {
                break;
            }
            rhs = parse_expr_helper(hashmap, args, rhs, subprec, error);
        }
        lhs = match prec {
            Precedence::UnOp | Precedence::BUnOp => {
                *error = true;
                false
            }
            Precedence::And => lhs && rhs,
            Precedence::Or => lhs || rhs,
            Precedence::BinOp => three(&[if lhs { b" " } else { b"" }, op, if rhs { b" " } else { b"" }], error),
            Precedence::Paren => unimplemented!(),  // TODO: implement parentheses
            _ => unreachable!()
        };
        if args.len() > 0 {
            prec = *hashmap.get(&args[0]).unwrap_or_else(|| {
                *error = true;
                &min_prec
            });
        }
    }
    lhs
}

#[inline]
fn setup_hashmap<'a>() -> HashMap<&'a [u8], Precedence> {
    let mut hashmap = HashMap::<&'a [u8], Precedence>::new();

    hashmap.insert(b"-b", Precedence::UnOp);
    hashmap.insert(b"-c", Precedence::UnOp);
    hashmap.insert(b"-d", Precedence::UnOp);
    hashmap.insert(b"-e", Precedence::UnOp);
    hashmap.insert(b"-f", Precedence::UnOp);
    hashmap.insert(b"-g", Precedence::UnOp);
    hashmap.insert(b"-h", Precedence::UnOp);
    hashmap.insert(b"-L", Precedence::UnOp);
    hashmap.insert(b"-n", Precedence::UnOp);
    hashmap.insert(b"-p", Precedence::UnOp);
    hashmap.insert(b"-r", Precedence::UnOp);
    hashmap.insert(b"-S", Precedence::UnOp);
    hashmap.insert(b"-s", Precedence::UnOp);
    hashmap.insert(b"-t", Precedence::UnOp);
    hashmap.insert(b"-u", Precedence::UnOp);
    hashmap.insert(b"-w", Precedence::UnOp);
    hashmap.insert(b"-x", Precedence::UnOp);
    hashmap.insert(b"-z", Precedence::UnOp);

    hashmap.insert(b"=", Precedence::BinOp);
    hashmap.insert(b"!=", Precedence::BinOp);
    hashmap.insert(b"-eq", Precedence::BinOp);
    hashmap.insert(b"-ne", Precedence::BinOp);
    hashmap.insert(b"-gt", Precedence::BinOp);
    hashmap.insert(b"-ge", Precedence::BinOp);
    hashmap.insert(b"-lt", Precedence::BinOp);
    hashmap.insert(b"-le", Precedence::BinOp);

    hashmap.insert(b"!", Precedence::BUnOp);

    hashmap.insert(b"-a", Precedence::And);
    hashmap.insert(b"-o", Precedence::Or);

    hashmap.insert(b"(", Precedence::Paren);
    hashmap.insert(b")", Precedence::Paren);

    hashmap
}

#[derive(Eq, PartialEq)]
enum PathCondition {
    BlockSpecial,
    CharacterSpecial,
    Directory,
    Exists,
    Regular,
    GroupIDFlag,
    SymLink,
    FIFO,
    Readable,
    Socket,
    NonEmpty,
    UserIDFlag,
    Writable,
    Executable,
}

#[cfg(not(windows))]
fn path(path: &[u8], cond: PathCondition) -> bool {
    use libc::{stat, lstat, S_IFMT, S_IFLNK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFREG};
    use libc::{S_IFIFO, mode_t};
    static S_ISUID: mode_t = 0o4000;
    static S_ISGID: mode_t = 0o2000;
    static S_IFSOCK: mode_t = 0o140000;

    enum Permission {
        Read    = 0o4,
        Write   = 0o2,
        Execute = 0o1,
    }
    let perm = |stat: stat, p: Permission| {
        use libc::{getgid, getuid};
        let (uid, gid) = unsafe { (getuid(), getgid()) };
        if uid == stat.st_uid {
            stat.st_mode & ((p as mode_t) << 6) != 0
        } else if gid == stat.st_gid {
            stat.st_mode & ((p as mode_t) << 3) != 0
        } else {
            stat.st_mode & ((p as mode_t) << 0) != 0
        }
    };

    let path = CString::new(path).unwrap();
    let mut stat = unsafe { std::mem::zeroed() };
    if cond == PathCondition::SymLink {
        if unsafe { lstat(path.as_ptr(), &mut stat) } == 0 {
            if stat.st_mode & S_IFMT == S_IFLNK {
                return true;
            }
        }
        return false;
    }
    if unsafe { libc::stat(path.as_ptr(), &mut stat) } != 0 {
        return false;
    }
    let file_type = stat.st_mode & S_IFMT;
    match cond {
        PathCondition::BlockSpecial     => file_type == S_IFBLK,
        PathCondition::CharacterSpecial => file_type == S_IFCHR,
        PathCondition::Directory        => file_type == S_IFDIR,
        PathCondition::Exists           => true,
        PathCondition::Regular          => file_type == S_IFREG,
        PathCondition::GroupIDFlag      => stat.st_mode & S_ISGID != 0,
        PathCondition::SymLink          => true,
        PathCondition::FIFO             => file_type == S_IFIFO,
        PathCondition::Readable         => perm(stat, Permission::Read),
        PathCondition::Socket           => file_type == S_IFSOCK,
        PathCondition::NonEmpty         => stat.st_size > 0,
        PathCondition::UserIDFlag       => stat.st_mode & S_ISUID != 0,
        PathCondition::Writable         => perm(stat, Permission::Write),
        PathCondition::Executable       => perm(stat, Permission::Execute),
    }
}

#[cfg(windows)]
fn path(path: &[u8], cond: PathCondition) -> bool {
    use std::fs::metadata;
    let path = from_utf8(path).unwrap();
    let stat = match metadata(path) {
        Ok(s) => s,
        _ => return false,
    };
    match cond {
        PathCondition::BlockSpecial     => false,
        PathCondition::CharacterSpecial => false,
        PathCondition::Directory        => stat.is_dir(),
        PathCondition::Exists           => true,
        PathCondition::Regular          => stat.is_file(),
        PathCondition::GroupIDFlag      => false,
        PathCondition::SymLink          => false,
        PathCondition::FIFO             => false,
        PathCondition::Readable         => false, // TODO
        PathCondition::Socket           => false,
        PathCondition::NonEmpty         => stat.len() > 0,
        PathCondition::UserIDFlag       => false,
        PathCondition::Writable         => false, // TODO
        PathCondition::Executable       => false, // TODO
    }
}
<commit_msg>Convert to byte vec w/o using deprecated methods.<commit_after>#![crate_name = "test"]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) mahkoh (ju.orth [at] gmail [dot] com)
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

extern crate libc;

use std::collections::HashMap;
use std::ffi::{CString, OsString};
use std::env::{args_os};
use std::str::{from_utf8};

static NAME: &'static str = "test";

// TODO: decide how to handle non-UTF8 input for all the utils
// Definitely don't use [u8], try keeping it as OsStr or OsString instead
pub fn uumain(_: Vec<String>) -> i32 {
    let args = args_os().collect::<Vec<OsString>>();
    // This is completely disregarding valid windows paths that aren't valid unicode
    let args = args.iter().map(|a| a.to_str().unwrap().as_bytes()).collect::<Vec<&[u8]>>();
    if args.len() == 0 {
        return 2;
    }
    let args =
        if !args[0].ends_with(NAME.as_bytes()) {
            &args[1..]
        } else {
            &args[..]
        };
    let args = match args[0] {
        b"[" => match args[args.len() - 1] {
            b"]" => &args[1..args.len() - 1],
            _ => return 2,
        },
        _ => &args[1..args.len()],
    };
    let mut error = false;
    let retval = 1 - parse_expr(args, &mut error) as i32;
    if error {
        2
    } else {
        retval
    }
}

fn one(args: &[&[u8]]) -> bool {
    args[0].len() > 0
}

fn two(args: &[&[u8]], error: &mut bool) -> bool {
    match args[0] {
        b"!" => !one(&args[1..]),
        b"-b" => path(args[1], PathCondition::BlockSpecial),
        b"-c" => path(args[1], PathCondition::CharacterSpecial),
        b"-d" => path(args[1], PathCondition::Directory),
        b"-e" => path(args[1], PathCondition::Exists),
        b"-f" => path(args[1], PathCondition::Regular),
        b"-g" => path(args[1], PathCondition::GroupIDFlag),
        b"-h" => path(args[1], PathCondition::SymLink),
        b"-L" => path(args[1], PathCondition::SymLink),
        b"-n" => one(&args[1..]),
        b"-p" => path(args[1], PathCondition::FIFO),
        b"-r" => path(args[1], PathCondition::Readable),
        b"-S" => path(args[1], PathCondition::Socket),
        b"-s" => path(args[1], PathCondition::NonEmpty),
        b"-t" => isatty(args[1]),
        b"-u" => path(args[1], PathCondition::UserIDFlag),
        b"-w" => path(args[1], PathCondition::Writable),
        b"-x" => path(args[1], PathCondition::Executable),
        b"-z" => !one(&args[1..]),
        _ => {
            *error = true;
            false
        }
    }
}

fn three(args: &[&[u8]], error: &mut bool) -> bool {
    match args[1] {
        b"=" => args[0] == args[2],
        b"==" => args[0] == args[2],
        b"!=" => args[0] != args[2],
        b"-eq" => integers(args[0], args[2], IntegerCondition::Equal),
        b"-ne" => integers(args[0], args[2], IntegerCondition::Unequal),
        b"-gt" => integers(args[0], args[2], IntegerCondition::Greater),
        b"-ge" => integers(args[0], args[2], IntegerCondition::GreaterEqual),
        b"-lt" => integers(args[0], args[2], IntegerCondition::Less),
        b"-le" => integers(args[0], args[2], IntegerCondition::LessEqual),
        _ => match args[0] {
            b"!" => !two(&args[1..], error),
            _ => {
                *error = true;
                false
            }
        }
    }
}

fn four(args: &[&[u8]], error: &mut bool) -> bool {
    match args[0] {
        b"!" => {
            !three(&args[1..], error)
        }
        _ => {
            *error = true;
            false
        }
    }
}

enum IntegerCondition {
    Equal,
    Unequal,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
}

fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool {
    let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) {
        (Ok(a), Ok(b)) => (a, b),
        _ => return false,
    };
    let (a, b): (i64, i64) = match (a.parse(), b.parse()) {
        (Ok(a), Ok(b)) => (a, b),
        _ => return false,
    };
    match cond {
        IntegerCondition::Equal        => a == b,
        IntegerCondition::Unequal      => a != b,
        IntegerCondition::Greater      => a >  b,
        IntegerCondition::GreaterEqual => a >= b,
        IntegerCondition::Less         => a <  b,
        IntegerCondition::LessEqual    => a <= b,
    }
}

fn isatty(fd: &[u8]) -> bool {
    use libc::{isatty};
    from_utf8(fd).ok().and_then(|s| s.parse().ok())
            .map(|i| unsafe { isatty(i) == 1 }).unwrap_or(false)
}

fn dispatch(args: &mut &[&[u8]], error: &mut bool) -> bool {
    let (val, idx) = match args.len() {
        0 => {
            *error = true;
            (false, 0)
        }
        1 => (one(*args), 1),
        2 => dispatch_two(args, error),
        3 => dispatch_three(args, error),
        _ => dispatch_four(args, error)
    };
    *args = &(*args)[idx..];
    val
}

fn dispatch_two(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
    let val = two(*args, error);
    if *error {
        *error = false;
        (one(*args), 1)
    } else {
        (val, 2)
    }
}

fn dispatch_three(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
    let val = three(*args, error);
    if *error {
        *error = false;
        dispatch_two(args, error)
    } else {
        (val, 3)
    }
}

fn dispatch_four(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
    let val = four(*args, error);
    if *error {
        *error = false;
        dispatch_three(args, error)
    } else {
        (val, 4)
    }
}

#[derive(Clone, Copy)]
enum Precedence {
    Unknown = 0,
    Paren,     // FIXME: this is useless (parentheses have not been implemented)
    Or,
    And,
    BUnOp,
    BinOp,
    UnOp
}

fn parse_expr(mut args: &[&[u8]], error: &mut bool) -> bool {
    if args.len() == 0 {
        false
    } else {
        let hashmap = setup_hashmap();
        let lhs = dispatch(&mut args, error);

        if args.len() > 0 {
            parse_expr_helper(&hashmap, &mut args, lhs, Precedence::Unknown, error)
        } else {
            lhs
        }
    }
}

fn parse_expr_helper<'a>(hashmap: &HashMap<&'a [u8], Precedence>,
                         args: &mut &[&'a [u8]],
                         mut lhs: bool,
                         min_prec: Precedence,
                         error: &mut bool) -> bool {
    let mut prec = *hashmap.get(&args[0]).unwrap_or_else(|| {
        *error = true;
        &min_prec
    });
    while !*error && args.len() > 0 && prec as usize >= min_prec as usize {
        let op = args[0];
        *args = &(*args)[1..];
        let mut rhs = dispatch(args, error);
        while args.len() > 0 {
            let subprec = *hashmap.get(&args[0]).unwrap_or_else(|| {
                *error = true;
                &min_prec
            });
            if subprec as usize <= prec as usize || *error {
                break;
            }
            rhs = parse_expr_helper(hashmap, args, rhs, subprec, error);
        }
        lhs = match prec {
            Precedence::UnOp | Precedence::BUnOp => {
                *error = true;
                false
            }
            Precedence::And => lhs && rhs,
            Precedence::Or => lhs || rhs,
            Precedence::BinOp => three(&[if lhs { b" " } else { b"" }, op, if rhs { b" " } else { b"" }], error),
            Precedence::Paren => unimplemented!(),  // TODO: implement parentheses
            _ => unreachable!()
        };
        if args.len() > 0 {
            prec = *hashmap.get(&args[0]).unwrap_or_else(|| {
                *error = true;
                &min_prec
            });
        }
    }
    lhs
}

#[inline]
fn setup_hashmap<'a>() -> HashMap<&'a [u8], Precedence> {
    let mut hashmap = HashMap::<&'a [u8], Precedence>::new();

    hashmap.insert(b"-b", Precedence::UnOp);
    hashmap.insert(b"-c", Precedence::UnOp);
    hashmap.insert(b"-d", Precedence::UnOp);
    hashmap.insert(b"-e", Precedence::UnOp);
    hashmap.insert(b"-f", Precedence::UnOp);
    hashmap.insert(b"-g", Precedence::UnOp);
    hashmap.insert(b"-h", Precedence::UnOp);
    hashmap.insert(b"-L", Precedence::UnOp);
    hashmap.insert(b"-n", Precedence::UnOp);
    hashmap.insert(b"-p", Precedence::UnOp);
    hashmap.insert(b"-r", Precedence::UnOp);
    hashmap.insert(b"-S", Precedence::UnOp);
    hashmap.insert(b"-s", Precedence::UnOp);
    hashmap.insert(b"-t", Precedence::UnOp);
    hashmap.insert(b"-u", Precedence::UnOp);
    hashmap.insert(b"-w", Precedence::UnOp);
    hashmap.insert(b"-x", Precedence::UnOp);
    hashmap.insert(b"-z", Precedence::UnOp);

    hashmap.insert(b"=", Precedence::BinOp);
    hashmap.insert(b"!=", Precedence::BinOp);
    hashmap.insert(b"-eq", Precedence::BinOp);
    hashmap.insert(b"-ne", Precedence::BinOp);
    hashmap.insert(b"-gt", Precedence::BinOp);
    hashmap.insert(b"-ge", Precedence::BinOp);
    hashmap.insert(b"-lt", Precedence::BinOp);
    hashmap.insert(b"-le", Precedence::BinOp);

    hashmap.insert(b"!", Precedence::BUnOp);

    hashmap.insert(b"-a", Precedence::And);
    hashmap.insert(b"-o", Precedence::Or);

    hashmap.insert(b"(", Precedence::Paren);
    hashmap.insert(b")", Precedence::Paren);

    hashmap
}

#[derive(Eq, PartialEq)]
enum PathCondition {
    BlockSpecial,
    CharacterSpecial,
    Directory,
    Exists,
    Regular,
    GroupIDFlag,
    SymLink,
    FIFO,
    Readable,
    Socket,
    NonEmpty,
    UserIDFlag,
    Writable,
    Executable,
}

#[cfg(not(windows))]
fn path(path: &[u8], cond: PathCondition) -> bool {
    use libc::{stat, lstat, S_IFMT, S_IFLNK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFREG};
    use libc::{S_IFIFO, mode_t};
    static S_ISUID: mode_t = 0o4000;
    static S_ISGID: mode_t = 0o2000;
    static S_IFSOCK: mode_t = 0o140000;

    enum Permission {
        Read    = 0o4,
        Write   = 0o2,
        Execute = 0o1,
    }
    let perm = |stat: stat, p: Permission| {
        use libc::{getgid, getuid};
        let (uid, gid) = unsafe { (getuid(), getgid()) };
        if uid == stat.st_uid {
            stat.st_mode & ((p as mode_t) << 6) != 0
        } else if gid == stat.st_gid {
            stat.st_mode & ((p as mode_t) << 3) != 0
        } else {
            stat.st_mode & ((p as mode_t) << 0) != 0
        }
    };

    let path = CString::new(path).unwrap();
    let mut stat = unsafe { std::mem::zeroed() };
    if cond == PathCondition::SymLink {
        if unsafe { lstat(path.as_ptr(), &mut stat) } == 0 {
            if stat.st_mode & S_IFMT == S_IFLNK {
                return true;
            }
        }
        return false;
    }
    if unsafe { libc::stat(path.as_ptr(), &mut stat) } != 0 {
        return false;
    }
    let file_type = stat.st_mode & S_IFMT;
    match cond {
        PathCondition::BlockSpecial     => file_type == S_IFBLK,
        PathCondition::CharacterSpecial => file_type == S_IFCHR,
        PathCondition::Directory        => file_type == S_IFDIR,
        PathCondition::Exists           => true,
        PathCondition::Regular          => file_type == S_IFREG,
        PathCondition::GroupIDFlag      => stat.st_mode & S_ISGID != 0,
        PathCondition::SymLink          => true,
        PathCondition::FIFO             => file_type == S_IFIFO,
        PathCondition::Readable         => perm(stat, Permission::Read),
        PathCondition::Socket           => file_type == S_IFSOCK,
        PathCondition::NonEmpty         => stat.st_size > 0,
        PathCondition::UserIDFlag       => stat.st_mode & S_ISUID != 0,
        PathCondition::Writable         => perm(stat, Permission::Write),
        PathCondition::Executable       => perm(stat, Permission::Execute),
    }
}

#[cfg(windows)]
fn path(path: &[u8], cond: PathCondition) -> bool {
    use std::fs::metadata;
    let path = from_utf8(path).unwrap();
    let stat = match metadata(path) {
        Ok(s) => s,
        _ => return false,
    };
    match cond {
        PathCondition::BlockSpecial     => false,
        PathCondition::CharacterSpecial => false,
        PathCondition::Directory        => stat.is_dir(),
        PathCondition::Exists           => true,
        PathCondition::Regular          => stat.is_file(),
        PathCondition::GroupIDFlag      => false,
        PathCondition::SymLink          => false,
        PathCondition::FIFO             => false,
        PathCondition::Readable         => false, // TODO
        PathCondition::Socket           => false,
        PathCondition::NonEmpty         => stat.len() > 0,
        PathCondition::UserIDFlag       => false,
        PathCondition::Writable         => false, // TODO
        PathCondition::Executable       => false, // TODO
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code, unused_imports)]
use super::*;
use std::env;

const ENV_KEY: &str = "PROXER_API_KEY";

#[test]
fn api_response()
{
	// is the api structured as we want it to be?
	let client = Client::with_env_key(ENV_KEY).unwrap();

	let req = client
		.api()
		.info()
		.get_fullentry(parameter::InfoGetFullEntry { id: 53 });

	let res = client.execute(req);

	eprintln!();

	match res
	{
		Err(e) => {
			match e
			{
				error::Error::Api(e) => {
					match e.error()
					{
						error::api::Errcode::NoApiPermissions => return,
						_ => panic!("this error is not expected: {}", e),
					}
				}
				error::Error::Json(e) => panic!("can't parse json: {}", e),
				error::Error::Unknown => panic!("unknown error"),
				_ => return,
			}
		}

		Ok(r) => {
			assert_eq!(r.medium, "animeseries");
		}
	}
}
<commit_msg>add error message<commit_after>#![allow(dead_code, unused_imports)]
use super::*;
use std::env;

const ENV_KEY: &str = "PROXER_API_KEY";

#[test]
fn api_response()
{
	// is the api structured as we want it to be?
	let client = Client::with_env_key(ENV_KEY)
		.expect("can't read api-key from environment");

	let req = client
		.api()
		.info()
		.get_fullentry(parameter::InfoGetFullEntry { id: 53 });

	let res = client.execute(req);

	eprintln!();

	match res
	{
		Err(e) => {
			match e
			{
				error::Error::Api(e) => {
					match e.error()
					{
						error::api::Errcode::NoApiPermissions => return,
						_ => panic!("this error is not expected: {}", e),
					}
				}
				error::Error::Json(e) => panic!("can't parse json: {}", e),
				error::Error::Unknown => panic!("unknown error"),
				_ => return,
			}
		}

		Ok(r) => {
			assert_eq!(r.medium, "animeseries");
		}
	}
}
<|endoftext|>