<commit_before>extern crate difference;
extern crate regex;
extern crate term;

use difference::{Changeset, Difference};

use std::fs;
use std::io::{Cursor, Read};
use std::path::{PathBuf, Path};
use std::process::{Command, Stdio};

// Given the default Rust test runner doesn't expose programmatic test cases
// we are using single "check_expansions" test to process all the data files.
//
// This is similar approach to what rustfmt does.

struct TestConfig {
    crate_path: PathBuf,
    data_path: PathBuf,
}

#[test]
fn check_expansions()
{
    let root_path = find_root().unwrap();

    let crate_path = root_path.join("intercom-attributes");
    let config = TestConfig {
        data_path: crate_path.join("tests/data"),
        crate_path,
    };
    // Running "cargo test" in a clean build directory does not
    // finalize the compilation of all the crates.
    // The final binaries are unavailable in the target directory.
    // Force the building here to ensure they are available
    // for the tests.
    build_crate("intercom");
    build_crate("intercom-fmt");

    let failed = test_path(&config, "macro", TestMode::Macro)
        + test_path(&config, "ui", TestMode::UI);

    // Ensure there were no failures.
    //
    // If we fail here, cargo will display our printlns to the user.
    assert_eq!(failed, 0, "{} tests failed", failed);
}

struct OutputResult
{
    message: String,
    actual: String,
    expected_path: Option<String>,
    changeset: Changeset,
}

impl OutputResult {

    /// Prints the diff.
    pub fn show_diff(&self, ctx_size: usize) {

        enum Lines
        {
            Expected(String),
            Actual(String),
            Same(String),
        };

        // Resolve the changed lines so we can print only the context.
        let mut all_lines = vec![];
        for cs in &self.changeset.diffs {
            match cs {
                Difference::Same(ref x) =>
                    all_lines.push(
                        x.lines()
                            .map(|l| Lines::Same(format!("  {}", l)))
                            .collect::<Vec<_>>(),
                        ),
                Difference::Add(ref x) =>
                    all_lines.push(
                        x.lines()
                            .map(|l| Lines::Expected(format!("E {}", l)))
                            .collect::<Vec<_>>(),
                        ),
                Difference::Rem(ref x) =>
                    all_lines.push(
                        x.lines()
                            .map(|l| Lines::Actual(format!("A {}", l)))
                            .collect::<Vec<_>>(),
                        ),
            }
        }
        let all_lines = all_lines.into_iter().flat_map(|i| i).collect::<Vec<_>>();

        let mut ctx_counter = 0;
        let mut ctx_lines = vec![];
        for l in &all_lines {
            match l {
                Lines::Expected(..) | Lines::Actual(..) => ctx_counter = ctx_size*2+1,
                _ if ctx_counter > 0 => ctx_counter -= 1,
                _ => {}
            }
            ctx_lines.push(ctx_counter);
        }
        for _ in 0..ctx_size {
            if ctx_counter > 0 { ctx_counter -= 1 }
            ctx_lines.push(ctx_counter);
        }

        let mut t = term::stdout().unwrap();
        let mut skip = false;
        for i in 0..all_lines.len() {
            if ctx_lines[i+ctx_size] == 0 {
                if ! skip {
                    writeln!(t, "...snip").expect("Write failed");
                    skip = true;
                }
                continue;
            }
            skip = false;
            match all_lines[i] {
                Lines::Same(ref x) => {
                    t.reset().unwrap();
                    writeln!(t, "{}", x).expect("Write failed");
                }
                Lines::Expected(ref x) => {
                    t.fg(term::color::GREEN).unwrap();
                    writeln!(t, "{}", x).expect("Write failed");
                }
                Lines::Actual(ref x) => {
                    t.fg(term::color::RED).unwrap();
                    writeln!(t, "{}", x).expect("Write failed");
                }
            }
        }
    }
}

/// Compiles a single file using rustc using similar options than what
/// cargo would have used.
fn build(cwd: &str, path: &str, mode: TestMode) -> (bool, String, String)
{
    #[cfg(debug_assertions)]
    let conf = "debug";

    #[cfg(not(debug_assertions))]
    let conf = "release";

    // Launch rustc.
    #[rustfmt::skip]
    let mut cmd = std::process::Command::new("rustc");
    cmd.current_dir( cwd )
        .env("CARGO_PKG_NAME", "TestLib")
        .args(&[
            "--crate-name", "source",
            "--crate-type", "lib",
            path,
            "--out-dir", "tests/out",
            "-L", &format!("dependency=../target/{}/deps", conf),
            "--extern", &format!("intercom=../target/{}/libintercom.rlib", conf),
        ] );

    // In expansion mode add the 'pretty=expanded' option.
    if let TestMode::Macro = mode {
        cmd.arg("--pretty=expanded");
        cmd.arg("-Z").arg("unstable-options");
    }

    // Get the output.
    let output = cmd.output().expect( "Failed to execute" );

    // stdout/err is utf8 byte stream. Parse it into a string.
    (
        output.status.success(),
        String::from_utf8(output.stdout).expect("Bad output"),
        String::from_utf8(output.stderr).expect("Bad stderr"),
    )
}

fn format(code: &str) -> String
{
    let intercom_fmt = find_intercom_fmt().unwrap();
    let mut formatter = Command::new(&intercom_fmt)
        .arg("-p")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect(&format!("Failed to launch formatter {:?}", intercom_fmt));

    // Send the code to the formatter.
    // "stdin" of the formatter will be closed automatically when the scope terminates.
    {
        let mut stdin = formatter.stdin.as_mut().unwrap();
        std::io::copy(&mut Cursor::new(code.as_bytes()), &mut stdin)
            .expect("Failed to send data for formatting.");
    }

    let output = formatter
        .wait_with_output()
        .expect("Failed to read formatter results.");
    let status = output.status;
    assert!(
        status.success() && status.code() == Some(0),
        "Formatting failed with status \"{:?}\".",
        status.code()
    );

    // Convert the UTF-8 output into a string.
    String::from_utf8(output.stdout).expect("Bad output")
}

/// Builds the "intercom" library
fn build_crate(module: &str)
{
    let mut cmd = Command::new("cargo");
    cmd.arg("build");

    #[cfg(not(debug_assertions))]
    {
        cmd.arg("--release");
    }

    let status = cmd
        .current_dir(find_root().unwrap().join(module))
        .status()
        .expect(&format!("Failed to build crate \"{0}\"", module));
    assert!(status.success());
}

fn find_root() -> std::io::Result<PathBuf>
{
    let mut root_path = std::env::current_exe()?;
    loop {
        if root_path.join("Cargo.toml").exists() {
            break;
        }
        assert!(root_path.pop());
    }

    Ok(root_path)
}

fn find_intercom_fmt() -> std::io::Result<PathBuf>
{
    // Avoid the need to determine the current build target by basing the search on the current
    // executable.
    let mut intercom_fmt_dir = std::env::current_exe()?;
    assert!(intercom_fmt_dir.pop()); // The name of the executable.
    let original_path = format!("{:?}", intercom_fmt_dir);
    loop {
        // Stop search when intercom-fmt has been found.
        if let Some(intercom_fmt) = has_intercom_fmt(&intercom_fmt_dir) {
            return Ok(intercom_fmt);
        }

        // Move towards root
        assert!(
            intercom_fmt_dir.pop(),
            format!(
                "Could not locate intercom-fmt. Search started from \"{0}\".",
                original_path
            )
        );
    }
}

/// Determines if the given directory has intercom-fmt
fn has_intercom_fmt(dir: &PathBuf) -> Option<PathBuf>
{
    #[cfg(windows)]
    let intercom_fmt = dir.join("intercom-fmt.exe");

    #[cfg(not(windows))]
    let intercom_fmt = dir.join("intercom-fmt");

    if intercom_fmt.exists() && intercom_fmt.metadata().unwrap().is_file() {
        Some(intercom_fmt)
    } else {
        None
    }
}

#[derive(Clone, Copy)]
enum TestMode {
    Macro,
    UI,
}

fn test_path(config: &TestConfig, sub_path: &str, mode: TestMode) -> usize
{
    // Get the source test data files.
    let test_data = fs::read_dir(config.data_path.join(sub_path)).unwrap();
    let source_paths = test_data
        .into_iter()
        .map(|e| e.expect("Failed to read entry").path())
        .map(|p| p.to_str().unwrap().to_owned())
        .filter(|p| p.ends_with(".rs"));
    let mut failed = 0;

    for source_path in source_paths {
        println!("Testing {}", source_path);

        // Get the source and target code.

        // The source is compiled using rustc
        let (_, result_code, result_stderr) =
            build(config.crate_path.to_str().unwrap(), &source_path, mode);

        // Generate diffs for both sources
        // Ensure the linebreaks are the same for both. This seems to be
        // somewhat of an issue on AppVeyor.
        let result_code = result_code.replace("\r", "");
        let result_stderr = result_stderr.replace("\r", "");

        let mut results = vec![];

        // Use rustfmt to format both pieces of code so that we have a
        // canonical format for them. Without rustfmt we'd need to match the
        // compiler pretty print format in the reference target files - which,
        // despite its name, isn't very pretty.
        match mode {
            TestMode::Macro => results.extend(assert_macro(result_code, result_stderr, &source_path)),
            TestMode::UI => results.extend(assert_compile(result_code, result_stderr, &source_path)),
        }

        // If these were equal, there's only one "Same" diff segment.
        // If there is more than one, they differed.
        for r in results {
            if std::env::var("UPDATE_TARGETS").is_ok() && r.expected_path.is_some() {
                // The user wants to update the targets.
                use std::io::Write;
                let mut target_file = fs::File::create(r.expected_path.as_ref().unwrap()).unwrap();
                target_file
                    .write_all(r.actual.as_bytes())
                    .expect(&format!("Writing target file {} failed", &r.expected_path.unwrap()));
            } else {
                r.show_diff(5);
                failed += 1;
            }
        }
    }
    failed
}

fn assert_macro(result_code: String, result_stderr: String, source_path: &str) -> Vec<OutputResult> {

    // Construct the target file path by replacing the ".source.rs" with a
    // ".target.rs". There's a small discrepancy here as the .source.rs had
    // to be at the end for the file to count as source file, but here
    // we are replacing the .target. everywhere in the file name.
    //
    // This shouldn't matter in practice as these are test files and we can
    // decide on their naming as we write them.
    vec![
        assert_output_with(
            result_code,
            source_path,
            "stdout",
            |s| format(s.trim())),
        assert_output(
            result_stderr,
            source_path,
            "stderr"),
    ].into_iter().flat_map(|v| v).collect()
}

fn assert_compile(result_code: String, result_stderr: String, source_path: &str) -> Vec<OutputResult> {
    vec![
        assert_output(
            result_code,
            source_path,
            "stdout"),
        assert_output(
            result_stderr,
            source_path,
            "stderr"),
    ].into_iter().flat_map(|v| v).collect()
}

fn assert_output(actual: String, actual_path: &str, output_kind: &str) -> Option<OutputResult> {
    assert_output_with(actual, actual_path, output_kind, |input| input)
}

