<commit_before>//! Licensed under the Apache License, Version 2.0
//! http://www.apache.org/licenses/LICENSE-2.0 or the MIT license
//! http://opensource.org/licenses/MIT, at your
//! option. This file may not be copied, modified, or distributed
//! except according to those terms.

#![feature(unboxed_closures)]
#![feature(phase)]

#[phase(plugin, link)] extern crate itertools;

extern crate test;

use std::fmt::Show;
use std::iter::order;
use itertools::Itertools;
use itertools::Interleave;

use itertools as it;

fn assert_iters_equal<
    A: PartialEq + Show,
    I: Iterator<A>,
    J: Iterator<A>>(mut it: I, mut jt: J)
{
    loop {
        let elti = it.next();
        let eltj = jt.next();
        assert_eq!(elti, eltj);
        if elti.is_none() { break; }
    }
}

#[test]
fn product2() {
    let s = "αβ";

    let mut prod = iproduct!(s.chars(), range(0, 2i));
    assert!(prod.next() == Some(('α', 0)));
    assert!(prod.next() == Some(('α', 1)));
    assert!(prod.next() == Some(('β', 0)));
    assert!(prod.next() == Some(('β', 1)));
    assert!(prod.next() == None);
}

#[test]
fn product3() {
    let prod = iproduct!(range(0, 3i), range(0, 2i), range(0, 2i));
    assert_eq!(prod.size_hint(), (12, Some(12)));
    let v = prod.collect::<Vec<_>>();
    for i in range(0,3i) {
        for j in range(0, 2i) {
            for k in range(0, 2i) {
                assert!((i, j, k) == v[(i * 2 * 2 + j * 2 + k) as uint]);
            }
        }
    }
    for (_, _, _, _) in iproduct!(range(0, 3i), range(0, 2i), range(0, 2i), range(0, 3i)) {
        /* test compiles */
    }
}

#[test]
fn izip3() {
    let mut zip = izip!(range(0, 3u), range(0, 2i), range(0, 2i8));
    for i in range(0, 2i) {
        assert!((i as uint, i, i as i8) == zip.next().unwrap());
    }
    assert!(zip.next().is_none());

    let xs: [int, ..0] = [];
    let mut zip = izip!(range(0, 3u), range(0, 2i), range(0, 2i8), xs.iter());
    assert!(zip.next().is_none());

    for (_, _, _, _) in izip!(range(0, 3i), range(0, 2i), range(0, 2i), range(0, 3i)) {
        /* test compiles */
    }
}

#[test]
fn fn_map() {
    let xs = [0, 1, 2i];
    fn mapper(x: &int) -> String { x.to_string() }
    let it = xs.iter().fn_map(mapper);
    let jt = it.clone();
    assert!(it.zip(jt).all(|(x,y)| x == y));
}

#[test]
fn map_unboxed() {
    let xs = [0, 1, 2i];
    fn mapper(x: &int) -> String { x.to_string() }
    let it = xs.iter().map_unboxed(mapper);
    let jt = it.clone();
    assert!(it.zip(jt).all(|(x,y)| x == y));

    // NOTE: This doesn't compile :(
    //let mut sum = 0i;
    //let it = xs.iter().map_unboxed(|x| sum += *x).drain();
    //assert_eq!(sum, 3);
}

#[test]
fn write_to() {
    let xs = [7i, 9, 8];
    let mut ys = [0i, ..5];
    let cnt = it::write(ys.iter_mut(), xs.iter().map(|x| *x));
    assert!(cnt == xs.len());
    assert!(ys == [7i, 9, 8, 0, 0]);

    let cnt = it::write(ys.iter_mut(), range(0,10i));
    assert!(cnt == ys.len());
    assert!(ys == [0, 1, 2, 3, 4]);
}

#[test]
fn interleave() {
    let xs: [u8, ..0]  = [];
    let ys = [7u8, 9, 8, 10];
    let zs = [2u8, 77];
    let it = Interleave::new(xs.iter(), ys.iter());
    assert!(order::eq(it, ys.iter()));

    let rs = [7u8, 2, 9, 77, 8, 10];
    let it = Interleave::new(ys.iter(), zs.iter());
    assert!(order::eq(it, rs.iter()));
}

#[test]
fn times() {
    assert!(it::times(0).count() == 0);
    assert!(it::times(5).count() == 5);
}

#[test]
fn drain() {
    let xs = [1i,2,3];
    let mut sum = 0;
    xs.iter().map(|elt| sum += *elt).drain();
    assert!(sum == 6);
}

#[test]
fn apply() {
    let xs = [1i, 2, 3];
    let mut sum = 0;
    xs.iter().apply(|elt| sum += *elt);
    assert!(sum == 6);
}

#[test]
fn dropn() {
    let xs = [1i, 2, 3];
    let mut it = xs.iter();
    assert!(it.dropn(2) == 2);
    assert!(it.next().is_some());
    assert!(it.next().is_none());
    let mut it = xs.iter();
    assert!(it.dropn(5) == 3);
    assert!(it.next().is_none());
}

#[test]
fn intersperse() {
    let xs = ["a", "", "b", "c"];
    let v: Vec<&str> = xs.iter().map(|x| x.clone()).intersperse(", ").collect();
    let text = v.concat();
    assert_eq!(text, "a, , b, c".to_string());

    let ys = [0, 1, 2, 3i];
    let mut it = ys.slice_to(0).iter().map(|x| *x).intersperse(1i);
    assert!(it.next() == None);
}

#[test]
fn linspace() {
    let mut iter = it::linspace::<f32>(0., 2., 3);
    assert_eq!(iter.next(), Some(0.0));
    assert_eq!(iter.next(), Some(1.0));
    assert_eq!(iter.next(), Some(2.0));
    assert_eq!(iter.next(), None);

    let mut iter = it::linspace::<f32>(0., -2., 4);
    assert_eq!(iter.next(), Some(0.));
    assert_eq!(iter.next(), Some(-0.666666666667));
    assert_eq!(iter.next(), Some(-1.333333333333));
    assert_eq!(iter.next(), Some(-2.));
    assert_eq!(iter.next(), None);

    let mut iter = it::linspace::<f32>(0., 1., 1);
    assert_eq!(iter.next(), Some(0.));
    assert_eq!(iter.next(), None);

    let mut iter = it::linspace::<f32>(0., 1., 0);
    assert_eq!(iter.next(), None);
}

#[test]
fn dedup() {
    let xs = [0i, 1, 1, 1, 2, 1, 3, 3];
    let ys = [0i, 1, 2, 1, 3];
    assert_iters_equal(ys.iter(), xs.iter().dedup());
    let xs = [0i, 0, 0, 0, 0];
    let ys = [0i];
    assert_iters_equal(ys.iter(), xs.iter().dedup());
}

#[test]
fn batching() {
    let xs = [0i, 1, 2, 1, 3];
    let ys = [(0i, 1i), (2, 1)];

    // An iterator that gathers elements up in pairs
    let pit = xs.iter().cloned().batching(|mut it| {
               match it.next() {
                   None => None,
                   Some(x) => match it.next() {
                       None => None,
                       Some(y) => Some((x, y)),
                   }
               }
           });
    assert_iters_equal(pit, ys.iter().cloned());
}

#[test]
fn group_by() {
    let xs = [0i, 1, 1, 1, 2, 1, 3, 3];
    let ans = vec![(0, vec![0]), (1, vec![1, 1, 1]),
                   (2, vec![2]), (1, vec![1]), (3, vec![3, 3])];

    let gb = xs.iter().cloned().group_by(|elt| *elt);
    assert_iters_equal(gb, ans.into_iter());
}

#[test]
fn put_back() {
    let xs = [0i, 1, 1, 1, 2, 1, 3, 3];
    let mut pb = it::PutBack::new(xs.iter().cloned());
    pb.next();
    pb.put_back(1);
    pb.put_back(0);
    assert_iters_equal(pb, xs.iter().cloned());
}
<commit_msg>The unboxed closure map thing already compiles :)<commit_after>//! Licensed under the Apache License, Version 2.0
//! http://www.apache.org/licenses/LICENSE-2.0 or the MIT license
//! http://opensource.org/licenses/MIT, at your
//! option. This file may not be copied, modified, or distributed
//! except according to those terms.

#![feature(unboxed_closures)]
#![feature(phase)]

#[phase(plugin, link)] extern crate itertools;

extern crate test;

use std::fmt::Show;
use std::iter::order;
use itertools::Itertools;
use itertools::Interleave;

use itertools as it;

fn assert_iters_equal<
    A: PartialEq + Show,
    I: Iterator<A>,
    J: Iterator<A>>(mut it: I, mut jt: J)
{
    loop {
        let elti = it.next();
        let eltj = jt.next();
        assert_eq!(elti, eltj);
        if elti.is_none() { break; }
    }
}

#[test]
fn product2() {
    let s = "αβ";

    let mut prod = iproduct!(s.chars(), range(0, 2i));
    assert!(prod.next() == Some(('α', 0)));
    assert!(prod.next() == Some(('α', 1)));
    assert!(prod.next() == Some(('β', 0)));
    assert!(prod.next() == Some(('β', 1)));
    assert!(prod.next() == None);
}

#[test]
fn product3() {
    let prod = iproduct!(range(0, 3i), range(0, 2i), range(0, 2i));
    assert_eq!(prod.size_hint(), (12, Some(12)));
    let v = prod.collect::<Vec<_>>();
    for i in range(0,3i) {
        for j in range(0, 2i) {
            for k in range(0, 2i) {
                assert!((i, j, k) == v[(i * 2 * 2 + j * 2 + k) as uint]);
            }
        }
    }
    for (_, _, _, _) in iproduct!(range(0, 3i), range(0, 2i), range(0, 2i), range(0, 3i)) {
        /* test compiles */
    }
}

#[test]
fn izip3() {
    let mut zip = izip!(range(0, 3u), range(0, 2i), range(0, 2i8));
    for i in range(0, 2i) {
        assert!((i as uint, i, i as i8) == zip.next().unwrap());
    }
    assert!(zip.next().is_none());

    let xs: [int, ..0] = [];
    let mut zip = izip!(range(0, 3u), range(0, 2i), range(0, 2i8), xs.iter());
    assert!(zip.next().is_none());

    for (_, _, _, _) in izip!(range(0, 3i), range(0, 2i), range(0, 2i), range(0, 3i)) {
        /* test compiles */
    }
}

#[test]
fn fn_map() {
    let xs = [0, 1, 2i];
    fn mapper(x: &int) -> String { x.to_string() }
    let it = xs.iter().fn_map(mapper);
    let jt = it.clone();
    assert!(it.zip(jt).all(|(x,y)| x == y));
}

