<commit_before>// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use std::convert::AsRef;
use std::ops::Deref;
use std::sync::Arc;
use std::{fmt, hash};

use deepsize::{known_deep_size, DeepSizeOf};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use pyo3::{FromPyObject, ToPyObject};
use smallvec::SmallVec;

use crate::externs;

///
/// Params represent a TypeId->Key map.
///
/// For efficiency and hashability, they're stored as sorted Keys (with distinct TypeIds).
///
#[derive(Clone, Debug, Default, DeepSizeOf, Eq, Hash, PartialEq)]
pub struct Params(SmallVec<[Key; 4]>);

impl<'x> Params {
  pub fn new<I: IntoIterator<Item = Key>>(param_inputs: I) -> Result<Params, String> {
    let mut params = param_inputs.into_iter().collect::<SmallVec<[Key; 4]>>();
    params.sort_by_key(|k| *k.type_id());

    if params.len() > 1 {
      let mut prev = &params[0];
      for param in &params[1..] {
        if param.type_id() == prev.type_id() {
          return Err(format!(
            "Values used as `Params` must have distinct types, but the following \
            values had the same type (`{}`):\n  {}\n  {}",
            prev.type_id(),
            prev,
            param,
          ));
        }
        prev = param;
      }
    }

    Ok(Params(params))
  }

  pub fn keys(&'x self) -> impl Iterator<Item = &'x Key> {
    self.0.iter()
  }

  ///
  /// Adds the given param Key to these Params, replacing an existing param with the same type if
  /// it exists.
  ///
  pub fn put(&mut self, param: Key) {
    match self.binary_search(param.type_id) {
      Ok(idx) => self.0[idx] = param,
      Err(idx) => self.0.insert(idx, param),
    }
  }

  ///
  /// Filters this Params object in-place to contain only params matching the given predicate.
  ///
  pub fn retain<F: FnMut(&mut Key) -> bool>(&mut self, f: F) {
    self.0.retain(f)
  }

  ///
  /// Returns the Key for the given TypeId if it is represented in this set of Params.
  ///
  pub fn find(&self, type_id: TypeId) -> Option<&Key> {
    self.binary_search(type_id).ok().map(|idx| &self.0[idx])
  }

  fn binary_search(&self, type_id: TypeId) -> Result<usize, usize> {
    self
      .0
      .binary_search_by(|probe| probe.type_id().cmp(&type_id))
  }

  pub fn type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
    self.0.iter().map(|k| *k.type_id())
  }

  pub fn is_empty(&self) -> bool {
    self.0.is_empty()
  }
}

///
pub fn display_sorted_in_parens<T>(items: T) -> String
where
  T: Iterator,
  T::Item: fmt::Display,
{
  let mut items: Vec<_> = items.map(|p| format!("{}", p)).collect();
  match items.len() {
    0 => "()".to_string(),
    1 => items.pop().unwrap(),
    _ => {
      items.sort();
      format!("({})", items.join(", "))
    }
  }
}

impl fmt::Display for Params {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Params{}", display_sorted_in_parens(self.0.iter()))
  }
}

pub type Id = u64;

/// A pointer to an underlying PyTypeObject instance.
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TypeId(*mut pyo3::ffi::PyTypeObject);

known_deep_size!(8; TypeId);
unsafe impl Send for TypeId {}
unsafe impl Sync for TypeId {}

impl TypeId {
  pub fn new(py_type: &PyType) -> Self {
    py_type.into()
  }

  pub fn as_py_type<'py>(&self, py: Python<'py>) -> &'py PyType {
    // NB: Dereferencing a pointer to a PyTypeObject is safe as long as the module defining the
    // type is not unloaded. That is true today, but would not be if we implemented support for hot
    // reloading of plugins.
    unsafe { PyType::from_type_ptr(py, self.0) }
  }

  pub fn is_union(&self) -> bool {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let unions_module = py.import("pants.engine.unions").unwrap();
    let is_union_func = unions_module.getattr("is_union").unwrap();
    is_union_func
      .call1((self.as_py_type(py),))
      .unwrap()
      .extract()
      .unwrap()
  }
}

impl From<&PyType> for TypeId {
  fn from(py_type: &PyType) -> Self {
    TypeId(py_type.as_type_ptr())
  }
}

impl fmt::Debug for TypeId {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    Python::with_gil(|py| {
      let name = self.as_py_type(py).name().unwrap();
      write!(f, "{}", name)
    })
  }
}

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

impl rule_graph::TypeId for TypeId {
  /// Render a string for a collection of TypeIds.
  fn display<I>(type_ids: I) -> String
  where
    I: Iterator<Item = TypeId>,
  {
    display_sorted_in_parens(type_ids)
  }
}

/// An identifier for a Python function.
#[derive(Clone, DeepSizeOf, Eq, Hash, PartialEq)]
pub struct Function(pub Key);

impl Function {
  /// The function represented as `path.to.module:lineno:func_name`.
  pub fn full_name(&self) -> String {
    let (module, name, line_no) = Python::with_gil(|py| {
      let val = self.0.to_value();
      let obj = (*val).as_ref(py);
      let module: String = externs::getattr(obj, "__module__").unwrap();
      let name: String = externs::getattr(obj, "__name__").unwrap();
      // NB: this is a custom dunder method that Python code should populate before sending the
      // function (e.g. an `@rule`) through FFI.
      let line_no: u64 = externs::getattr(obj, "__line_number__").unwrap();
      (module, name, line_no)
    });
    format!("{}:{}:{}", module, line_no, name)
  }
}

impl fmt::Debug for Function {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}()", self.full_name())
  }
}

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

/// An interned key for a Value for use as a key in HashMaps and sets.
#[derive(Clone, DeepSizeOf)]
pub struct Key {
  id: Id,
  type_id: TypeId,
  pub value: Value,
}

impl Eq for Key {}

impl PartialEq for Key {
  fn eq(&self, other: &Key) -> bool {
    self.id == other.id
  }
}

impl hash::Hash for Key {
  fn hash<H: hash::Hasher>(&self, state: &mut H) {
    self.id.hash(state);
  }
}

impl fmt::Debug for Key {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{:?}", self.to_value())
  }
}

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

impl Key {
  pub fn new(id: Id, type_id: TypeId, value: Value) -> Key {
    Key { id, type_id, value }
  }

  pub fn id(&self) -> Id {
    self.id
  }

  pub fn type_id(&self) -> &TypeId {
    &self.type_id
  }

  pub fn from_value(val: Value) -> PyResult<Key> {
    let gil = Python::acquire_gil();
    let py = gil.python();
    externs::INTERNS.key_insert(py, val.consume_into_py_object(py))
  }

  pub fn to_value(&self) -> Value {
    self.value.clone()
  }
}

// NB: Although `PyObject` (aka `Py<PyAny>`) directly implements `Clone`, it's ~4% faster to wrap
// in `Arc` like this, because `Py<T>` internally acquires a (non-GIL) global lock during `Clone`
// and `Drop`.
#[derive(Clone)]
pub struct Value(Arc<PyObject>);

// NB: The size of objects held by a Graph is tracked independently, so we assert that each Value
// is only as large as its pointer.
known_deep_size!(8; Value);

impl Value {
  pub fn new(obj: PyObject) -> Value {
    Value(Arc::new(obj))
  }

  // NB: Longer name because overloaded in a few places.
  pub fn consume_into_py_object(self, py: Python) -> PyObject {
    match Arc::try_unwrap(self.0) {
      Ok(obj) => obj,
      Err(arc_handle) => arc_handle.clone_ref(py),
    }
  }
}

impl workunit_store::Value for Value {
  fn as_any(&self) -> &dyn std::any::Any {
    self
  }
}

impl PartialEq for Value {
  fn eq(&self, other: &Value) -> bool {
    Python::with_gil(|py| externs::equals((*self.0).as_ref(py), (*other.0).as_ref(py)))
  }
}

impl Eq for Value {}

impl Deref for Value {
  type Target = PyObject;

  fn deref(&self) -> &PyObject {
    &self.0
  }
}

impl AsRef<PyObject> for Value {
  fn as_ref(&self) -> &PyObject {
    &self.0
  }
}

impl fmt::Debug for Value {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let repr = Python::with_gil(|py| {
      let obj = (*self.0).as_ref(py);
      externs::val_to_str(obj)
    });
    write!(f, "{}", repr)
  }
}

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

impl<'source> FromPyObject<'source> for Value {
  fn extract(obj: &'source PyAny) -> PyResult<Self> {
    let py = obj.py();
    Ok(obj.into_py(py).into())
  }
}

impl ToPyObject for &Value {
  fn to_object(&self, py: Python) -> PyObject {
    self.0.clone_ref(py)
  }
}

impl From<Value> for PyObject {
  fn from(value: Value) -> Self {
    match Arc::try_unwrap(value.0) {
      Ok(obj) => obj,
      Err(arc_handle) => Python::with_gil(|py| arc_handle.clone_ref(py)),
    }
  }
}

impl From<PyObject> for Value {
  fn from(obj: PyObject) -> Self {
    Value::new(obj)
  }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Failure {
  /// A Node failed because a filesystem change invalidated it or its inputs.
  /// A root requestor should usually immediately retry their request.
  Invalidated,
  /// An error was thrown.
  Throw {
    // A python exception value, which might have a python-level stacktrace
    val: Value,
    // A pre-formatted python exception traceback.
    python_traceback: String,
    // A stack of engine-side "frame" information generated from Nodes.
    engine_traceback: Vec<String>,
  },
}

impl Failure {
  ///
  /// Consumes this Failure to produce a new Failure with an additional engine_traceback entry.
  ///
  pub fn with_pushed_frame(self, frame: &impl fmt::Display) -> Failure {
    match self {
      Failure::Invalidated => Failure::Invalidated,
      Failure::Throw {
        val,
        python_traceback,
        mut engine_traceback,
      } => {
        engine_traceback.push(format!("{}", frame));
        Failure::Throw {
          val,
          python_traceback,
          engine_traceback,
        }
      }
    }
  }
}

impl Failure {
  pub fn from_py_err(py_err: PyErr) -> Failure {
    let gil = Python::acquire_gil();
    let py = gil.python();
    Failure::from_py_err_with_gil(py, py_err)
  }