fn assert_output_with(actual: String, actual_path: &str, output_kind: &str, sanitize: impl Fn(String) -> String) -> Option<OutputResult> {

    let name = Path::new(actual_path).file_name().unwrap().to_string_lossy();
    let expected_path = format!("{}.{}", actual_path, output_kind);
    let expected_path = Path::new(&expected_path);
    if expected_path.exists() {
        let expected = std::fs::read_to_string(expected_path).unwrap().replace("\r", "");
        if expected != actual {
            Some(OutputResult {
                message: format!("{} {} output differs", name, output_kind),
                changeset: Changeset::new(
                    &sanitize(actual.clone()),
                    &sanitize(expected),
                    "\n",
                ),
                actual: actual,
                expected_path: None,
            })
        } else {
            None
        }
    } else if ! actual.is_empty() {
        Some(OutputResult {
            message: format!("{} {} output was not expected", name, output_kind),
            changeset: Changeset::new(
                &actual,
                "",
                "\n",
            ),
            actual: actual,
            expected_path: None,
        })
    } else {
        None
    }
}
<commit_msg>Add support for UI tests<commit_after>extern crate difference;
extern crate regex;
extern crate term;

use difference::{Changeset, Difference};

use std::fs;
use std::io::{Cursor, Write};
use std::path::{PathBuf, Path};
use std::process::{Command, Stdio};
use term::Terminal;

// Given the default Rust test runner doesn't expose programmatic test cases
// we are using single "check_expansions" test to process all the data files.
//
// This is similar approach to what rustfmt does.

struct TestConfig {
    crate_path: PathBuf,
    data_path: PathBuf,
}

#[test]
fn check_expansions()
{
    let root_path = find_root().unwrap();

    let crate_path = root_path.join("intercom-attributes");
    let config = TestConfig {
        data_path: crate_path.join("tests/data"),
        crate_path,
    };
    // Running "cargo test" in a clean build directory does not
    // finalize the compilation of all the crates.
    // The final binaries are unavailable in the target directory.
    // Force the building here to ensure they are available
    // for the tests.
    build_crate("intercom");
    build_crate("intercom-fmt");

    let failed = test_path(&config, "macro", TestMode::Macro)
        + test_path(&config, "ui", TestMode::UI);

    // Ensure there were no failures.
    //
    // If we fail here, cargo will display our printlns to the user.
    assert_eq!(failed, 0, "{} tests failed", failed);
}

struct OutputResult
{
    message: String,
    actual: String,
    expected_path: Option<String>,
    changeset: Changeset,
}

impl OutputResult {

    /// Prints the diff.
    pub fn show_diff(&self, ctx_size: usize, t: &mut dyn Terminal<Output = std::io::Stdout>) {

        enum Lines
        {
            Expected(String),
            Actual(String),
            Same(String),
        };

        // Resolve the changed lines so we can print only the context.
        let mut all_lines = vec![];
        for cs in &self.changeset.diffs {
            match cs {
                Difference::Same(ref x) =>
                    all_lines.push(
                        x.lines()
                            .map(|l| Lines::Same(format!("  {}", l)))
                            .collect::<Vec<_>>(),
                        ),
                Difference::Add(ref x) =>
                    all_lines.push(
                        x.lines()
                            .map(|l| Lines::Expected(format!("E {}", l)))
                            .collect::<Vec<_>>(),
                        ),
                Difference::Rem(ref x) =>
                    all_lines.push(
                        x.lines()
                            .map(|l| Lines::Actual(format!("A {}", l)))
                            .collect::<Vec<_>>(),
                        ),
            }
        }
        let all_lines = all_lines.into_iter().flat_map(|i| i).collect::<Vec<_>>();

        let mut ctx_counter = 0;
        let mut ctx_lines = vec![];
        for l in &all_lines {
            match l {
                Lines::Expected(..) | Lines::Actual(..) => ctx_counter = ctx_size*2+1,
                _ if ctx_counter > 0 => ctx_counter -= 1,
                _ => {}
            }
            ctx_lines.push(ctx_counter);
        }
        for _ in 0..ctx_size {
            if ctx_counter > 0 { ctx_counter -= 1 }
            ctx_lines.push(ctx_counter);
        }

        let mut skip = false;
        for i in 0..all_lines.len() {
            if ctx_lines[i+ctx_size] == 0 {
                if ! skip {
                    writeln!(t, "...snip").expect("Write failed");
                    skip = true;
                }
                continue;
            }
            skip = false;
            match all_lines[i] {
                Lines::Same(ref x) => {
                    t.reset().unwrap();
                    writeln!(t, "{}", x).expect("Write failed");
                }
                Lines::Expected(ref x) => {
                    t.fg(term::color::GREEN).unwrap();
                    writeln!(t, "{}", x).expect("Write failed");
                }
                Lines::Actual(ref x) => {
                    t.fg(term::color::RED).unwrap();
                    writeln!(t, "{}", x).expect("Write failed");
                }
            }
        }
        t.reset().unwrap();
    }
}

/// Compiles a single file using rustc using similar options than what
/// cargo would have used.
fn build(cwd: &str, path: &str, mode: TestMode) -> (bool, String, String)
{
    #[cfg(debug_assertions)]
    let conf = "debug";

    #[cfg(not(debug_assertions))]
    let conf = "release";

    // Launch rustc.
    #[rustfmt::skip]
    let mut cmd = std::process::Command::new("rustc");
    cmd.current_dir( cwd )
        .env("CARGO_PKG_NAME", "TestLib")
        .args(&[
            "--crate-name", "source",
            "--crate-type", "lib",
            path,
            "--out-dir", "tests/out",
            "-L", &format!("dependency=../target/{}/deps", conf),
            "--extern", &format!("intercom=../target/{}/libintercom.rlib", conf),
        ] );

    // In expansion mode add the 'pretty=expanded' option.
    if let TestMode::Macro = mode {
        cmd.arg("--pretty=expanded");
        cmd.arg("-Z").arg("unstable-options");
    }

    // Get the output.
    let output = cmd.output().expect( "Failed to execute" );

    // stdout/err is utf8 byte stream. Parse it into a string.
    (
        output.status.success(),
        String::from_utf8(output.stdout).expect("Bad output"),
        String::from_utf8(output.stderr).expect("Bad stderr"),
    )
}

fn format(code: &str) -> String
{
    let intercom_fmt = find_intercom_fmt().unwrap();
    let mut formatter = Command::new(&intercom_fmt)
        .arg("-p")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect(&format!("Failed to launch formatter {:?}", intercom_fmt));

    // Send the code to the formatter.
    // "stdin" of the formatter will be closed automatically when the scope terminates.
    {
        let mut stdin = formatter.stdin.as_mut().unwrap();
        std::io::copy(&mut Cursor::new(code.as_bytes()), &mut stdin)
            .expect("Failed to send data for formatting.");
    }

    let output = formatter
        .wait_with_output()
        .expect("Failed to read formatter results.");
    let status = output.status;
    assert!(
        status.success() && status.code() == Some(0),
        "Formatting failed with status \"{:?}\".",
        status.code()
    );

    // Convert the UTF-8 output into a string.
    String::from_utf8(output.stdout).expect("Bad output")
}

/// Builds the "intercom" library
fn build_crate(module: &str)
{
    let mut cmd = Command::new("cargo");
    cmd.arg("build");

    #[cfg(not(debug_assertions))]
    {
        cmd.arg("--release");
    }

    let status = cmd
        .current_dir(find_root().unwrap().join(module))
        .status()
        .expect(&format!("Failed to build crate \"{0}\"", module));
    assert!(status.success());
}

fn find_root() -> std::io::Result<PathBuf>
{
    let mut root_path = std::env::current_exe()?;
    loop {
        if root_path.join("Cargo.toml").exists() {
            break;
        }
        assert!(root_path.pop());
    }

    Ok(root_path)
}

fn find_intercom_fmt() -> std::io::Result<PathBuf>
{
    // Avoid the need to determine the current build target by basing the search on the current
    // executable.
    let mut intercom_fmt_dir = std::env::current_exe()?;
    assert!(intercom_fmt_dir.pop()); // The name of the executable.
    let original_path = format!("{:?}", intercom_fmt_dir);
    loop {
        // Stop search when intercom-fmt has been found.
        if let Some(intercom_fmt) = has_intercom_fmt(&intercom_fmt_dir) {
            return Ok(intercom_fmt);
        }

        // Move towards root
        assert!(
            intercom_fmt_dir.pop(),
            format!(
                "Could not locate intercom-fmt. Search started from \"{0}\".",
                original_path
            )
        );
    }
}

/// Determines if the given directory has intercom-fmt
fn has_intercom_fmt(dir: &PathBuf) -> Option<PathBuf>
{
    #[cfg(windows)]
    let intercom_fmt = dir.join("intercom-fmt.exe");

    #[cfg(not(windows))]
    let intercom_fmt = dir.join("intercom-fmt");

    if intercom_fmt.exists() && intercom_fmt.metadata().unwrap().is_file() {
        Some(intercom_fmt)
    } else {
        None
    }
}

#[derive(Clone, Copy)]
enum TestMode {
    Macro,
    UI,
}

fn test_path(config: &TestConfig, sub_path: &str, mode: TestMode) -> usize
{
    // Get the source test data files.
    let test_data = fs::read_dir(config.data_path.join(sub_path)).unwrap();
    let source_paths = test_data
        .into_iter()
        .map(|e| e.expect("Failed to read entry").path())
        .map(|p| p.to_str().unwrap().to_owned())
        .filter(|p| p.ends_with(".rs"));
    let mut failed = 0;

    for source_path in source_paths {
        println!("Testing {}", source_path);

        // Get the source and target code.

        // The source is compiled using rustc
        let (_, result_code, result_stderr) =
            build(config.crate_path.to_str().unwrap(), &source_path, mode);

        // Generate diffs for both sources
        // Ensure the linebreaks are the same for both. This seems to be
        // somewhat of an issue on AppVeyor.
        let result_code = result_code.replace("\r", "");
        let result_stderr = result_stderr.replace("\r", "");

        let mut results = vec![];

        // Use rustfmt to format both pieces of code so that we have a
        // canonical format for them. Without rustfmt we'd need to match the
        // compiler pretty print format in the reference target files - which,
        // despite its name, isn't very pretty.
        match mode {
            TestMode::Macro => results.extend(assert_macro(result_code, result_stderr, &source_path)),
            TestMode::UI => results.extend(assert_compile(result_code, result_stderr, &source_path)),
        }

        // If these were equal, there's only one "Same" diff segment.
        // If there is more than one, they differed.
        let mut t = term::stdout().unwrap();
        for r in results {
            if std::env::var("UPDATE_TARGETS").is_ok() && r.expected_path.is_some() {
                // The user wants to update the targets.
                let mut target_file = fs::File::create(r.expected_path.as_ref().unwrap()).unwrap();
                target_file
                    .write_all(r.actual.as_bytes())
                    .expect(&format!("Writing target file {} failed", r.expected_path.as_ref().unwrap()));
                writeln!(t, "-----------------------------------------------------------------").unwrap();
                writeln!(t, "UPDATED: {}", r.expected_path.unwrap()).unwrap();
                writeln!(t, "-----------------------------------------------------------------").unwrap();
            } else {
                writeln!(t, "-----------------------------------------------------------------").unwrap();
                writeln!(t, "FAIL: {}", r.message).unwrap();
                writeln!(t, "").unwrap();
                r.show_diff(5, &mut *t);
                writeln!(t, "").unwrap();
                writeln!(t, "-----------------------------------------------------------------").unwrap();
                failed += 1;
            }
        }
    }
    failed
}