#[test]
fn map_unboxed() {
    let xs = [0, 1, 2i];
    fn mapper(x: &int) -> String { x.to_string() }
    let it = xs.iter().map_unboxed(mapper);
    let jt = it.clone();
    assert!(it.zip(jt).all(|(x,y)| x == y));

    let mut sum = 0i;
    xs.iter().map_unboxed(|x| sum += *x).drain();
    assert_eq!(sum, 3);
}

#[test]
fn write_to() {
    let xs = [7i, 9, 8];
    let mut ys = [0i, ..5];
    let cnt = it::write(ys.iter_mut(), xs.iter().map(|x| *x));
    assert!(cnt == xs.len());
    assert!(ys == [7i, 9, 8, 0, 0]);

    let cnt = it::write(ys.iter_mut(), range(0,10i));
    assert!(cnt == ys.len());
    assert!(ys == [0, 1, 2, 3, 4]);
}

#[test]
fn interleave() {
    let xs: [u8, ..0]  = [];
    let ys = [7u8, 9, 8, 10];
    let zs = [2u8, 77];
    let it = Interleave::new(xs.iter(), ys.iter());
    assert!(order::eq(it, ys.iter()));

    let rs = [7u8, 2, 9, 77, 8, 10];
    let it = Interleave::new(ys.iter(), zs.iter());
    assert!(order::eq(it, rs.iter()));
}

#[test]
fn times() {
    assert!(it::times(0).count() == 0);
    assert!(it::times(5).count() == 5);
}

#[test]
fn drain() {
    let xs = [1i,2,3];
    let mut sum = 0;
    xs.iter().map(|elt| sum += *elt).drain();
    assert!(sum == 6);
}

#[test]
fn apply() {
    let xs = [1i, 2, 3];
    let mut sum = 0;
    xs.iter().apply(|elt| sum += *elt);
    assert!(sum == 6);
}

#[test]
fn dropn() {
    let xs = [1i, 2, 3];
    let mut it = xs.iter();
    assert!(it.dropn(2) == 2);
    assert!(it.next().is_some());
    assert!(it.next().is_none());
    let mut it = xs.iter();
    assert!(it.dropn(5) == 3);
    assert!(it.next().is_none());
}

#[test]
fn intersperse() {
    let xs = ["a", "", "b", "c"];
    let v: Vec<&str> = xs.iter().map(|x| x.clone()).intersperse(", ").collect();
    let text = v.concat();
    assert_eq!(text, "a, , b, c".to_string());

    let ys = [0, 1, 2, 3i];
    let mut it = ys.slice_to(0).iter().map(|x| *x).intersperse(1i);
    assert!(it.next() == None);
}

#[test]
fn linspace() {
    let mut iter = it::linspace::<f32>(0., 2., 3);
    assert_eq!(iter.next(), Some(0.0));
    assert_eq!(iter.next(), Some(1.0));
    assert_eq!(iter.next(), Some(2.0));
    assert_eq!(iter.next(), None);

    let mut iter = it::linspace::<f32>(0., -2., 4);
    assert_eq!(iter.next(), Some(0.));
    assert_eq!(iter.next(), Some(-0.666666666667));
    assert_eq!(iter.next(), Some(-1.333333333333));
    assert_eq!(iter.next(), Some(-2.));
    assert_eq!(iter.next(), None);

    let mut iter = it::linspace::<f32>(0., 1., 1);
    assert_eq!(iter.next(), Some(0.));
    assert_eq!(iter.next(), None);

    let mut iter = it::linspace::<f32>(0., 1., 0);
    assert_eq!(iter.next(), None);
}

#[test]
fn dedup() {
    let xs = [0i, 1, 1, 1, 2, 1, 3, 3];
    let ys = [0i, 1, 2, 1, 3];
    assert_iters_equal(ys.iter(), xs.iter().dedup());
    let xs = [0i, 0, 0, 0, 0];
    let ys = [0i];
    assert_iters_equal(ys.iter(), xs.iter().dedup());
}

#[test]
fn batching() {
    let xs = [0i, 1, 2, 1, 3];
    let ys = [(0i, 1i), (2, 1)];

    // An iterator that gathers elements up in pairs
    let pit = xs.iter().cloned().batching(|mut it| {
               match it.next() {
                   None => None,
                   Some(x) => match it.next() {
                       None => None,
                       Some(y) => Some((x, y)),
                   }
               }
           });
    assert_iters_equal(pit, ys.iter().cloned());
}

#[test]
fn group_by() {
    let xs = [0i, 1, 1, 1, 2, 1, 3, 3];
    let ans = vec![(0, vec![0]), (1, vec![1, 1, 1]),
                   (2, vec![2]), (1, vec![1]), (3, vec![3, 3])];

    let gb = xs.iter().cloned().group_by(|elt| *elt);
    assert_iters_equal(gb, ans.into_iter());
}

#[test]
fn put_back() {
    let xs = [0i, 1, 1, 1, 2, 1, 3, 3];
    let mut pb = it::PutBack::new(xs.iter().cloned());
    pb.next();
    pb.put_back(1);
    pb.put_back(0);
    assert_iters_equal(pb, xs.iter().cloned());
}
<|endoftext|>


<commit_before>use super::internal::*;
use super::*;
use std::cmp::min;
use std::iter::Chain;
use std::option::IntoIter as OptionIter;
use std::str::{Chars, Split};

/// Test if a byte is the start of a UTF-8 character.
/// (extracted from `str::is_char_boundary`)
fn is_char_boundary(b: u8) -> bool {
    // This is bit magic equivalent to: b < 128 || b >= 192
    (b as i8) >= -0x40
}

/// Find the index of a character boundary near the midpoint.
fn find_char_midpoint(chars: &str) -> usize {
    let mid = chars.len() / 2;

    // We want to split near the midpoint, but we need to find an actual
    // character boundary.  So we look at the raw bytes, first scanning
    // forward from the midpoint for a boundary, then trying backward.
    let (left, right) = chars.as_bytes().split_at(mid);
    right.iter()
        .cloned()
        .position(is_char_boundary)
        .map(|i| mid + i)
        .or_else(|| left.iter().cloned().rposition(is_char_boundary))
        .unwrap_or(0)
}

/// Parallel extensions for strings.
pub trait ParallelString {
    type Chars: ParallelIterator;
    type Split: ParallelIterator;

    /// Returns a parallel iterator over the characters of a string.
    fn par_chars(self) -> Self::Chars;

    /// Returns a parallel iterator over substrings separated by a
    /// given character, similar to `str::split`.
    fn par_split(self, char) -> Self::Split;
}

impl<'a> ParallelString for &'a str {
    type Chars = ParChars<'a>;
    type Split = ParSplit<'a>;

    fn par_chars(self) -> Self::Chars {
        ParChars { chars: self }
    }

    fn par_split(self, separator: char) -> Self::Split {
        ParSplit::new(self, separator)
    }
}

// /////////////////////////////////////////////////////////////////////////

pub struct ParChars<'a> {
    chars: &'a str,
}

impl<'a> ParallelIterator for ParChars<'a> {
    type Item = char;

    fn drive_unindexed<C>(self, consumer: C) -> C::Result
        where C: UnindexedConsumer<Self::Item>
    {
        bridge_unindexed(self, consumer)
    }
}

impl<'a> UnindexedProducer for ParChars<'a> {
    fn split(&mut self) -> Option<Self> {
        let index = find_char_midpoint(self.chars);
        if index > 0 {
            let (left, right) = self.chars.split_at(index);
            self.chars = left;
            Some(ParChars { chars: right })
        } else {
            None
        }
    }
}

impl<'a> IntoIterator for ParChars<'a> {
    type Item = char;
    type IntoIter = Chars<'a>;

    fn into_iter(self) -> Self::IntoIter {
        self.chars.chars()
    }
}

// /////////////////////////////////////////////////////////////////////////

pub struct ParSplit<'a> {
    chars: &'a str,
    separator: char,

    /// Marks the endpoint beyond which we've already found no separators.
    tail: usize,
}

impl<'a> ParSplit<'a> {
    fn new(chars: &'a str, separator: char) -> Self {
        ParSplit {
            chars: chars,
            separator: separator,
            tail: chars.len(),
        }
    }
}

impl<'a> ParallelIterator for ParSplit<'a> {
    type Item = &'a str;

    fn drive_unindexed<C>(self, consumer: C) -> C::Result
        where C: UnindexedConsumer<Self::Item>
    {
        bridge_unindexed(self, consumer)
    }
}

impl<'a> UnindexedProducer for ParSplit<'a> {
    fn split(&mut self) -> Option<Self> {
        let ParSplit { chars, separator, tail } = *self;

        // First find a suitable UTF-8 boundary in the unsearched region.
        let char_index = find_char_midpoint(&chars[..tail]);

        // Look forward for the separator, and failing that look backward.
        let index = chars[char_index..tail]
            .find(separator)
            .map(|i| char_index + i)
            .or_else(|| chars[..char_index].rfind(separator));

        if let Some(index) = index {
            // Update `self` as the region before the separator.
            self.chars = &chars[..index];
            self.tail = min(char_index, index);

            // Create the right split following the separator.
            let right_index = index + separator.len_utf8();
            let mut right = ParSplit {
                chars: &chars[right_index..],
                separator: separator,
                tail: tail - right_index,
            };

            // If we scanned backwards to find the separator, everything in
            // the right side is exhausted, with no separators left to find.
            if index < char_index {
                right.tail = 0;
            }

            Some(right)

        } else {
            self.tail = 0;
            None
        }
    }
}

impl<'a> IntoIterator for ParSplit<'a> {
    type Item = &'a str;
    type IntoIter = Chain<Split<'a, char>, OptionIter<&'a str>>;

    fn into_iter(self) -> Self::IntoIter {
        let ParSplit { chars, separator, tail } = self;

        if tail == chars.len() {
            // No tail section, so just let `str::split` handle it.
            chars.split(separator).chain(None)

        } else if let Some(index) = chars[..tail].rfind(separator) {
            // We found the last separator to complete the tail, so
            // end with that slice after `str::split` finds the rest.
            let head = &chars[..index];
            let last = &chars[index + separator.len_utf8()..];
            head.split(separator).chain(Some(last))

        } else {
            // We know there are no separators at all.  Return our whole string,
            // but for type correctness we need to chain an emptied `Split` too.
            let mut empty = "".split('\0');
            Iterator::last(&mut empty);
            empty.chain(Some(chars))
        }
    }
}
<commit_msg>Ditch the associated types on `ParallelString`<commit_after>use super::internal::*;
use super::*;
use std::cmp::min;
use std::iter::Chain;
use std::option::IntoIter as OptionIter;
use std::str::{Chars, Split};

/// Test if a byte is the start of a UTF-8 character.
/// (extracted from `str::is_char_boundary`)
fn is_char_boundary(b: u8) -> bool {
    // This is bit magic equivalent to: b < 128 || b >= 192
    (b as i8) >= -0x40
}