  pub fn from_py_err_with_gil(py: Python, py_err: PyErr) -> Failure {
    let maybe_ptraceback = py_err
      .traceback(py)
      .map(|traceback| traceback.to_object(py));
    let val = Value::from(py_err.into_py(py));
    let python_traceback = if let Some(tb) = maybe_ptraceback {
      let locals = PyDict::new(py);
      locals
        .set_item("traceback", py.import("traceback").unwrap())
        .unwrap();
      locals.set_item("tb", tb).unwrap();
      locals.set_item("val", &val).unwrap();
      py.eval(
        "''.join(traceback.format_exception(etype=None, value=val, tb=tb))",
        None,
        Some(locals),
      )
      .unwrap()
      .extract::<String>()
      .unwrap()
    } else {
      Self::native_traceback(&externs::val_to_str((*val).as_ref(py)))
    };
    Failure::Throw {
      val,
      python_traceback,
      engine_traceback: Vec::new(),
    }
  }

  pub fn native_traceback(msg: &str) -> String {
    format!(
      "Traceback (no traceback):\n  <pants native internals>\nException: {}",
      msg
    )
  }
}

impl fmt::Display for Failure {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      Failure::Invalidated => write!(f, "Giving up on retrying due to changed files."),
      Failure::Throw { val, .. } => {
        let repr = Python::with_gil(|py| {
          let obj = (*val.0).as_ref(py);
          externs::val_to_str(obj)
        });
        write!(f, "{}", repr)
      }
    }
  }
}

impl From<String> for Failure {
  fn from(err: String) -> Self {
    throw(err)
  }
}

pub fn throw(msg: String) -> Failure {
  let gil = Python::acquire_gil();
  let python_traceback = Failure::native_traceback(&msg);
  Failure::Throw {
    val: externs::create_exception(gil.python(), msg),
    python_traceback,
    engine_traceback: Vec::new(),
  }
}
<commit_msg>Remove deprecated traceback formatting argument. (#15320)<commit_after>// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use std::convert::AsRef;
use std::ops::Deref;
use std::sync::Arc;
use std::{fmt, hash};

use deepsize::{known_deep_size, DeepSizeOf};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use pyo3::{FromPyObject, ToPyObject};
use smallvec::SmallVec;

use crate::externs;

///
/// Params represent a TypeId->Key map.
///
/// For efficiency and hashability, they're stored as sorted Keys (with distinct TypeIds).
///
#[derive(Clone, Debug, Default, DeepSizeOf, Eq, Hash, PartialEq)]
pub struct Params(SmallVec<[Key; 4]>);

impl<'x> Params {
  pub fn new<I: IntoIterator<Item = Key>>(param_inputs: I) -> Result<Params, String> {
    let mut params = param_inputs.into_iter().collect::<SmallVec<[Key; 4]>>();
    params.sort_by_key(|k| *k.type_id());

    if params.len() > 1 {
      let mut prev = &params[0];
      for param in &params[1..] {
        if param.type_id() == prev.type_id() {
          return Err(format!(
            "Values used as `Params` must have distinct types, but the following \
            values had the same type (`{}`):\n  {}\n  {}",
            prev.type_id(),
            prev,
            param,
          ));
        }
        prev = param;
      }
    }

    Ok(Params(params))
  }

  pub fn keys(&'x self) -> impl Iterator<Item = &'x Key> {
    self.0.iter()
  }

  ///
  /// Adds the given param Key to these Params, replacing an existing param with the same type if
  /// it exists.
  ///
  pub fn put(&mut self, param: Key) {
    match self.binary_search(param.type_id) {
      Ok(idx) => self.0[idx] = param,
      Err(idx) => self.0.insert(idx, param),
    }
  }

  ///
  /// Filters this Params object in-place to contain only params matching the given predicate.
  ///
  pub fn retain<F: FnMut(&mut Key) -> bool>(&mut self, f: F) {
    self.0.retain(f)
  }

  ///
  /// Returns the Key for the given TypeId if it is represented in this set of Params.
  ///
  pub fn find(&self, type_id: TypeId) -> Option<&Key> {
    self.binary_search(type_id).ok().map(|idx| &self.0[idx])
  }

  fn binary_search(&self, type_id: TypeId) -> Result<usize, usize> {
    self
      .0
      .binary_search_by(|probe| probe.type_id().cmp(&type_id))
  }

  pub fn type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
    self.0.iter().map(|k| *k.type_id())
  }

  pub fn is_empty(&self) -> bool {
    self.0.is_empty()
  }
}

///
pub fn display_sorted_in_parens<T>(items: T) -> String
where
  T: Iterator,
  T::Item: fmt::Display,
{
  let mut items: Vec<_> = items.map(|p| format!("{}", p)).collect();
  match items.len() {
    0 => "()".to_string(),
    1 => items.pop().unwrap(),
    _ => {
      items.sort();
      format!("({})", items.join(", "))
    }
  }
}

impl fmt::Display for Params {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Params{}", display_sorted_in_parens(self.0.iter()))
  }
}

pub type Id = u64;

/// A pointer to an underlying PyTypeObject instance.
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TypeId(*mut pyo3::ffi::PyTypeObject);

known_deep_size!(8; TypeId);
unsafe impl Send for TypeId {}
unsafe impl Sync for TypeId {}

impl TypeId {
  pub fn new(py_type: &PyType) -> Self {
    py_type.into()
  }

  pub fn as_py_type<'py>(&self, py: Python<'py>) -> &'py PyType {
    // NB: Dereferencing a pointer to a PyTypeObject is safe as long as the module defining the
    // type is not unloaded. That is true today, but would not be if we implemented support for hot
    // reloading of plugins.
    unsafe { PyType::from_type_ptr(py, self.0) }
  }

  pub fn is_union(&self) -> bool {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let unions_module = py.import("pants.engine.unions").unwrap();
    let is_union_func = unions_module.getattr("is_union").unwrap();
    is_union_func
      .call1((self.as_py_type(py),))
      .unwrap()
      .extract()
      .unwrap()
  }
}

impl From<&PyType> for TypeId {
  fn from(py_type: &PyType) -> Self {
    TypeId(py_type.as_type_ptr())
  }
}

impl fmt::Debug for TypeId {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    Python::with_gil(|py| {
      let name = self.as_py_type(py).name().unwrap();
      write!(f, "{}", name)
    })
  }
}

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

impl rule_graph::TypeId for TypeId {
  /// Render a string for a collection of TypeIds.
  fn display<I>(type_ids: I) -> String
  where
    I: Iterator<Item = TypeId>,
  {
    display_sorted_in_parens(type_ids)
  }
}

/// An identifier for a Python function.
#[derive(Clone, DeepSizeOf, Eq, Hash, PartialEq)]
pub struct Function(pub Key);

impl Function {
  /// The function represented as `path.to.module:lineno:func_name`.
  pub fn full_name(&self) -> String {
    let (module, name, line_no) = Python::with_gil(|py| {
      let val = self.0.to_value();
      let obj = (*val).as_ref(py);
      let module: String = externs::getattr(obj, "__module__").unwrap();
      let name: String = externs::getattr(obj, "__name__").unwrap();
      // NB: this is a custom dunder method that Python code should populate before sending the
      // function (e.g. an `@rule`) through FFI.
      let line_no: u64 = externs::getattr(obj, "__line_number__").unwrap();
      (module, name, line_no)
    });
    format!("{}:{}:{}", module, line_no, name)
  }
}

impl fmt::Debug for Function {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}()", self.full_name())
  }
}

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

/// An interned key for a Value for use as a key in HashMaps and sets.
#[derive(Clone, DeepSizeOf)]
pub struct Key {
  id: Id,
  type_id: TypeId,
  pub value: Value,
}

impl Eq for Key {}

impl PartialEq for Key {
  fn eq(&self, other: &Key) -> bool {
    self.id == other.id
  }
}

impl hash::Hash for Key {
  fn hash<H: hash::Hasher>(&self, state: &mut H) {
    self.id.hash(state);
  }
}

impl fmt::Debug for Key {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{:?}", self.to_value())
  }
}

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

impl Key {
  pub fn new(id: Id, type_id: TypeId, value: Value) -> Key {
    Key { id, type_id, value }
  }

  pub fn id(&self) -> Id {
    self.id
  }

  pub fn type_id(&self) -> &TypeId {
    &self.type_id
  }

  pub fn from_value(val: Value) -> PyResult<Key> {
    let gil = Python::acquire_gil();
    let py = gil.python();
    externs::INTERNS.key_insert(py, val.consume_into_py_object(py))
  }

  pub fn to_value(&self) -> Value {
    self.value.clone()
  }
}

// NB: Although `PyObject` (aka `Py<PyAny>`) directly implements `Clone`, it's ~4% faster to wrap
// in `Arc` like this, because `Py<T>` internally acquires a (non-GIL) global lock during `Clone`
// and `Drop`.
#[derive(Clone)]
pub struct Value(Arc<PyObject>);

// NB: The size of objects held by a Graph is tracked independently, so we assert that each Value
// is only as large as its pointer.
known_deep_size!(8; Value);