fn assert_macro(result_code: String, result_stderr: String, source_path: &str) -> Vec<OutputResult> {

    // Construct the target file path by replacing the ".source.rs" with a
    // ".target.rs". There's a small discrepancy here as the .source.rs had
    // to be at the end for the file to count as source file, but here
    // we are replacing the .target. everywhere in the file name.
    //
    // This shouldn't matter in practice as these are test files and we can
    // decide on their naming as we write them.
    vec![
        assert_output_with(
            result_code,
            source_path,
            "stdout",
            |s| format(s.trim())),
        assert_output(
            result_stderr,
            source_path,
            "stderr"),
    ].into_iter().flat_map(|v| v).collect()
}

fn assert_compile(result_code: String, result_stderr: String, source_path: &str) -> Vec<OutputResult> {
    vec![
        assert_output(
            result_code,
            source_path,
            "stdout"),
        assert_output(
            result_stderr,
            source_path,
            "stderr"),
    ].into_iter().flat_map(|v| v).collect()
}

fn assert_output(actual: String, actual_path: &str, output_kind: &str) -> Option<OutputResult> {
    assert_output_with(actual, actual_path, output_kind, |input| input)
}

fn assert_output_with(actual: String, actual_path: &str, output_kind: &str, sanitize: impl Fn(String) -> String) -> Option<OutputResult> {

    let name = Path::new(actual_path).file_name().unwrap().to_string_lossy();
    let expected_path_string = format!("{}.{}", actual_path, output_kind);
    let expected_path = Path::new(&expected_path_string);
    if expected_path.exists() {
        let expected = std::fs::read_to_string(expected_path).unwrap().replace("\r", "");
        if expected != actual {
            Some(OutputResult {
                message: format!("{} {} output differs", name, output_kind),
                changeset: Changeset::new(
                    &sanitize(actual.clone()),
                    &sanitize(expected),
                    "\n",
                ),
                actual: actual,
                expected_path: Some(expected_path_string),
            })
        } else {
            None
        }
    } else if ! actual.is_empty() {
        Some(OutputResult {
            message: format!("{} {} output was not expected", name, output_kind),
            changeset: Changeset::new(
                &actual,
                "",
                "\n",
            ),
            actual: actual,
            expected_path: None,
        })
    } else {
        None
    }
}
<|endoftext|>


<commit_before>use mio::Ready;
use mio_uds::UnixStream;
use std::iter::Iterator;
use std::str::from_utf8;
use std::marker::PhantomData;
use std::io::{self,Read,Write,ErrorKind};
use std::cmp::min;
use log;
use serde_json;
use serde::ser::Serialize;
use serde::de::Deserialize;

use network::buffer::Buffer;

#[derive(Debug,PartialEq)]
pub enum ConnError {
  Continue,
  ParseError,
  SocketError,
}

pub struct CommandChannel<Tx,Rx> {
  pub sock:        UnixStream,
  front_buf:       Buffer,
  back_buf:        Buffer,
  max_buffer_size: usize,
  pub readiness:   Ready,
  pub interest:    Ready,
  phantom_tx:      PhantomData<Tx>,
  phantom_rx:      PhantomData<Rx>,
}

impl<Tx: Serialize, Rx: Deserialize> CommandChannel<Tx,Rx> {
  pub fn new(sock: UnixStream, buffer_size: usize, max_buffer_size: usize) -> CommandChannel<Tx,Rx> {
    CommandChannel {
      sock:            sock,
      front_buf:       Buffer::with_capacity(buffer_size),
      back_buf:        Buffer::with_capacity(buffer_size),
      max_buffer_size: max_buffer_size,
      readiness:       Ready::none(),
      interest:        Ready::readable(),
      phantom_tx:      PhantomData,
      phantom_rx:      PhantomData,
    }
  }

  fn handle_events(&mut self, events: Ready) {
    self.readiness = self.readiness | events;
  }

  fn run(&mut self) {
    let interest = self.interest & self.readiness;

    if interest.is_readable() {
      self.readable();
    }

    if interest.is_writable() {
      self.writable();
    }
  }

  fn readable(&mut self) -> Result<usize,ConnError> {
    if !(self.interest & self.readiness).is_readable() {
      return Err(ConnError::Continue);
    }

    let mut count = 0usize;
    loop {
      let size = self.front_buf.available_space();
      if size == 0 {
        self.interest.remove(Ready::readable());
        break;
      }

      match self.sock.read(self.front_buf.space()) {
        Ok(0) => {
          break;
        },
        Err(e) => {
          match e.kind() {
            ErrorKind::WouldBlock => {
              self.readiness.remove(Ready::readable());
              break;
            },
            code => {
              //log!(log::LogLevel::Error, "UNIX CLIENT[{}] read error (kind: {:?}): {:?}", tok.0, code, e);
              self.interest  = Ready::none();
              self.readiness = Ready::none();
              return Err(ConnError::SocketError);
            }
          }
        },
        Ok(r) => {
          count += r;
          self.front_buf.fill(r);
          //debug!("UNIX CLIENT[{}] sent {} bytes: {:?}", tok.0, r, from_utf8(self.buf.data()));
        },
      };
    }

    Ok(count)
  }

  fn writable(&mut self) -> Result<usize,ConnError> {
    if !(self.interest & self.readiness).is_writable() {
      return Err(ConnError::Continue);
    }

    let mut count = 0usize;
    loop {
      let size = self.back_buf.available_data();
      if size == 0 {
        self.interest.remove(Ready::writable());
        break;
      }

      match self.sock.write(self.back_buf.data()) {
        Ok(0) => {
          break;
        },
        Ok(r) => {
          count += r;
          self.back_buf.consume(r);
        },
        Err(e) => {
          match e.kind() {
            ErrorKind::WouldBlock => {
              self.interest.remove(Ready::writable());
              break;
            },
            code => {
              self.interest  = Ready::none();
              self.readiness = Ready::none();
              return Err(ConnError::SocketError);
            }
          }
        }
      }
    }

    Ok(count)
  }

  fn read_message(&mut self) -> Option<Rx> {
    let mut offset = 0usize;
    let mut grow   = false;

    if let Some(pos) = self.front_buf.data().iter().position(|&x| x == 0) {
      let mut res = None;

      if let Ok(s) = from_utf8(&self.front_buf.data()[..pos]) {
        if let Ok(message) = serde_json::from_str(s) {
          res = Some(message);
        } else {
          error!("could not parse message, ignoring:\n{}", s);
        }
      } else {
        error!("invalid utf-8 encoding in command message, ignoring");
      }

      self.front_buf.consume(pos+1);
      res
    } else {
      if self.front_buf.available_space() == 0 {
        if self.front_buf.capacity() == self.max_buffer_size {
          error!("command buffer full, cannot grow more, ignoring");
        } else {
          let new_size = min(self.front_buf.capacity()+5000, self.max_buffer_size);
          self.front_buf.grow(new_size);
        }
      }

      self.interest.insert(Ready::readable());
      None
    }
  }

  pub fn write_message(&mut self, message: Tx) -> bool {
    let message = &serde_json::to_string(&message).map(|s| s.into_bytes()).unwrap_or(vec!());

    let msg_len = message.len() + 1;
    if msg_len > self.back_buf.available_space() {
      self.back_buf.shift();
    }

    if msg_len > self.back_buf.available_space() {
      if msg_len - self.back_buf.available_space() + self.back_buf.capacity() > self.max_buffer_size {
        error!("message is too large to write to back buffer");
        return false;
      }

      let new_len = msg_len - self.back_buf.available_space() + self.back_buf.capacity();
      self.back_buf.grow( new_len );
    }

    self.back_buf.write(message);
    self.back_buf.write(&b"\0"[..]);
    true
  }
}

impl<Tx: Serialize, Rx: Deserialize> Iterator for CommandChannel<Tx,Rx> {
  type Item = Rx;
  fn next(&mut self) -> Option<Self::Item> {
    self.read_message()
  }
}
<commit_msg>handle blocking read and writes<commit_after>use mio::{Evented,Poll,PollOpt,Ready,Token};
use mio_uds::UnixStream;
use std::fmt::Debug;
use std::iter::Iterator;
use std::str::from_utf8;
use std::marker::PhantomData;
use std::io::{self,Read,Write,ErrorKind};
use std::os::unix::net;
use std::os::unix::io::{AsRawFd,FromRawFd,IntoRawFd};
use std::cmp::min;
use log;
use serde_json;
use serde::ser::Serialize;
use serde::de::Deserialize;

use network::buffer::Buffer;

#[derive(Debug,PartialEq)]
pub enum ConnError {
  Continue,
  ParseError,
  SocketError,
}

pub struct CommandChannel<Tx,Rx> {
  pub sock:        UnixStream,
  front_buf:       Buffer,
  back_buf:        Buffer,
  max_buffer_size: usize,
  pub readiness:   Ready,
  pub interest:    Ready,
  blocking:        bool,
  phantom_tx:      PhantomData<Tx>,
  phantom_rx:      PhantomData<Rx>,
}

impl<Tx: Debug+Serialize, Rx: Debug+Deserialize> CommandChannel<Tx,Rx> {
  pub fn new(sock: UnixStream, buffer_size: usize, max_buffer_size: usize) -> CommandChannel<Tx,Rx> {
    CommandChannel {
      sock:            sock,
      front_buf:       Buffer::with_capacity(buffer_size),
      back_buf:        Buffer::with_capacity(buffer_size),
      max_buffer_size: max_buffer_size,
      readiness:       Ready::none(),
      interest:        Ready::readable(),
      blocking:        false,
      phantom_tx:      PhantomData,
      phantom_rx:      PhantomData,
    }
  }

  pub fn set_nonblocking(&mut self, nonblocking: bool) {
    unsafe {
      let fd = self.sock.as_raw_fd();
      let mut stream = net::UnixStream::from_raw_fd(fd);
      stream.set_nonblocking(nonblocking);
      let fd = stream.into_raw_fd();
    }
    self.blocking = !nonblocking;
  }

  pub fn handle_events(&mut self, events: Ready) {
    println!("handle_events: readiness = {:?}, events= {:?}, result = {:?}", self.readiness, events, self.readiness | events);
    self.readiness = self.readiness | events;
  }

  pub fn run(&mut self) {
    let interest = self.interest & self.readiness;
    println!("run: interest = {:?}", interest);

    if interest.is_readable() {
      self.readable();
    }

    if interest.is_writable() {
      self.writable();
    }
  }

  pub fn readable(&mut self) -> Result<usize,ConnError> {
    println!("readable[{}]", line!());
    if !(self.interest & self.readiness).is_readable() {
      return Err(ConnError::Continue);
    }

    println!("readable[{}]", line!());
    let mut count = 0usize;
    loop {
      println!("readable[{}]", line!());
      let size = self.front_buf.available_space();
      if size == 0 {
        self.interest.remove(Ready::readable());
        break;
      }

      match self.sock.read(self.front_buf.space()) {
        Ok(0) => {
          break;
        },
        Err(e) => {
          match e.kind() {
            ErrorKind::WouldBlock => {
              self.readiness.remove(Ready::readable());
              break;
            },
            code => {
              //log!(log::LogLevel::Error, "UNIX CLIENT[{}] read error (kind: {:?}): {:?}", tok.0, code, e);
              self.interest  = Ready::none();
              self.readiness = Ready::none();
              return Err(ConnError::SocketError);
            }
          }
        },
        Ok(r) => {
          count += r;
          self.front_buf.fill(r);
          //debug!("UNIX CLIENT[{}] sent {} bytes: {:?}", tok.0, r, from_utf8(self.buf.data()));
        },
      };
    }

    Ok(count)
  }

