[PATCH 1 of 8] rust-hglib: import the latest version and update URLs

Gregory Szorc gregory.szorc at gmail.com
Sun Apr 1 14:53:47 EDT 2018


On Sun, Apr 1, 2018 at 4:14 AM, Yuya Nishihara <yuya at tcha.org> wrote:

> # HG changeset patch
> # User Kevin Bullock <kbullock at ringworld.org>
> # Date 1522477348 -32400
> #      Sat Mar 31 15:22:28 2018 +0900
> # Node ID 9e25c96124d51e11022b0ce64783f5f333ede7fb
> # Parent  2ed180117f7658d0cbf6a1ece20944465c55c947
> rust-hglib: import the latest version and update URLs
>

Queued parts 1-3 because vendoring rust-hglib has been talked about and
agreed upon IIRC. They are pretty mechanical changes and I don't see any
major issues with them.

The remaining parts look reasonable to me. But I figured I'd wait for
someone with more Rust experience to look at them.

Also, Kevin Kox has been giving good Rust reviews. But I believe they are
only active in Phabricator. Consider submitting futures Rust patches to
Phabricator or CC'ing Kevin explicitly.


>
> Source:
>   http://kbullock.ringworld.org/hg/rust-hglib/ -r 742ad4a344d7
>   "merge rust-1.4 into default" (2016-07-12 12:27 -0500)
>
> All mercurial.selenic.com URLs are replaced with www.mercurial-scm.org
> to silence the check-code.
>
> Committed by Yuya Nishihara <yuya at tcha.org>
>
> diff --git a/rust/hglib/Cargo.toml b/rust/hglib/Cargo.toml
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/Cargo.toml
> @@ -0,0 +1,25 @@
> +[package]
> +name = "hglib"
> +version = "0.1.1"
> +authors = ["Kevin Bullock <kbullock at ringworld.org>"]
> +description = "Mercurial command server client library."
> +readme = "README.md"
> +documentation = "http://kbullock.ringworld.org/rustdoc/hglib/"
> +repository = "http://kbullock.ringworld.org/hg/rust-hglib/"
> +license = "MIT"
> +
> +include = [
> +    "LICENSE",
> +    "README.md",
> +    "RELEASES.md",
> +    "src/**/*.rs",
> +    "Cargo.toml",
> +    "examples/**/*.rs",
> +]
> +
> +[dependencies]
> +byteorder = "0.3.13"
> +
> +[dev-dependencies]
> +tempdir = "0.3.4"
> +lazy_static = "0.1.14"
> diff --git a/rust/hglib/LICENSE b/rust/hglib/LICENSE
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/LICENSE
> @@ -0,0 +1,20 @@
> +Copyright (c) 2015 Kevin R. Bullock
> +
> +Permission is hereby granted, free of charge, to any person obtaining
> +a copy of this software and associated documentation files (the
> +"Software"), to deal in the Software without restriction, including
> +without limitation the rights to use, copy, modify, merge, publish,
> +distribute, sublicense, and/or sell copies of the Software, and to
> +permit persons to whom the Software is furnished to do so, subject to
> +the following conditions:
> +
> +The above copyright notice and this permission notice shall be
> +included in all copies or substantial portions of the Software.
> +
> +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
> +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> diff --git a/rust/hglib/README.md b/rust/hglib/README.md
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/README.md
> @@ -0,0 +1,22 @@
> +HgLib: Rust Client Library for Mercurial Command Server
> +=======================================================
> +
> +This crate provides a client interface to the Mercurial distributed
> +version control system (DVCS) in Rust, using Mercurial's
> +[command server][]. The command server is designed to allow tools to be
> +built around Mercurial repositories, without being tied into Mercurial's
> +internal API or licensing.
> +
> +[command server]: https://www.mercurial-scm.org/wiki/CommandServer
> +
> +[![](http://meritbadge.herokuapp.com/hglib)](https://crates
> .io/crates/hglib)
> +
> +API documentation: <http://kbullock.ringworld.org/rustdoc/hglib/>
> +
> +Installation
> +------------
> +
> +To use hglib, add the crate to your Cargo.toml:
> +
> +    [dependencies]
> +    hglib = "0.1.1"
> diff --git a/rust/hglib/RELEASES.md b/rust/hglib/RELEASES.md
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/RELEASES.md
> @@ -0,0 +1,9 @@
> +Version 0.1.1 (2015-09-23)
> +==========================
> +
> +* Now builds on Rust 1.3 (stable).
> +
> +Version 0.1.0 (2015-09-21)
> +==========================
> +
> +* Initial release.
> diff --git a/rust/hglib/examples/client.rs b/rust/hglib/examples/client.rs
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/examples/client.rs
> @@ -0,0 +1,48 @@
> +extern crate hglib;
> +extern crate tempdir;
> +
> +use hglib::cmdserver::CommandServer;
> +use hglib::Chunk;
> +use std::env;
> +use tempdir::TempDir;
> +
> +fn run_command(cmdserver: &mut CommandServer, command: Vec<&[u8]>) ->
> (i32, Vec<u8>, Vec<u8>) {
> +    let (mut result, mut output, mut error) =
> +        (-1i32, vec![], vec![]);
> +    let run = cmdserver.connection
> +        .raw_command(command)
> +        .expect("failed to send 'log' command");
> +
> +    for chunk in run {
> +        match chunk {
> +            Ok(Chunk::Output(s)) => output.extend(s),
> +            Ok(Chunk::Error(s)) => error.extend(s),
> +            Ok(Chunk::Result(r)) => { result = r },
> +            Ok(_) => unimplemented!(),
> +            Err(e) => panic!("failed to read command results: {}", e),
> +        }
> +    }
> +
> +    (result, output, error)
> +}
> +
> +fn main() {
> +    let tmpdir = TempDir::new("hglib").unwrap();
> +    env::set_current_dir(tmpdir.path()).unwrap();
> +
> +    let mut cmdserver = CommandServer::new().expect("failed to start
> command server");
> +    println!("capabilities: {:?}", cmdserver.capabilities);
> +    println!("encoding: {:?}", cmdserver.encoding);
> +
> +    let _ = run_command(&mut cmdserver, vec![b"init"]);
> +
> +    let (result, output, error) =
> +        run_command(&mut cmdserver, vec![b"log", b"-l", b"5"]);
> +    cmdserver.connection.close().expect("command server did not stop
> cleanly");
> +
> +    println!("output: {}",
> +             String::from_utf8(output).unwrap().trim_right_matches('\n')
> );
> +    println!("error: {}",
> +             String::from_utf8(error).unwrap().trim_right_matches('\n'));
> +    println!("result: {:?}", result);
> +}
> diff --git a/rust/hglib/src/cmdserver.rs b/rust/hglib/src/cmdserver.rs
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/src/cmdserver.rs
> @@ -0,0 +1,46 @@
> +//! High-level interface to the Mercurial command server.
> +//!
> +//! This module is currently only a skeletal implementation. In the
> +//! future this will allow you to run Mercurial commands by calling
> +//! methods of the same name:
> +//!
> +//! ```ignore
> +//! use hglib::cmdserver::CommandServer;
> +//! let cmdserver = CommandServer::new().expect("failed to start command
> server");
> +//! let statcmd = cmdserver.status();
> +//! ```
> +//!
> +//! and get the results back as native Rust data types. The details of
> +//! this implementation are yet to be determined. For an idea of what's
> +//! in store, see the [Python hglib documentation][python-hglib].
> +//!
> +//! [python-hglib]: https://www.mercurial-scm.org/wiki/PythonHglib
> +
> +use std::io;
> +use connection::*;
> +
> +/// Spawns and communicates with a command server process.
> +pub struct CommandServer {
> +    /// A handle on the spawned process.
> +    pub connection: Connection,
> +    /// The list of capabilities the server reported on startup.
> +    pub capabilities: Vec<String>,
> +    /// The character encoding the server reported on startup.
> +    pub encoding: String,
> +}
> +
> +impl CommandServer {
> +    /// Constructs and starts up a command server instance, or returns an
> error.
> +    pub fn new() -> io::Result<CommandServer> {
> +        let mut conn = try!(Connection::new());
> +        let (capabilities, encoding) = match conn.read_hello() {
> +            Ok((caps, enc)) => (caps, enc),
> +            Err(e)   => panic!("failed to read server hello: {}", e),
> +        };
> +        Ok(CommandServer {
> +            connection: conn,
> +            capabilities: capabilities,
> +            encoding: encoding,
> +        })
> +    }
> +}
> diff --git a/rust/hglib/src/connection.rs b/rust/hglib/src/connection.rs
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/src/connection.rs
> @@ -0,0 +1,263 @@
> +//! Raw command server API.
> +//!
> +//! Creating a [`Connection`](struct.Connection.html) spawns an instance
> +//! of the command server and allows you to interact with it. When it
> +//! starts up, the command server sends a hello message on its output
> +//! channel, which can be read and parsed by
> +//! [`read_hello()`](struct.Connection.html#method.read_hello):
> +//!
> +//! ```rust
> +//! # use std::io;
> +//! # use hglib::connection::Connection;
> +//! # use hglib::Chunk;
> +//! let mut conn = Connection::new().expect("failed to start command
> server");
> +//! let (capabilities, encoding) =
> +//!     conn.read_hello().expect("failed to read server hello");
> +//! ```
> +
> +use std::ascii::AsciiExt;
> +use std::error::Error;
> +use std::fmt::{self, Display};
> +use std::io;
> +use std::io::prelude::*;
> +use std::process::{Command, Stdio, Child, ChildStdout, ExitStatus};
> +use std::str;
> +
> +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
> +
> +#[derive(Debug)]
> +pub struct UnimplementedChannelError(u8);
> +
> +impl Display for UnimplementedChannelError {
> +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
> +        write!(f, "{}", self.0 as char)
> +    }
> +}
> +
> +impl Error for UnimplementedChannelError {
> +    fn description(&self) -> &str {
> +        if (self.0 as char).is_uppercase() {
> +            return "unimplemented required channel";
> +        }
> +        "unimplemented channel"
> +    }
> +}
> +
> +impl From<UnimplementedChannelError> for io::Error {
> +    fn from(err: UnimplementedChannelError) -> io::Error {
> +        io::Error::new(io::ErrorKind::Other, err)
> +    }
> +}
> +
> +#[repr(u8)]
> +enum Channel {
> +    Output = b'o',
> +    Error = b'e',
> +    Result = b'r',
> +    Debug = b'd',
> +    Input = b'I',
> +    LineInput = b'L',
> +}
> +
> +impl Channel {
> +    fn from_u8(byte: u8) -> Result<Channel, UnimplementedChannelError> {
> +        match byte {
> +            b'o' => Ok(Channel::Output),
> +            b'e' => Ok(Channel::Error),
> +            b'r' => Ok(Channel::Result),
> +            b'd' => Ok(Channel::Debug),
> +            b'I' => Ok(Channel::Input),
> +            b'L' => Ok(Channel::LineInput),
> +            _ => Err(UnimplementedChannelError(byte)),
> +        }
> +    }
> +}
> +
> +use Chunk;
> +
> +/// An iterator over the results of a single Mercurial command.
> +///
> +/// Each iteration yields a [`Chunk`](../enum.Chunk.html) with the
> +/// output data or length of input the server is expecting.
> +pub struct CommandRun<'a> {
> +    connection: &'a mut Connection,
> +    done: bool,
> +}
> +
> +impl<'a> Iterator for CommandRun<'a> {
> +    type Item = io::Result<Chunk>;
> +
> +    fn next(&mut self) -> Option<Self::Item> {
> +        if self.done {
> +            return None;
> +        }
> +        let (chan, length) = match self.connection.read_header() {
> +            Ok(t) => t,
> +            Err(e) => return Some(Err(e)),
> +        };
> +        match chan {
> +            Channel::Output => {
> +                Some(self.connection.read_body
> (length).map(Chunk::Output))
> +            },
> +            Channel::Error => {
> +                Some(self.connection.read_body(length).map(Chunk::Error))
> +            },
> +            Channel::Result => {
> +                self.done = true;
> +                Some(self.connection.read_result().map(Chunk::Result))
> +            },
> +            Channel::Debug => {
> +                unimplemented!()
> +            },
> +            Channel::Input => {
> +                Some(Ok(Chunk::Input(length)))
> +            },
> +            Channel::LineInput => {
> +                Some(Ok(Chunk::LineInput(length)))
> +            },
> +        }
> +    }
> +}
> +
> +/// A handle to a running command server instance.
> +pub struct Connection {
> +    child: Child,
> +}
> +
> +impl Connection {
> +    /// Spawns a new command server process.
> +    pub fn new() -> io::Result<Connection> {
> +        let cmdserver = try!(
> +            Command::new("hg")
> +                .args(&["serve", "--cmdserver", "pipe", "--config",
> "ui.interactive=True"])
> +                .stdin(Stdio::piped())
> +                .stdout(Stdio::piped())
> +                .spawn());
> +
> +        Ok(Connection {
> +            child: cmdserver,
> +        })
> +    }
> +
> +    fn child_stdout(&mut self) -> &mut ChildStdout {
> +        // We just unwrap the Option<ChildStdout> because we know that
> +        // we set up the pipe in Connection::new(). We have to call
> +        // .as_mut() because .unwrap()'s signature moves the `self`
> +        // value out.
> +        self.child.stdout.as_mut().unwrap()
> +    }
> +
> +    /// Reads and parses the server hello message. Returns a tuple of
> +    /// ([capabilities], encoding).
> +    ///
> +    /// ## Errors
> +    ///
> +    /// Returns an I/O error if reading or parsing the hello message
> +    /// failed.
> +    pub fn read_hello(&mut self) -> io::Result<(Vec<String>, String)> {
> +        fn fetch_field(line: Option<&[u8]>, field: &[u8]) ->
> io::Result<Vec<u8>>
> +        {
> +            let mut label = field.to_vec();
> +            label.extend(b": ");
> +
> +            match line {
> +                Some(l) if l.is_ascii() && l.starts_with(&label) => {
> +                    Ok(l[label.len()..].to_vec())
> +                },
> +                Some(l) => {
> +                    let err_data = match str::from_utf8(l) {
> +                        Ok(s)  => s.to_string(),
> +                        Err(e) => format!("{:?} (bad encoding: {})", l,
> e),
> +                    };
> +                    return Err(io::Error::new(
> +                        io::ErrorKind::InvalidData,
> +                        format!("expected '{}: ', got {:?}",
> +                                String::from_utf8_lossy(field),
> err_data)));
> +                },
> +                None => return Err(io::Error::new(
> +                    // TODO: When read_exact stabilizes we can use
> UnexpectedEOF
> +                    io::ErrorKind::Other,
> +                    format!("missing field '{}' in server hello",
> +                            String::from_utf8_lossy(field)))),
> +            }
> +        }
> +
> +        fn parse_capabilities(cap_line: Vec<u8>) -> Vec<String> {
> +            let mut caps = vec![];
> +            for cap in cap_line.split(|byte| *byte == b' ') {
> +                let cap = str::from_utf8(cap)
> +                    .expect("failed to decode ASCII as UTF-8?!");
> +                caps.push(cap.to_string());
> +            }
> +            caps
> +        }
> +
> +        let (_, length) = try!(self.read_header());
> +        let hello = try!(self.read_body(length));
> +        let mut hello = hello.split(|byte| *byte == b'\n');
> +
> +        let caps = try!(fetch_field(hello.next(), b"capabilities")
> +                        .map(|l| parse_capabilities(l)));
> +        let enc = try!(fetch_field(hello.next(), b"encoding")
> +                       .map(|l| String::from_utf8(l)
> +                            .expect("failed to decode ASCII as
> UTF-8?!")));
> +        Ok((caps, enc))
> +    }
> +
> +    fn read_header(&mut self) -> io::Result<(Channel, i32)> {
> +        let pout = self.child_stdout();
> +        let chan = try!(pout.read_u8());
> +        let chan = try!(Channel::from_u8(chan));
> +        let length = try!(pout.read_i32::<BigEndian>());
> +        Ok((chan, length))
> +    }
> +
> +    fn read_body(&mut self, length: i32) -> io::Result<Vec<u8>> {
> +        let pout = self.child_stdout();
> +        let mut buf = Vec::with_capacity(length as usize);
> +        try!(pout.take(length as u64).read_to_end(&mut buf));
> +        Ok(buf)
> +    }
> +
> +    fn read_result(&mut self) -> io::Result<i32> {
> +        let pout = self.child_stdout();
> +        let result = try!(pout.read_i32::<BigEndian>());
> +        Ok(result)
> +    }
> +
> +    fn _raw_command(&mut self, command: Vec<&[u8]>) -> io::Result<()> {
> +        // sum of lengths of all arguments, plus null byte separators
> +        let len = command.iter().map(|item| item.len())
> +            .fold(command.len() - 1, |acc, l| acc + l);
> +        if len > i32::max_value() as usize {
> +            return Err(io::Error::new(io::ErrorKind::InvalidInput,
> "message too long"));
> +        }
> +
> +        let pin = self.child.stdin.as_mut().unwrap();
> +        try!(pin.write(b"runcommand\n"));
> +        try!(pin.write_i32::<BigEndian>(len as i32));
> +        try!(pin.write(command[0]));
> +        for arg in &command[1..] {
> +            try!(pin.write(b"\0"));
> +            try!(pin.write(arg));
> +        }
> +        Ok(())
> +    }
> +
> +    /// Sends the given `command` to Mercurial, returning an iterator
> +    /// over the results.
> +    pub fn raw_command(&mut self, command: Vec<&[u8]>) ->
> io::Result<CommandRun> {
> +        try!(self._raw_command(command));
> +        Ok(CommandRun {
> +            connection: self,
> +            done: false,
> +        })
> +    }
> +
> +    /// Shuts down the command server process.
> +    pub fn close(&mut self) -> io::Result<ExitStatus> {
> +        // This will close the command server's stdin, which signals
> +        // that it should exit. Returns the command server's exit code.
> +        self.child.wait()
> +    }
> +}
> diff --git a/rust/hglib/src/lib.rs b/rust/hglib/src/lib.rs
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/src/lib.rs
> @@ -0,0 +1,76 @@
> +//! Client library for the Mercurial command server
> +//!
> +//! This crate provides a client interface to the Mercurial distributed
> +//! version control system (DVCS) in Rust, using Mercurial's [command
> +//! server][]. The command server is designed to allow tools to be built
> +//! around Mercurial repositories, without being tied into Mercurial's
> +//! internal API or licensing.
> +//!
> +//! [command server]: https://www.mercurial-scm.org/wiki/CommandServer
> +//!
> +//! ## High-level API
> +//!
> +//! The [`cmdserver`](cmdserver/index.html) module provides a high-level
> +//! interface which manages spawning and communicating with a command
> +//! server instance:
> +//!
> +//! ```rust
> +//! use hglib::cmdserver::CommandServer;
> +//! let cmdserver = CommandServer::new().expect("failed to start command
> server");
> +//! ```
> +//!
> +//! This high-level interface is largely unimplemented so far, but
> +//! builds on the raw API that is already functional.
> +//!
> +//! ## Raw API
> +//!
> +//! The lower-level API in the [`connection`](connection/index.html)
> +//! module allows you to run commands at the level of the command server
> +//! protocol. Assembling the command and reading the result
> +//! chunk-by-chunk is done manually.
> +//!
> +//! ```rust
> +//! # use std::io;
> +//! # use std::io::prelude::*;
> +//! use hglib::connection::Connection;
> +//! use hglib::Chunk;
> +//! let mut conn = Connection::new().expect("failed to start command
> server");
> +//! let (capabilities, encoding) =
> +//!     conn.read_hello().expect("failed to read server hello");
> +//!
> +//! let cmditer =
> +//!     conn.raw_command(vec![b"log", b"-l", b"5"])
> +//!         .expect("failed to send raw command");
> +//! for chunk in cmditer {
> +//!     match chunk {
> +//!         Ok(Chunk::Output(s)) => { io::stdout().write(&s); },
> +//!         Ok(Chunk::Error(s)) => { io::stdout().write(&s); },
> +//!         Ok(Chunk::Result(r)) => println!("command exited with status:
> {}", r),
> +//!         Ok(_) => {},
> +//!         Err(e) => panic!("failed to read chunk: {}", e),
> +//!     }
> +//! }
> +//! ```
> +
> +extern crate byteorder;
> +
> +/// A type representing a "chunk" of data received from the command
> server.
> +#[derive(Debug)]
> +pub enum Chunk {
> +    /// Data received on the output channel (equivalent to stdout).
> +    Output(Vec<u8>),
> +    /// Data received on the error channel (equivalent to stderr).
> +    Error(Vec<u8>),
> +    /// Data received on the debug channel (log entries).
> +    Debug(Vec<u8>),
> +    /// The exit code of a Mercurial command.
> +    Result(i32),
> +    /// Indicates that the client should send input of the given maximum
> length.
> +    Input(i32),
> +    /// Indicates that the client should send line-oriented input of the
> +    /// given maximum length.
> +    LineInput(i32),
> +}
> +
> +pub mod connection;
> +pub mod cmdserver;
> diff --git a/rust/hglib/tests/cmdserver.rs b/rust/hglib/tests/cmdserver.rs
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/tests/cmdserver.rs
> @@ -0,0 +1,10 @@
> +extern crate hglib;
> +
> +use hglib::cmdserver::CommandServer;
> +
> +#[test]
> +fn capabilities() {
> +    let cmdserver = CommandServer::new().unwrap();
> +    assert!(cmdserver.capabilities.len() > 0);
> +    assert!(cmdserver.capabilities.iter().any(|cap| cap ==
> "runcommand"));
> +}
> diff --git a/rust/hglib/tests/connection.rs b/rust/hglib/tests/connection.
> rs
> new file mode 100644
> --- /dev/null
> +++ b/rust/hglib/tests/connection.rs
> @@ -0,0 +1,88 @@
> +extern crate hglib;
> +extern crate tempdir;
> +#[macro_use] extern crate lazy_static;
> +
> +use hglib::connection::*;
> +use hglib::Chunk;
> +use std::env;
> +use std::path::PathBuf;
> +use std::process::Command;
> +use std::sync::Mutex;
> +use tempdir::TempDir;
> +
> +lazy_static! {
> +    static ref WDLOCK: Mutex<PathBuf> = {
> +        let oldwd = env::current_dir().unwrap();
> +        Mutex::new(oldwd)
> +    };
> +}
> +
> +fn with_temp_repo<F>(f: F) where F : Fn() {
> +    let tmpdir = TempDir::new("hglib")
> +        .expect("failed to create temporary directory for tests");
> +
> +    let lock = (*WDLOCK).lock().unwrap();
> +    env::set_current_dir(tmpdir.path())
> +        .expect("failed to change into temporary directory");
> +
> +    // FIXME: make this work on platforms other than Unix
> +    Command::new("/bin/sh").arg("-c")
> +        .arg("hg init && echo a>a && hg ci -qAm0")
> +        .spawn()
> +        .expect("failed to initialize temporary repository")
> +        .wait().unwrap();
> +
> +    f();
> +
> +    env::set_current_dir(&*lock).unwrap();
> +}
> +
> +#[test]
> +fn raw_command() {
> +    with_temp_repo(|| {
> +        let mut connection = Connection::new().unwrap();
> +        connection.read_hello().unwrap();
> +        let (mut result, mut output) = (-1i32, vec![]);
> +        {
> +            let run = connection.raw_command(vec![b"summary"]).unwrap();
> +            for chunk in run {
> +                match chunk {
> +                    Ok(Chunk::Output(s)) => output.extend(s),
> +                    Ok(Chunk::Error(_)) => continue,
> +                    Ok(Chunk::Result(r)) => result = r,
> +                    Ok(c) => panic!("unexpected chunk: {:?}", c),
> +                    Err(e) => panic!("failed to read command results:
> {}", e),
> +                }
> +            }
> +        }
> +        assert!(output.starts_with(b"parent:"));
> +        assert_eq!(result, 0);
> +
> +        connection.close().unwrap();
> +    });
> +}
> +
> +#[test]
> +fn raw_command_error() {
> +    with_temp_repo(|| {
> +        let mut connection = Connection::new().unwrap();
> +        connection.read_hello().unwrap();
> +        let (mut result, mut error) = (-1i32, vec![]);
> +        {
> +            let run = connection.raw_command(vec![b"noexist"]).unwrap();
> +            for chunk in run {
> +                match chunk {
> +                    Ok(Chunk::Output(_)) => continue,
> +                    Ok(Chunk::Error(s)) => error.extend(s),
> +                    Ok(Chunk::Result(r)) => { result = r },
> +                    Ok(c) => panic!("unexpected chunk: {:?}", c),
> +                    Err(e) => panic!("failed to read command results:
> {}", e),
> +                }
> +            }
> +        }
> +        assert_eq!(result, 255);
> +        assert!(error.starts_with(b"hg: unknown command 'noexist'"));
> +
> +        connection.close().unwrap();
> +    });
> +}
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.mercurial-scm.org/pipermail/mercurial-devel/attachments/20180401/b51d7557/attachment.html>


More information about the Mercurial-devel mailing list