/// Find the index of a character boundary near the midpoint.
fn find_char_midpoint(chars: &str) -> usize {
    let mid = chars.len() / 2;

    // We want to split near the midpoint, but we need to find an actual
    // character boundary.  So we look at the raw bytes, first scanning
    // forward from the midpoint for a boundary, then trying backward.
    let (left, right) = chars.as_bytes().split_at(mid);
    right.iter()
        .cloned()
        .position(is_char_boundary)
        .map(|i| mid + i)
        .or_else(|| left.iter().cloned().rposition(is_char_boundary))
        .unwrap_or(0)
}

/// Parallel extensions for strings.
pub trait ParallelString {
    /// Returns a parallel iterator over the characters of a string.
    fn par_chars(&self) -> ParChars;

    /// Returns a parallel iterator over substrings separated by a
    /// given character, similar to `str::split`.
    fn par_split(&self, char) -> ParSplit;
}

impl ParallelString for str {
    fn par_chars(&self) -> ParChars {
        ParChars { chars: self }
    }

    fn par_split(&self, separator: char) -> ParSplit {
        ParSplit::new(self, separator)
    }
}

// /////////////////////////////////////////////////////////////////////////

pub struct ParChars<'a> {
    chars: &'a str,
}

impl<'a> ParallelIterator for ParChars<'a> {
    type Item = char;

    fn drive_unindexed<C>(self, consumer: C) -> C::Result
        where C: UnindexedConsumer<Self::Item>
    {
        bridge_unindexed(self, consumer)
    }
}

impl<'a> UnindexedProducer for ParChars<'a> {
    fn split(&mut self) -> Option<Self> {
        let index = find_char_midpoint(self.chars);
        if index > 0 {
            let (left, right) = self.chars.split_at(index);
            self.chars = left;
            Some(ParChars { chars: right })
        } else {
            None
        }
    }
}

impl<'a> IntoIterator for ParChars<'a> {
    type Item = char;
    type IntoIter = Chars<'a>;

    fn into_iter(self) -> Self::IntoIter {
        self.chars.chars()
    }
}

// /////////////////////////////////////////////////////////////////////////

pub struct ParSplit<'a> {
    chars: &'a str,
    separator: char,

    /// Marks the endpoint beyond which we've already found no separators.
    tail: usize,
}

impl<'a> ParSplit<'a> {
    fn new(chars: &'a str, separator: char) -> Self {
        ParSplit {
            chars: chars,
            separator: separator,
            tail: chars.len(),
        }
    }
}

impl<'a> ParallelIterator for ParSplit<'a> {
    type Item = &'a str;

    fn drive_unindexed<C>(self, consumer: C) -> C::Result
        where C: UnindexedConsumer<Self::Item>
    {
        bridge_unindexed(self, consumer)
    }
}

impl<'a> UnindexedProducer for ParSplit<'a> {
    fn split(&mut self) -> Option<Self> {
        let ParSplit { chars, separator, tail } = *self;

        // First find a suitable UTF-8 boundary in the unsearched region.
        let char_index = find_char_midpoint(&chars[..tail]);

        // Look forward for the separator, and failing that look backward.
        let index = chars[char_index..tail]
            .find(separator)
            .map(|i| char_index + i)
            .or_else(|| chars[..char_index].rfind(separator));

        if let Some(index) = index {
            // Update `self` as the region before the separator.
            self.chars = &chars[..index];
            self.tail = min(char_index, index);

            // Create the right split following the separator.
            let right_index = index + separator.len_utf8();
            let mut right = ParSplit {
                chars: &chars[right_index..],
                separator: separator,
                tail: tail - right_index,
            };

            // If we scanned backwards to find the separator, everything in
            // the right side is exhausted, with no separators left to find.
            if index < char_index {
                right.tail = 0;
            }

            Some(right)

        } else {
            self.tail = 0;
            None
        }
    }
}

impl<'a> IntoIterator for ParSplit<'a> {
    type Item = &'a str;
    type IntoIter = Chain<Split<'a, char>, OptionIter<&'a str>>;

    fn into_iter(self) -> Self::IntoIter {
        let ParSplit { chars, separator, tail } = self;

        if tail == chars.len() {
            // No tail section, so just let `str::split` handle it.
            chars.split(separator).chain(None)

        } else if let Some(index) = chars[..tail].rfind(separator) {
            // We found the last separator to complete the tail, so
            // end with that slice after `str::split` finds the rest.
            let head = &chars[..index];
            let last = &chars[index + separator.len_utf8()..];
            head.split(separator).chain(Some(last))

        } else {
            // We know there are no separators at all.  Return our whole string,
            // but for type correctness we need to chain an emptied `Split` too.
            let mut empty = "".split('\0');
            Iterator::last(&mut empty);
            empty.chain(Some(chars))
        }
    }
}
<|endoftext|>


<commit_before>use std::io;

use {Future, Poll, Task};
use io::{ReadStream, WriteStream};

pub struct Copy<R, W> {
    reader: R,
    read_ready: bool,
    writer: W,
    write_ready: bool,
    pos: usize,
    cap: usize,
    amt: u64,
    buf: [u8; 2048],
}

pub fn copy<R, W>(reader: R, writer: W) -> Copy<R, W>
    where R: ReadStream,
          W: WriteStream,
{
    Copy {
        reader: reader,
        read_ready: true,
        writer: writer,
        write_ready: true,
        amt: 0,
        pos: 0,
        cap: 0,
        buf: [0; 2048],
    }
}

impl<R, W> Future for Copy<R, W>
    where R: ReadStream,
          W: WriteStream,
{
    type Item = u64;
    type Error = io::Error;

    fn poll(&mut self, task: &mut Task) -> Poll<u64, io::Error> {
        loop {
            // If our buffer is empty, then we need to read some data to
            // continue.
            if self.pos == self.cap {
                if !self.read_ready {
                    match try_poll!(self.reader.poll(task)) {
                        Ok(_) => self.read_ready = true,
                        Err(e) => return Poll::Err(e),
                    }
                }
                match self.reader.read(&mut self.buf) {
                    Ok(Some(0)) => return Poll::Ok(self.amt),
                    Ok(Some(i)) => {
                        self.pos = 0;
                        self.cap = i;
                    }
                    Ok(None) => {
                        self.read_ready = false;
                        return Poll::NotReady
                    }
                    Err(e) => return Poll::Err(e),
                }
            }

            // Now that our buffer has some data, let's write it out!
            while self.pos < self.cap {
                if !self.write_ready {
                    match try_poll!(self.writer.poll(task)) {
                        Ok(_) => self.write_ready = true,
                        Err(e) => return Poll::Err(e),
                    }
                }
                match self.writer.write(&self.buf[self.pos..self.cap]) {
                    Ok(Some(i)) => {
                        self.pos += i;
                        self.amt += i as u64;
                    }
                    Ok(None) => {
                        self.write_ready = false;
                        return Poll::NotReady
                    }
                    Err(e) => return Poll::Err(e),
                }
            }
        }
    }

    fn schedule(&mut self, task: &mut Task) {
        if self.read_ready && self.write_ready {
            task.notify();
        }
        if !self.read_ready {
            self.reader.schedule(task);
        }
        if !self.write_ready {
            self.writer.schedule(task);
        }
    }
}
<commit_msg>Be sure to flush() as part of io::copy()<commit_after>use std::io;

use {Future, Poll, Task};
use io::{ReadStream, WriteStream};

pub struct Copy<R, W> {
    reader: R,
    read_ready: bool,
    read_done: bool,
    writer: W,
    write_ready: bool,
    flush_done: bool,
    pos: usize,
    cap: usize,
    amt: u64,
    buf: [u8; 2048],
}

pub fn copy<R, W>(reader: R, writer: W) -> Copy<R, W>
    where R: ReadStream,
          W: WriteStream,
{
    Copy {
        reader: reader,
        read_ready: true,
        read_done: false,
        writer: writer,
        write_ready: true,
        flush_done: false,
        amt: 0,
        pos: 0,
        cap: 0,
        buf: [0; 2048],
    }
}

impl<R, W> Future for Copy<R, W>
    where R: ReadStream,
          W: WriteStream,
{
    type Item = u64;
    type Error = io::Error;

    fn poll(&mut self, task: &mut Task) -> Poll<u64, io::Error> {
        loop {
            // If our buffer is empty, then we need to read some data to
            // continue.
            if !self.read_done && self.pos == self.cap {
                if !self.read_ready {
                    match try_poll!(self.reader.poll(task)) {
                        Ok(_) => self.read_ready = true,
                        Err(e) => return Poll::Err(e),
                    }
                }
                match self.reader.read(&mut self.buf) {
                    Ok(Some(0)) => self.read_done = true,
                    Ok(Some(i)) => {
                        self.pos = 0;
                        self.cap = i;
                    }
                    Ok(None) => {
                        self.read_ready = false;
                        return Poll::NotReady
                    }
                    Err(e) => return Poll::Err(e),
                }
            }

            // Now that our buffer has some data, let's write it out!
            while self.pos < self.cap || (self.read_done && !self.flush_done) {
                if !self.write_ready {
                    match try_poll!(self.writer.poll(task)) {
                        Ok(_) => self.write_ready = true,
                        Err(e) => return Poll::Err(e),
                    }
                }
                if self.pos == self.cap {
                    match self.writer.flush() {
                        Ok(true) => self.flush_done = true,
                        Ok(false) => return Poll::NotReady,
                        Err(e) => return Poll::Err(e),
                    }
                    break
                }
                match self.writer.write(&self.buf[self.pos..self.cap]) {
                    Ok(Some(i)) => {
                        self.pos += i;
                        self.amt += i as u64;
                    }
                    Ok(None) => {
                        self.write_ready = false;
                        return Poll::NotReady
                    }
                    Err(e) => return Poll::Err(e),
                }
            }

            if self.read_done && self.flush_done {
                return Poll::Ok(self.amt)
            }
        }
    }

    fn schedule(&mut self, task: &mut Task) {
        if self.read_ready && self.write_ready {
            task.notify();
        }
        if !self.read_ready && !self.read_done {
            self.reader.schedule(task);
        }
        if !self.write_ready && !self.flush_done {
            self.writer.schedule(task);
        }
    }
}
<|endoftext|>


<commit_before>//! Utility functionality for integrating git hooks in the store
//!
//! Contains primitives to create a repository within the store path

use git2::Repository;
use git2::RepositoryInitOptions;

use libimagstore::store::Store;

use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::result::Result;

pub fn mkrepo(store: &Store) -> Result<()> {
    let mut opts = RepositoryInitOptions::new();
    opts.bare(false);
    opts.no_reinit(true);
    opts.mkdir(false);
    opts.external_template(false);
    Repository::init_opts(store.path(), &opts)
        .map(|_| ())
        .map_err_into(GHEK::MkRepo)
}