  pub fn writable(&mut self) -> Result<usize,ConnError> {
    println!("writable[{}]", line!());
    if !(self.interest & self.readiness).is_writable() {
      return Err(ConnError::Continue);
    }

    println!("writable[{}]", line!());
    let mut count = 0usize;
    loop {
    println!("writable[{}]", line!());
      let size = self.back_buf.available_data();
      if size == 0 {
        self.interest.remove(Ready::writable());
        break;
      }

      match self.sock.write(self.back_buf.data()) {
        Ok(0) => {
          break;
        },
        Ok(r) => {
          count += r;
          self.back_buf.consume(r);
        },
        Err(e) => {
          match e.kind() {
            ErrorKind::WouldBlock => {
              self.interest.remove(Ready::writable());
              break;
            },
            code => {
              self.interest  = Ready::none();
              self.readiness = Ready::none();
              return Err(ConnError::SocketError);
            }
          }
        }
      }
    }

    Ok(count)
  }

  pub fn read_message(&mut self) -> Option<Rx> {
    if self.blocking {
      self.read_message_blocking()
    } else {
      self.read_message_nonblocking()
    }
  }

  pub fn read_message_nonblocking(&mut self) -> Option<Rx> {
    let mut offset = 0usize;
    let mut grow   = false;

    if let Some(pos) = self.front_buf.data().iter().position(|&x| x == 0) {
      let mut res = None;

      if let Ok(s) = from_utf8(&self.front_buf.data()[..pos]) {
        if let Ok(message) = serde_json::from_str(s) {
          res = Some(message);
        } else {
          error!("could not parse message, ignoring:\n{}", s);
        }
      } else {
        error!("invalid utf-8 encoding in command message, ignoring");
      }

      self.front_buf.consume(pos+1);
      res
    } else {
      if self.front_buf.available_space() == 0 {
        if self.front_buf.capacity() == self.max_buffer_size {
          error!("command buffer full, cannot grow more, ignoring");
        } else {
          let new_size = min(self.front_buf.capacity()+5000, self.max_buffer_size);
          self.front_buf.grow(new_size);
        }
      }

      self.interest.insert(Ready::readable());
      None
    }
  }

  pub fn read_message_blocking(&mut self) -> Option<Rx> {
    let mut offset = 0usize;
    let mut grow   = false;

    loop {
      if let Some(pos) = self.front_buf.data().iter().position(|&x| x == 0) {
        let mut res = None;

        if let Ok(s) = from_utf8(&self.front_buf.data()[..pos]) {
          if let Ok(message) = serde_json::from_str(s) {
            res = Some(message);
          } else {
            error!("could not parse message, ignoring:\n{}", s);
          }
        } else {
          error!("invalid utf-8 encoding in command message, ignoring");
        }

        self.front_buf.consume(pos+1);
        return res;
      } else {
        if self.front_buf.available_space() == 0 {
          if self.front_buf.capacity() == self.max_buffer_size {
            error!("command buffer full, cannot grow more, ignoring");
            return None;
          } else {
            let new_size = min(self.front_buf.capacity()+5000, self.max_buffer_size);
            self.front_buf.grow(new_size);
          }
        }

        match self.sock.read(self.front_buf.space()) {
          Ok(0) => {
          },
          Err(e) => { return None; },
          Ok(r) => {
            self.front_buf.fill(r);
          },
        };
      }
    }
  }

  pub fn write_message(&mut self, message: Tx) -> bool {
    if self.blocking {
      self.write_message_blocking(message)
    } else {
      self.write_message_nonblocking(message)
    }
  }

  pub fn write_message_nonblocking(&mut self, message: Tx) -> bool {
    println!("write_message: {:?}", message);
    let message = &serde_json::to_string(&message).map(|s| s.into_bytes()).unwrap_or(vec!());

    let msg_len = message.len() + 1;
    if msg_len > self.back_buf.available_space() {
      self.back_buf.shift();
    }

    if msg_len > self.back_buf.available_space() {
      if msg_len - self.back_buf.available_space() + self.back_buf.capacity() > self.max_buffer_size {
        error!("message is too large to write to back buffer");
        return false;
      }

      let new_len = msg_len - self.back_buf.available_space() + self.back_buf.capacity();
      self.back_buf.grow( new_len );
    }

    self.back_buf.write(message);
    self.back_buf.write(&b"\0"[..]);
    self.interest.insert(Ready::writable());

    true
  }

  pub fn write_message_blocking(&mut self, message: Tx) -> bool {
    println!("write_message: {:?}", message);
    let message = &serde_json::to_string(&message).map(|s| s.into_bytes()).unwrap_or(vec!());

    let msg_len = message.len() + 1;
    if msg_len > self.back_buf.available_space() {
      self.back_buf.shift();
    }

    if msg_len > self.back_buf.available_space() {
      if msg_len - self.back_buf.available_space() + self.back_buf.capacity() > self.max_buffer_size {
        error!("message is too large to write to back buffer");
        return false;
      }

      let new_len = msg_len - self.back_buf.available_space() + self.back_buf.capacity();
      self.back_buf.grow( new_len );
    }

    self.back_buf.write(message);
    self.back_buf.write(&b"\0"[..]);

    loop {
      let size = self.back_buf.available_data();
      if size == 0 {
        break;
      }

      match self.sock.write(self.back_buf.data()) {
        Ok(r) => {
          self.back_buf.consume(r);
        },
        Err(e) => {
          return true;
        }
      }
    }
    true
  }
}

impl<Tx: Debug+Serialize, Rx: Debug+Deserialize> Iterator for CommandChannel<Tx,Rx> {
  type Item = Rx;
  fn next(&mut self) -> Option<Self::Item> {
    self.read_message()
  }
}

impl<Tx,Rx> Evented for CommandChannel<Tx,Rx> {
  fn register(&self,
              poll: &Poll,
              token: Token,
              interest: Ready,
              opts: PollOpt)
              -> io::Result<()> {
    self.sock.register(poll, token, interest, opts)
  }

  fn reregister(&self,
                poll: &Poll,
                token: Token,
                interest: Ready,
                opts: PollOpt)
                -> io::Result<()> {
    self.sock.reregister(poll, token, interest, opts)
  }

  fn deregister(&self, poll: &Poll) -> io::Result<()> {
    self.sock.deregister(poll)
  }
}
<|endoftext|>


<commit_before>pub use telegram_bot_raw::{CanReplySendLocation, CanSendLocation};
pub use telegram_bot_raw::{CanReplySendMessage, CanSendMessage};
pub use telegram_bot_raw::{CanForwardMessage};
pub use telegram_bot_raw::{CanSendChatAction};
pub use telegram_bot_raw::{CanGetChat, CanGetChatAdministrators, CanGetChatMembersCount};
pub use telegram_bot_raw::{CanGetChatMemberForChat, CanGetChatMemberForUser};
pub use telegram_bot_raw::{CanKickChatMemberForChat, CanKickChatMemberForUser};
pub use telegram_bot_raw::{CanUnbanChatMemberForChat, CanUnbanChatMemberForUser};
pub use telegram_bot_raw::{CanLeaveChat};
pub use telegram_bot_raw::{CanEditMessageText, CanEditMessageCaption, CanEditMessageReplyMarkup};
pub use telegram_bot_raw::{CanGetUserProfilePhotos};
<commit_msg>Add "Can..." functions to prelude.<commit_after>pub use telegram_bot_raw::{CanReplySendContact, CanSendContact};
pub use telegram_bot_raw::{CanReplySendLocation, CanSendLocation};
pub use telegram_bot_raw::{CanReplySendMessage, CanSendMessage};
pub use telegram_bot_raw::{CanForwardMessage};
pub use telegram_bot_raw::{CanSendChatAction};
pub use telegram_bot_raw::{CanGetChat, CanGetChatAdministrators, CanGetChatMembersCount};
pub use telegram_bot_raw::{CanGetChatMemberForChat, CanGetChatMemberForUser};
pub use telegram_bot_raw::{CanKickChatMemberForChat, CanKickChatMemberForUser};
pub use telegram_bot_raw::{CanUnbanChatMemberForChat, CanUnbanChatMemberForUser};
pub use telegram_bot_raw::{CanLeaveChat};
pub use telegram_bot_raw::{CanEditMessageText, CanEditMessageCaption, CanEditMessageReplyMarkup};
pub use telegram_bot_raw::{CanGetUserProfilePhotos};
<|endoftext|>


<commit_before>#![allow(dead_code)]
/// The XML structure for a cobatura report is roughly as follows:
/// ```xml
/// <coverage lines-valid="5" lines-covered="0" line-rate="0.0" branches-valid="0"
/// branches-covered="0" branch-rate="0.0" version="1.9" timestamp="...">
///   <sources>
///     <source>PATH</source>
///     ...
///   </sources>
///
///   <packages>
///     <package name=".."  line-rate="0.0" branch-rate="0.0" complexity="0.0">
///       <classes>
///         <class name="Main" filename="main.rs" line-rate="0.0" branch-rate="0.0" complexity="0.0">
///           <methods>
///             <method name="main" signature="()" line-rate="0.0" branch-rate="0.0">
///               <lines>
///                 <line number="1" hits="5" branch="false"/>
///                 <line number="3" hits="2" branch="true">
///                   <conditions>
///                     <condition number="0" type="jump" coverage="50%"/>
///                     ...
///                   </conditions>
///                 </line>
///               </lines>
///             </method>
///             ...
///           </methods>
///
///           <lines>
///             <line number="10" hits="4" branch="false"/>
///           </lines>
///         </class>
///         ...
///       </classes>
///     </package>
///     ...
///   </packages>
/// </coverage>
/// ```
use std::collections::HashSet;
use std::error;
use std::fmt;
use std::fs::File;
use std::io::{Cursor, Write};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

use quick_xml::{
    events::{BytesDecl, BytesEnd, BytesStart, Event},
    Writer,
};

use chrono::offset::Utc;

use crate::config::Config;
use crate::traces::{CoverageStat, Trace, TraceMap};

pub fn report(traces: &TraceMap, config: &Config) -> Result<(), Error> {
    let result = Report::render(config, traces)?;
    result.export(config)
}

#[derive(Debug)]
pub enum Error {
    Unknown,
    ExportError(quick_xml::Error),
}

impl error::Error for Error {
    #[inline]
    fn cause(&self) -> Option<&dyn error::Error> {
        None
    }
}

impl fmt::Display for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::ExportError(ref e) => write!(f, "Export Error {}", e),
            Error::Unknown => write!(f, "Unknown Error"),
        }
    }
}

#[derive(Debug)]
pub struct Report {
    timestamp: i64,
    lines_covered: usize,
    lines_valid: usize,
    line_rate: f64,
    branches_covered: usize,
    branches_valid: usize,
    branch_rate: f64,
    sources: Vec<PathBuf>,
    packages: Vec<Package>,
}