impl Value {
  pub fn new(obj: PyObject) -> Value {
    Value(Arc::new(obj))
  }

  // NB: Longer name because overloaded in a few places.
  pub fn consume_into_py_object(self, py: Python) -> PyObject {
    match Arc::try_unwrap(self.0) {
      Ok(obj) => obj,
      Err(arc_handle) => arc_handle.clone_ref(py),
    }
  }
}

impl workunit_store::Value for Value {
  fn as_any(&self) -> &dyn std::any::Any {
    self
  }
}

impl PartialEq for Value {
  fn eq(&self, other: &Value) -> bool {
    Python::with_gil(|py| externs::equals((*self.0).as_ref(py), (*other.0).as_ref(py)))
  }
}

impl Eq for Value {}

impl Deref for Value {
  type Target = PyObject;

  fn deref(&self) -> &PyObject {
    &self.0
  }
}

impl AsRef<PyObject> for Value {
  fn as_ref(&self) -> &PyObject {
    &self.0
  }
}

impl fmt::Debug for Value {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let repr = Python::with_gil(|py| {
      let obj = (*self.0).as_ref(py);
      externs::val_to_str(obj)
    });
    write!(f, "{}", repr)
  }
}

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

impl<'source> FromPyObject<'source> for Value {
  fn extract(obj: &'source PyAny) -> PyResult<Self> {
    let py = obj.py();
    Ok(obj.into_py(py).into())
  }
}

impl ToPyObject for &Value {
  fn to_object(&self, py: Python) -> PyObject {
    self.0.clone_ref(py)
  }
}

impl From<Value> for PyObject {
  fn from(value: Value) -> Self {
    match Arc::try_unwrap(value.0) {
      Ok(obj) => obj,
      Err(arc_handle) => Python::with_gil(|py| arc_handle.clone_ref(py)),
    }
  }
}

impl From<PyObject> for Value {
  fn from(obj: PyObject) -> Self {
    Value::new(obj)
  }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Failure {
  /// A Node failed because a filesystem change invalidated it or its inputs.
  /// A root requestor should usually immediately retry their request.
  Invalidated,
  /// An error was thrown.
  Throw {
    // A python exception value, which might have a python-level stacktrace
    val: Value,
    // A pre-formatted python exception traceback.
    python_traceback: String,
    // A stack of engine-side "frame" information generated from Nodes.
    engine_traceback: Vec<String>,
  },
}

impl Failure {
  ///
  /// Consumes this Failure to produce a new Failure with an additional engine_traceback entry.
  ///
  pub fn with_pushed_frame(self, frame: &impl fmt::Display) -> Failure {
    match self {
      Failure::Invalidated => Failure::Invalidated,
      Failure::Throw {
        val,
        python_traceback,
        mut engine_traceback,
      } => {
        engine_traceback.push(format!("{}", frame));
        Failure::Throw {
          val,
          python_traceback,
          engine_traceback,
        }
      }
    }
  }
}

impl Failure {
  pub fn from_py_err(py_err: PyErr) -> Failure {
    let gil = Python::acquire_gil();
    let py = gil.python();
    Failure::from_py_err_with_gil(py, py_err)
  }

  pub fn from_py_err_with_gil(py: Python, py_err: PyErr) -> Failure {
    let maybe_ptraceback = py_err
      .traceback(py)
      .map(|traceback| traceback.to_object(py));
    let val = Value::from(py_err.into_py(py));
    let python_traceback = if let Some(tb) = maybe_ptraceback {
      let locals = PyDict::new(py);
      locals
        .set_item("traceback", py.import("traceback").unwrap())
        .unwrap();
      locals.set_item("tb", tb).unwrap();
      locals.set_item("val", &val).unwrap();
      py.eval(
        "''.join(traceback.format_exception(None, value=val, tb=tb))",
        None,
        Some(locals),
      )
      .unwrap()
      .extract::<String>()
      .unwrap()
    } else {
      Self::native_traceback(&externs::val_to_str((*val).as_ref(py)))
    };
    Failure::Throw {
      val,
      python_traceback,
      engine_traceback: Vec::new(),
    }
  }

  pub fn native_traceback(msg: &str) -> String {
    format!(
      "Traceback (no traceback):\n  <pants native internals>\nException: {}",
      msg
    )
  }
}

impl fmt::Display for Failure {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      Failure::Invalidated => write!(f, "Giving up on retrying due to changed files."),
      Failure::Throw { val, .. } => {
        let repr = Python::with_gil(|py| {
          let obj = (*val.0).as_ref(py);
          externs::val_to_str(obj)
        });
        write!(f, "{}", repr)
      }
    }
  }
}

impl From<String> for Failure {
  fn from(err: String) -> Self {
    throw(err)
  }
}

pub fn throw(msg: String) -> Failure {
  let gil = Python::acquire_gil();
  let python_traceback = Failure::native_traceback(&msg);
  Failure::Throw {
    val: externs::create_exception(gil.python(), msg),
    python_traceback,
    engine_traceback: Vec::new(),
  }
}
<|endoftext|>


<commit_before>use crate::io::prelude::*;

use super::{Command, Output, Stdio};
use crate::io::ErrorKind;
use crate::str;

// FIXME(#10380) these tests should not all be ignored on android.

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn smoke() {
    let p = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 0"]).spawn()
    } else {
        Command::new("true").spawn()
    };
    assert!(p.is_ok());
    let mut p = p.unwrap();
    assert!(p.wait().unwrap().success());
}

#[test]
#[cfg_attr(target_os = "android", ignore)]
fn smoke_failure() {
    match Command::new("if-this-is-a-binary-then-the-world-has-ended").spawn() {
        Ok(..) => panic!(),
        Err(..) => {}
    }
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn exit_reported_right() {
    let p = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).spawn()
    } else {
        Command::new("false").spawn()
    };
    assert!(p.is_ok());
    let mut p = p.unwrap();
    assert!(p.wait().unwrap().code() == Some(1));
    drop(p.wait());
}

#[test]
#[cfg(unix)]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn signal_reported_right() {
    use crate::os::unix::process::ExitStatusExt;

    let mut p =
        Command::new("/bin/sh").arg("-c").arg("read a").stdin(Stdio::piped()).spawn().unwrap();
    p.kill().unwrap();
    match p.wait().unwrap().signal() {
        Some(9) => {}
        result => panic!("not terminated by signal 9 (instead, {:?})", result),
    }
}

pub fn run_output(mut cmd: Command) -> String {
    let p = cmd.spawn();
    assert!(p.is_ok());
    let mut p = p.unwrap();
    assert!(p.stdout.is_some());
    let mut ret = String::new();
    p.stdout.as_mut().unwrap().read_to_string(&mut ret).unwrap();
    assert!(p.wait().unwrap().success());
    return ret;
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn stdout_works() {
    if cfg!(target_os = "windows") {
        let mut cmd = Command::new("cmd");
        cmd.args(&["/C", "echo foobar"]).stdout(Stdio::piped());
        assert_eq!(run_output(cmd), "foobar\r\n");
    } else {
        let mut cmd = Command::new("echo");
        cmd.arg("foobar").stdout(Stdio::piped());
        assert_eq!(run_output(cmd), "foobar\n");
    }
}

#[test]
#[cfg_attr(any(windows, target_os = "android", target_os = "vxworks"), ignore)]
fn set_current_dir_works() {
    let mut cmd = Command::new("/bin/sh");
    cmd.arg("-c").arg("pwd").current_dir("/").stdout(Stdio::piped());
    assert_eq!(run_output(cmd), "/\n");
}

#[test]
#[cfg_attr(any(windows, target_os = "android", target_os = "vxworks"), ignore)]
fn stdin_works() {
    let mut p = Command::new("/bin/sh")
        .arg("-c")
        .arg("read line; echo $line")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();
    p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap();
    drop(p.stdin.take());
    let mut out = String::new();
    p.stdout.as_mut().unwrap().read_to_string(&mut out).unwrap();
    assert!(p.wait().unwrap().success());
    assert_eq!(out, "foobar\n");
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_process_status() {
    let mut status = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).status().unwrap()
    } else {
        Command::new("false").status().unwrap()
    };
    assert!(status.code() == Some(1));

    status = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 0"]).status().unwrap()
    } else {
        Command::new("true").status().unwrap()
    };
    assert!(status.success());
}

#[test]
fn test_process_output_fail_to_start() {
    match Command::new("/no-binary-by-this-name-should-exist").output() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound),
        Ok(..) => panic!(),
    }
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_process_output_output() {
    let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "echo hello"]).output().unwrap()
    } else {
        Command::new("echo").arg("hello").output().unwrap()
    };
    let output_str = str::from_utf8(&stdout).unwrap();

    assert!(status.success());
    assert_eq!(output_str.trim().to_string(), "hello");
    assert_eq!(stderr, Vec::new());
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_process_output_error() {
    let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "mkdir ."]).output().unwrap()
    } else {
        Command::new("mkdir").arg("./").output().unwrap()
    };

    assert!(status.code() == Some(1));
    assert_eq!(stdout, Vec::new());
    assert!(!stderr.is_empty());
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_finish_once() {
    let mut prog = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
    } else {
        Command::new("false").spawn().unwrap()
    };
    assert!(prog.wait().unwrap().code() == Some(1));
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_finish_twice() {
    let mut prog = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
    } else {
        Command::new("false").spawn().unwrap()
    };
    assert!(prog.wait().unwrap().code() == Some(1));
    assert!(prog.wait().unwrap().code() == Some(1));
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_wait_with_output_once() {
    let prog = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "echo hello"]).stdout(Stdio::piped()).spawn().unwrap()
    } else {
        Command::new("echo").arg("hello").stdout(Stdio::piped()).spawn().unwrap()
    };

    let Output { status, stdout, stderr } = prog.wait_with_output().unwrap();
    let output_str = str::from_utf8(&stdout).unwrap();

    assert!(status.success());
    assert_eq!(output_str.trim().to_string(), "hello");
    assert_eq!(stderr, Vec::new());
}