pub fn hasrepo(store: &Store) -> bool {
    Repository::open(store.path()).is_ok()
}

<commit_msg>Remove unused fn: hasrepo()<commit_after>//! Utility functionality for integrating git hooks in the store
//!
//! Contains primitives to create a repository within the store path

use git2::Repository;
use git2::RepositoryInitOptions;

use libimagstore::store::Store;

use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::result::Result;

pub fn mkrepo(store: &Store) -> Result<()> {
    let mut opts = RepositoryInitOptions::new();
    opts.bare(false);
    opts.no_reinit(true);
    opts.mkdir(false);
    opts.external_template(false);
    Repository::init_opts(store.path(), &opts)
        .map(|_| ())
        .map_err_into(GHEK::MkRepo)
}

<|endoftext|>


<commit_before>use std::{
    io::{self, Error, ErrorKind},
    mem,
    net::{SocketAddr, SocketAddrV4, SocketAddrV6},
    os::unix::io::{AsRawFd, RawFd},
};

use cfg_if::cfg_if;
use tokio::net::{TcpStream, UdpSocket};

use crate::context::Context;

cfg_if! {
    if #[cfg(any(target_os = "macos",
                 target_os = "ios",
                 target_os = "freebsd",
                 target_os = "netbsd",
                 target_os = "openbsd"))] {
        pub mod bsd_pf;
    }
}

/// Convert `sockaddr_storage` to `SocketAddr`
#[allow(dead_code)]
pub fn sockaddr_to_std(saddr: &libc::sockaddr_storage) -> io::Result<SocketAddr> {
    match saddr.ss_family as libc::c_int {
        libc::AF_INET => unsafe {
            let addr: SocketAddrV4 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V4(addr))
        },
        libc::AF_INET6 => unsafe {
            let addr: SocketAddrV6 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V6(addr))
        },
        _ => {
            let err = Error::new(ErrorKind::InvalidData, "family must be either AF_INET or AF_INET6");
            Err(err)
        }
    }
}

cfg_if! {
    if #[cfg(target_os = "android")] {
        /// This is a RPC for Android to `protect()` socket for connecting to remote servers
        ///
        /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
        ///
        /// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project.
        fn protect(protect_path: &Option<String>, fd: RawFd) -> io::Result<()> {
            use std::{io::Read, os::unix::net::UnixStream, time::Duration};

            use sendfd::{SendWithFd};

            // ignore if protect_path is not specified
            let path = match protect_path {
                Some(path) => path,
                None => return Ok(()),
            };

            // it's safe to use blocking socket here
            let mut stream = UnixStream::connect(path)?;
            stream
                .set_read_timeout(Some(Duration::new(1, 0)))
                .expect("couldn't set read timeout");
            stream
                .set_write_timeout(Some(Duration::new(1, 0)))
                .expect("couldn't set write timeout");

            // send fds
            let dummy: [u8; 1] = [1];
            let fds: [RawFd; 1] = [fd];
            stream.send_with_fd(&dummy, &fds)?;

            // receive the return value
            let mut response = [0; 1];
            stream.read(&mut response)?;
            if response[0] == 0xFF {
                return Err(Error::new(ErrorKind::Other, "protect() failed"));
            }

            Ok(())
        }
    } else {
        #[inline(always)]
        fn protect(_protect_path: &Option<String>, _fd: RawFd) -> io::Result<()> {
            Ok(())
        }
    }
}

/// create a new TCP stream
#[inline(always)]
pub async fn tcp_stream_connect(saddr: &SocketAddr, context: &Context) -> io::Result<TcpStream> {
    let stream = TcpStream::connect(saddr).await?;

    // Any traffic to localhost should be protected
    // This is a workaround for VPNService
    if cfg!(target_os = "android") && !saddr.ip().is_loopback() {
        protect(&context.config().protect_path, stream.as_raw_fd())?;
    }

    Ok(stream)
}

/// Create a `UdpSocket` binded to `addr`
#[inline(always)]
pub async fn create_udp_socket_with_context(addr: &SocketAddr, context: &Context) -> io::Result<UdpSocket> {
    let socket = UdpSocket::bind(addr).await?;

    // Any traffic to localhost should be protected
    // This is a workaround for VPNService
    if cfg!(target_os = "android") && !addr.ip().is_loopback() {
        protect(&context.config().protect_path, socket.as_raw_fd())?;
    }

    Ok(socket)
}

/// Create a `UdpSocket` binded to `addr`
#[inline(always)]
pub async fn create_udp_socket(addr: &SocketAddr) -> io::Result<UdpSocket> {
    UdpSocket::bind(addr).await
}
<commit_msg>Ensure socket is protected before connecting<commit_after>use std::{
    io::{self, Error, ErrorKind},
    mem,
    net::{SocketAddr, SocketAddrV4, SocketAddrV6},
    os::unix::io::{AsRawFd, RawFd},
};

use cfg_if::cfg_if;
use socket2::{Domain, Protocol, Socket, Type};
use tokio::net::{TcpStream, UdpSocket};

use crate::context::Context;

cfg_if! {
    if #[cfg(any(target_os = "macos",
                 target_os = "ios",
                 target_os = "freebsd",
                 target_os = "netbsd",
                 target_os = "openbsd"))] {
        pub mod bsd_pf;
    }
}

/// Convert `sockaddr_storage` to `SocketAddr`
#[allow(dead_code)]
pub fn sockaddr_to_std(saddr: &libc::sockaddr_storage) -> io::Result<SocketAddr> {
    match saddr.ss_family as libc::c_int {
        libc::AF_INET => unsafe {
            let addr: SocketAddrV4 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V4(addr))
        },
        libc::AF_INET6 => unsafe {
            let addr: SocketAddrV6 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V6(addr))
        },
        _ => {
            let err = Error::new(ErrorKind::InvalidData, "family must be either AF_INET or AF_INET6");
            Err(err)
        }
    }
}

cfg_if! {
    if #[cfg(target_os = "android")] {
        /// This is a RPC for Android to `protect()` socket for connecting to remote servers
        ///
        /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
        ///
        /// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project.
        fn protect(protect_path: &Option<String>, fd: RawFd) -> io::Result<()> {
            use std::{io::Read, os::unix::net::UnixStream, time::Duration};

            use sendfd::{SendWithFd};

            // ignore if protect_path is not specified
            let path = match protect_path {
                Some(path) => path,
                None => return Ok(()),
            };

            // it's safe to use blocking socket here
            let mut stream = UnixStream::connect(path)?;
            stream
                .set_read_timeout(Some(Duration::new(1, 0)))
                .expect("couldn't set read timeout");
            stream
                .set_write_timeout(Some(Duration::new(1, 0)))
                .expect("couldn't set write timeout");

            // send fds
            let dummy: [u8; 1] = [1];
            let fds: [RawFd; 1] = [fd];
            stream.send_with_fd(&dummy, &fds)?;

            // receive the return value
            let mut response = [0; 1];
            stream.read(&mut response)?;
            if response[0] == 0xFF {
                return Err(Error::new(ErrorKind::Other, "protect() failed"));
            }

            Ok(())
        }
    } else {
        #[inline(always)]
        fn protect(_protect_path: &Option<String>, _fd: RawFd) -> io::Result<()> {
            Ok(())
        }
    }
}

/// create a new TCP stream
#[inline(always)]
pub async fn tcp_stream_connect(saddr: &SocketAddr, context: &Context) -> io::Result<TcpStream> {
    let domain = match *saddr {
        SocketAddr::V4(..) => Domain::ipv4(),
        SocketAddr::V6(..) => Domain::ipv6(),
    };

    let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
    socket.set_nonblocking(true)?;

    // Any traffic to localhost should not be protected
    // This is a workaround for VPNService
    if cfg!(target_os = "android") && !saddr.ip().is_loopback() {
        protect(&context.config().protect_path, socket.as_raw_fd())?;
    }

    // it's important that the socket is protected before connecting
    let stream = socket.into_tcp_stream();
    TcpStream::connect_std(stream, &saddr).await
}

/// Create a `UdpSocket` binded to `addr`
#[inline(always)]
pub async fn create_udp_socket_with_context(addr: &SocketAddr, context: &Context) -> io::Result<UdpSocket> {
    let socket = UdpSocket::bind(addr).await?;

    // Any traffic to localhost should be protected
    // This is a workaround for VPNService
    if cfg!(target_os = "android") && !addr.ip().is_loopback() {
        protect(&context.config().protect_path, socket.as_raw_fd())?;
    }

    Ok(socket)
}

/// Create a `UdpSocket` binded to `addr`
#[inline(always)]
pub async fn create_udp_socket(addr: &SocketAddr) -> io::Result<UdpSocket> {
    UdpSocket::bind(addr).await
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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
//
//     http://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.

use std::net::IpAddr;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};

use hab_core::channel::{BLDR_CHANNEL_ENVVAR, STABLE_CHANNEL};
use hab_core::env;
use hab_core::fs;
use hab_core::url::BLDR_URL_ENVVAR;
use hab_core::AUTH_TOKEN_ENVVAR;

use error::{Error, Result};
use runner::log_pipe::LogPipe;
use runner::{NONINTERACTIVE_ENVVAR, RUNNER_DEBUG_ENVVAR};
use runner::workspace::Workspace;

pub static STUDIO_UID: AtomicUsize = ATOMIC_USIZE_INIT;
pub static STUDIO_GID: AtomicUsize = ATOMIC_USIZE_INIT;
pub const DEBUG_ENVVARS: &'static [&'static str] = &["RUST_LOG", "DEBUG"];
pub const STUDIO_USER: &'static str = "krangschnak";
pub const STUDIO_GROUP: &'static str = "krangschnak";

lazy_static! {
    /// Absolute path to the Studio program
    static ref STUDIO_PROGRAM: PathBuf = fs::resolve_cmd_in_pkg(
        "hab-studio",
        include_str!(concat!(env!("OUT_DIR"), "/STUDIO_PKG_IDENT")),
    );

    pub static ref STUDIO_HOME: Mutex<PathBuf> = {
        Mutex::new(PathBuf::new())
    };
}

pub struct Studio<'a> {
    workspace: &'a Workspace,
    bldr_url: &'a str,
    auth_token: &'a str,
    airlock_enabled: bool,
    networking: Option<(&'a str, &'a IpAddr)>,
}

impl<'a> Studio<'a> {
    /// Creates a new Studio runner for a given `Workspace` and Builder URL.
    pub fn new(
        workspace: &'a Workspace,
        bldr_url: &'a str,
        auth_token: &'a str,
        airlock_enabled: bool,
        networking: Option<(&'a str, &'a IpAddr)>,
    ) -> Self {
        Studio {
            workspace,
            bldr_url,
            auth_token,
            airlock_enabled,
            networking,
        }
    }