impl Report {
    pub fn render(config: &Config, traces: &TraceMap) -> Result<Self, Error> {
        let timestamp = Utc::now().timestamp();
        let sources = render_sources(config);
        let packages = render_packages(config, traces);
        let mut line_rate = 0.0;
        let mut branch_rate = 0.0;

        if packages.len() > 0 {
            line_rate = packages.iter().map(|x| x.line_rate).sum::<f64>() / packages.len() as f64;
            branch_rate =
                packages.iter().map(|x| x.branch_rate).sum::<f64>() / packages.len() as f64;
        }

        Ok(Report {
            timestamp: timestamp,
            lines_covered: traces.total_covered(),
            lines_valid: traces.total_coverable(),
            line_rate: line_rate,
            branches_covered: 0,
            branches_valid: 0,
            branch_rate: branch_rate,
            sources: sources,
            packages: packages,
        })
    }

    pub fn export(&self, config: &Config) -> Result<(), Error> {
        let file_path = config.output_directory.join("cobertura.xml");
        let mut file =
            File::create(file_path).map_err(|e| Error::ExportError(quick_xml::Error::Io(e)))?;

        let mut writer = Writer::new(Cursor::new(vec![]));
        writer
            .write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))
            .map_err(|e| Error::ExportError(e))?;

        let cov_tag = b"coverage";
        let mut cov = BytesStart::borrowed(cov_tag, cov_tag.len());
        cov.push_attribute(("lines-covered", self.lines_covered.to_string().as_ref()));
        cov.push_attribute(("lines-valid", self.lines_valid.to_string().as_ref()));
        cov.push_attribute(("line-rate", self.line_rate.to_string().as_ref()));
        cov.push_attribute((
            "branches-covered",
            self.branches_covered.to_string().as_ref(),
        ));
        cov.push_attribute(("branches-valid", self.branches_valid.to_string().as_ref()));
        cov.push_attribute(("branch-rate", self.branch_rate.to_string().as_ref()));
        cov.push_attribute(("complexity", "0"));
        cov.push_attribute(("version", "1.9"));

        let secs = match SystemTime::now().duration_since(UNIX_EPOCH) {
            Ok(s) => s.as_secs().to_string(),
            Err(_) => String::from("0"),
        };
        cov.push_attribute(("timestamp", secs.as_ref()));

        writer
            .write_event(Event::Start(cov))
            .map_err(|e| Error::ExportError(e))?;

        self.export_header(&mut writer)
            .map_err(|e| Error::ExportError(e))?;

        self.export_packages(&mut writer)
            .map_err(|e| Error::ExportError(e))?;

        writer
            .write_event(Event::End(BytesEnd::borrowed(cov_tag)))
            .map_err(|e| Error::ExportError(e))?;

        let result = writer.into_inner().into_inner();
        file.write_all(&result)
            .map_err(|e| Error::ExportError(quick_xml::Error::Io(e)))
    }

    fn export_header<T: Write>(&self, writer: &mut Writer<T>) -> Result<(), quick_xml::Error> {
        let sources_tag = b"sources";
        let source_tag = b"source";
        writer.write_event(Event::Start(BytesStart::borrowed(
            sources_tag,
            sources_tag.len(),
        )))?;
        for source in self.sources.iter() {
            if let Some(ref path) = source.to_str() {
                writer.write_event(Event::Start(BytesStart::borrowed(
                    source_tag,
                    source_tag.len(),
                )))?;
                writer.write(path.as_bytes())?;
                writer.write_event(Event::End(BytesEnd::borrowed(source_tag)))?;
            }
        }
        writer
            .write_event(Event::End(BytesEnd::borrowed(sources_tag)))
            .map(|_| ())
    }

    fn export_packages<T: Write>(&self, writer: &mut Writer<T>) -> Result<(), quick_xml::Error> {
        let packages_tag = b"packages";
        let pack_tag = b"package";

        writer.write_event(Event::Start(BytesStart::borrowed(
            packages_tag,
            packages_tag.len(),
        )))?;
        // Export the package
        for package in &self.packages {
            let mut pack = BytesStart::borrowed(pack_tag, pack_tag.len());
            pack.push_attribute(("name", package.name.as_str()));
            pack.push_attribute(("line-rate", package.line_rate.to_string().as_ref()));
            pack.push_attribute(("branch-rate", package.branch_rate.to_string().as_ref()));
            pack.push_attribute(("complexity", package.complexity.to_string().as_ref()));

            writer.write_event(Event::Start(pack))?;
            self.export_classes(&package.classes, writer)?;
            writer.write_event(Event::End(BytesEnd::borrowed(pack_tag)))?;
        }

        writer
            .write_event(Event::End(BytesEnd::borrowed(packages_tag)))
            .map(|_| ())
    }

    fn export_classes<T: Write>(
        &self,
        classes: &[Class],
        writer: &mut Writer<T>,
    ) -> Result<(), quick_xml::Error> {
        let classes_tag = b"classes";
        let class_tag = b"class";
        let methods_tag = b"methods";

        writer.write_event(Event::Start(BytesStart::borrowed(
            classes_tag,
            classes_tag.len(),
        )))?;
        for class in classes {
            let mut c = BytesStart::borrowed(class_tag, class_tag.len());
            c.push_attribute(("name", class.name.as_ref()));
            c.push_attribute(("filename", class.file_name.as_ref()));
            c.push_attribute(("line-rate", class.line_rate.to_string().as_ref()));
            c.push_attribute(("branch-rate", class.branch_rate.to_string().as_ref()));
            c.push_attribute(("complexity", class.complexity.to_string().as_ref()));

            writer.write_event(Event::Start(c))?;
            writer.write_event(Event::Empty(BytesStart::borrowed(
                methods_tag,
                methods_tag.len(),
            )))?;
            self.export_lines(&class.lines, writer)?;
            writer.write_event(Event::End(BytesEnd::borrowed(class_tag)))?;
        }
        writer
            .write_event(Event::End(BytesEnd::borrowed(classes_tag)))
            .map(|_| ())
    }

    fn export_lines<T: Write>(
        &self,
        lines: &[Line],
        writer: &mut Writer<T>,
    ) -> Result<(), quick_xml::Error> {
        let lines_tag = b"lines";
        let line_tag = b"line";

        writer.write_event(Event::Start(BytesStart::borrowed(
            lines_tag,
            lines_tag.len(),
        )))?;
        for line in lines {
            let mut l = BytesStart::borrowed(line_tag, line_tag.len());
            match line {
                Line::Plain {
                    ref number,
                    ref hits,
                } => {
                    l.push_attribute(("number", number.to_string().as_ref()));
                    l.push_attribute(("hits", hits.to_string().as_ref()));
                }
                Line::Branch { .. } => {}
            }
            writer.write_event(Event::Empty(l))?;
        }
        writer
            .write_event(Event::End(BytesEnd::borrowed(lines_tag)))
            .map(|_| ())
    }
}

fn render_sources(config: &Config) -> Vec<PathBuf> {
    vec![config.get_base_dir()]
}

#[derive(Debug)]
struct Package {
    name: String,
    line_rate: f64,
    branch_rate: f64,
    complexity: f64,
    classes: Vec<Class>,
}

fn render_packages(config: &Config, traces: &TraceMap) -> Vec<Package> {
    let dirs: HashSet<&Path> = traces
        .files()
        .into_iter()
        .filter_map(|x| x.parent())
        .collect();

    dirs.into_iter()
        .map(|x| render_package(config, traces, x))
        .collect()
}

fn render_package(config: &Config, traces: &TraceMap, pkg: &Path) -> Package {
    let name = config.strip_base_dir(pkg).to_str().unwrap().to_string();

    let line_cover = traces.covered_in_path(pkg) as f64;
    let line_rate = line_cover / (traces.coverable_in_path(pkg) as f64);

    Package {
        name: name,
        line_rate: line_rate,
        branch_rate: 0.0,
        complexity: 0.0,
        classes: render_classes(config, traces, pkg),
    }
}

#[derive(Debug)]
struct Class {
    name: String,
    file_name: String,
    line_rate: f64,
    branch_rate: f64,
    complexity: f64,
    lines: Vec<Line>,
    methods: Vec<Method>,
}

fn render_classes(config: &Config, traces: &TraceMap, pkg: &Path) -> Vec<Class> {
    traces
        .files()
        .iter()
        .filter(|x| x.parent() == Some(pkg))
        .map(|x| render_class(config, traces, x))
        .collect()
}

// TODO: Cobertura distinguishes between lines outside methods, and methods
// (which also contain lines). As there is currently no way to get traces from
// a particular function only, all traces are put into lines, and the vector
// of methods is empty.
//
// Until this is fixed, the render_method function will panic if called, as it
// cannot be properly implemented.
//
fn render_class(config: &Config, traces: &TraceMap, file: &Path) -> Class {
    let name = file
        .file_stem()
        .map(|x| x.to_str().unwrap())
        .unwrap_or_default()
        .to_string();

    let file_name = config.strip_base_dir(file).to_str().unwrap().to_string();

    let covered = traces.covered_in_path(file) as f64;
    let line_rate = covered / traces.coverable_in_path(file) as f64;
    let lines = traces
        .get_child_traces(file)
        .iter()
        .map(|x| render_line(x))
        .collect();

    Class {
        name: name,
        file_name: file_name,
        line_rate: line_rate,
        branch_rate: 0.0,
        complexity: 0.0,
        lines: lines,
        methods: vec![],
    }
}

#[derive(Debug)]
struct Method {
    name: String,
    signature: String,
    line_rate: f64,
    branch_rate: f64,
    lines: Vec<Line>,
}

fn render_methods() -> Vec<Method> {
    unimplemented!()
}

fn render_method() -> Method {
    unimplemented!()
}

#[derive(Debug)]
enum Line {
    Plain {
        number: usize,
        hits: usize,
    },

    Branch {
        number: usize,
        hits: usize,
        conditions: Vec<Condition>,
    },
}

fn render_line(trace: &Trace) -> Line {
    match &trace.stats {
        CoverageStat::Line(hits) => Line::Plain {
            number: trace.line as usize,
            hits: *hits as usize,
        },

        // TODO: Branches in cobertura are given a fresh number as a label,
        // which would require having some form of context when rendering.
        //
        _ => panic!("Not currently supported"),
    }
}

#[derive(Debug)]
struct Condition {
    number: usize,
    cond_type: ConditionType,
    coverage: f64,
}

// Condition types
#[derive(Debug)]
enum ConditionType {
    Jump,
}
<commit_msg>remove deprecated Error::cause<commit_after>#![allow(dead_code)]
/// The XML structure for a cobatura report is roughly as follows:
/// ```xml
/// <coverage lines-valid="5" lines-covered="0" line-rate="0.0" branches-valid="0"
/// branches-covered="0" branch-rate="0.0" version="1.9" timestamp="...">
///   <sources>
///     <source>PATH</source>
///     ...
///   </sources>
///
///   <packages>
///     <package name=".."  line-rate="0.0" branch-rate="0.0" complexity="0.0">
///       <classes>
///         <class name="Main" filename="main.rs" line-rate="0.0" branch-rate="0.0" complexity="0.0">
///           <methods>
///             <method name="main" signature="()" line-rate="0.0" branch-rate="0.0">
///               <lines>
///                 <line number="1" hits="5" branch="false"/>
///                 <line number="3" hits="2" branch="true">
///                   <conditions>
///                     <condition number="0" type="jump" coverage="50%"/>
///                     ...
///                   </conditions>
///                 </line>
///               </lines>
///             </method>
///             ...
///           </methods>
///
///           <lines>
///             <line number="10" hits="4" branch="false"/>
///           </lines>
///         </class>
///         ...
///       </classes>
///     </package>
///     ...
///   </packages>
/// </coverage>
/// ```
use std::collections::HashSet;
use std::error;
use std::fmt;
use std::fs::File;
use std::io::{Cursor, Write};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