#[cfg(all(unix, not(target_os = "android")))]
pub fn env_cmd() -> Command {
    Command::new("env")
}
#[cfg(target_os = "android")]
pub fn env_cmd() -> Command {
    let mut cmd = Command::new("/system/bin/sh");
    cmd.arg("-c").arg("set");
    cmd
}

#[cfg(windows)]
pub fn env_cmd() -> Command {
    let mut cmd = Command::new("cmd");
    cmd.arg("/c").arg("set");
    cmd
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_override_env() {
    use crate::env;

    // In some build environments (such as chrooted Nix builds), `env` can
    // only be found in the explicitly-provided PATH env variable, not in
    // default places such as /bin or /usr/bin. So we need to pass through
    // PATH to our sub-process.
    let mut cmd = env_cmd();
    cmd.env_clear().env("RUN_TEST_NEW_ENV", "123");
    if let Some(p) = env::var_os("PATH") {
        cmd.env("PATH", &p);
    }
    let result = cmd.output().unwrap();
    let output = String::from_utf8_lossy(&result.stdout).to_string();

    assert!(
        output.contains("RUN_TEST_NEW_ENV=123"),
        "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}",
        output
    );
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_add_to_env() {
    let result = env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap();
    let output = String::from_utf8_lossy(&result.stdout).to_string();

    assert!(
        output.contains("RUN_TEST_NEW_ENV=123"),
        "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}",
        output
    );
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_capture_env_at_spawn() {
    use crate::env;

    let mut cmd = env_cmd();
    cmd.env("RUN_TEST_NEW_ENV1", "123");

    // This variable will not be present if the environment has already
    // been captured above.
    env::set_var("RUN_TEST_NEW_ENV2", "456");
    let result = cmd.output().unwrap();
    env::remove_var("RUN_TEST_NEW_ENV2");

    let output = String::from_utf8_lossy(&result.stdout).to_string();

    assert!(
        output.contains("RUN_TEST_NEW_ENV1=123"),
        "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}",
        output
    );
    assert!(
        output.contains("RUN_TEST_NEW_ENV2=456"),
        "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}",
        output
    );
}

// Regression tests for #30858.
#[test]
fn test_interior_nul_in_progname_is_error() {
    match Command::new("has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
fn test_interior_nul_in_arg_is_error() {
    match Command::new("echo").arg("has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
fn test_interior_nul_in_args_is_error() {
    match Command::new("echo").args(&["has-some-\0\0s-inside"]).spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
fn test_interior_nul_in_current_dir_is_error() {
    match Command::new("echo").current_dir("has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

// Regression tests for #30862.
#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_interior_nul_in_env_key_is_error() {
    match env_cmd().env("has-some-\0\0s-inside", "value").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_interior_nul_in_env_value_is_error() {
    match env_cmd().env("key", "has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

/// Tests that process creation flags work by debugging a process.
/// Other creation flags make it hard or impossible to detect
/// behavioral changes in the process.
#[test]
#[cfg(windows)]
fn test_creation_flags() {
    use crate::os::windows::process::CommandExt;
    use crate::sys::c::{BOOL, DWORD, INFINITE};
    #[repr(C, packed)]
    struct DEBUG_EVENT {
        pub event_code: DWORD,
        pub process_id: DWORD,
        pub thread_id: DWORD,
        // This is a union in the real struct, but we don't
        // need this data for the purposes of this test.
        pub _junk: [u8; 164],
    }

    extern "system" {
        fn WaitForDebugEvent(lpDebugEvent: *mut DEBUG_EVENT, dwMilliseconds: DWORD) -> BOOL;
        fn ContinueDebugEvent(
            dwProcessId: DWORD,
            dwThreadId: DWORD,
            dwContinueStatus: DWORD,
        ) -> BOOL;
    }

    const DEBUG_PROCESS: DWORD = 1;
    const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5;
    const DBG_EXCEPTION_NOT_HANDLED: DWORD = 0x80010001;

    let mut child =
        Command::new("cmd").creation_flags(DEBUG_PROCESS).stdin(Stdio::piped()).spawn().unwrap();
    child.stdin.take().unwrap().write_all(b"exit\r\n").unwrap();
    let mut events = 0;
    let mut event = DEBUG_EVENT { event_code: 0, process_id: 0, thread_id: 0, _junk: [0; 164] };
    loop {
        if unsafe { WaitForDebugEvent(&mut event as *mut DEBUG_EVENT, INFINITE) } == 0 {
            panic!("WaitForDebugEvent failed!");
        }
        events += 1;

        if event.event_code == EXIT_PROCESS_DEBUG_EVENT {
            break;
        }

        if unsafe {
            ContinueDebugEvent(event.process_id, event.thread_id, DBG_EXCEPTION_NOT_HANDLED)
        } == 0
        {
            panic!("ContinueDebugEvent failed!");
        }
    }
    assert!(events > 0);
}

#[test]
fn test_command_implements_send_sync() {
    fn take_send_sync_type<T: Send + Sync>(_: T) {}
    take_send_sync_type(Command::new(""))
}
<commit_msg>Test that `env_clear` works on Windows<commit_after>use crate::io::prelude::*;

use super::{Command, Output, Stdio};
use crate::io::ErrorKind;
use crate::str;

// FIXME(#10380) these tests should not all be ignored on android.

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn smoke() {
    let p = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 0"]).spawn()
    } else {
        Command::new("true").spawn()
    };
    assert!(p.is_ok());
    let mut p = p.unwrap();
    assert!(p.wait().unwrap().success());
}

#[test]
#[cfg_attr(target_os = "android", ignore)]
fn smoke_failure() {
    match Command::new("if-this-is-a-binary-then-the-world-has-ended").spawn() {
        Ok(..) => panic!(),
        Err(..) => {}
    }
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn exit_reported_right() {
    let p = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).spawn()
    } else {
        Command::new("false").spawn()
    };
    assert!(p.is_ok());
    let mut p = p.unwrap();
    assert!(p.wait().unwrap().code() == Some(1));
    drop(p.wait());
}

#[test]
#[cfg(unix)]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn signal_reported_right() {
    use crate::os::unix::process::ExitStatusExt;

    let mut p =
        Command::new("/bin/sh").arg("-c").arg("read a").stdin(Stdio::piped()).spawn().unwrap();
    p.kill().unwrap();
    match p.wait().unwrap().signal() {
        Some(9) => {}
        result => panic!("not terminated by signal 9 (instead, {:?})", result),
    }
}

pub fn run_output(mut cmd: Command) -> String {
    let p = cmd.spawn();
    assert!(p.is_ok());
    let mut p = p.unwrap();
    assert!(p.stdout.is_some());
    let mut ret = String::new();
    p.stdout.as_mut().unwrap().read_to_string(&mut ret).unwrap();
    assert!(p.wait().unwrap().success());
    return ret;
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn stdout_works() {
    if cfg!(target_os = "windows") {
        let mut cmd = Command::new("cmd");
        cmd.args(&["/C", "echo foobar"]).stdout(Stdio::piped());
        assert_eq!(run_output(cmd), "foobar\r\n");
    } else {
        let mut cmd = Command::new("echo");
        cmd.arg("foobar").stdout(Stdio::piped());
        assert_eq!(run_output(cmd), "foobar\n");
    }
}

#[test]
#[cfg_attr(any(windows, target_os = "android", target_os = "vxworks"), ignore)]
fn set_current_dir_works() {
    let mut cmd = Command::new("/bin/sh");
    cmd.arg("-c").arg("pwd").current_dir("/").stdout(Stdio::piped());
    assert_eq!(run_output(cmd), "/\n");
}

#[test]
#[cfg_attr(any(windows, target_os = "android", target_os = "vxworks"), ignore)]
fn stdin_works() {
    let mut p = Command::new("/bin/sh")
        .arg("-c")
        .arg("read line; echo $line")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();
    p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap();
    drop(p.stdin.take());
    let mut out = String::new();
    p.stdout.as_mut().unwrap().read_to_string(&mut out).unwrap();
    assert!(p.wait().unwrap().success());
    assert_eq!(out, "foobar\n");
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_process_status() {
    let mut status = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).status().unwrap()
    } else {
        Command::new("false").status().unwrap()
    };
    assert!(status.code() == Some(1));

    status = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 0"]).status().unwrap()
    } else {
        Command::new("true").status().unwrap()
    };
    assert!(status.success());
}

#[test]
fn test_process_output_fail_to_start() {
    match Command::new("/no-binary-by-this-name-should-exist").output() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound),
        Ok(..) => panic!(),
    }
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_process_output_output() {
    let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "echo hello"]).output().unwrap()
    } else {
        Command::new("echo").arg("hello").output().unwrap()
    };
    let output_str = str::from_utf8(&stdout).unwrap();

    assert!(status.success());
    assert_eq!(output_str.trim().to_string(), "hello");
    assert_eq!(stderr, Vec::new());
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_process_output_error() {
    let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "mkdir ."]).output().unwrap()
    } else {
        Command::new("mkdir").arg("./").output().unwrap()
    };

    assert!(status.code() == Some(1));
    assert_eq!(stdout, Vec::new());
    assert!(!stderr.is_empty());
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_finish_once() {
    let mut prog = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
    } else {
        Command::new("false").spawn().unwrap()
    };
    assert!(prog.wait().unwrap().code() == Some(1));
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_finish_twice() {
    let mut prog = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
    } else {
        Command::new("false").spawn().unwrap()
    };
    assert!(prog.wait().unwrap().code() == Some(1));
    assert!(prog.wait().unwrap().code() == Some(1));
}

#[test]
#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
fn test_wait_with_output_once() {
    let prog = if cfg!(target_os = "windows") {
        Command::new("cmd").args(&["/C", "echo hello"]).stdout(Stdio::piped()).spawn().unwrap()
    } else {
        Command::new("echo").arg("hello").stdout(Stdio::piped()).spawn().unwrap()
    };

    let Output { status, stdout, stderr } = prog.wait_with_output().unwrap();
    let output_str = str::from_utf8(&stdout).unwrap();

    assert!(status.success());
    assert_eq!(output_str.trim().to_string(), "hello");
    assert_eq!(stderr, Vec::new());
}

#[cfg(all(unix, not(target_os = "android")))]
pub fn env_cmd() -> Command {
    Command::new("env")
}
#[cfg(target_os = "android")]
pub fn env_cmd() -> Command {
    let mut cmd = Command::new("/system/bin/sh");
    cmd.arg("-c").arg("set");
    cmd
}

#[cfg(windows)]
pub fn env_cmd() -> Command {
    let mut cmd = Command::new("cmd");
    cmd.arg("/c").arg("set");
    cmd
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_override_env() {
    use crate::env;

    // In some build environments (such as chrooted Nix builds), `env` can
    // only be found in the explicitly-provided PATH env variable, not in
    // default places such as /bin or /usr/bin. So we need to pass through
    // PATH to our sub-process.
    let mut cmd = env_cmd();
    cmd.env_clear().env("RUN_TEST_NEW_ENV", "123");
    if let Some(p) = env::var_os("PATH") {
        cmd.env("PATH", &p);
    }
    let result = cmd.output().unwrap();
    let output = String::from_utf8_lossy(&result.stdout).to_string();

    assert!(
        output.contains("RUN_TEST_NEW_ENV=123"),
        "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}",
        output
    );
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_add_to_env() {
    let result = env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap();
    let output = String::from_utf8_lossy(&result.stdout).to_string();

    assert!(
        output.contains("RUN_TEST_NEW_ENV=123"),
        "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}",
        output
    );
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_capture_env_at_spawn() {
    use crate::env;

    let mut cmd = env_cmd();
    cmd.env("RUN_TEST_NEW_ENV1", "123");

    // This variable will not be present if the environment has already
    // been captured above.
    env::set_var("RUN_TEST_NEW_ENV2", "456");
    let result = cmd.output().unwrap();
    env::remove_var("RUN_TEST_NEW_ENV2");

    let output = String::from_utf8_lossy(&result.stdout).to_string();

    assert!(
        output.contains("RUN_TEST_NEW_ENV1=123"),
        "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}",
        output
    );
    assert!(
        output.contains("RUN_TEST_NEW_ENV2=456"),
        "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}",
        output
    );
}

// Regression tests for #30858.
#[test]
fn test_interior_nul_in_progname_is_error() {
    match Command::new("has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
fn test_interior_nul_in_arg_is_error() {
    match Command::new("echo").arg("has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
fn test_interior_nul_in_args_is_error() {
    match Command::new("echo").args(&["has-some-\0\0s-inside"]).spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
fn test_interior_nul_in_current_dir_is_error() {
    match Command::new("echo").current_dir("has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

// Regression tests for #30862.
#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_interior_nul_in_env_key_is_error() {
    match env_cmd().env("has-some-\0\0s-inside", "value").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

#[test]
#[cfg_attr(target_os = "vxworks", ignore)]
fn test_interior_nul_in_env_value_is_error() {
    match env_cmd().env("key", "has-some-\0\0s-inside").spawn() {
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
        Ok(_) => panic!(),
    }
}

/// Tests that process creation flags work by debugging a process.
/// Other creation flags make it hard or impossible to detect
/// behavioral changes in the process.
#[test]
#[cfg(windows)]
fn test_creation_flags() {
    use crate::os::windows::process::CommandExt;
    use crate::sys::c::{BOOL, DWORD, INFINITE};
    #[repr(C, packed)]
    struct DEBUG_EVENT {
        pub event_code: DWORD,
        pub process_id: DWORD,
        pub thread_id: DWORD,
        // This is a union in the real struct, but we don't
        // need this data for the purposes of this test.
        pub _junk: [u8; 164],
    }

    extern "system" {
        fn WaitForDebugEvent(lpDebugEvent: *mut DEBUG_EVENT, dwMilliseconds: DWORD) -> BOOL;
        fn ContinueDebugEvent(
            dwProcessId: DWORD,
            dwThreadId: DWORD,
            dwContinueStatus: DWORD,
        ) -> BOOL;
    }

    const DEBUG_PROCESS: DWORD = 1;
    const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5;
    const DBG_EXCEPTION_NOT_HANDLED: DWORD = 0x80010001;

    let mut child =
        Command::new("cmd").creation_flags(DEBUG_PROCESS).stdin(Stdio::piped()).spawn().unwrap();
    child.stdin.take().unwrap().write_all(b"exit\r\n").unwrap();
    let mut events = 0;
    let mut event = DEBUG_EVENT { event_code: 0, process_id: 0, thread_id: 0, _junk: [0; 164] };
    loop {
        if unsafe { WaitForDebugEvent(&mut event as *mut DEBUG_EVENT, INFINITE) } == 0 {
            panic!("WaitForDebugEvent failed!");
        }
        events += 1;

        if event.event_code == EXIT_PROCESS_DEBUG_EVENT {
            break;
        }

        if unsafe {
            ContinueDebugEvent(event.process_id, event.thread_id, DBG_EXCEPTION_NOT_HANDLED)
        } == 0
        {
            panic!("ContinueDebugEvent failed!");
        }
    }
    assert!(events > 0);
}

#[test]
fn test_command_implements_send_sync() {
    fn take_send_sync_type<T: Send + Sync>(_: T) {}
    take_send_sync_type(Command::new(""))
}

// Ensure that starting a process with no environment variables works on Windows.
// This will fail if the environment block is ill-formed.
#[test]
#[cfg(windows)]
fn env_empty() {
    let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn();
    assert!(p.is_ok());
}
<|endoftext|>


<commit_before>//! A small GPU compute shader demonstration.
//!
//! Here we use a compute shader to calculate the amplitude of `OSCILLATOR_COUNT` number of
//! oscillators. The oscillator amplitudes are then laid out across the screen using rectangles
//! with a gray value equal to the amplitude. Real-time interaction is demonstrated by providing
//! access to time, frequency (mouse `x`) and the number of oscillators via uniform data.

use nannou::prelude::*;
use std::sync::{Arc, Mutex};

struct Model {
    compute: Compute,
    oscillators: Arc<Mutex<Vec<f32>>>,
}

struct Compute {
    oscillator_buffer: wgpu::Buffer,
    oscillator_buffer_size: wgpu::BufferAddress,
    uniform_buffer: wgpu::Buffer,
    bind_group: wgpu::BindGroup,
    pipeline: wgpu::ComputePipeline,
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct Uniforms {
    time: f32,
    freq: f32,
    oscillator_count: u32,
}

const OSCILLATOR_COUNT: u32 = 128;

fn main() {
    nannou::app(model).update(update).run();
}

fn model(app: &App) -> Model {
    let w_id = app.new_window().size(1440, 512).view(view).build().unwrap();
    let window = app.window(w_id).unwrap();
    let device = window.swap_chain_device();

    // Create the compute shader module.
    let cs = include_bytes!("shaders/comp.spv");
    let cs_spirv = wgpu::read_spirv(std::io::Cursor::new(&cs[..])).unwrap();
    let cs_mod = device.create_shader_module(&cs_spirv);

    // Create the buffer that will store the result of our compute operation.
    let oscillator_buffer_size =
        (OSCILLATOR_COUNT as usize * std::mem::size_of::<f32>()) as wgpu::BufferAddress;
    let oscillator_buffer = device.create_buffer(&wgpu::BufferDescriptor {
        size: oscillator_buffer_size,
        usage: wgpu::BufferUsage::STORAGE
            | wgpu::BufferUsage::COPY_DST
            | wgpu::BufferUsage::COPY_SRC,
    });

    // Create the buffer that will store time.
    let uniforms = create_uniforms(app.time, app.mouse.x, window.rect());
    let uniform_buffer = device
        .create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
        .fill_from_slice(&[uniforms]);

    // Create the bind group and pipeline.
    let bind_group_layout = create_bind_group_layout(device);
    let bind_group = create_bind_group(
        device,
        &bind_group_layout,
        &oscillator_buffer,
        oscillator_buffer_size,
        &uniform_buffer,
    );
    let pipeline_layout = create_pipeline_layout(device, &bind_group_layout);
    let pipeline = create_compute_pipeline(device, &pipeline_layout, &cs_mod);

    let compute = Compute {
        oscillator_buffer,
        oscillator_buffer_size,
        uniform_buffer,
        bind_group,
        pipeline,
    };

    // The vector that we will write oscillator values to.
    let oscillators = Arc::new(Mutex::new(vec![0.0; OSCILLATOR_COUNT as usize]));

    Model {
        compute,
        oscillators,
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    let window = app.main_window();
    let device = window.swap_chain_device();
    let win_rect = window.rect();
    let compute = &mut model.compute;

    // The buffer into which we'll read some data.
    let read_buffer = device.create_buffer(&wgpu::BufferDescriptor {
        size: compute.oscillator_buffer_size,
        usage: wgpu::BufferUsage::MAP_READ
            | wgpu::BufferUsage::COPY_DST
            | wgpu::BufferUsage::COPY_SRC,
    });

    // An update for the uniform buffer with the current time.
    let uniforms = create_uniforms(app.time, app.mouse.x, win_rect);
    let uniforms_size = std::mem::size_of::<Uniforms>() as wgpu::BufferAddress;
    let new_uniform_buffer = device
        .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
        .fill_from_slice(&[uniforms]);

    // The encoder we'll use to encode the compute pass.
    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
    encoder.copy_buffer_to_buffer(
        &new_uniform_buffer,
        0,
        &compute.uniform_buffer,
        0,
        uniforms_size,
    );
    {
        let mut cpass = encoder.begin_compute_pass();
        cpass.set_pipeline(&compute.pipeline);
        cpass.set_bind_group(0, &compute.bind_group, &[]);
        cpass.dispatch(OSCILLATOR_COUNT as u32, 1, 1);
    }
    encoder.copy_buffer_to_buffer(
        &compute.oscillator_buffer,
        0,
        &read_buffer,
        0,
        compute.oscillator_buffer_size,
    );

    // Submit the compute pass to the device's queue.
    window
        .swap_chain_queue()
        .lock()
        .unwrap()
        .submit(&[encoder.finish()]);

    // Register a callback for reading the result of the compute pass.
    let oscillators = model.oscillators.clone();
    read_buffer.map_read_async(
        0,
        compute.oscillator_buffer_size,
        move |result: wgpu::BufferMapAsyncResult<&[f32]>| {
            if let Ok(mapping) = result {
                if let Ok(mut oscillators) = oscillators.lock() {
                    oscillators.copy_from_slice(&mapping.data);
                }
            }
        },
    );

    // Check for resource cleanups and mapping callbacks.
    //
    // Note that this is not strictly necessary in our case, as the device we are using already
    // gets polled when nannou submits the command buffer for drawing and presentation after `view`
    // completes. If we were to use a standalone device to create our buffer and perform our
    // compute (rather than the device requested during window creation), calling `poll` regularly
    // would be a must.
    device.poll(false);
}

fn view(app: &App, model: &Model, frame: Frame) {
    frame.clear(BLACK);
    let draw = app.draw();
    let window = app.window(frame.window_id()).unwrap();
    let rect = window.rect();

    if let Ok(oscillators) = model.oscillators.lock() {
        let w = rect.w() / OSCILLATOR_COUNT as f32;
        let h = rect.h();
        let half_w = w * 0.5;
        for (i, &osc) in oscillators.iter().enumerate() {
            let x = half_w + map_range(i as u32, 0, OSCILLATOR_COUNT, rect.left(), rect.right());
            draw.rect().w_h(w, h).x(x).color(gray(osc));
        }
    }

    draw.to_frame(app, &frame).unwrap();
}

fn create_uniforms(time: f32, mouse_x: f32, win_rect: geom::Rect) -> Uniforms {
    let freq = map_range(
        mouse_x,
        win_rect.left(),
        win_rect.right(),
        0.0,
        win_rect.w(),
    );
    let oscillator_count = OSCILLATOR_COUNT;
    Uniforms {
        time,
        freq,
        oscillator_count,
    }
}

fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
    let oscillator_buffer_binding = wgpu::BindGroupLayoutBinding {
        binding: 0,
        visibility: wgpu::ShaderStage::COMPUTE,
        ty: wgpu::BindingType::StorageBuffer {
            dynamic: false,
            readonly: false,
        },
    };
    let uniforms_binding = wgpu::BindGroupLayoutBinding {
        binding: 1,
        visibility: wgpu::ShaderStage::COMPUTE,
        ty: wgpu::BindingType::UniformBuffer { dynamic: false },
    };
    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
        bindings: &[oscillator_buffer_binding, uniforms_binding],
    })
}

fn create_bind_group(
    device: &wgpu::Device,
    layout: &wgpu::BindGroupLayout,
    oscillator_buffer: &wgpu::Buffer,
    oscillator_buffer_size: wgpu::BufferAddress,
    uniform_buffer: &wgpu::Buffer,
) -> wgpu::BindGroup {
    let oscillator_buffer_binding = wgpu::Binding {
        binding: 0,
        resource: wgpu::BindingResource::Buffer {
            buffer: &oscillator_buffer,
            range: 0..oscillator_buffer_size,
        },
    };
    let uniforms_binding = wgpu::Binding {
        binding: 1,
        resource: wgpu::BindingResource::Buffer {
            buffer: &uniform_buffer,
            range: 0..std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
        },
    };
    let bindings = &[oscillator_buffer_binding, uniforms_binding];
    device.create_bind_group(&wgpu::BindGroupDescriptor { layout, bindings })
}

fn create_pipeline_layout(
    device: &wgpu::Device,
    bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::PipelineLayout {
    device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        bind_group_layouts: &[&bind_group_layout],
    })
}

fn create_compute_pipeline(
    device: &wgpu::Device,
    layout: &wgpu::PipelineLayout,
    cs_mod: &wgpu::ShaderModule,
) -> wgpu::ComputePipeline {
    let compute_stage = wgpu::ProgrammableStageDescriptor {
        module: &cs_mod,
        entry_point: "main",
    };
    let desc = wgpu::ComputePipelineDescriptor {
        layout,
        compute_stage,
    };
    device.create_compute_pipeline(&desc)
}
<commit_msg>Remove unnecessary poll in compute example to improve macOS performance<commit_after>//! A small GPU compute shader demonstration.
//!
//! Here we use a compute shader to calculate the amplitude of `OSCILLATOR_COUNT` number of
//! oscillators. The oscillator amplitudes are then laid out across the screen using rectangles
//! with a gray value equal to the amplitude. Real-time interaction is demonstrated by providing
//! access to time, frequency (mouse `x`) and the number of oscillators via uniform data.

use nannou::prelude::*;
use std::sync::{Arc, Mutex};

struct Model {
    compute: Compute,
    oscillators: Arc<Mutex<Vec<f32>>>,
}

struct Compute {
    oscillator_buffer: wgpu::Buffer,
    oscillator_buffer_size: wgpu::BufferAddress,
    uniform_buffer: wgpu::Buffer,
    bind_group: wgpu::BindGroup,
    pipeline: wgpu::ComputePipeline,
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct Uniforms {
    time: f32,
    freq: f32,
    oscillator_count: u32,
}

const OSCILLATOR_COUNT: u32 = 128;

fn main() {
    nannou::app(model).update(update).run();
}

fn model(app: &App) -> Model {
    let w_id = app.new_window().size(1440, 512).view(view).build().unwrap();
    let window = app.window(w_id).unwrap();
    let device = window.swap_chain_device();

    // Create the compute shader module.
    let cs = include_bytes!("shaders/comp.spv");
    let cs_spirv = wgpu::read_spirv(std::io::Cursor::new(&cs[..])).unwrap();
    let cs_mod = device.create_shader_module(&cs_spirv);

    // Create the buffer that will store the result of our compute operation.
    let oscillator_buffer_size =
        (OSCILLATOR_COUNT as usize * std::mem::size_of::<f32>()) as wgpu::BufferAddress;
    let oscillator_buffer = device.create_buffer(&wgpu::BufferDescriptor {
        size: oscillator_buffer_size,
        usage: wgpu::BufferUsage::STORAGE
            | wgpu::BufferUsage::COPY_DST
            | wgpu::BufferUsage::COPY_SRC,
    });

    // Create the buffer that will store time.
    let uniforms = create_uniforms(app.time, app.mouse.x, window.rect());
    let uniform_buffer = device
        .create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
        .fill_from_slice(&[uniforms]);

    // Create the bind group and pipeline.
    let bind_group_layout = create_bind_group_layout(device);
    let bind_group = create_bind_group(
        device,
        &bind_group_layout,
        &oscillator_buffer,
        oscillator_buffer_size,
        &uniform_buffer,
    );
    let pipeline_layout = create_pipeline_layout(device, &bind_group_layout);
    let pipeline = create_compute_pipeline(device, &pipeline_layout, &cs_mod);

    let compute = Compute {
        oscillator_buffer,
        oscillator_buffer_size,
        uniform_buffer,
        bind_group,
        pipeline,
    };

    // The vector that we will write oscillator values to.
    let oscillators = Arc::new(Mutex::new(vec![0.0; OSCILLATOR_COUNT as usize]));

    Model {
        compute,
        oscillators,
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    let window = app.main_window();
    let device = window.swap_chain_device();
    let win_rect = window.rect();
    let compute = &mut model.compute;

    // The buffer into which we'll read some data.
    let read_buffer = device.create_buffer(&wgpu::BufferDescriptor {
        size: compute.oscillator_buffer_size,
        usage: wgpu::BufferUsage::MAP_READ
            | wgpu::BufferUsage::COPY_DST
            | wgpu::BufferUsage::COPY_SRC,
    });

    // An update for the uniform buffer with the current time.
    let uniforms = create_uniforms(app.time, app.mouse.x, win_rect);
    let uniforms_size = std::mem::size_of::<Uniforms>() as wgpu::BufferAddress;
    let new_uniform_buffer = device
        .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
        .fill_from_slice(&[uniforms]);

    // The encoder we'll use to encode the compute pass.
    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
    encoder.copy_buffer_to_buffer(
        &new_uniform_buffer,
        0,
        &compute.uniform_buffer,
        0,
        uniforms_size,
    );
    {
        let mut cpass = encoder.begin_compute_pass();
        cpass.set_pipeline(&compute.pipeline);
        cpass.set_bind_group(0, &compute.bind_group, &[]);
        cpass.dispatch(OSCILLATOR_COUNT as u32, 1, 1);
    }
    encoder.copy_buffer_to_buffer(
        &compute.oscillator_buffer,
        0,
        &read_buffer,
        0,
        compute.oscillator_buffer_size,
    );

    // Submit the compute pass to the device's queue.
    window
        .swap_chain_queue()
        .lock()
        .unwrap()
        .submit(&[encoder.finish()]);

    // Register a callback for reading the result of the compute pass.
    let oscillators = model.oscillators.clone();
    read_buffer.map_read_async(
        0,
        compute.oscillator_buffer_size,
        move |result: wgpu::BufferMapAsyncResult<&[f32]>| {
            if let Ok(mapping) = result {
                if let Ok(mut oscillators) = oscillators.lock() {
                    oscillators.copy_from_slice(&mapping.data);
                }
            }
        },
    );

    // Check for resource cleanups and mapping callbacks.
    //
    // Note that this line is not necessary in our case, as the device we are using already gets
    // polled when nannou submits the command buffer for drawing and presentation after `view`
    // completes. If we were to use a standalone device to create our buffer and perform our
    // compute (rather than the device requested during window creation), calling `poll` regularly
    // would be a must.
    //
    // device.poll(false);
}

fn view(app: &App, model: &Model, frame: Frame) {
    frame.clear(BLACK);
    let draw = app.draw();
    let window = app.window(frame.window_id()).unwrap();
    let rect = window.rect();

    if let Ok(oscillators) = model.oscillators.lock() {
        let w = rect.w() / OSCILLATOR_COUNT as f32;
        let h = rect.h();
        let half_w = w * 0.5;
        for (i, &osc) in oscillators.iter().enumerate() {
            let x = half_w + map_range(i as u32, 0, OSCILLATOR_COUNT, rect.left(), rect.right());
            draw.rect().w_h(w, h).x(x).color(gray(osc));
        }
    }

    draw.to_frame(app, &frame).unwrap();
}

fn create_uniforms(time: f32, mouse_x: f32, win_rect: geom::Rect) -> Uniforms {
    let freq = map_range(
        mouse_x,
        win_rect.left(),
        win_rect.right(),
        0.0,
        win_rect.w(),
    );
    let oscillator_count = OSCILLATOR_COUNT;
    Uniforms {
        time,
        freq,
        oscillator_count,
    }
}

fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
    let oscillator_buffer_binding = wgpu::BindGroupLayoutBinding {
        binding: 0,
        visibility: wgpu::ShaderStage::COMPUTE,
        ty: wgpu::BindingType::StorageBuffer {
            dynamic: false,
            readonly: false,
        },
    };
    let uniforms_binding = wgpu::BindGroupLayoutBinding {
        binding: 1,
        visibility: wgpu::ShaderStage::COMPUTE,
        ty: wgpu::BindingType::UniformBuffer { dynamic: false },
    };
    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
        bindings: &[oscillator_buffer_binding, uniforms_binding],
    })
}

fn create_bind_group(
    device: &wgpu::Device,
    layout: &wgpu::BindGroupLayout,
    oscillator_buffer: &wgpu::Buffer,
    oscillator_buffer_size: wgpu::BufferAddress,
    uniform_buffer: &wgpu::Buffer,
) -> wgpu::BindGroup {
    let oscillator_buffer_binding = wgpu::Binding {
        binding: 0,
        resource: wgpu::BindingResource::Buffer {
            buffer: &oscillator_buffer,
            range: 0..oscillator_buffer_size,
        },
    };
    let uniforms_binding = wgpu::Binding {
        binding: 1,
        resource: wgpu::BindingResource::Buffer {
            buffer: &uniform_buffer,
            range: 0..std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
        },
    };
    let bindings = &[oscillator_buffer_binding, uniforms_binding];
    device.create_bind_group(&wgpu::BindGroupDescriptor { layout, bindings })
}

fn create_pipeline_layout(
    device: &wgpu::Device,
    bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::PipelineLayout {
    device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        bind_group_layouts: &[&bind_group_layout],
    })
}

fn create_compute_pipeline(
    device: &wgpu::Device,
    layout: &wgpu::PipelineLayout,
    cs_mod: &wgpu::ShaderModule,
) -> wgpu::ComputePipeline {
    let compute_stage = wgpu::ProgrammableStageDescriptor {
        module: &cs_mod,
        entry_point: "main",
    };
    let desc = wgpu::ComputePipelineDescriptor {
        layout,
        compute_stage,
    };
    device.create_compute_pipeline(&desc)
}
<|endoftext|>


<commit_before>use gtk;

use std::collections::BTreeMap;

use std::ops::Deref;

#[derive(Clone, Debug)]
pub struct Chapter {
    pub title: String,
    pub iter: gtk::TreeIter,
}

impl PartialEq for Chapter {
    fn eq(&self, other: &Chapter) -> bool {
        self.title == other.title
    }
}

#[derive(Debug, PartialEq)]
pub struct SuccessiveChapters {
    pub prev: Option<Chapter>,
    pub next: Option<Chapter>,
}

pub struct ChaptersBoundaries(BTreeMap<u64, SuccessiveChapters>);

impl ChaptersBoundaries {
    pub fn new() -> Self {
        ChaptersBoundaries(BTreeMap::new())
    }

    pub fn clear(&mut self) {
        self.0.clear();
    }

    pub fn add_chapter(&mut self, start: u64, end: u64, title: &str, iter: &gtk::TreeIter) {
        debug!("add_chapter {}, {}, {}", start, end, title);

        // the chapter to add can share at most one boundary with a previous chapter
        let (start_exists, next_chapter) = match self.0.get_mut(&start) {
            Some(chapters_at_start) => {
                // a boundary already exists at start
                let next_chapter = chapters_at_start.next.take();
                chapters_at_start.next = Some(Chapter {
                    title: title.to_owned(),
                    iter: iter.clone(),
                });
                (true, next_chapter)
            }
            None => (false, None),
        };

        if start_exists {
            self.0.insert(
                end,
                SuccessiveChapters {
                    prev: Some(Chapter {
                        title: title.to_owned(),
                        iter: iter.clone(),
                    }),
                    next: next_chapter,
                },
            );
        } else {
            // no chapter at start
            let (end_exists, prev_chapter) = match self.0.get_mut(&end) {
                Some(chapters_at_end) => {
                    // a boundary already exists at end
                    let prev_chapter = chapters_at_end.prev.take();
                    chapters_at_end.prev = Some(Chapter {
                        title: title.to_owned(),
                        iter: iter.clone(),
                    });
                    (true, prev_chapter)
                }
                None => (false, None),
            };

            self.0.insert(
                start,
                SuccessiveChapters {
                    prev: prev_chapter,
                    next: Some(Chapter {
                        title: title.to_owned(),
                        iter: iter.clone(),
                    }),
                },
            );

            if !end_exists {
                self.0.insert(
                    end,
                    SuccessiveChapters {
                        prev: Some(Chapter {
                            title: title.to_owned(),
                            iter: iter.clone(),
                        }),
                        next: None,
                    },
                );
            }
        }
    }

    pub fn remove_chapter(&mut self, start: u64, end: u64) {
        debug!("remove_chapter {}, {}", start, end);

        let prev_chapter = self.0.get_mut(&start).unwrap().prev.take();
        self.0.remove(&start);

        let can_remove_end = {
            let chapters_at_end = self.0.get_mut(&end).unwrap();
            if prev_chapter.is_none() && chapters_at_end.next.is_none() {
                true
            } else {
                chapters_at_end.prev = prev_chapter;
                false
            }
        };
        if can_remove_end {
            self.0.remove(&end);
        }
    }

    pub fn rename_chapter(&mut self, start: u64, end: u64, new_title: &str) {
        debug!("rename_chapter {}, {}, {}", start, end, new_title);

        self.0.get_mut(&start).unwrap().next.as_mut().unwrap().title = new_title.to_owned();
        self.0.get_mut(&end).unwrap().prev.as_mut().unwrap().title = new_title.to_owned();
    }

    pub fn move_boundary(&mut self, boundary: u64, to_position: u64) {
        let chapters = self.0.remove(&boundary).unwrap();
        if self.0.insert(to_position, chapters).is_some() {
            panic!(
                "ChaptersBoundaries::move_boundary attempt to replace entry at {}",
                to_position
            );
        }
    }
}

impl Deref for ChaptersBoundaries {
    type Target = BTreeMap<u64, SuccessiveChapters>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/* Discard these tests for now as it fails for Travis-CI's linux host
#[cfg(test)]
mod tests {
    use glib;
    use gtk;
    use gtk::TreeStoreExt;
    use super::*;

    fn new_chapter(store: &gtk::TreeStore, title: &str) -> Chapter {
        Chapter {
            title: title.to_owned(),
            iter: store.append(None),
        }
    }

    #[test]
    fn chapters_boundaries() {
        if gtk::init().is_err() {
            panic!("tests::chapters_boundaries failed to initialize GTK");
        }
        // fake store
        let store = gtk::TreeStore::new(&[glib::Type::Bool]);

        let mut boundaries = ChaptersBoundaries::new();

        assert!(boundaries.is_empty());

        // Add incrementally

        let chapter_1 = new_chapter(&store, "1");
        boundaries.add_chapter(0, 1, &chapter_1.title, &chapter_1.iter);
        assert_eq!(2, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: None,
            }),
            boundaries.get(&1),
        );

        let chapter_2 = new_chapter(&store, "2");
        boundaries.add_chapter(1, 2, &chapter_2.title, &chapter_2.iter);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: Some(chapter_2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_2.clone()),
                next: None,
            }),
            boundaries.get(&2),
        );

        let chapter_3 = new_chapter(&store, "3");
        boundaries.add_chapter(2, 4, &chapter_3.title, &chapter_3.iter);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: Some(chapter_2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_2.clone()),
                next: Some(chapter_3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Rename
        let chapter_r2 = new_chapter(&store, "r2");
        boundaries.rename_chapter(1, 2, &chapter_r2.title);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: Some(chapter_r2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r2.clone()),
                next: Some(chapter_3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        let chapter_r1 = new_chapter(&store, "r1");
        boundaries.rename_chapter(0, 1, &chapter_r1.title);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_r2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r2.clone()),
                next: Some(chapter_3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        let chapter_r3 = new_chapter(&store, "r3");
        boundaries.rename_chapter(2, 4, &chapter_r3.title);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_r2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Remove in the middle
        boundaries.remove_chapter(1, 2);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Add in the middle
        let chapter_n2 = new_chapter(&store, "n2");
        boundaries.add_chapter(1, 2, &chapter_n2.title, &chapter_n2.iter);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Remove first
        boundaries.remove_chapter(0, 1);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Add first
        let chapter_n1 = new_chapter(&store, "n1");
        boundaries.add_chapter(0, 1, &chapter_n1.title, &chapter_n1.iter);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_n1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n1.clone()),
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Remove last
        boundaries.remove_chapter(2, 4);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_n1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n1.clone()),
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );
    }
}
*/
<commit_msg>ChaptersBoundaries: retry unit test<commit_after>use gtk;

use std::collections::BTreeMap;

use std::ops::Deref;

#[derive(Clone, Debug)]
pub struct Chapter {
    pub title: String,
    pub iter: gtk::TreeIter,
}

impl PartialEq for Chapter {
    fn eq(&self, other: &Chapter) -> bool {
        self.title == other.title
    }
}

#[derive(Debug, PartialEq)]
pub struct SuccessiveChapters {
    pub prev: Option<Chapter>,
    pub next: Option<Chapter>,
}

pub struct ChaptersBoundaries(BTreeMap<u64, SuccessiveChapters>);

impl ChaptersBoundaries {
    pub fn new() -> Self {
        ChaptersBoundaries(BTreeMap::new())
    }

    pub fn clear(&mut self) {
        self.0.clear();
    }

    pub fn add_chapter(&mut self, start: u64, end: u64, title: &str, iter: &gtk::TreeIter) {
        debug!("add_chapter {}, {}, {}", start, end, title);

        // the chapter to add can share at most one boundary with a previous chapter
        let (start_exists, next_chapter) = match self.0.get_mut(&start) {
            Some(chapters_at_start) => {
                // a boundary already exists at start
                let next_chapter = chapters_at_start.next.take();
                chapters_at_start.next = Some(Chapter {
                    title: title.to_owned(),
                    iter: iter.clone(),
                });
                (true, next_chapter)
            }
            None => (false, None),
        };

        if start_exists {
            self.0.insert(
                end,
                SuccessiveChapters {
                    prev: Some(Chapter {
                        title: title.to_owned(),
                        iter: iter.clone(),
                    }),
                    next: next_chapter,
                },
            );
        } else {
            // no chapter at start
            let (end_exists, prev_chapter) = match self.0.get_mut(&end) {
                Some(chapters_at_end) => {
                    // a boundary already exists at end
                    let prev_chapter = chapters_at_end.prev.take();
                    chapters_at_end.prev = Some(Chapter {
                        title: title.to_owned(),
                        iter: iter.clone(),
                    });
                    (true, prev_chapter)
                }
                None => (false, None),
            };

            self.0.insert(
                start,
                SuccessiveChapters {
                    prev: prev_chapter,
                    next: Some(Chapter {
                        title: title.to_owned(),
                        iter: iter.clone(),
                    }),
                },
            );

            if !end_exists {
                self.0.insert(
                    end,
                    SuccessiveChapters {
                        prev: Some(Chapter {
                            title: title.to_owned(),
                            iter: iter.clone(),
                        }),
                        next: None,
                    },
                );
            }
        }
    }

    pub fn remove_chapter(&mut self, start: u64, end: u64) {
        debug!("remove_chapter {}, {}", start, end);

        let prev_chapter = self.0.get_mut(&start).unwrap().prev.take();
        self.0.remove(&start);

        let can_remove_end = {
            let chapters_at_end = self.0.get_mut(&end).unwrap();
            if prev_chapter.is_none() && chapters_at_end.next.is_none() {
                true
            } else {
                chapters_at_end.prev = prev_chapter;
                false
            }
        };
        if can_remove_end {
            self.0.remove(&end);
        }
    }

    pub fn rename_chapter(&mut self, start: u64, end: u64, new_title: &str) {
        debug!("rename_chapter {}, {}, {}", start, end, new_title);

        self.0.get_mut(&start).unwrap().next.as_mut().unwrap().title = new_title.to_owned();
        self.0.get_mut(&end).unwrap().prev.as_mut().unwrap().title = new_title.to_owned();
    }

    pub fn move_boundary(&mut self, boundary: u64, to_position: u64) {
        let chapters = self.0.remove(&boundary).unwrap();
        if self.0.insert(to_position, chapters).is_some() {
            panic!(
                "ChaptersBoundaries::move_boundary attempt to replace entry at {}",
                to_position
            );
        }
    }
}

impl Deref for ChaptersBoundaries {
    type Target = BTreeMap<u64, SuccessiveChapters>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

// Ignore these tests for now as it fails for Travis-CI's linux host
#[cfg(test)]
mod tests {
    use glib;
    use gtk;
    use gtk::TreeStoreExt;
    use super::*;

    fn new_chapter(store: &gtk::TreeStore, title: &str) -> Chapter {
        Chapter {
            title: title.to_owned(),
            iter: store.append(None),
        }
    }

    #[test]
    fn chapters_boundaries() {
        if gtk::init().is_err() {
            panic!("tests::chapters_boundaries failed to initialize GTK");
        }
        // fake store
        let store = gtk::TreeStore::new(&[glib::Type::Bool]);

        let mut boundaries = ChaptersBoundaries::new();

        assert!(boundaries.is_empty());

        // Add incrementally

        let chapter_1 = new_chapter(&store, "1");
        boundaries.add_chapter(0, 1, &chapter_1.title, &chapter_1.iter);
        assert_eq!(2, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: None,
            }),
            boundaries.get(&1),
        );

        let chapter_2 = new_chapter(&store, "2");
        boundaries.add_chapter(1, 2, &chapter_2.title, &chapter_2.iter);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: Some(chapter_2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_2.clone()),
                next: None,
            }),
            boundaries.get(&2),
        );

        let chapter_3 = new_chapter(&store, "3");
        boundaries.add_chapter(2, 4, &chapter_3.title, &chapter_3.iter);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: Some(chapter_2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_2.clone()),
                next: Some(chapter_3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Rename
        let chapter_r2 = new_chapter(&store, "r2");
        boundaries.rename_chapter(1, 2, &chapter_r2.title);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_1.clone()),
                next: Some(chapter_r2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r2.clone()),
                next: Some(chapter_3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        let chapter_r1 = new_chapter(&store, "r1");
        boundaries.rename_chapter(0, 1, &chapter_r1.title);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_r2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r2.clone()),
                next: Some(chapter_3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        let chapter_r3 = new_chapter(&store, "r3");
        boundaries.rename_chapter(2, 4, &chapter_r3.title);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_r2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Remove in the middle
        boundaries.remove_chapter(1, 2);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Add in the middle
        let chapter_n2 = new_chapter(&store, "n2");
        boundaries.add_chapter(1, 2, &chapter_n2.title, &chapter_n2.iter);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_r1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r1.clone()),
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Remove first
        boundaries.remove_chapter(0, 1);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Add first
        let chapter_n1 = new_chapter(&store, "n1");
        boundaries.add_chapter(0, 1, &chapter_n1.title, &chapter_n1.iter);
        assert_eq!(4, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_n1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n1.clone()),
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: Some(chapter_r3.clone()),
            }),
            boundaries.get(&2),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_r3.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );

        // Remove last
        boundaries.remove_chapter(2, 4);
        assert_eq!(3, boundaries.len());
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: None,
                next: Some(chapter_n1.clone()),
            }),
            boundaries.get(&0),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n1.clone()),
                next: Some(chapter_n2.clone()),
            }),
            boundaries.get(&1),
        );
        assert_eq!(
            Some(&SuccessiveChapters{
                prev: Some(chapter_n2.clone()),
                next: None,
            }),
            boundaries.get(&4),
        );
    }
}
<|endoftext|>