    /// Spawns a Studio build command, pipes output streams to the given `LogPipe` and returns the
    /// process' `ExitStatus`.
    ///
    /// # Errors
    ///
    /// * If the child process can't be spawned
    /// * If the calling thread can't wait on the child process
    /// * If the `LogPipe` fails to pipe output
    pub fn build(&self, log_pipe: &mut LogPipe) -> Result<ExitStatus> {
        if self.networking.is_some() {
            self.create_network_namespace()?;
        } else {
            info!("Airlock networking is not configured, skipping network creation");
        }

        let channel = if self.workspace.job.has_channel() {
            self.workspace.job.get_channel()
        } else {
            STABLE_CHANNEL
        };

        let mut cmd = self.studio_command()?;
        cmd.current_dir(self.workspace.src());
        if let Some(val) = env::var_os(RUNNER_DEBUG_ENVVAR) {
            cmd.env("DEBUG", val);
        }
        cmd.env("PATH", env::var("PATH").unwrap_or(String::from(""))); // Sets `$PATH`
        cmd.env(NONINTERACTIVE_ENVVAR, "true"); // Disables progress bars
        cmd.env("TERM", "xterm-256color"); // Emits ANSI color codes
        // propagate debugging environment variables into Airlock and Studio
        for var in DEBUG_ENVVARS {
            if let Ok(val) = env::var(var) {
                cmd.env(var, val);
            }
        }
        cmd.stdout(Stdio::piped());
        // TED TODO: This will not work on windows. A more robust threading solution will be required for log_pipe
        // to support consuming stderr and stdout.
        // This manifests when a child starts (studio) and has an error (often unseen) then suddenly stops all execution.
        cmd.stderr(Stdio::null()); // Log stderr to stdout
        cmd.arg("-k"); // Origin key
        cmd.arg(self.workspace.job.origin());
        cmd.arg("build");
        cmd.arg(build_path(self.workspace.job.get_project().get_plan_path()));
        debug!("building studio build command, cmd={:?}", &cmd);
        debug!(
            "setting studio build command env, {}={}",
            BLDR_CHANNEL_ENVVAR,
            &channel
        );
        cmd.env(BLDR_CHANNEL_ENVVAR, channel);
        debug!(
            "setting studio build command env, {}={}",
            BLDR_URL_ENVVAR,
            self.bldr_url
        );
        cmd.env(BLDR_URL_ENVVAR, self.bldr_url);
        cmd.env(AUTH_TOKEN_ENVVAR, self.auth_token);

        debug!("spawning studio build command");
        let mut child = cmd.spawn().map_err(|e| {
            Error::StudioBuild(self.workspace.studio().to_path_buf(), e)
        })?;
        log_pipe.pipe(&mut child)?;
        let exit_status = child.wait().map_err(|e| {
            Error::StudioBuild(self.workspace.studio().to_path_buf(), e)
        })?;
        debug!("completed studio build command, status={:?}", exit_status);

        if self.networking.is_some() {
            self.destroy_network_namespace()?;
        } else {
            info!("Airlock networking is not configured, skipping network destruction");
        }

        Ok(exit_status)
    }

    fn studio_command(&self) -> Result<Command> {
        if self.airlock_enabled {
            let mut cmd = Command::new("airlock");
            cmd.uid(studio_uid());
            cmd.gid(studio_gid());
            cmd.env_clear();
            cmd.env("HOME", &*STUDIO_HOME.lock().unwrap()); // Sets `$HOME` for build user
            cmd.env("USER", STUDIO_USER); // Sets `$USER` for build user
            cmd.arg("run");
            cmd.arg("--fs-root");
            cmd.arg(self.workspace.studio());
            cmd.arg("--no-rm");
            if self.networking.is_some() {
                cmd.arg("--use-userns");
                cmd.arg(self.workspace.ns_dir().join("userns"));
                cmd.arg("--use-netns");
                cmd.arg(self.workspace.ns_dir().join("netns"));
            }
            cmd.arg(&*STUDIO_PROGRAM);

            Ok(cmd)
        } else {
            let mut cmd = Command::new(&*STUDIO_PROGRAM);
            cmd.env_clear();
            debug!("HAB_CACHE_KEY_PATH: {:?}", key_path());
            cmd.env("NO_ARTIFACT_PATH", "true"); // Disables artifact cache mounting
            cmd.env("HAB_CACHE_KEY_PATH", key_path()); // Sets key cache to build user's home

            info!("Airlock is not enabled, running uncontained Studio");
            Ok(cmd)
        }
    }

    fn create_network_namespace(&self) -> Result<()> {
        let mut cmd = Command::new("airlock");
        cmd.arg("netns");
        cmd.arg("create");
        cmd.arg("--interface");
        cmd.arg(self.networking.unwrap().0);
        cmd.arg("--gateway");
        cmd.arg(self.networking.unwrap().1.to_string());
        cmd.arg("--ns-dir");
        cmd.arg(self.workspace.ns_dir());
        cmd.arg("--user");
        cmd.arg(STUDIO_USER);
        debug!("building airlock networking setup command, cmd={:?}", &cmd);

        debug!("spawning airlock networking setup command");
        let mut child = cmd.spawn().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        let exit_status = child.wait().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        debug!(
            "completed airlock networking setup command, status={:?}",
            exit_status
        );

        if exit_status.success() {
            Ok(())
        } else {
            Err(Error::AirlockFailure(exit_status))
        }
    }

    fn destroy_network_namespace(&self) -> Result<()> {
        let mut cmd = Command::new("airlock");
        cmd.arg("netns");
        cmd.arg("destroy");
        cmd.arg("--ns-dir");
        cmd.arg(self.workspace.ns_dir());
        debug!(
            "building airlock networking destroy command, cmd={:?}",
            &cmd
        );

        debug!("spawning airlock networking destroy command");
        let mut child = cmd.spawn().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        let exit_status = child.wait().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        debug!(
            "completed airlock networking destroy command, status={:?}",
            exit_status
        );

        if exit_status.success() {
            Ok(())
        } else {
            Err(Error::AirlockFailure(exit_status))
        }
    }
}

pub fn studio_gid() -> u32 {
    STUDIO_GID.load(Ordering::Relaxed) as u32
}

pub fn studio_uid() -> u32 {
    STUDIO_UID.load(Ordering::Relaxed) as u32
}

pub fn set_studio_gid(gid: u32) {
    STUDIO_GID.store(gid as usize, Ordering::Relaxed);
}

pub fn set_studio_uid(uid: u32) {
    STUDIO_UID.store(uid as usize, Ordering::Relaxed);
}

pub fn key_path() -> PathBuf {
    (&*STUDIO_HOME).lock().unwrap().join(format!(
        ".{}",
        fs::CACHE_KEY_PATH
    ))
}

/// Returns a path argument suitable to pass to a Studio build command.
fn build_path(plan_path: &str) -> String {
    let mut parts: Vec<_> = plan_path.split("/").collect();
    if parts.last().map_or("", |p| *p) == "plan.sh" {
        parts.pop();
    }
    if parts.last().map_or("", |p| *p) == "habitat" {
        parts.pop();
    }

    if parts.is_empty() {
        String::from(".")
    } else {
        parts.join("/")
    }
}

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

    #[test]
    fn build_path_with_plan_sh() {
        assert_eq!(".", build_path("plan.sh"));
    }

    #[test]
    fn build_path_with_habitat_plan_sh() {
        assert_eq!(".", build_path("habitat/plan.sh"));
    }

    #[test]
    fn build_path_with_subdir_plan_sh() {
        assert_eq!("haaay", build_path("haaay/plan.sh"));
    }

    #[test]
    fn build_path_with_subdir_habitat_plan_sh() {
        assert_eq!(
            "components/yep",
            build_path("components/yep/habitat/plan.sh")
        );
    }
}
<commit_msg>Get builds working again<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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
//
//     http://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.

use std::net::IpAddr;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};

use hab_core::channel::{BLDR_CHANNEL_ENVVAR, STABLE_CHANNEL};
use hab_core::env;
use hab_core::fs;
use hab_core::url::BLDR_URL_ENVVAR;
use hab_core::AUTH_TOKEN_ENVVAR;

use error::{Error, Result};
use runner::log_pipe::LogPipe;
use runner::{NONINTERACTIVE_ENVVAR, RUNNER_DEBUG_ENVVAR};
use runner::workspace::Workspace;

pub static STUDIO_UID: AtomicUsize = ATOMIC_USIZE_INIT;
pub static STUDIO_GID: AtomicUsize = ATOMIC_USIZE_INIT;
pub const DEBUG_ENVVARS: &'static [&'static str] = &["RUST_LOG", "DEBUG"];
pub const STUDIO_USER: &'static str = "krangschnak";
pub const STUDIO_GROUP: &'static str = "krangschnak";

lazy_static! {
    /// Absolute path to the Studio program
    static ref STUDIO_PROGRAM: PathBuf = fs::resolve_cmd_in_pkg(
        "hab-studio",
        include_str!(concat!(env!("OUT_DIR"), "/STUDIO_PKG_IDENT")),
    );

    pub static ref STUDIO_HOME: Mutex<PathBuf> = {
        Mutex::new(PathBuf::new())
    };
}

pub struct Studio<'a> {
    workspace: &'a Workspace,
    bldr_url: &'a str,
    auth_token: &'a str,
    airlock_enabled: bool,
    networking: Option<(&'a str, &'a IpAddr)>,
}

impl<'a> Studio<'a> {
    /// Creates a new Studio runner for a given `Workspace` and Builder URL.
    pub fn new(
        workspace: &'a Workspace,
        bldr_url: &'a str,
        auth_token: &'a str,
        airlock_enabled: bool,
        networking: Option<(&'a str, &'a IpAddr)>,
    ) -> Self {
        Studio {
            workspace,
            bldr_url,
            auth_token,
            airlock_enabled,
            networking,
        }
    }