use quick_xml::{
    events::{BytesDecl, BytesEnd, BytesStart, Event},
    Writer,
};

use chrono::offset::Utc;

use crate::config::Config;
use crate::traces::{CoverageStat, Trace, TraceMap};

pub fn report(traces: &TraceMap, config: &Config) -> Result<(), Error> {
    let result = Report::render(config, traces)?;
    result.export(config)
}

#[derive(Debug)]
pub enum Error {
    Unknown,
    ExportError(quick_xml::Error),
}

impl error::Error for Error {}

impl fmt::Display for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::ExportError(ref e) => write!(f, "Export Error {}", e),
            Error::Unknown => write!(f, "Unknown Error"),
        }
    }
}

#[derive(Debug)]
pub struct Report {
    timestamp: i64,
    lines_covered: usize,
    lines_valid: usize,
    line_rate: f64,
    branches_covered: usize,
    branches_valid: usize,
    branch_rate: f64,
    sources: Vec<PathBuf>,
    packages: Vec<Package>,
}

impl Report {
    pub fn render(config: &Config, traces: &TraceMap) -> Result<Self, Error> {
        let timestamp = Utc::now().timestamp();
        let sources = render_sources(config);
        let packages = render_packages(config, traces);
        let mut line_rate = 0.0;
        let mut branch_rate = 0.0;

        if packages.len() > 0 {
            line_rate = packages.iter().map(|x| x.line_rate).sum::<f64>() / packages.len() as f64;
            branch_rate =
                packages.iter().map(|x| x.branch_rate).sum::<f64>() / packages.len() as f64;
        }

        Ok(Report {
            timestamp: timestamp,
            lines_covered: traces.total_covered(),
            lines_valid: traces.total_coverable(),
            line_rate: line_rate,
            branches_covered: 0,
            branches_valid: 0,
            branch_rate: branch_rate,
            sources: sources,
            packages: packages,
        })
    }

    pub fn export(&self, config: &Config) -> Result<(), Error> {
        let file_path = config.output_directory.join("cobertura.xml");
        let mut file =
            File::create(file_path).map_err(|e| Error::ExportError(quick_xml::Error::Io(e)))?;

        let mut writer = Writer::new(Cursor::new(vec![]));
        writer
            .write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))
            .map_err(|e| Error::ExportError(e))?;

        let cov_tag = b"coverage";
        let mut cov = BytesStart::borrowed(cov_tag, cov_tag.len());
        cov.push_attribute(("lines-covered", self.lines_covered.to_string().as_ref()));
        cov.push_attribute(("lines-valid", self.lines_valid.to_string().as_ref()));
        cov.push_attribute(("line-rate", self.line_rate.to_string().as_ref()));
        cov.push_attribute((
            "branches-covered",
            self.branches_covered.to_string().as_ref(),
        ));
        cov.push_attribute(("branches-valid", self.branches_valid.to_string().as_ref()));
        cov.push_attribute(("branch-rate", self.branch_rate.to_string().as_ref()));
        cov.push_attribute(("complexity", "0"));
        cov.push_attribute(("version", "1.9"));

        let secs = match SystemTime::now().duration_since(UNIX_EPOCH) {
            Ok(s) => s.as_secs().to_string(),
            Err(_) => String::from("0"),
        };
        cov.push_attribute(("timestamp", secs.as_ref()));

        writer
            .write_event(Event::Start(cov))
            .map_err(|e| Error::ExportError(e))?;

        self.export_header(&mut writer)
            .map_err(|e| Error::ExportError(e))?;

        self.export_packages(&mut writer)
            .map_err(|e| Error::ExportError(e))?;

        writer
            .write_event(Event::End(BytesEnd::borrowed(cov_tag)))
            .map_err(|e| Error::ExportError(e))?;

        let result = writer.into_inner().into_inner();
        file.write_all(&result)
            .map_err(|e| Error::ExportError(quick_xml::Error::Io(e)))
    }

    fn export_header<T: Write>(&self, writer: &mut Writer<T>) -> Result<(), quick_xml::Error> {
        let sources_tag = b"sources";
        let source_tag = b"source";
        writer.write_event(Event::Start(BytesStart::borrowed(
            sources_tag,
            sources_tag.len(),
        )))?;
        for source in self.sources.iter() {
            if let Some(ref path) = source.to_str() {
                writer.write_event(Event::Start(BytesStart::borrowed(
                    source_tag,
                    source_tag.len(),
                )))?;
                writer.write(path.as_bytes())?;
                writer.write_event(Event::End(BytesEnd::borrowed(source_tag)))?;
            }
        }
        writer
            .write_event(Event::End(BytesEnd::borrowed(sources_tag)))
            .map(|_| ())
    }

    fn export_packages<T: Write>(&self, writer: &mut Writer<T>) -> Result<(), quick_xml::Error> {
        let packages_tag = b"packages";
        let pack_tag = b"package";

        writer.write_event(Event::Start(BytesStart::borrowed(
            packages_tag,
            packages_tag.len(),
        )))?;
        // Export the package
        for package in &self.packages {
            let mut pack = BytesStart::borrowed(pack_tag, pack_tag.len());
            pack.push_attribute(("name", package.name.as_str()));
            pack.push_attribute(("line-rate", package.line_rate.to_string().as_ref()));
            pack.push_attribute(("branch-rate", package.branch_rate.to_string().as_ref()));
            pack.push_attribute(("complexity", package.complexity.to_string().as_ref()));

            writer.write_event(Event::Start(pack))?;
            self.export_classes(&package.classes, writer)?;
            writer.write_event(Event::End(BytesEnd::borrowed(pack_tag)))?;
        }

        writer
            .write_event(Event::End(BytesEnd::borrowed(packages_tag)))
            .map(|_| ())
    }

    fn export_classes<T: Write>(
        &self,
        classes: &[Class],
        writer: &mut Writer<T>,
    ) -> Result<(), quick_xml::Error> {
        let classes_tag = b"classes";
        let class_tag = b"class";
        let methods_tag = b"methods";

        writer.write_event(Event::Start(BytesStart::borrowed(
            classes_tag,
            classes_tag.len(),
        )))?;
        for class in classes {
            let mut c = BytesStart::borrowed(class_tag, class_tag.len());
            c.push_attribute(("name", class.name.as_ref()));
            c.push_attribute(("filename", class.file_name.as_ref()));
            c.push_attribute(("line-rate", class.line_rate.to_string().as_ref()));
            c.push_attribute(("branch-rate", class.branch_rate.to_string().as_ref()));
            c.push_attribute(("complexity", class.complexity.to_string().as_ref()));

            writer.write_event(Event::Start(c))?;
            writer.write_event(Event::Empty(BytesStart::borrowed(
                methods_tag,
                methods_tag.len(),
            )))?;
            self.export_lines(&class.lines, writer)?;
            writer.write_event(Event::End(BytesEnd::borrowed(class_tag)))?;
        }
        writer
            .write_event(Event::End(BytesEnd::borrowed(classes_tag)))
            .map(|_| ())
    }

    fn export_lines<T: Write>(
        &self,
        lines: &[Line],
        writer: &mut Writer<T>,
    ) -> Result<(), quick_xml::Error> {
        let lines_tag = b"lines";
        let line_tag = b"line";

        writer.write_event(Event::Start(BytesStart::borrowed(
            lines_tag,
            lines_tag.len(),
        )))?;
        for line in lines {
            let mut l = BytesStart::borrowed(line_tag, line_tag.len());
            match line {
                Line::Plain {
                    ref number,
                    ref hits,
                } => {
                    l.push_attribute(("number", number.to_string().as_ref()));
                    l.push_attribute(("hits", hits.to_string().as_ref()));
                }
                Line::Branch { .. } => {}
            }
            writer.write_event(Event::Empty(l))?;
        }
        writer
            .write_event(Event::End(BytesEnd::borrowed(lines_tag)))
            .map(|_| ())
    }
}

fn render_sources(config: &Config) -> Vec<PathBuf> {
    vec![config.get_base_dir()]
}

#[derive(Debug)]
struct Package {
    name: String,
    line_rate: f64,
    branch_rate: f64,
    complexity: f64,
    classes: Vec<Class>,
}

fn render_packages(config: &Config, traces: &TraceMap) -> Vec<Package> {
    let dirs: HashSet<&Path> = traces
        .files()
        .into_iter()
        .filter_map(|x| x.parent())
        .collect();

    dirs.into_iter()
        .map(|x| render_package(config, traces, x))
        .collect()
}

fn render_package(config: &Config, traces: &TraceMap, pkg: &Path) -> Package {
    let name = config.strip_base_dir(pkg).to_str().unwrap().to_string();

    let line_cover = traces.covered_in_path(pkg) as f64;
    let line_rate = line_cover / (traces.coverable_in_path(pkg) as f64);

    Package {
        name: name,
        line_rate: line_rate,
        branch_rate: 0.0,
        complexity: 0.0,
        classes: render_classes(config, traces, pkg),
    }
}

#[derive(Debug)]
struct Class {
    name: String,
    file_name: String,
    line_rate: f64,
    branch_rate: f64,
    complexity: f64,
    lines: Vec<Line>,
    methods: Vec<Method>,
}

fn render_classes(config: &Config, traces: &TraceMap, pkg: &Path) -> Vec<Class> {
    traces
        .files()
        .iter()
        .filter(|x| x.parent() == Some(pkg))
        .map(|x| render_class(config, traces, x))
        .collect()
}

// TODO: Cobertura distinguishes between lines outside methods, and methods
// (which also contain lines). As there is currently no way to get traces from
// a particular function only, all traces are put into lines, and the vector
// of methods is empty.
//
// Until this is fixed, the render_method function will panic if called, as it
// cannot be properly implemented.
//
fn render_class(config: &Config, traces: &TraceMap, file: &Path) -> Class {
    let name = file
        .file_stem()
        .map(|x| x.to_str().unwrap())
        .unwrap_or_default()
        .to_string();

    let file_name = config.strip_base_dir(file).to_str().unwrap().to_string();

    let covered = traces.covered_in_path(file) as f64;
    let line_rate = covered / traces.coverable_in_path(file) as f64;
    let lines = traces
        .get_child_traces(file)
        .iter()
        .map(|x| render_line(x))
        .collect();

    Class {
        name: name,
        file_name: file_name,
        line_rate: line_rate,
        branch_rate: 0.0,
        complexity: 0.0,
        lines: lines,
        methods: vec![],
    }
}

#[derive(Debug)]
struct Method {
    name: String,
    signature: String,
    line_rate: f64,
    branch_rate: f64,
    lines: Vec<Line>,
}

fn render_methods() -> Vec<Method> {
    unimplemented!()
}

fn render_method() -> Method {
    unimplemented!()
}