<commit_before>use std::path::PathBuf;
use context::Context;
use buffer::Buffer;
use utils;

pub fn handle_open_file(context: &Context, filename: String) {
    let filename = utils::expand_variables(&filename).to_string();

    let mut bc = context.buffers();
    if let Some(_buffer) = bc.find_by_filename(&filename) {
        info!("Buffer for {} already exists.", &filename);
        return;
    }

    info!("Buffer for {} does not exist, creating new buffer.", &filename);
    let mut b = Buffer::new();
    b.filename = Some(PathBuf::from(&filename));
    context.state().mru().insert(filename);
    bc.add(b);
}
<commit_msg>Commands: get basic handle_open_file working again.<commit_after>use std::path::PathBuf;
use context::Context;
use buffer::Buffer;
use utils;

pub fn handle_open_file(context: &Context, filename: String) {
    let filename = utils::expand_variables(&filename).to_string();

    let mut bc = context.buffers();
    if let Some(_buffer) = bc.find_by_filename(&filename) {
        info!("Buffer for {} already exists.", &filename);
        return;
    }

    info!("Buffer for {} does not exist, creating new buffer.", &filename);
    match Buffer::open_file(&filename) {
        Some(buffer) => {
            bc.add(buffer);
            context.state().mru().insert(filename);
        },
        None => {}
    }
}
<|endoftext|>