    /// Spawns a Studio build command, pipes output streams to the given `LogPipe` and returns the
    /// process' `ExitStatus`.
    ///
    /// # Errors
    ///
    /// * If the child process can't be spawned
    /// * If the calling thread can't wait on the child process
    /// * If the `LogPipe` fails to pipe output
    pub fn build(&self, log_pipe: &mut LogPipe) -> Result<ExitStatus> {
        if self.networking.is_some() {
            self.create_network_namespace()?;
        } else {
            info!("Airlock networking is not configured, skipping network creation");
        }

        let channel = if self.workspace.job.has_channel() {
            self.workspace.job.get_channel()
        } else {
            STABLE_CHANNEL
        };

        let mut cmd = self.studio_command()?;
        cmd.current_dir(self.workspace.src());
        if let Some(val) = env::var_os(RUNNER_DEBUG_ENVVAR) {
            cmd.env("DEBUG", val);
        }
        cmd.env("PATH", env::var("PATH").unwrap_or(String::from(""))); // Sets `$PATH`
        cmd.env(NONINTERACTIVE_ENVVAR, "true"); // Disables progress bars
        cmd.env("TERM", "xterm-256color"); // Emits ANSI color codes
        // propagate debugging environment variables into Airlock and Studio
        for var in DEBUG_ENVVARS {
            if let Ok(val) = env::var(var) {
                cmd.env(var, val);
            }
        }
        cmd.stdout(Stdio::piped());
        // TED TODO: This will not work on windows. A more robust threading solution will be required for log_pipe
        // to support consuming stderr and stdout.
        // This manifests when a child starts (studio) and has an error (often unseen) then suddenly stops all execution.
        cmd.arg("-k"); // Origin key
        cmd.arg(self.workspace.job.origin());
        cmd.arg("build");
        cmd.arg(build_path(self.workspace.job.get_project().get_plan_path()));
        debug!("building studio build command, cmd={:?}", &cmd);
        debug!(
            "setting studio build command env, {}={}",
            BLDR_CHANNEL_ENVVAR,
            &channel
        );
        cmd.env(BLDR_CHANNEL_ENVVAR, channel);
        debug!(
            "setting studio build command env, {}={}",
            BLDR_URL_ENVVAR,
            self.bldr_url
        );
        cmd.env(BLDR_URL_ENVVAR, self.bldr_url);
        cmd.env(AUTH_TOKEN_ENVVAR, self.auth_token);

        debug!("spawning studio build command");
        let mut child = cmd.spawn().map_err(|e| {
            Error::StudioBuild(self.workspace.studio().to_path_buf(), e)
        })?;
        log_pipe.pipe(&mut child)?;
        let exit_status = child.wait().map_err(|e| {
            Error::StudioBuild(self.workspace.studio().to_path_buf(), e)
        })?;
        debug!("completed studio build command, status={:?}", exit_status);

        if self.networking.is_some() {
            self.destroy_network_namespace()?;
        } else {
            info!("Airlock networking is not configured, skipping network destruction");
        }

        Ok(exit_status)
    }

    fn studio_command(&self) -> Result<Command> {
        if self.airlock_enabled {
            let mut cmd = Command::new("airlock");
            cmd.uid(studio_uid());
            cmd.gid(studio_gid());
            cmd.env_clear();
            cmd.env("HOME", &*STUDIO_HOME.lock().unwrap()); // Sets `$HOME` for build user
            cmd.env("USER", STUDIO_USER); // Sets `$USER` for build user
            cmd.arg("run");
            cmd.arg("--fs-root");
            cmd.arg(self.workspace.studio());
            cmd.arg("--no-rm");
            if self.networking.is_some() {
                cmd.arg("--use-userns");
                cmd.arg(self.workspace.ns_dir().join("userns"));
                cmd.arg("--use-netns");
                cmd.arg(self.workspace.ns_dir().join("netns"));
            }
            cmd.arg(&*STUDIO_PROGRAM);

            Ok(cmd)
        } else {
            let mut cmd = Command::new(&*STUDIO_PROGRAM);
            cmd.env_clear();
            debug!("HAB_CACHE_KEY_PATH: {:?}", key_path());
            cmd.env("NO_ARTIFACT_PATH", "true"); // Disables artifact cache mounting
            cmd.env("HAB_CACHE_KEY_PATH", key_path()); // Sets key cache to build user's home

            info!("Airlock is not enabled, running uncontained Studio");
            Ok(cmd)
        }
    }

    fn create_network_namespace(&self) -> Result<()> {
        let mut cmd = Command::new("airlock");
        cmd.arg("netns");
        cmd.arg("create");
        cmd.arg("--interface");
        cmd.arg(self.networking.unwrap().0);
        cmd.arg("--gateway");
        cmd.arg(self.networking.unwrap().1.to_string());
        cmd.arg("--ns-dir");
        cmd.arg(self.workspace.ns_dir());
        cmd.arg("--user");
        cmd.arg(STUDIO_USER);
        debug!("building airlock networking setup command, cmd={:?}", &cmd);

        debug!("spawning airlock networking setup command");
        let mut child = cmd.spawn().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        let exit_status = child.wait().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        debug!(
            "completed airlock networking setup command, status={:?}",
            exit_status
        );

        if exit_status.success() {
            Ok(())
        } else {
            Err(Error::AirlockFailure(exit_status))
        }
    }

    fn destroy_network_namespace(&self) -> Result<()> {
        let mut cmd = Command::new("airlock");
        cmd.arg("netns");
        cmd.arg("destroy");
        cmd.arg("--ns-dir");
        cmd.arg(self.workspace.ns_dir());
        debug!(
            "building airlock networking destroy command, cmd={:?}",
            &cmd
        );

        debug!("spawning airlock networking destroy command");
        let mut child = cmd.spawn().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        let exit_status = child.wait().map_err(|e| {
            Error::AirlockNetworking(self.workspace.ns_dir().to_path_buf(), e)
        })?;
        debug!(
            "completed airlock networking destroy command, status={:?}",
            exit_status
        );

        if exit_status.success() {
            Ok(())
        } else {
            Err(Error::AirlockFailure(exit_status))
        }
    }
}

pub fn studio_gid() -> u32 {
    STUDIO_GID.load(Ordering::Relaxed) as u32
}

pub fn studio_uid() -> u32 {
    STUDIO_UID.load(Ordering::Relaxed) as u32
}

pub fn set_studio_gid(gid: u32) {
    STUDIO_GID.store(gid as usize, Ordering::Relaxed);
}

pub fn set_studio_uid(uid: u32) {
    STUDIO_UID.store(uid as usize, Ordering::Relaxed);
}

pub fn key_path() -> PathBuf {
    (&*STUDIO_HOME).lock().unwrap().join(format!(
        ".{}",
        fs::CACHE_KEY_PATH
    ))
}

/// Returns a path argument suitable to pass to a Studio build command.
fn build_path(plan_path: &str) -> String {
    let mut parts: Vec<_> = plan_path.split("/").collect();
    if parts.last().map_or("", |p| *p) == "plan.sh" {
        parts.pop();
    }
    if parts.last().map_or("", |p| *p) == "habitat" {
        parts.pop();
    }

    if parts.is_empty() {
        String::from(".")
    } else {
        parts.join("/")
    }
}

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

    #[test]
    fn build_path_with_plan_sh() {
        assert_eq!(".", build_path("plan.sh"));
    }

    #[test]
    fn build_path_with_habitat_plan_sh() {
        assert_eq!(".", build_path("habitat/plan.sh"));
    }

    #[test]
    fn build_path_with_subdir_plan_sh() {
        assert_eq!("haaay", build_path("haaay/plan.sh"));
    }

    #[test]
    fn build_path_with_subdir_habitat_plan_sh() {
        assert_eq!(
            "components/yep",
            build_path("components/yep/habitat/plan.sh")
        );
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015-2017 the original author or 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
 *
 *     http://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.
 */

use common::run_java;

mod common;

#[test]
fn basic() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ThreadExhaustion", "", &[], &["Resource exhaustion event: the JVM was unable to create a thread.",
        "ERROR: PoolStats action failed: cannot determine memory usage statistics since the JVM is unable to create a thread"]));
}

#[test]
fn print_memory_usage_0() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ThreadExhaustion", "=printMemoryUsage=0", &[], &["jvmkill killing current process"]));
}

#[test]
fn time_10_count_2() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ThreadExhaustion", "=time=10,count=2,printHeapHistogram=1,heapHistogramMaxEntries=10,printMemoryUsage=0", &[], &[]));
}

#[test]
fn parallel_time_10_count_2() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ParallelThreadExhaustion", "=time=10,count=2", &[], &[]));
}
<commit_msg>Debug CI failure<commit_after>/*
 * Copyright (c) 2015-2017 the original author or 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
 *
 *     http://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.
 */

use common::run_java;

mod common;

#[test]
fn basic() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ThreadExhaustion", "", &[], &["Resource exhaustion event: the JVM was unable to create a thread.",
        "ERROR: PoolStats action failed"]));
}

#[test]
fn print_memory_usage_0() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ThreadExhaustion", "=printMemoryUsage=0", &[], &["jvmkill killing current process"]));
}

#[test]
fn time_10_count_2() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ThreadExhaustion", "=time=10,count=2,printHeapHistogram=1,heapHistogramMaxEntries=10,printMemoryUsage=0",
                      &[], &["ResourceExhausted! (1/2)", "jvmkill killing current process"]));
}