#[derive(Debug)]
enum Line {
    Plain {
        number: usize,
        hits: usize,
    },

    Branch {
        number: usize,
        hits: usize,
        conditions: Vec<Condition>,
    },
}

fn render_line(trace: &Trace) -> Line {
    match &trace.stats {
        CoverageStat::Line(hits) => Line::Plain {
            number: trace.line as usize,
            hits: *hits as usize,
        },

        // TODO: Branches in cobertura are given a fresh number as a label,
        // which would require having some form of context when rendering.
        //
        _ => panic!("Not currently supported"),
    }
}

#[derive(Debug)]
struct Condition {
    number: usize,
    cond_type: ConditionType,
    coverage: f64,
}

// Condition types
#[derive(Debug)]
enum ConditionType {
    Jump,
}
<|endoftext|>


<commit_before>import std::map::hashmap;
import middle::ty;
import middle::ty::{arg, bound_region, br_anon, br_named, canon_mode};
import middle::ty::{ck_block, ck_box, ck_uniq, constr, ctxt, field, method};
import middle::ty::{mt, re_bound, re_free, re_scope, re_var, region, t};
import middle::ty::{ty_bool, ty_bot, ty_box, ty_class, ty_constr, ty_enum};
import middle::ty::{ty_estr, ty_evec, ty_float, ty_fn, ty_trait, ty_int};
import middle::ty::{ty_nil, ty_opaque_box, ty_opaque_closure_ptr, ty_param};
import middle::ty::{ty_ptr, ty_rec, ty_rptr, ty_self, ty_str, ty_tup};
import middle::ty::{ty_type, ty_uniq, ty_uint, ty_var, ty_var_integral};
import middle::ty::{ty_vec, ty_unboxed_vec, vid};
import metadata::encoder;
import syntax::codemap;
import syntax::print::pprust;
import syntax::print::pprust::{path_to_str, constr_args_to_str, proto_to_str,
                               mode_to_str, purity_to_str};
import syntax::{ast, ast_util};
import syntax::ast_map;
import driver::session::session;

fn bound_region_to_str(cx: ctxt, br: bound_region) -> str {
    alt br {
      br_anon                        { "&" }
      br_named(str)                  { #fmt["&%s", *str] }
      br_self if cx.sess.ppregions() { "&<self>" }
      br_self                        { "&self" }
    }
}

fn re_scope_id_to_str(cx: ctxt, node_id: ast::node_id) -> str {
    alt cx.items.find(node_id) {
      some(ast_map::node_block(blk)) {
        #fmt("<block at %s>",
             codemap::span_to_str(blk.span, cx.sess.codemap))
      }
      some(ast_map::node_expr(expr)) {
        alt expr.node {
          ast::expr_call(*) {
            #fmt("<call at %s>",
                 codemap::span_to_str(expr.span, cx.sess.codemap))
          }
          ast::expr_alt(*) {
            #fmt("<alt at %s>",
                 codemap::span_to_str(expr.span, cx.sess.codemap))
          }
          ast::expr_field(*) | ast::expr_unary(*) | ast::expr_binary(*) {
            #fmt("<method at %s>",
                 codemap::span_to_str(expr.span, cx.sess.codemap))
          }
          _ { cx.sess.bug(
              #fmt["re_scope refers to %s",
                   ast_map::node_id_to_str(cx.items, node_id)]) }
        }
      }
      _ { cx.sess.bug(
          #fmt["re_scope refers to %s",
               ast_map::node_id_to_str(cx.items, node_id)]) }
    }
}

fn region_to_str(cx: ctxt, region: region) -> str {
    alt region {
      re_scope(node_id) { #fmt["&%s", re_scope_id_to_str(cx, node_id)] }
      re_bound(br) { bound_region_to_str(cx, br) }
      re_free(id, br) {
        if cx.sess.ppregions() {
            // For debugging, this version is sometimes helpful:
            #fmt["{%d} %s", id, bound_region_to_str(cx, br)]
        } else {
            // But this version is what the user expects to see:
            bound_region_to_str(cx, br)
        }
      }

      // These two should not be seen by end-users (very often, anyhow):
      re_var(id)    { #fmt("&%s", id.to_str()) }
      re_static     { "&static" }
    }
}

fn mt_to_str(cx: ctxt, m: mt) -> str {
    let mstr = alt m.mutbl {
      ast::m_mutbl { "mut " }
      ast::m_imm { "" }
      ast::m_const { "const " }
    };
    ret mstr + ty_to_str(cx, m.ty);
}

fn vstore_to_str(cx: ctxt, vs: ty::vstore) -> str {
    alt vs {
      ty::vstore_fixed(n) { #fmt["%u", n] }
      ty::vstore_uniq { "~" }
      ty::vstore_box { "@" }
      ty::vstore_slice(r) { region_to_str(cx, r) }
    }
}

fn tys_to_str(cx: ctxt, ts: ~[t]) -> str {
    let mut rs = "";
    for ts.each |t| { rs += ty_to_str(cx, t); }
    rs
}

fn ty_to_str(cx: ctxt, typ: t) -> str {
    fn fn_input_to_str(cx: ctxt, input: {mode: ast::mode, ty: t}) ->
       str {
        let {mode, ty} = input;
        let modestr = alt canon_mode(cx, mode) {
          ast::infer(_) { "" }
          ast::expl(m) {
            if !ty::type_needs_infer(ty) &&
                m == ty::default_arg_mode_for_ty(ty) {
                ""
            } else {
                mode_to_str(ast::expl(m))
            }
          }
        };
        modestr + ty_to_str(cx, ty)
    }
    fn fn_to_str(cx: ctxt, purity: ast::purity, proto: ast::proto,
                 ident: option<ast::ident>,
                 inputs: ~[arg], output: t, cf: ast::ret_style,
                 constrs: ~[@constr]) -> str {
        let mut s;

        s = alt purity {
          ast::impure_fn {""}
          _ {purity_to_str(purity) + " "}
        };
        s += proto_to_str(proto);
        alt ident { some(i) { s += " "; s += *i; } _ { } }
        s += "(";
        let mut strs = ~[];
        for inputs.each |a| { vec::push(strs, fn_input_to_str(cx, a)); }
        s += str::connect(strs, ", ");
        s += ")";
        if ty::get(output).struct != ty_nil {
            s += " -> ";
            alt cf {
              ast::noreturn { s += "!"; }
              ast::return_val { s += ty_to_str(cx, output); }
            }
        }
        s += constrs_str(constrs);
        ret s;
    }
    fn method_to_str(cx: ctxt, m: method) -> str {
        ret fn_to_str(
            cx, m.fty.purity, m.fty.proto, some(m.ident), m.fty.inputs,
            m.fty.output, m.fty.ret_style, m.fty.constraints) + ";";
    }
    fn field_to_str(cx: ctxt, f: field) -> str {
        ret *f.ident + ": " + mt_to_str(cx, f.mt);
    }

    // if there is an id, print that instead of the structural type:
    for ty::type_def_id(typ).each |def_id| {
        // note that this typedef cannot have type parameters
        ret ast_map::path_to_str(ty::item_path(cx, def_id));
    }

    // pretty print the structural type representation:
    ret alt ty::get(typ).struct {
      ty_nil { "()" }
      ty_bot { "_|_" }
      ty_bool { "bool" }
      ty_int(ast::ty_i) { "int" }
      ty_int(ast::ty_char) { "char" }
      ty_int(t) { ast_util::int_ty_to_str(t) }
      ty_uint(ast::ty_u) { "uint" }
      ty_uint(t) { ast_util::uint_ty_to_str(t) }
      ty_float(ast::ty_f) { "float" }
      ty_float(t) { ast_util::float_ty_to_str(t) }
      ty_str { "str" }
      ty_box(tm) { "@" + mt_to_str(cx, tm) }
      ty_uniq(tm) { "~" + mt_to_str(cx, tm) }
      ty_ptr(tm) { "*" + mt_to_str(cx, tm) }
      ty_rptr(r, tm) {
        let rs = region_to_str(cx, r);
        if str::len(rs) == 1u {
            rs + mt_to_str(cx, tm)
        } else {
            rs + "." + mt_to_str(cx, tm)
        }
      }
      ty_vec(tm) { "[" + mt_to_str(cx, tm) + "]" }
      ty_unboxed_vec(tm) { "unboxed_vec<" + mt_to_str(cx, tm) + ">" }
      ty_type { "type" }
      ty_rec(elems) {
        let mut strs: ~[str] = ~[];
        for elems.each |fld| { vec::push(strs, field_to_str(cx, fld)); }
        "{" + str::connect(strs, ",") + "}"
      }
      ty_tup(elems) {
        let mut strs = ~[];
        for elems.each |elem| { vec::push(strs, ty_to_str(cx, elem)); }
        "(" + str::connect(strs, ",") + ")"
      }
      ty_fn(f) {
        fn_to_str(cx, f.purity, f.proto, none, f.inputs,
                  f.output, f.ret_style, f.constraints)
      }
      ty_var(v) { v.to_str() }
      ty_var_integral(v) { v.to_str() }
      ty_param(id, _) {
        "'" + str::from_bytes(~[('a' as u8) + (id as u8)])
      }
      ty_self { "self" }
      ty_enum(did, substs) | ty_class(did, substs) {
        let path = ty::item_path(cx, did);
        let base = ast_map::path_to_str(path);
        parameterized(cx, base, substs.self_r, substs.tps)
      }
      ty_trait(did, substs) {
        let path = ty::item_path(cx, did);
        let base = ast_map::path_to_str(path);
        parameterized(cx, base, substs.self_r, substs.tps)
      }
      ty_evec(mt, vs) {
        #fmt["[%s]/%s", mt_to_str(cx, mt),
             vstore_to_str(cx, vs)]
      }
      ty_estr(vs) { #fmt["str/%s", vstore_to_str(cx, vs)] }
      ty_opaque_box { "@?" }
      ty_constr(t, _) { "@?" }
      ty_opaque_closure_ptr(ck_block) { "closure&" }
      ty_opaque_closure_ptr(ck_box) { "closure@" }
      ty_opaque_closure_ptr(ck_uniq) { "closure~" }
    }
}

fn parameterized(cx: ctxt,
                 base: str,
                 self_r: option<ty::region>,
                 tps: ~[ty::t]) -> str {

    let r_str = alt self_r {
      none { "" }
      some(r) {
        #fmt["/%s", region_to_str(cx, r)]
      }
    };

    if vec::len(tps) > 0u {
        let strs = vec::map(tps, |t| ty_to_str(cx, t) );
        #fmt["%s%s<%s>", base, r_str, str::connect(strs, ",")]
    } else {
        #fmt["%s%s", base, r_str]
    }
}

fn ty_to_short_str(cx: ctxt, typ: t) -> str {
    let mut s = encoder::encoded_ty(cx, typ);
    if str::len(s) >= 32u { s = str::slice(s, 0u, 32u); }
    ret s;
}

fn constr_to_str(c: @constr) -> str {
    ret path_to_str(c.node.path) +
            pprust::constr_args_to_str(pprust::uint_to_str, c.node.args);
}

fn constrs_str(constrs: ~[@constr]) -> str {
    let mut s = "";
    let mut colon = true;
    for constrs.each |c| {
        if colon { s += " : "; colon = false; } else { s += ", "; }
        s += constr_to_str(c);
    }
    ret s;
}

fn ty_constr_to_str<Q>(c: @ast::spanned<ast::constr_general_<@ast::path, Q>>)
   -> str {
    ret path_to_str(c.node.path) +
            constr_args_to_str::<@ast::path>(path_to_str, c.node.args);
}

// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
<commit_msg>paper over #2586 by not failing when the key is not found<commit_after>import std::map::hashmap;
import middle::ty;
import middle::ty::{arg, bound_region, br_anon, br_named, canon_mode};
import middle::ty::{ck_block, ck_box, ck_uniq, constr, ctxt, field, method};
import middle::ty::{mt, re_bound, re_free, re_scope, re_var, region, t};
import middle::ty::{ty_bool, ty_bot, ty_box, ty_class, ty_constr, ty_enum};
import middle::ty::{ty_estr, ty_evec, ty_float, ty_fn, ty_trait, ty_int};
import middle::ty::{ty_nil, ty_opaque_box, ty_opaque_closure_ptr, ty_param};
import middle::ty::{ty_ptr, ty_rec, ty_rptr, ty_self, ty_str, ty_tup};
import middle::ty::{ty_type, ty_uniq, ty_uint, ty_var, ty_var_integral};
import middle::ty::{ty_vec, ty_unboxed_vec, vid};
import metadata::encoder;
import syntax::codemap;
import syntax::print::pprust;
import syntax::print::pprust::{path_to_str, constr_args_to_str, proto_to_str,
                               mode_to_str, purity_to_str};
import syntax::{ast, ast_util};
import syntax::ast_map;
import driver::session::session;

fn bound_region_to_str(cx: ctxt, br: bound_region) -> str {
    alt br {
      br_anon                        { "&" }
      br_named(str)                  { #fmt["&%s", *str] }
      br_self if cx.sess.ppregions() { "&<self>" }
      br_self                        { "&self" }
    }
}

fn re_scope_id_to_str(cx: ctxt, node_id: ast::node_id) -> str {
    alt cx.items.find(node_id) {
      some(ast_map::node_block(blk)) {
        #fmt("<block at %s>",
             codemap::span_to_str(blk.span, cx.sess.codemap))
      }
      some(ast_map::node_expr(expr)) {
        alt expr.node {
          ast::expr_call(*) {
            #fmt("<call at %s>",
                 codemap::span_to_str(expr.span, cx.sess.codemap))
          }
          ast::expr_alt(*) {
            #fmt("<alt at %s>",
                 codemap::span_to_str(expr.span, cx.sess.codemap))
          }
          ast::expr_field(*) | ast::expr_unary(*) | ast::expr_binary(*) {
            #fmt("<method at %s>",
                 codemap::span_to_str(expr.span, cx.sess.codemap))
          }
          _ { cx.sess.bug(
              #fmt["re_scope refers to %s",
                   ast_map::node_id_to_str(cx.items, node_id)]) }
        }
      }
      none {
        // FIXME(#2586)
        #fmt["<unknown-%d>", node_id]
      }
      _ { cx.sess.bug(
          #fmt["re_scope refers to %s",
               ast_map::node_id_to_str(cx.items, node_id)]) }
    }
}

fn region_to_str(cx: ctxt, region: region) -> str {
    alt region {
      re_scope(node_id) { #fmt["&%s", re_scope_id_to_str(cx, node_id)] }
      re_bound(br) { bound_region_to_str(cx, br) }
      re_free(id, br) {
        if cx.sess.ppregions() {
            // For debugging, this version is sometimes helpful:
            #fmt["{%d} %s", id, bound_region_to_str(cx, br)]
        } else {
            // But this version is what the user expects to see:
            bound_region_to_str(cx, br)
        }
      }

      // These two should not be seen by end-users (very often, anyhow):
      re_var(id)    { #fmt("&%s", id.to_str()) }
      re_static     { "&static" }
    }
}

fn mt_to_str(cx: ctxt, m: mt) -> str {
    let mstr = alt m.mutbl {
      ast::m_mutbl { "mut " }
      ast::m_imm { "" }
      ast::m_const { "const " }
    };
    ret mstr + ty_to_str(cx, m.ty);
}

fn vstore_to_str(cx: ctxt, vs: ty::vstore) -> str {
    alt vs {
      ty::vstore_fixed(n) { #fmt["%u", n] }
      ty::vstore_uniq { "~" }
      ty::vstore_box { "@" }
      ty::vstore_slice(r) { region_to_str(cx, r) }
    }
}

fn tys_to_str(cx: ctxt, ts: ~[t]) -> str {
    let mut rs = "";
    for ts.each |t| { rs += ty_to_str(cx, t); }
    rs
}

fn ty_to_str(cx: ctxt, typ: t) -> str {
    fn fn_input_to_str(cx: ctxt, input: {mode: ast::mode, ty: t}) ->
       str {
        let {mode, ty} = input;
        let modestr = alt canon_mode(cx, mode) {
          ast::infer(_) { "" }
          ast::expl(m) {
            if !ty::type_needs_infer(ty) &&
                m == ty::default_arg_mode_for_ty(ty) {
                ""
            } else {
                mode_to_str(ast::expl(m))
            }
          }
        };
        modestr + ty_to_str(cx, ty)
    }
    fn fn_to_str(cx: ctxt, purity: ast::purity, proto: ast::proto,
                 ident: option<ast::ident>,
                 inputs: ~[arg], output: t, cf: ast::ret_style,
                 constrs: ~[@constr]) -> str {
        let mut s;

        s = alt purity {
          ast::impure_fn {""}
          _ {purity_to_str(purity) + " "}
        };
        s += proto_to_str(proto);
        alt ident { some(i) { s += " "; s += *i; } _ { } }
        s += "(";
        let mut strs = ~[];
        for inputs.each |a| { vec::push(strs, fn_input_to_str(cx, a)); }
        s += str::connect(strs, ", ");
        s += ")";
        if ty::get(output).struct != ty_nil {
            s += " -> ";
            alt cf {
              ast::noreturn { s += "!"; }
              ast::return_val { s += ty_to_str(cx, output); }
            }
        }
        s += constrs_str(constrs);
        ret s;
    }
    fn method_to_str(cx: ctxt, m: method) -> str {
        ret fn_to_str(
            cx, m.fty.purity, m.fty.proto, some(m.ident), m.fty.inputs,
            m.fty.output, m.fty.ret_style, m.fty.constraints) + ";";
    }
    fn field_to_str(cx: ctxt, f: field) -> str {
        ret *f.ident + ": " + mt_to_str(cx, f.mt);
    }

    // if there is an id, print that instead of the structural type:
    for ty::type_def_id(typ).each |def_id| {
        // note that this typedef cannot have type parameters
        ret ast_map::path_to_str(ty::item_path(cx, def_id));
    }

    // pretty print the structural type representation:
    ret alt ty::get(typ).struct {
      ty_nil { "()" }
      ty_bot { "_|_" }
      ty_bool { "bool" }
      ty_int(ast::ty_i) { "int" }
      ty_int(ast::ty_char) { "char" }
      ty_int(t) { ast_util::int_ty_to_str(t) }
      ty_uint(ast::ty_u) { "uint" }
      ty_uint(t) { ast_util::uint_ty_to_str(t) }
      ty_float(ast::ty_f) { "float" }
      ty_float(t) { ast_util::float_ty_to_str(t) }
      ty_str { "str" }
      ty_box(tm) { "@" + mt_to_str(cx, tm) }
      ty_uniq(tm) { "~" + mt_to_str(cx, tm) }
      ty_ptr(tm) { "*" + mt_to_str(cx, tm) }
      ty_rptr(r, tm) {
        let rs = region_to_str(cx, r);
        if str::len(rs) == 1u {
            rs + mt_to_str(cx, tm)
        } else {
            rs + "." + mt_to_str(cx, tm)
        }
      }
      ty_vec(tm) { "[" + mt_to_str(cx, tm) + "]" }
      ty_unboxed_vec(tm) { "unboxed_vec<" + mt_to_str(cx, tm) + ">" }
      ty_type { "type" }
      ty_rec(elems) {
        let mut strs: ~[str] = ~[];
        for elems.each |fld| { vec::push(strs, field_to_str(cx, fld)); }
        "{" + str::connect(strs, ",") + "}"
      }
      ty_tup(elems) {
        let mut strs = ~[];
        for elems.each |elem| { vec::push(strs, ty_to_str(cx, elem)); }
        "(" + str::connect(strs, ",") + ")"
      }
      ty_fn(f) {
        fn_to_str(cx, f.purity, f.proto, none, f.inputs,
                  f.output, f.ret_style, f.constraints)
      }
      ty_var(v) { v.to_str() }
      ty_var_integral(v) { v.to_str() }
      ty_param(id, _) {
        "'" + str::from_bytes(~[('a' as u8) + (id as u8)])
      }
      ty_self { "self" }
      ty_enum(did, substs) | ty_class(did, substs) {
        let path = ty::item_path(cx, did);
        let base = ast_map::path_to_str(path);
        parameterized(cx, base, substs.self_r, substs.tps)
      }
      ty_trait(did, substs) {
        let path = ty::item_path(cx, did);
        let base = ast_map::path_to_str(path);
        parameterized(cx, base, substs.self_r, substs.tps)
      }
      ty_evec(mt, vs) {
        #fmt["[%s]/%s", mt_to_str(cx, mt),
             vstore_to_str(cx, vs)]
      }
      ty_estr(vs) { #fmt["str/%s", vstore_to_str(cx, vs)] }
      ty_opaque_box { "@?" }
      ty_constr(t, _) { "@?" }
      ty_opaque_closure_ptr(ck_block) { "closure&" }
      ty_opaque_closure_ptr(ck_box) { "closure@" }
      ty_opaque_closure_ptr(ck_uniq) { "closure~" }
    }
}

fn parameterized(cx: ctxt,
                 base: str,
                 self_r: option<ty::region>,
                 tps: ~[ty::t]) -> str {

    let r_str = alt self_r {
      none { "" }
      some(r) {
        #fmt["/%s", region_to_str(cx, r)]
      }
    };

    if vec::len(tps) > 0u {
        let strs = vec::map(tps, |t| ty_to_str(cx, t) );
        #fmt["%s%s<%s>", base, r_str, str::connect(strs, ",")]
    } else {
        #fmt["%s%s", base, r_str]
    }
}

fn ty_to_short_str(cx: ctxt, typ: t) -> str {
    let mut s = encoder::encoded_ty(cx, typ);
    if str::len(s) >= 32u { s = str::slice(s, 0u, 32u); }
    ret s;
}

fn constr_to_str(c: @constr) -> str {
    ret path_to_str(c.node.path) +
            pprust::constr_args_to_str(pprust::uint_to_str, c.node.args);
}

fn constrs_str(constrs: ~[@constr]) -> str {
    let mut s = "";
    let mut colon = true;
    for constrs.each |c| {
        if colon { s += " : "; colon = false; } else { s += ", "; }
        s += constr_to_str(c);
    }
    ret s;
}

fn ty_constr_to_str<Q>(c: @ast::spanned<ast::constr_general_<@ast::path, Q>>)
   -> str {
    ret path_to_str(c.node.path) +
            constr_args_to_str::<@ast::path>(path_to_str, c.node.args);
}

// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
<|endoftext|>