#[test]
fn parallel_time_10_count_2() {
    assert!(!run_java("org.cloudfoundry.jvmkill.ParallelThreadExhaustion", "=time=10,count=2", &[],
                      &["ResourceExhausted! (1/2)", "jvmkill killing current process"]));
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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
//
//     http://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.

use hyper::status::StatusCode;
use hcore::package::PackageIdent;
use std::str::FromStr;

use api_client;
use depot_client;
use common::ui::{Status, UI};

use {PRODUCT, VERSION};
use error::{Error, Result};

fn is_ident(s: &str) -> bool {
    PackageIdent::from_str(s).is_ok()
}

fn in_origin(ident: &str, origin: Option<&str>) -> bool {
    if origin.is_some() {
        let pi = PackageIdent::from_str(ident).unwrap(); // unwrap Ok
        origin.unwrap() == pi.origin
    } else {
        true
    }
}

pub fn get_ident_list(
    ui: &mut UI,
    bldr_url: &str,
    group_id: u64,
    origin: Option<&str>,
    interactive: bool,
) -> Result<Vec<String>> {
    let depot_client = depot_client::Client::new(bldr_url, PRODUCT, VERSION, None)
        .map_err(Error::DepotClient)?;

    let group_status = depot_client.get_schedule(group_id as i64).map_err(|e| {
        Error::ScheduleStatus(e)
    })?;

    let mut idents: Vec<String> = group_status
        .projects
        .iter()
        .cloned()
        .filter(|p| p.state == "Success" && in_origin(&p.ident, origin))
        .map(|p| p.ident)
        .collect();

    if idents.len() == 0 || !interactive {
        return Ok(idents);
    }

    let prelude = "# This is the list of package identifiers that will be processed.\n\
                   # You may edit this file and remove any packages that you do\n\
                   # not want to apply to the specified channel.\n"
        .to_string();

    idents.insert(0, prelude);

    Ok(
        ui.edit(&idents)?
            .split("\n")
            .filter(|s| is_ident(s))
            .map(|s: &str| s.to_string())
            .collect(),
    )
}

pub fn start(
    ui: &mut UI,
    bldr_url: &str,
    group_id: &str,
    channel: &str,
    origin: Option<&str>,
    interactive: bool,
    verbose: bool,
    token: &str,
    promote: bool,
) -> Result<()> {
    let api_client = api_client::Client::new(bldr_url, PRODUCT, VERSION, None)
        .map_err(Error::APIClient)?;
    let (promoted_demoted, promoting_demoting, to_from, changing_status, changed_status) =
        if promote {
            (
                "promoted",
                "Promoting",
                "to",
                Status::Promoting,
                Status::Promoted,
            )
        } else {
            (
                "demoted",
                "Demoting",
                "from",
                Status::Demoting,
                Status::Demoted,
            )
        };

    let gid = match group_id.parse::<u64>() {
        Ok(g) => g,
        Err(e) => {
            ui.fatal(format!("Failed to parse group id: {}", e))?;
            return Err(Error::ParseIntError(e));
        }
    };

    let idents = get_ident_list(ui, bldr_url, gid, origin, interactive)?;

    if idents.len() == 0 {
        ui.warn("No matching packages found")?;
        return Ok(());
    }

    if verbose {
        println!("Packages being {}:", promoted_demoted);
        for ident in idents.iter() {
            println!("  {}", ident)
        }
    }

    let question = format!(
        "{} {} package(s) to channel '{}'. Continue?",
        promoting_demoting,
        idents.len(),
        channel
    );

    if !ui.prompt_yes_no(&question, Some(true))? {
        ui.fatal("Aborted")?;
        return Ok(());
    }

    ui.status(
        changing_status,
        format!(
            "job group {} {} channel '{}'",
            group_id,
            to_from,
            channel
        ),
    )?;

    match api_client.job_group_promote_or_demote(gid, &idents, channel, token, promote) {
        Ok(_) => {
            ui.status(
                changed_status,
                format!(
                    "job group {} {} channel '{}'",
                    group_id,
                    to_from,
                    channel
                ),
            )?;
        }
        Err(api_client::Error::APIError(StatusCode::UnprocessableEntity, _)) => {
            return Err(Error::JobGroupPromoteOrDemoteUnprocessable(promote));
        }
        Err(e) => {
            return Err(Error::JobGroupPromoteOrDemote(e, promote));
        }
    };

    Ok(())
}
<commit_msg>Fix bldr job promote --interactive to inject newlines in pkg ident strings before passing to an editor<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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
//
//     http://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.

use hyper::status::StatusCode;
use hcore::package::PackageIdent;
use std::str::FromStr;

use api_client;
use depot_client::{self, SchedulerResponse};
use common::ui::{Status, UI};

use {PRODUCT, VERSION};
use error::{Error, Result};

fn is_ident(s: &str) -> bool {
    PackageIdent::from_str(s).is_ok()
}

fn in_origin(ident: &str, origin: Option<&str>) -> bool {
    if origin.is_some() {
        let pi = PackageIdent::from_str(ident).unwrap(); // unwrap Ok
        origin.unwrap() == pi.origin
    } else {
        true
    }
}

pub fn get_ident_list(
    ui: &mut UI,
    group_status: &SchedulerResponse,
    origin: Option<&str>,
    interactive: bool,
) -> Result<Vec<String>> {
    let mut idents: Vec<String> = group_status
        .projects
        .iter()
        .cloned()
        .filter(|p| p.state == "Success" && in_origin(&p.ident, origin))
        .map(|p| p.ident)
        .collect();

    if idents.len() == 0 || !interactive {
        return Ok(idents);
    }

    let prelude = "# This is the list of package identifiers that will be processed.\n\
                   # You may edit this file and remove any packages that you do\n\
                   # not want to apply to the specified channel.\n"
        .to_string();

    idents.insert(0, prelude);

    idents = idents.iter().map(|s| format!("{}\n", s)).collect();

    Ok(
        ui.edit(&idents)?
            .split("\n")
            .filter(|s| is_ident(s))
            .map(|s: &str| s.to_string())
            .collect(),
    )
}

fn get_group_status(bldr_url: &str, group_id: u64) -> Result<SchedulerResponse> {
    let depot_client = depot_client::Client::new(bldr_url, PRODUCT, VERSION, None)
        .map_err(Error::DepotClient)?;

    let group_status = depot_client.get_schedule(group_id as i64).map_err(|e| {
        Error::ScheduleStatus(e)
    })?;

    Ok(group_status)
}

pub fn start(
    ui: &mut UI,
    bldr_url: &str,
    group_id: &str,
    channel: &str,
    origin: Option<&str>,
    interactive: bool,
    verbose: bool,
    token: &str,
    promote: bool,
) -> Result<()> {
    let api_client = api_client::Client::new(bldr_url, PRODUCT, VERSION, None)
        .map_err(Error::APIClient)?;
    let (promoted_demoted, promoting_demoting, to_from, changing_status, changed_status) =
        if promote {
            (
                "promoted",
                "Promoting",
                "to",
                Status::Promoting,
                Status::Promoted,
            )
        } else {
            (
                "demoted",
                "Demoting",
                "from",
                Status::Demoting,
                Status::Demoted,
            )
        };

    let gid = match group_id.parse::<u64>() {
        Ok(g) => g,
        Err(e) => {
            ui.fatal(format!("Failed to parse group id: {}", e))?;
            return Err(Error::ParseIntError(e));
        }
    };

    let group_status = get_group_status(bldr_url, gid)?;
    let idents = get_ident_list(ui, &group_status, origin, interactive)?;

    if idents.len() == 0 {
        ui.warn("No matching packages found")?;
        return Ok(());
    }

    if verbose {
        println!("Packages being {}:", promoted_demoted);
        for ident in idents.iter() {
            println!("  {}", ident)
        }
    }

    let question = format!(
        "{} {} package(s) to channel '{}'. Continue?",
        promoting_demoting,
        idents.len(),
        channel
    );

    if !ui.prompt_yes_no(&question, Some(true))? {
        ui.fatal("Aborted")?;
        return Ok(());
    }

    ui.status(
        changing_status,
        format!(
            "job group {} {} channel '{}'",
            group_id,
            to_from,
            channel
        ),
    )?;

    match api_client.job_group_promote_or_demote(gid, &idents, channel, token, promote) {
        Ok(_) => {
            ui.status(
                changed_status,
                format!(
                    "job group {} {} channel '{}'",
                    group_id,
                    to_from,
                    channel
                ),
            )?;
        }
        Err(api_client::Error::APIError(StatusCode::UnprocessableEntity, _)) => {
            return Err(Error::JobGroupPromoteOrDemoteUnprocessable(promote));
        }
        Err(e) => {
            return Err(Error::JobGroupPromoteOrDemote(e, promote));
        }
    };

    Ok(())
}

#[cfg(test)]
mod test {
    use std::env;
    use std::io::{self, Cursor, Write};
    use std::sync::{Arc, RwLock};

    use common::ui::{Coloring, UI};
    use depot_client::{Project, SchedulerResponse};
    use super::get_ident_list;

    fn sample_project_list() -> Vec<Project> {
        let project1 = Project {
            name: "Project1".to_string(),
            ident: "core/project1/1.0.0/20180101000000".to_string(),
            state: "Success".to_string(),
            job_id: "12345678".to_string(),
        };
        let project2 = Project {
            name: "Project2".to_string(),
            ident: "core/project2/1.0.0/20180101000000".to_string(),
            state: "Success".to_string(),
            job_id: "12345678".to_string(),
        };

        vec![project1, project2]
    }

    fn ui() -> (UI, OutputBuffer, OutputBuffer) {
        let stdout_buf = OutputBuffer::new();
        let stderr_buf = OutputBuffer::new();

        let ui = UI::with_streams(
            Box::new(io::empty()),
            || Box::new(stdout_buf.clone()),
            || Box::new(stderr_buf.clone()),
            Coloring::Never,
            false,
        );

        (ui, stdout_buf, stderr_buf)
    }

    #[derive(Clone)]
    pub struct OutputBuffer {
        pub cursor: Arc<RwLock<Cursor<Vec<u8>>>>,
    }

    impl OutputBuffer {
        fn new() -> Self {
            OutputBuffer { cursor: Arc::new(RwLock::new(Cursor::new(Vec::new()))) }
        }
    }

    impl Write for OutputBuffer {
        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
            self.cursor
                .write()
                .expect("Cursor lock is poisoned")
                .write(buf)
        }

        fn flush(&mut self) -> io::Result<()> {
            self.cursor
                .write()
                .expect("Cursor lock is poisoned")
                .flush()
        }
    }

    #[test]
    fn test_get_ident_list() {
        let (mut ui, _stdout, _stderr) = ui();
        let group_status = SchedulerResponse {
            id: "12345678".to_string(),
            state: "Finished".to_string(),
            projects: sample_project_list(),
            created_at: "Properly formated timestamp".to_string(),
            project_name: "Test Project".to_string(),
        };

        let ident_list = get_ident_list(&mut ui, &group_status, Some("core"), false)
            .expect("Error fetching ident list");

        assert_eq!(
            ident_list,
            [
                "core/project1/1.0.0/20180101000000",
                "core/project2/1.0.0/20180101000000",
            ]
        )
    }

    #[test]
    fn test_get_ident_list_interactive() {
        let (mut ui, _stdout, _stderr) = ui();
        let group_status = SchedulerResponse {
            id: "12345678".to_string(),
            state: "Finished".to_string(),
            projects: sample_project_list(),
            created_at: "Properly formated timestamp".to_string(),
            project_name: "Test Project".to_string(),
        };
        env::set_var("EDITOR", "cat");

        let ident_list = get_ident_list(&mut ui, &group_status, Some("core"), true)
            .expect("Error fetching ident list");

        assert_eq!(
            ident_list,
            [
                "core/project1/1.0.0/20180101000000",
                "core/project2/1.0.0/20180101000000",
            ]
        )
    }
}
<|endoftext|>


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

//! An iterator over a list of rules.

use context::QuirksMode;
use media_queries::Device;
use shared_lock::SharedRwLockReadGuard;
use smallvec::SmallVec;
use std::slice;
use stylesheets::{CssRule, CssRules, DocumentRule, ImportRule, MediaRule, SupportsRule};

/// An iterator over a list of rules.
pub struct RulesIterator<'a, 'b, C>
    where 'b: 'a,
          C: NestedRuleIterationCondition + 'static,
{
    device: &'a Device,
    quirks_mode: QuirksMode,
    guard: &'a SharedRwLockReadGuard<'b>,
    stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>,
    _phantom: ::std::marker::PhantomData<C>,
}

impl<'a, 'b, C> RulesIterator<'a, 'b, C>
    where 'b: 'a,
          C: NestedRuleIterationCondition + 'static,
{
    /// Creates a new `RulesIterator` to iterate over `rules`.
    pub fn new(
        device: &'a Device,
        quirks_mode: QuirksMode,
        guard: &'a SharedRwLockReadGuard<'b>,
        rules: &'a CssRules)
        -> Self
    {
        let mut stack = SmallVec::new();
        stack.push(rules.0.iter());
        Self {
            device: device,
            quirks_mode: quirks_mode,
            guard: guard,
            stack: stack,
            _phantom: ::std::marker::PhantomData,
        }
    }

    /// Skips all the remaining children of the last nested rule processed.
    pub fn skip_children(&mut self) {
        self.stack.pop();
    }
}

impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C>
    where 'b: 'a,
          C: NestedRuleIterationCondition + 'static,
{
    type Item = &'a CssRule;

    fn next(&mut self) -> Option<Self::Item> {
        let mut nested_iter_finished = false;
        while !self.stack.is_empty() {
            if nested_iter_finished {
                self.stack.pop();
                nested_iter_finished = false;
                continue;
            }

            let rule;
            let sub_iter;
            {
                let mut nested_iter = self.stack.last_mut().unwrap();
                rule = match nested_iter.next() {
                    Some(r) => r,
                    None => {
                        nested_iter_finished = true;
                        continue
                    }
                };

                sub_iter = match *rule {
                    CssRule::Import(ref import_rule) => {
                        let import_rule = import_rule.read_with(self.guard);
                        if !C::process_import(self.guard,
                                              self.device,
                                              self.quirks_mode,
                                              import_rule) {
                            continue;
                        }
                        Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
                    }
                    CssRule::Document(ref doc_rule) => {
                        let doc_rule = doc_rule.read_with(self.guard);
                        if !C::process_document(self.guard,
                                                self.device,
                                                self.quirks_mode,
                                                doc_rule) {
                            continue;
                        }
                        Some(doc_rule.rules.read_with(self.guard).0.iter())
                    }
                    CssRule::Media(ref lock) => {
                        let media_rule = lock.read_with(self.guard);
                        if !C::process_media(self.guard,
                                             self.device,
                                             self.quirks_mode,
                                             media_rule) {
                            continue;
                        }
                        Some(media_rule.rules.read_with(self.guard).0.iter())
                    }
                    CssRule::Supports(ref lock) => {
                        let supports_rule = lock.read_with(self.guard);
                        if !C::process_supports(self.guard,
                                                self.device,
                                                self.quirks_mode,
                                                supports_rule) {
                            continue;
                        }
                        Some(supports_rule.rules.read_with(self.guard).0.iter())
                    }
                    CssRule::Namespace(_) |
                    CssRule::Style(_) |
                    CssRule::FontFace(_) |
                    CssRule::CounterStyle(_) |
                    CssRule::Viewport(_) |
                    CssRule::Keyframes(_) |
                    CssRule::Page(_) => None,
                };
            }

            if let Some(sub_iter) = sub_iter {
                self.stack.push(sub_iter);
            }

            return Some(rule);
        }

        None
    }
}

/// RulesIterator.
pub trait NestedRuleIterationCondition {
    /// Whether we should process the nested rules in a given `@import` rule.
    fn process_import(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &ImportRule)
        -> bool;

    /// Whether we should process the nested rules in a given `@media` rule.
    fn process_media(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &MediaRule)
        -> bool;

    /// Whether we should process the nested rules in a given `@-moz-document`
    /// rule.
    fn process_document(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &DocumentRule)
        -> bool;

    /// Whether we should process the nested rules in a given `@supports` rule.
    fn process_supports(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &SupportsRule)
        -> bool;
}

/// A struct that represents the condition that a rule applies to the document.
pub struct EffectiveRules;

impl NestedRuleIterationCondition for EffectiveRules {
    fn process_import(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &ImportRule)
        -> bool
    {
        rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode)
    }

    fn process_media(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &MediaRule)
        -> bool
    {
        rule.media_queries.read_with(guard).evaluate(device, quirks_mode)
    }

    fn process_document(
        _: &SharedRwLockReadGuard,
        device: &Device,
        _: QuirksMode,
        rule: &DocumentRule)
        -> bool
    {
        rule.condition.evaluate(device)
    }

    fn process_supports(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        rule: &SupportsRule)
        -> bool
    {
        rule.enabled
    }
}

/// A filter that processes all the rules in a rule list.
pub struct AllRules;

impl NestedRuleIterationCondition for AllRules {
    fn process_import(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &ImportRule)
        -> bool
    {
        true
    }

    fn process_media(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &MediaRule)
        -> bool
    {
        true
    }

    fn process_document(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &DocumentRule)
        -> bool
    {
        true
    }

    fn process_supports(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &SupportsRule)
        -> bool
    {
        true
    }
}

/// An iterator over all the effective rules of a stylesheet.
///
/// NOTE: This iterator recurses into `@import` rules.
pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>;
<commit_msg>Auto merge of #17330 - emilio:rules-iter-perf, r=heycam<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! An iterator over a list of rules.

use context::QuirksMode;
use media_queries::Device;
use shared_lock::SharedRwLockReadGuard;
use smallvec::SmallVec;
use std::slice;
use stylesheets::{CssRule, CssRules, DocumentRule, ImportRule, MediaRule, SupportsRule};

/// An iterator over a list of rules.
pub struct RulesIterator<'a, 'b, C>
    where 'b: 'a,
          C: NestedRuleIterationCondition + 'static,
{
    device: &'a Device,
    quirks_mode: QuirksMode,
    guard: &'a SharedRwLockReadGuard<'b>,
    stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>,
    _phantom: ::std::marker::PhantomData<C>,
}

impl<'a, 'b, C> RulesIterator<'a, 'b, C>
    where 'b: 'a,
          C: NestedRuleIterationCondition + 'static,
{
    /// Creates a new `RulesIterator` to iterate over `rules`.
    pub fn new(
        device: &'a Device,
        quirks_mode: QuirksMode,
        guard: &'a SharedRwLockReadGuard<'b>,
        rules: &'a CssRules)
        -> Self
    {
        let mut stack = SmallVec::new();
        stack.push(rules.0.iter());
        Self {
            device: device,
            quirks_mode: quirks_mode,
            guard: guard,
            stack: stack,
            _phantom: ::std::marker::PhantomData,
        }
    }

    /// Skips all the remaining children of the last nested rule processed.
    pub fn skip_children(&mut self) {
        self.stack.pop();
    }
}

impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C>
    where 'b: 'a,
          C: NestedRuleIterationCondition + 'static,
{
    type Item = &'a CssRule;

    fn next(&mut self) -> Option<Self::Item> {
        let mut nested_iter_finished = false;
        while !self.stack.is_empty() {
            if nested_iter_finished {
                self.stack.pop();
                nested_iter_finished = false;
                continue;
            }

            let rule;
            let sub_iter = {
                let mut nested_iter = self.stack.last_mut().unwrap();
                rule = match nested_iter.next() {
                    Some(r) => r,
                    None => {
                        nested_iter_finished = true;
                        continue
                    }
                };

                match *rule {
                    CssRule::Namespace(_) |
                    CssRule::Style(_) |
                    CssRule::FontFace(_) |
                    CssRule::CounterStyle(_) |
                    CssRule::Viewport(_) |
                    CssRule::Keyframes(_) |
                    CssRule::Page(_) => {
                        return Some(rule)
                    },
                    CssRule::Import(ref import_rule) => {
                        let import_rule = import_rule.read_with(self.guard);
                        if !C::process_import(self.guard,
                                              self.device,
                                              self.quirks_mode,
                                              import_rule) {
                            continue;
                        }
                        import_rule.stylesheet.rules.read_with(self.guard).0.iter()
                    }
                    CssRule::Document(ref doc_rule) => {
                        let doc_rule = doc_rule.read_with(self.guard);
                        if !C::process_document(self.guard,
                                                self.device,
                                                self.quirks_mode,
                                                doc_rule) {
                            continue;
                        }
                        doc_rule.rules.read_with(self.guard).0.iter()
                    }
                    CssRule::Media(ref lock) => {
                        let media_rule = lock.read_with(self.guard);
                        if !C::process_media(self.guard,
                                             self.device,
                                             self.quirks_mode,
                                             media_rule) {
                            continue;
                        }
                        media_rule.rules.read_with(self.guard).0.iter()
                    }
                    CssRule::Supports(ref lock) => {
                        let supports_rule = lock.read_with(self.guard);
                        if !C::process_supports(self.guard,
                                                self.device,
                                                self.quirks_mode,
                                                supports_rule) {
                            continue;
                        }
                        supports_rule.rules.read_with(self.guard).0.iter()
                    }
                }
            };

            self.stack.push(sub_iter);
            return Some(rule);
        }

        None
    }
}

/// RulesIterator.
pub trait NestedRuleIterationCondition {
    /// Whether we should process the nested rules in a given `@import` rule.
    fn process_import(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &ImportRule)
        -> bool;

    /// Whether we should process the nested rules in a given `@media` rule.
    fn process_media(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &MediaRule)
        -> bool;

    /// Whether we should process the nested rules in a given `@-moz-document`
    /// rule.
    fn process_document(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &DocumentRule)
        -> bool;

    /// Whether we should process the nested rules in a given `@supports` rule.
    fn process_supports(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &SupportsRule)
        -> bool;
}

/// A struct that represents the condition that a rule applies to the document.
pub struct EffectiveRules;

impl NestedRuleIterationCondition for EffectiveRules {
    fn process_import(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &ImportRule)
        -> bool
    {
        rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode)
    }

    fn process_media(
        guard: &SharedRwLockReadGuard,
        device: &Device,
        quirks_mode: QuirksMode,
        rule: &MediaRule)
        -> bool
    {
        rule.media_queries.read_with(guard).evaluate(device, quirks_mode)
    }

    fn process_document(
        _: &SharedRwLockReadGuard,
        device: &Device,
        _: QuirksMode,
        rule: &DocumentRule)
        -> bool
    {
        rule.condition.evaluate(device)
    }

    fn process_supports(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        rule: &SupportsRule)
        -> bool
    {
        rule.enabled
    }
}

/// A filter that processes all the rules in a rule list.
pub struct AllRules;

impl NestedRuleIterationCondition for AllRules {
    fn process_import(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &ImportRule)
        -> bool
    {
        true
    }

    fn process_media(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &MediaRule)
        -> bool
    {
        true
    }

    fn process_document(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &DocumentRule)
        -> bool
    {
        true
    }

    fn process_supports(
        _: &SharedRwLockReadGuard,
        _: &Device,
        _: QuirksMode,
        _: &SupportsRule)
        -> bool
    {
        true
    }
}

/// An iterator over all the effective rules of a stylesheet.
///
/// NOTE: This iterator recurses into `@import` rules.
pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>;
<|endoftext|>