<commit_before>// http://rosettacode.org/wiki/Sorting_algorithms/Bogosort
extern crate rand;
use rand::{thread_rng, Rng};

fn bogo_sort<T: Ord>(mut v: &mut [T]) {
    let mut rng = thread_rng();
    while !check_sort(v) {
        rng.shuffle(&mut v);
    }
}
// helper function that checks for ascending order (might be renamed)
fn check_sort<T: Ord>(v: &[T]) -> bool {
    if v.len() > 1 {
        for i in 0..(v.len() - 1) {
            if v[i] > v[i + 1] {
                return false;
            }
        }
    }
    true
}
fn main() {
    let mut numbers = [4i32, 65, 2, -31, 0, 99, 2, 83, 782, 1];
    println!("Before: {:?}", numbers);
    bogo_sort(&mut numbers);
    println!("After: {:?}", numbers);
}
#[cfg(test)]
mod tests {
    use super::bogo_sort;
    fn check_sort<T: Ord>(v: &[T]) {
        if v.len() > 1 {
            for i in 0..(v.len() - 1) {
                assert!(v[i] <= v[i + 1]);
            }
        }
    }
    #[test]
    fn test_rosetta_vector() {
        let numbers = &mut [4i32, 65, 2, -31, 0, 99, 2, 83, 782, 1];
        bogo_sort(numbers);
        check_sort(numbers);
    }
    #[test]
    fn test_empty_vector() {
        let mut numbers: Vec<i32> = Vec::new();
        bogo_sort(&mut numbers[..]);
        check_sort(&mut numbers[..]);
    }
    #[test]
    fn test_one_element_vector() {
        let numbers = &mut [0i32];
        bogo_sort(numbers);
        check_sort(numbers);
    }
    #[test]
    fn test_repeat_vector() {
        let numbers = &mut [1i32, 1, 1, 1, 1];
        bogo_sort(numbers);
        check_sort(numbers);
    }
    #[test]
    fn test_string_vector() {
        let strings = &mut ["beach", "hotel", "airplane", "car", "house", "art"];
        bogo_sort(strings);
        check_sort(strings);
    }
}
<commit_msg>format and refactor bogo_sort<commit_after>// http://rosettacode.org/wiki/Sorting_algorithms/Bogosort
extern crate rand;
use rand::{thread_rng, Rng};

fn bogo_sort<T: Ord>(mut v: &mut [T]) {
    let mut rng = thread_rng();
    while !is_sorted(v) {
        rng.shuffle(&mut v);
    }
}

// helper function that checks for ascending order
fn is_sorted<T: Ord>(v: &[T]) -> bool {
    if v.len() > 1 {
        for i in 0..(v.len() - 1) {
            if v[i] > v[i + 1] {
                return false;
            }
        }
    }
    true
}

fn main() {
    let mut numbers = [4i32, 65, 2, -31, 0, 99, 2, 83, 782, 1];
    println!("Before: {:?}", numbers);
    bogo_sort(&mut numbers);
    println!("After: {:?}", numbers);
}

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

    fn check_sort<T: Ord>(v: &[T]) {
        assert!(super::is_sorted(v));
    }

    #[test]
    fn test_rosetta_vector() {
        let numbers = &mut [4i32, 65, 2, -31, 0, 99, 2, 83, 782, 1];
        bogo_sort(numbers);
        check_sort(numbers);
    }

    #[test]
    fn test_empty_vector() {
        let mut numbers: Vec<i32> = Vec::new();
        bogo_sort(&mut numbers[..]);
        check_sort(&mut numbers[..]);
    }

    #[test]
    fn test_one_element_vector() {
        let numbers = &mut [0i32];
        bogo_sort(numbers);
        check_sort(numbers);
    }

    #[test]
    fn test_repeat_vector() {
        let numbers = &mut [1i32, 1, 1, 1, 1];
        bogo_sort(numbers);
        check_sort(numbers);
    }

    #[test]
    fn test_string_vector() {
        let strings = &mut ["beach", "hotel", "airplane", "car", "house", "art"];
        bogo_sort(strings);
        check_sort(strings);
    }
}
<|endoftext|>


<commit_before>extern mod std;
use std::hashmap::HashMap;
use sexpr;

// A very simple LISP-like language
// Globally scoped, no closures

/// Our value types
#[deriving(Clone)]
pub enum LispValue {
	List(~[LispValue]),
	Atom(~str),
	Str(~str),
	Num(float),
	Fn(~[~str], ~LispValue), // args, body
	BIF(~str, ~[~str], extern fn(~[~LispValue])->~LispValue) // built-in function (args, closure)
}

// XXX: this is ugly but it won't automatically derive Eq because of the extern fn
impl Eq for LispValue {
	fn eq(&self, other: &LispValue) -> bool {
		match (self.clone(), other.clone()) {
			(BIF(ref x, _, _), BIF(ref y, _, _)) if *x == *y => true,
			(Str(ref x), Str(ref y)) if *x == *y => true,
			(Num(ref x), Num(ref y)) if *x == *y => true,
			(Atom(ref x), Atom(ref y)) if *x == *y => true,
			(List(ref x), List(ref y)) if *x == *y => true,
			(Fn(ref x, ref x2), Fn(ref y, ref y2)) if *x == *y && *x2 == *y2 => true,
			_ => false
		}
	}
}

fn from_sexpr(sexpr: &sexpr::Value) -> ~LispValue {
	match *sexpr {
		sexpr::List(ref v) => ~List(v.map(|x| *from_sexpr(x))),
		sexpr::Num(v) => ~Num(v),
		sexpr::Str(ref v) => ~Str(v.clone()),
		sexpr::Atom(ref v) => ~Atom(v.clone())
	}
}

/// The type of the global symbol table (string to a value mapping).
type SymbolTable = HashMap<~str, ~LispValue>;

/// Creates a new symbol table and returns it
pub fn new_symt() -> SymbolTable {
	HashMap::new()
}

/// Binds a symbol in the symbol table. Replaces if it already exists.
pub fn bind(symt: &mut SymbolTable, name: ~str, value: ~LispValue) {
	symt.insert(name, value);
}

/// Look up a symbol in the symbol table. Fails if not found.
pub fn lookup(symt: &SymbolTable, name: ~str) -> ~LispValue {
	match symt.find(&name) {
		Some(v) => v.clone(),
		None => fail!("couldn't find symbol: %s", name)
	}
}

fn id_(v: ~[~LispValue]) -> ~LispValue { v[0] }

/// Initializes standard library functions
pub fn init_std(symt: &mut SymbolTable) {
	bind(symt, ~"id", ~BIF(~"id", ~[~"x"], id_));
}

fn apply(symt: &mut SymbolTable, f: ~LispValue, args: ~[~LispValue]) -> ~LispValue {
	fail!("stub: apply")
}

/// Evaluates an s-expression and returns a value.
pub fn eval(symt: &mut SymbolTable, input: sexpr::Value) -> ~LispValue {
	match input {
		sexpr::List(v) => {
			if(v.len() == 0) {
				fail!("eval given empty list")
			}

			// XXX: If we don't clone, the `match` partially moves v,
			// so we can't use it in the match arms.
			let v_ = v.clone();

			// evaluate a list as a function call
			match v[0] {
				sexpr::Atom(sym) => {
					let f = lookup(symt, sym);
					let args = v_.slice(1, v_.len());
					let xargs = args.map(|x| eval(symt, x.clone())); // eval'd args
					apply(symt, f, xargs)
				}
				_ => fail!("function calls take an atom"),
			}

		}
		_ => from_sexpr(&input) // return non-list values as they are
	}
}

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

	fn read(input: &str) -> sexpr::Value {
		from_str(input).unwrap()
	}

	#[test]
	fn test_eval() {
		let mut symt = new_symt();
		init_std(&mut symt);
		assert_eq!(eval(&mut symt, read("123")), ~Num(123.0));
		assert_eq!(eval(&mut symt, read("(id 123)")), ~Num(123.0));
		// should fail: assert_eq!(eval(&mut symt, read("(1 2 3)")), ~List(~[Num(1.0), Num(2.0), Num(3.0)]));
	}
}<commit_msg>implement apply for BIFs<commit_after>extern mod std;
use std::hashmap::HashMap;
use sexpr;

// A very simple LISP-like language
// Globally scoped, no closures

/// Our value types
#[deriving(Clone)]
pub enum LispValue {
	List(~[LispValue]),
	Atom(~str),
	Str(~str),
	Num(float),
	Fn(~[~str], ~LispValue), // args, body
	BIF(~str, ~[~str], extern fn(~[~LispValue])->~LispValue) // built-in function (args, closure)
}

// XXX: this is ugly but it won't automatically derive Eq because of the extern fn
impl Eq for LispValue {
	fn eq(&self, other: &LispValue) -> bool {
		match (self.clone(), other.clone()) {
			(BIF(ref x, _, _), BIF(ref y, _, _)) if *x == *y => true,
			(Str(ref x), Str(ref y)) if *x == *y => true,
			(Num(ref x), Num(ref y)) if *x == *y => true,
			(Atom(ref x), Atom(ref y)) if *x == *y => true,
			(List(ref x), List(ref y)) if *x == *y => true,
			(Fn(ref x, ref x2), Fn(ref y, ref y2)) if *x == *y && *x2 == *y2 => true,
			_ => false
		}
	}
}

fn from_sexpr(sexpr: &sexpr::Value) -> ~LispValue {
	match *sexpr {
		sexpr::List(ref v) => ~List(v.map(|x| *from_sexpr(x))),
		sexpr::Num(v) => ~Num(v),
		sexpr::Str(ref v) => ~Str(v.clone()),
		sexpr::Atom(ref v) => ~Atom(v.clone())
	}
}

/// The type of the global symbol table (string to a value mapping).
type SymbolTable = HashMap<~str, ~LispValue>;

/// Creates a new symbol table and returns it
pub fn new_symt() -> SymbolTable {
	HashMap::new()
}

/// Binds a symbol in the symbol table. Replaces if it already exists.
pub fn bind(symt: &mut SymbolTable, name: ~str, value: ~LispValue) {
	symt.insert(name, value);
}

/// Look up a symbol in the symbol table. Fails if not found.
pub fn lookup(symt: &SymbolTable, name: ~str) -> ~LispValue {
	match symt.find(&name) {
		Some(v) => v.clone(),
		None => fail!("couldn't find symbol: %s", name)
	}
}

fn id_(v: ~[~LispValue]) -> ~LispValue { v[0] }

/// Initializes standard library functions
pub fn init_std(symt: &mut SymbolTable) {
	bind(symt, ~"id", ~BIF(~"id", ~[~"x"], id_));
}

fn apply(symt: &mut SymbolTable, f: ~LispValue, args: ~[~LispValue]) -> ~LispValue {
	match *f {
		BIF(name, fnargs, bif) => {
			// apply built-in function
			if fnargs.len() != args.len() {
				fail!("function '%s' requires %u arguments, but it received %u arguments",
					name, fnargs.len(), args.len())
			}

			bif(args)
		}

		_ => fail!("apply: need function")
	}
}

/// Evaluates an s-expression and returns a value.
pub fn eval(symt: &mut SymbolTable, input: sexpr::Value) -> ~LispValue {
	match input {
		sexpr::List(v) => {
			if(v.len() == 0) {
				fail!("eval given empty list")
			}

			// XXX: If we don't clone, the `match` partially moves v,
			// so we can't use it in the match arms.
			let v_ = v.clone();

			// evaluate a list as a function call
			match v[0] {
				sexpr::Atom(sym) => {
					let f = lookup(symt, sym);
					let args = v_.slice(1, v_.len());
					let xargs = args.map(|x| eval(symt, x.clone())); // eval'd args
					apply(symt, f, xargs)
				}
				_ => fail!("function calls take an atom"),
			}

		}
		_ => from_sexpr(&input) // return non-list values as they are
	}
}

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

	fn read(input: &str) -> sexpr::Value {
		from_str(input).unwrap()
	}

	#[test]
	fn test_eval() {
		let mut symt = new_symt();
		init_std(&mut symt);
		assert_eq!(eval(&mut symt, read("123")), ~Num(123.0));
		assert_eq!(eval(&mut symt, read("(id 123)")), ~Num(123.0));
		// should fail: assert_eq!(eval(&mut symt, read("(1 2 3)")), ~List(~[Num(1.0), Num(2.0), Num(3.0)]));
	}
}<|endoftext|>


<commit_before>use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;

use colored::*;
use comments::ErrorMatch;
use crossbeam::queue::SegQueue;
use regex::Regex;

pub use crate::comments::Comments;

mod comments;

#[derive(Debug)]
pub struct Config {
    /// Arguments passed to the binary that is executed.
    pub args: Vec<String>,
    /// `None` to run on the host, otherwise a target triple
    pub target: Option<String>,
    /// Filters applied to stderr output before processing it
    pub stderr_filters: Filter,
    /// Filters applied to stdout output before processing it
    pub stdout_filters: Filter,
    /// The folder in which to start searching for .rs files
    pub root_dir: PathBuf,
    pub mode: Mode,
    pub program: PathBuf,
    pub output_conflict_handling: OutputConflictHandling,
}

#[derive(Debug)]
pub enum OutputConflictHandling {
    /// The default: emit a diff of the expected/actual output.
    Error,
    /// Ignore mismatches in the stderr/stdout files.
    Ignore,
    /// Instead of erroring if the stderr/stdout differs from the expected
    /// automatically replace it with the found output (after applying filters).
    Bless,
}

pub type Filter = Vec<(Regex, &'static str)>;

pub fn run_tests(config: Config) {
    eprintln!("   Compiler flags: {:?}", config.args);

    // Get the triple with which to run the tests
    let target = config.target.clone().unwrap_or_else(|| config.get_host());

    // A queue for files or folders to process
    let todo = SegQueue::new();
    todo.push(config.root_dir.clone());

    // Some statistics and failure reports.
    let failures = Mutex::new(vec![]);
    let succeeded = AtomicUsize::default();
    let ignored = AtomicUsize::default();

    crossbeam::scope(|s| {
        for _ in 0..std::thread::available_parallelism().unwrap().get() {
            s.spawn(|_| {
                while let Some(path) = todo.pop() {
                    // Collect everything inside directories
                    if path.is_dir() {
                        for entry in std::fs::read_dir(path).unwrap() {
                            todo.push(entry.unwrap().path());
                        }
                        continue;
                    }
                    // Only look at .rs files
                    if !path.extension().map(|ext| ext == "rs").unwrap_or(false) {
                        continue;
                    }
                    let comments = Comments::parse_file(&path);
                    // Ignore file if only/ignore rules do (not) apply
                    if ignore_file(&comments, &target) {
                        ignored.fetch_add(1, Ordering::Relaxed);
                        eprintln!("{} .. {}", path.display(), "ignored".yellow());
                        continue;
                    }
                    // Run the test for all revisions
                    for revision in
                        comments.revisions.clone().unwrap_or_else(|| vec![String::new()])
                    {
                        let (m, errors) = run_test(&path, &config, &target, &revision, &comments);

                        // Using `format` to prevent messages from threads from getting intermingled.
                        let mut msg = format!("{} ", path.display());
                        if !revision.is_empty() {
                            write!(msg, "(revision `{revision}`) ").unwrap();
                        }
                        write!(msg, "... ").unwrap();
                        if errors.is_empty() {
                            eprintln!("{msg}{}", "ok".green());
                            succeeded.fetch_add(1, Ordering::Relaxed);
                        } else {
                            eprintln!("{msg}{}", "FAILED".red().bold());
                            failures.lock().unwrap().push((path.clone(), m, revision, errors));
                        }
                    }
                }
            });
        }
    })
    .unwrap();

    // Print all errors in a single thread to show reliable output
    let failures = failures.into_inner().unwrap();
    let succeeded = succeeded.load(Ordering::Relaxed);
    let ignored = ignored.load(Ordering::Relaxed);
    if !failures.is_empty() {
        for (path, miri, revision, errors) in &failures {
            eprintln!();
            eprint!("{}", path.display().to_string().underline());
            if !revision.is_empty() {
                eprint!(" (revision `{}`)", revision);
            }
            eprint!(" {}", "FAILED".red());
            eprintln!();
            eprintln!("command: {:?}", miri);
            eprintln!();
            let mut dump_stderr = None;
            for error in errors {
                match error {
                    Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
                    Error::PatternNotFound { stderr, pattern, definition_line } => {
                        eprintln!("`{pattern}` {} in stderr output", "not found".red());
                        eprintln!(
                            "expected because of pattern here: {}:{definition_line}",
                            path.display()
                        );
                        dump_stderr = Some(stderr.clone())
                    }
                    Error::NoPatternsFound =>
                        eprintln!("{}", "no error patterns found in failure test".red()),
                    Error::PatternFoundInPassTest =>
                        eprintln!("{}", "error pattern found in success test".red()),
                    Error::OutputDiffers { path, actual, expected } => {
                        dump_stderr = None;
                        eprintln!("actual output differed from expected {}", path.display());
                        eprintln!("{}", pretty_assertions::StrComparison::new(expected, actual));
                        eprintln!()
                    }
                }
                eprintln!();
            }
            if let Some(stderr) = dump_stderr {
                eprintln!("actual stderr:");
                eprintln!("{}", stderr);
                eprintln!();
            }
        }
        eprintln!(
            "{} tests failed, {} tests passed, {} ignored",
            failures.len().to_string().red().bold(),
            succeeded.to_string().green(),
            ignored.to_string().yellow()
        );
        std::process::exit(1);
    }
    eprintln!();
    eprintln!(
        "test result: {}. {} tests passed, {} ignored",
        "ok".green(),
        succeeded.to_string().green(),
        ignored.to_string().yellow()
    );
    eprintln!();
}

#[derive(Debug)]
pub enum Error {
    /// Got an invalid exit status for the given mode.
    ExitStatus(Mode, ExitStatus),
    PatternNotFound {
        stderr: String,
        pattern: String,
        definition_line: usize,
    },
    /// A ui test checking for failure does not have any failure patterns
    NoPatternsFound,
    /// A ui test checking for success has failure patterns
    PatternFoundInPassTest,
    /// Stderr/Stdout differed from the `.stderr`/`.stdout` file present.
    OutputDiffers {
        path: PathBuf,
        actual: String,
        expected: String,
    },
}

pub type Errors = Vec<Error>;

fn run_test(
    path: &Path,
    config: &Config,
    target: &str,
    revision: &str,
    comments: &Comments,
) -> (Command, Errors) {
    // Run miri
    let mut miri = Command::new(&config.program);
    miri.args(config.args.iter());
    miri.arg(path);
    if !revision.is_empty() {
        miri.arg(format!("--cfg={revision}"));
    }
    for arg in &comments.compile_flags {
        miri.arg(arg);
    }
    for (k, v) in &comments.env_vars {
        miri.env(k, v);
    }
    let output = miri.output().expect("could not execute miri");
    let mut errors = config.mode.ok(output.status);
    // Check output files (if any)
    let revised = |extension: &str| {
        if revision.is_empty() {
            extension.to_string()
        } else {
            format!("{}.{}", revision, extension)
        }
    };
    // Check output files against actual output
    check_output(
        &output.stderr,
        path,
        &mut errors,
        revised("stderr"),
        target,
        &config.stderr_filters,
        &config,
        comments,
    );
    check_output(
        &output.stdout,
        path,
        &mut errors,
        revised("stdout"),
        target,
        &config.stdout_filters,
        &config,
        comments,
    );
    // Check error annotations in the source against output
    check_annotations(&output.stderr, &mut errors, config, revision, comments);
    (miri, errors)
}

pub fn check_annotations(
    unnormalized_stderr: &[u8],
    errors: &mut Errors,
    config: &Config,
    revision: &str,
    comments: &Comments,
) {
    let unnormalized_stderr = std::str::from_utf8(unnormalized_stderr).unwrap();
    // erase annotations from the stderr so they don't match themselves
    let annotations = Regex::new(r"\s*//~.*").unwrap();
    let unnormalized_stderr = annotations.replace(unnormalized_stderr, "");
    let mut found_annotation = false;
    if let Some((ref error_pattern, definition_line)) = comments.error_pattern {
        if !unnormalized_stderr.contains(error_pattern) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: error_pattern.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    for &ErrorMatch { ref matched, revision: ref rev, definition_line } in &comments.error_matches {
        // FIXME: check that the error happens on the marked line

        if let Some(rev) = rev {
            if rev != revision {
                continue;
            }
        }

        if !unnormalized_stderr.contains(matched) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: matched.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    match (config.mode, found_annotation) {
        (Mode::Pass, true) | (Mode::Panic, true) => errors.push(Error::PatternFoundInPassTest),
        (Mode::Fail, false) => errors.push(Error::NoPatternsFound),
        _ => {}
    };
}

fn check_output(
    output: &[u8],
    path: &Path,
    errors: &mut Errors,
    kind: String,
    target: &str,
    filters: &Filter,
    config: &Config,
    comments: &Comments,
) {
    let output = std::str::from_utf8(&output).unwrap();
    let output = normalize(path, output, filters, comments);
    let path = output_path(path, comments, kind, target);
    match config.output_conflict_handling {
        OutputConflictHandling::Bless =>
            if output.is_empty() {
                let _ = std::fs::remove_file(path);
            } else {
                std::fs::write(path, &output).unwrap();
            },
        OutputConflictHandling::Error => {
            let expected_output = std::fs::read_to_string(&path).unwrap_or_default();
            if output != expected_output {
                errors.push(Error::OutputDiffers {
                    path,
                    actual: output,
                    expected: expected_output,
                });
            }
        }
        OutputConflictHandling::Ignore => {}
    }
}

fn output_path(path: &Path, comments: &Comments, kind: String, target: &str) -> PathBuf {
    if comments.stderr_per_bitwidth {
        return path.with_extension(format!("{}.{kind}", get_pointer_width(target)));
    }
    path.with_extension(kind)
}

fn ignore_file(comments: &Comments, target: &str) -> bool {
    for s in &comments.ignore {
        if target.contains(s) {
            return true;
        }
        if get_pointer_width(target) == s {
            return true;
        }
    }
    for s in &comments.only {
        if !target.contains(s) {
            return true;
        }
        if get_pointer_width(target) != s {
            return true;
        }
    }
    false
}

// Taken 1:1 from compiletest-rs
fn get_pointer_width(triple: &str) -> &'static str {
    if (triple.contains("64") && !triple.ends_with("gnux32") && !triple.ends_with("gnu_ilp32"))
        || triple.starts_with("s390x")
    {
        "64bit"
    } else if triple.starts_with("avr") {
        "16bit"
    } else {
        "32bit"
    }
}

fn normalize(path: &Path, text: &str, filters: &Filter, comments: &Comments) -> String {
    // Useless paths
    let mut text = text.replace(&path.parent().unwrap().display().to_string(), "$DIR");
    if let Some(lib_path) = option_env!("RUSTC_LIB_PATH") {
        text = text.replace(lib_path, "RUSTLIB");
    }

    for (regex, replacement) in filters.iter() {
        text = regex.replace_all(&text, *replacement).to_string();
    }

    for (from, to) in &comments.normalize_stderr {
        text = from.replace_all(&text, to).to_string();
    }
    text
}

impl Config {
    fn get_host(&self) -> String {
        rustc_version::VersionMeta::for_command(std::process::Command::new(&self.program))
            .expect("failed to parse rustc version info")
            .host
    }
}

#[derive(Copy, Clone, Debug)]
pub enum Mode {
    // The test passes a full execution of the rustc driver
    Pass,
    // The rustc driver panicked
    Panic,
    // The rustc driver emitted an error
    Fail,
}

impl Mode {
    fn ok(self, status: ExitStatus) -> Errors {
        match (status.code().unwrap(), self) {
            (1, Mode::Fail) | (101, Mode::Panic) | (0, Mode::Pass) => vec![],
            _ => vec![Error::ExitStatus(self, status)],
        }
    }
}
<commit_msg>Make the file path of the failure more visible to be able to click it faster<commit_after>use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;

use colored::*;
use comments::ErrorMatch;
use crossbeam::queue::SegQueue;
use regex::Regex;

pub use crate::comments::Comments;

mod comments;

#[derive(Debug)]
pub struct Config {
    /// Arguments passed to the binary that is executed.
    pub args: Vec<String>,
    /// `None` to run on the host, otherwise a target triple
    pub target: Option<String>,
    /// Filters applied to stderr output before processing it
    pub stderr_filters: Filter,
    /// Filters applied to stdout output before processing it
    pub stdout_filters: Filter,
    /// The folder in which to start searching for .rs files
    pub root_dir: PathBuf,
    pub mode: Mode,
    pub program: PathBuf,
    pub output_conflict_handling: OutputConflictHandling,
}

#[derive(Debug)]
pub enum OutputConflictHandling {
    /// The default: emit a diff of the expected/actual output.
    Error,
    /// Ignore mismatches in the stderr/stdout files.
    Ignore,
    /// Instead of erroring if the stderr/stdout differs from the expected
    /// automatically replace it with the found output (after applying filters).
    Bless,
}

pub type Filter = Vec<(Regex, &'static str)>;

pub fn run_tests(config: Config) {
    eprintln!("   Compiler flags: {:?}", config.args);

    // Get the triple with which to run the tests
    let target = config.target.clone().unwrap_or_else(|| config.get_host());

    // A queue for files or folders to process
    let todo = SegQueue::new();
    todo.push(config.root_dir.clone());

    // Some statistics and failure reports.
    let failures = Mutex::new(vec![]);
    let succeeded = AtomicUsize::default();
    let ignored = AtomicUsize::default();

    crossbeam::scope(|s| {
        for _ in 0..std::thread::available_parallelism().unwrap().get() {
            s.spawn(|_| {
                while let Some(path) = todo.pop() {
                    // Collect everything inside directories
                    if path.is_dir() {
                        for entry in std::fs::read_dir(path).unwrap() {
                            todo.push(entry.unwrap().path());
                        }
                        continue;
                    }
                    // Only look at .rs files
                    if !path.extension().map(|ext| ext == "rs").unwrap_or(false) {
                        continue;
                    }
                    let comments = Comments::parse_file(&path);
                    // Ignore file if only/ignore rules do (not) apply
                    if ignore_file(&comments, &target) {
                        ignored.fetch_add(1, Ordering::Relaxed);
                        eprintln!("{} .. {}", path.display(), "ignored".yellow());
                        continue;
                    }
                    // Run the test for all revisions
                    for revision in
                        comments.revisions.clone().unwrap_or_else(|| vec![String::new()])
                    {
                        let (m, errors) = run_test(&path, &config, &target, &revision, &comments);

                        // Using `format` to prevent messages from threads from getting intermingled.
                        let mut msg = format!("{} ", path.display());
                        if !revision.is_empty() {
                            write!(msg, "(revision `{revision}`) ").unwrap();
                        }
                        write!(msg, "... ").unwrap();
                        if errors.is_empty() {
                            eprintln!("{msg}{}", "ok".green());
                            succeeded.fetch_add(1, Ordering::Relaxed);
                        } else {
                            eprintln!("{msg}{}", "FAILED".red().bold());
                            failures.lock().unwrap().push((path.clone(), m, revision, errors));
                        }
                    }
                }
            });
        }
    })
    .unwrap();

    // Print all errors in a single thread to show reliable output
    let failures = failures.into_inner().unwrap();
    let succeeded = succeeded.load(Ordering::Relaxed);
    let ignored = ignored.load(Ordering::Relaxed);
    if !failures.is_empty() {
        for (path, miri, revision, errors) in &failures {
            eprintln!();
            eprint!("{}", path.display().to_string().underline());
            if !revision.is_empty() {
                eprint!(" (revision `{}`)", revision);
            }
            eprint!(" {}", "FAILED".red());
            eprintln!();
            eprintln!("command: {:?}", miri);
            eprintln!();
            let mut dump_stderr = None;
            for error in errors {
                match error {
                    Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
                    Error::PatternNotFound { stderr, pattern, definition_line } => {
                        eprintln!("`{pattern}` {} in stderr output", "not found".red());
                        eprintln!(
                            "expected because of pattern here: {}:{definition_line}",
                            path.display().to_string().bold()
                        );
                        dump_stderr = Some(stderr.clone())
                    }
                    Error::NoPatternsFound =>
                        eprintln!("{}", "no error patterns found in failure test".red()),
                    Error::PatternFoundInPassTest =>
                        eprintln!("{}", "error pattern found in success test".red()),
                    Error::OutputDiffers { path, actual, expected } => {
                        dump_stderr = None;
                        eprintln!("actual output differed from expected {}", path.display());
                        eprintln!("{}", pretty_assertions::StrComparison::new(expected, actual));
                        eprintln!()
                    }
                }
                eprintln!();
            }
            if let Some(stderr) = dump_stderr {
                eprintln!("actual stderr:");
                eprintln!("{}", stderr);
                eprintln!();
            }
        }
        eprintln!(
            "{} tests failed, {} tests passed, {} ignored",
            failures.len().to_string().red().bold(),
            succeeded.to_string().green(),
            ignored.to_string().yellow()
        );
        std::process::exit(1);
    }
    eprintln!();
    eprintln!(
        "test result: {}. {} tests passed, {} ignored",
        "ok".green(),
        succeeded.to_string().green(),
        ignored.to_string().yellow()
    );
    eprintln!();
}

#[derive(Debug)]
pub enum Error {
    /// Got an invalid exit status for the given mode.
    ExitStatus(Mode, ExitStatus),
    PatternNotFound {
        stderr: String,
        pattern: String,
        definition_line: usize,
    },
    /// A ui test checking for failure does not have any failure patterns
    NoPatternsFound,
    /// A ui test checking for success has failure patterns
    PatternFoundInPassTest,
    /// Stderr/Stdout differed from the `.stderr`/`.stdout` file present.
    OutputDiffers {
        path: PathBuf,
        actual: String,
        expected: String,
    },
}

pub type Errors = Vec<Error>;

fn run_test(
    path: &Path,
    config: &Config,
    target: &str,
    revision: &str,
    comments: &Comments,
) -> (Command, Errors) {
    // Run miri
    let mut miri = Command::new(&config.program);
    miri.args(config.args.iter());
    miri.arg(path);
    if !revision.is_empty() {
        miri.arg(format!("--cfg={revision}"));
    }
    for arg in &comments.compile_flags {
        miri.arg(arg);
    }
    for (k, v) in &comments.env_vars {
        miri.env(k, v);
    }
    let output = miri.output().expect("could not execute miri");
    let mut errors = config.mode.ok(output.status);
    // Check output files (if any)
    let revised = |extension: &str| {
        if revision.is_empty() {
            extension.to_string()
        } else {
            format!("{}.{}", revision, extension)
        }
    };
    // Check output files against actual output
    check_output(
        &output.stderr,
        path,
        &mut errors,
        revised("stderr"),
        target,
        &config.stderr_filters,
        &config,
        comments,
    );
    check_output(
        &output.stdout,
        path,
        &mut errors,
        revised("stdout"),
        target,
        &config.stdout_filters,
        &config,
        comments,
    );
    // Check error annotations in the source against output
    check_annotations(&output.stderr, &mut errors, config, revision, comments);
    (miri, errors)
}

pub fn check_annotations(
    unnormalized_stderr: &[u8],
    errors: &mut Errors,
    config: &Config,
    revision: &str,
    comments: &Comments,
) {
    let unnormalized_stderr = std::str::from_utf8(unnormalized_stderr).unwrap();
    // erase annotations from the stderr so they don't match themselves
    let annotations = Regex::new(r"\s*//~.*").unwrap();
    let unnormalized_stderr = annotations.replace(unnormalized_stderr, "");
    let mut found_annotation = false;
    if let Some((ref error_pattern, definition_line)) = comments.error_pattern {
        if !unnormalized_stderr.contains(error_pattern) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: error_pattern.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    for &ErrorMatch { ref matched, revision: ref rev, definition_line } in &comments.error_matches {
        // FIXME: check that the error happens on the marked line

        if let Some(rev) = rev {
            if rev != revision {
                continue;
            }
        }

        if !unnormalized_stderr.contains(matched) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: matched.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    match (config.mode, found_annotation) {
        (Mode::Pass, true) | (Mode::Panic, true) => errors.push(Error::PatternFoundInPassTest),
        (Mode::Fail, false) => errors.push(Error::NoPatternsFound),
        _ => {}
    };
}

fn check_output(
    output: &[u8],
    path: &Path,
    errors: &mut Errors,
    kind: String,
    target: &str,
    filters: &Filter,
    config: &Config,
    comments: &Comments,
) {
    let output = std::str::from_utf8(&output).unwrap();
    let output = normalize(path, output, filters, comments);
    let path = output_path(path, comments, kind, target);
    match config.output_conflict_handling {
        OutputConflictHandling::Bless =>
            if output.is_empty() {
                let _ = std::fs::remove_file(path);
            } else {
                std::fs::write(path, &output).unwrap();
            },
        OutputConflictHandling::Error => {
            let expected_output = std::fs::read_to_string(&path).unwrap_or_default();
            if output != expected_output {
                errors.push(Error::OutputDiffers {
                    path,
                    actual: output,
                    expected: expected_output,
                });
            }
        }
        OutputConflictHandling::Ignore => {}
    }
}

fn output_path(path: &Path, comments: &Comments, kind: String, target: &str) -> PathBuf {
    if comments.stderr_per_bitwidth {
        return path.with_extension(format!("{}.{kind}", get_pointer_width(target)));
    }
    path.with_extension(kind)
}

fn ignore_file(comments: &Comments, target: &str) -> bool {
    for s in &comments.ignore {
        if target.contains(s) {
            return true;
        }
        if get_pointer_width(target) == s {
            return true;
        }
    }
    for s in &comments.only {
        if !target.contains(s) {
            return true;
        }
        if get_pointer_width(target) != s {
            return true;
        }
    }
    false
}

// Taken 1:1 from compiletest-rs
fn get_pointer_width(triple: &str) -> &'static str {
    if (triple.contains("64") && !triple.ends_with("gnux32") && !triple.ends_with("gnu_ilp32"))
        || triple.starts_with("s390x")
    {
        "64bit"
    } else if triple.starts_with("avr") {
        "16bit"
    } else {
        "32bit"
    }
}

fn normalize(path: &Path, text: &str, filters: &Filter, comments: &Comments) -> String {
    // Useless paths
    let mut text = text.replace(&path.parent().unwrap().display().to_string(), "$DIR");
    if let Some(lib_path) = option_env!("RUSTC_LIB_PATH") {
        text = text.replace(lib_path, "RUSTLIB");
    }

    for (regex, replacement) in filters.iter() {
        text = regex.replace_all(&text, *replacement).to_string();
    }

    for (from, to) in &comments.normalize_stderr {
        text = from.replace_all(&text, to).to_string();
    }
    text
}

impl Config {
    fn get_host(&self) -> String {
        rustc_version::VersionMeta::for_command(std::process::Command::new(&self.program))
            .expect("failed to parse rustc version info")
            .host
    }
}

#[derive(Copy, Clone, Debug)]
pub enum Mode {
    // The test passes a full execution of the rustc driver
    Pass,
    // The rustc driver panicked
    Panic,
    // The rustc driver emitted an error
    Fail,
}

impl Mode {
    fn ok(self, status: ExitStatus) -> Errors {
        match (status.code().unwrap(), self) {
            (1, Mode::Fail) | (101, Mode::Panic) | (0, Mode::Pass) => vec![],
            _ => vec![Error::ExitStatus(self, status)],
        }
    }
}
<|endoftext|>


<commit_before>#[cfg(feature = "rustc-serialize")]
use serialize::{Encodable, Encoder, Decodable, Decoder};

use std::hash;
use std::iter::FromIterator;
use std::iter::IntoIterator;
use std::ops::{
    Index,
    IndexMut,
};

use super::{
    Dimension, Ix,
    Elements,
    ElementsMut,
    ArrayBase,
    ArrayView,
    ArrayViewMut,
    Data,
    DataMut,
    DataOwned,
    NdIndex,
};

/// Access the element at **index**.
///
/// **Panics** if index is out of bounds.
impl<S, D, I> Index<I> for ArrayBase<S, D>
    where D: Dimension,
          I: NdIndex<Dim=D>,
          S: Data,
{
    type Output = S::Elem;
    #[inline]
    fn index(&self, index: I) -> &S::Elem {
        self.get(index).expect("Array::index: out of bounds")
    }
}

/// Access the element at **index** mutably.
///
/// **Panics** if index is out of bounds.
impl<S, D, I> IndexMut<I> for ArrayBase<S, D>
    where D: Dimension,
          I: NdIndex<Dim=D>,
          S: DataMut,
{
    #[inline]
    fn index_mut(&mut self, index: I) -> &mut S::Elem {
        self.get_mut(index).expect("Array::index_mut: out of bounds")
    }
}

impl<S, S2, D> PartialEq<ArrayBase<S2, D>> for ArrayBase<S, D>
    where D: Dimension,
          S: Data,
          S2: Data<Elem = S::Elem>,
          S::Elem: PartialEq,
{
    /// Return `true` if the array shapes and all elements of `self` and
    /// `rhs` are equal. Return `false` otherwise.
    fn eq(&self, rhs: &ArrayBase<S2, D>) -> bool
    {
        if self.shape() != rhs.shape() {
            return false;
        }
        if let Some(self_s) = self.as_slice() {
            if let Some(rhs_s) = rhs.as_slice() {
                return self_s == rhs_s;
            }
        }
        self.iter().zip(rhs.iter()).all(|(a, b)| a == b)
    }
}

impl<S, D> Eq for ArrayBase<S, D>
    where D: Dimension,
          S: Data,
          S::Elem: Eq,
{ }

impl<A, S> FromIterator<A> for ArrayBase<S, Ix>
    where S: DataOwned<Elem=A>
{
    fn from_iter<I>(iterable: I) -> ArrayBase<S, Ix>
        where I: IntoIterator<Item=A>,
    {
        ArrayBase::from_iter(iterable)
    }
}

impl<'a, S, D> IntoIterator for &'a ArrayBase<S, D>
    where D: Dimension,
          S: Data,
{
    type Item = &'a S::Elem;
    type IntoIter = Elements<'a, S::Elem, D>;

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

impl<'a, S, D> IntoIterator for &'a mut ArrayBase<S, D>
    where D: Dimension,
          S: DataMut,
{
    type Item = &'a mut S::Elem;
    type IntoIter = ElementsMut<'a, S::Elem, D>;

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

impl<'a, A, D> IntoIterator for ArrayView<'a, A,  D>
    where D: Dimension,
{
    type Item = &'a A;
    type IntoIter = Elements<'a, A, D>;

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

impl<'a, A, D> IntoIterator for ArrayViewMut<'a, A,  D>
    where D: Dimension,
{
    type Item = &'a mut A;
    type IntoIter = ElementsMut<'a, A, D>;

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

impl<'a, S, D> hash::Hash for ArrayBase<S, D>
    where D: Dimension,
          S: Data,
          S::Elem: hash::Hash,
{
    fn hash<H: hash::Hasher>(&self, state: &mut H)
    {
        self.shape().hash(state);
        for elt in self.iter() {
            elt.hash(state)
        }
    }
}

// NOTE: ArrayBase keeps an internal raw pointer that always
// points into the storage. This is Sync & Send as long as we
// follow the usual inherited mutability rules, as we do with
// Vec, &[] and &mut []

/// `ArrayBase` is `Sync` when the storage type is.
unsafe impl<S, D> Sync for ArrayBase<S, D>
    where S: Sync + Data, D: Sync
{ }

/// `ArrayBase` is `Send` when the storage type is.
unsafe impl<S, D> Send for ArrayBase<S, D>
    where S: Send + Data, D: Send
{ }

#[cfg(feature = "rustc-serialize")]
// Use version number so we can add a packed format later.
static ARRAY_FORMAT_VERSION: u8 = 1u8;

/// **Requires `feature = "rustc-serialize"`**
#[cfg(feature = "rustc-serialize")]
impl<A, S, D> Encodable for ArrayBase<S, D>
    where A: Encodable, D: Dimension + Encodable,
          S: Data<Elem=A>,
{
    fn encode<E: Encoder>(&self, s: &mut E) -> Result<(), E::Error>
    {
        s.emit_struct("Array", 3, |e| {
            try!(e.emit_struct_field("v", 0, |e| {
                ARRAY_FORMAT_VERSION.encode(e)
            }));
            // FIXME: Write self.dim as a slice (self.shape)
            // The problem is decoding it.
            try!(e.emit_struct_field("dim", 1,
                                           |e| self.dim.encode(e)));
            try!(e.emit_struct_field("data", 2, |e| {
                let sz = self.dim.size();
                e.emit_seq(sz, |e| {
                    for (i, elt) in self.iter().enumerate() {
                        try!(e.emit_seq_elt(i, |e| {
                            elt.encode(e)
                        }))
                    }
                    Ok(())
                })
            }));
            Ok(())
        })
    }
}

/// **Requires `feature = "rustc-serialize"`**
#[cfg(feature = "rustc-serialize")]
impl<A, S, D> Decodable for ArrayBase<S, D>
    where A: Decodable, D: Dimension + Decodable,
          S: DataOwned<Elem=A>,
{
    fn decode<E: Decoder>(d: &mut E) -> Result<ArrayBase<S, D>, E::Error>
    {
        d.read_struct("Array", 3, |d| {
            let version: u8 = try!(d.read_struct_field("v", 0, Decodable::decode));
            if version > ARRAY_FORMAT_VERSION {
                return Err(d.error("unknown array version"))
            }
            let dim: D = try!(d.read_struct_field("dim", 1, |d| {
                Decodable::decode(d)
            }));
            let elements = try!(
                d.read_struct_field("data", 2, |d| {
                    d.read_seq(|d, len| {
                        if len != dim.size() {
                            Err(d.error("data and dimension must match in size"))
                        } else {
                            let mut elements = Vec::with_capacity(len);
                            for i in 0..len {
                                elements.push(try!(d.read_seq_elt::<A, _>(i, Decodable::decode)))
                            }
                            Ok(elements)
                        }
                    })
            }));
            unsafe {
                Ok(ArrayBase::from_vec_dim(dim, elements))
            }
        })
    }
}
<commit_msg>Use separate function for panic on out of bounds index<commit_after>#[cfg(feature = "rustc-serialize")]
use serialize::{Encodable, Encoder, Decodable, Decoder};

use std::hash;
use std::iter::FromIterator;
use std::iter::IntoIterator;
use std::ops::{
    Index,
    IndexMut,
};

use super::{
    Dimension, Ix,
    Elements,
    ElementsMut,
    ArrayBase,
    ArrayView,
    ArrayViewMut,
    Data,
    DataMut,
    DataOwned,
    NdIndex,
};

#[cold]
#[inline(never)]
fn array_out_of_bounds() -> ! {
    panic!("ndarray: index out of bounds");
}

/// Access the element at **index**.
///
/// **Panics** if index is out of bounds.
impl<S, D, I> Index<I> for ArrayBase<S, D>
    where D: Dimension,
          I: NdIndex<Dim=D>,
          S: Data,
{
    type Output = S::Elem;
    #[inline]
    fn index(&self, index: I) -> &S::Elem {
        self.get(index).unwrap_or_else(|| array_out_of_bounds())
    }
}

/// Access the element at **index** mutably.
///
/// **Panics** if index is out of bounds.
impl<S, D, I> IndexMut<I> for ArrayBase<S, D>
    where D: Dimension,
          I: NdIndex<Dim=D>,
          S: DataMut,
{
    #[inline]
    fn index_mut(&mut self, index: I) -> &mut S::Elem {
        self.get_mut(index).unwrap_or_else(|| array_out_of_bounds())
    }
}

impl<S, S2, D> PartialEq<ArrayBase<S2, D>> for ArrayBase<S, D>
    where D: Dimension,
          S: Data,
          S2: Data<Elem = S::Elem>,
          S::Elem: PartialEq,
{
    /// Return `true` if the array shapes and all elements of `self` and
    /// `rhs` are equal. Return `false` otherwise.
    fn eq(&self, rhs: &ArrayBase<S2, D>) -> bool
    {
        if self.shape() != rhs.shape() {
            return false;
        }
        if let Some(self_s) = self.as_slice() {
            if let Some(rhs_s) = rhs.as_slice() {
                return self_s == rhs_s;
            }
        }
        self.iter().zip(rhs.iter()).all(|(a, b)| a == b)
    }
}

impl<S, D> Eq for ArrayBase<S, D>
    where D: Dimension,
          S: Data,
          S::Elem: Eq,
{ }

impl<A, S> FromIterator<A> for ArrayBase<S, Ix>
    where S: DataOwned<Elem=A>
{
    fn from_iter<I>(iterable: I) -> ArrayBase<S, Ix>
        where I: IntoIterator<Item=A>,
    {
        ArrayBase::from_iter(iterable)
    }
}

impl<'a, S, D> IntoIterator for &'a ArrayBase<S, D>
    where D: Dimension,
          S: Data,
{
    type Item = &'a S::Elem;
    type IntoIter = Elements<'a, S::Elem, D>;

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

impl<'a, S, D> IntoIterator for &'a mut ArrayBase<S, D>
    where D: Dimension,
          S: DataMut,
{
    type Item = &'a mut S::Elem;
    type IntoIter = ElementsMut<'a, S::Elem, D>;

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

impl<'a, A, D> IntoIterator for ArrayView<'a, A,  D>
    where D: Dimension,
{
    type Item = &'a A;
    type IntoIter = Elements<'a, A, D>;

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

impl<'a, A, D> IntoIterator for ArrayViewMut<'a, A,  D>
    where D: Dimension,
{
    type Item = &'a mut A;
    type IntoIter = ElementsMut<'a, A, D>;

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

impl<'a, S, D> hash::Hash for ArrayBase<S, D>
    where D: Dimension,
          S: Data,
          S::Elem: hash::Hash,
{
    fn hash<H: hash::Hasher>(&self, state: &mut H)
    {
        self.shape().hash(state);
        for elt in self.iter() {
            elt.hash(state)
        }
    }
}

// NOTE: ArrayBase keeps an internal raw pointer that always
// points into the storage. This is Sync & Send as long as we
// follow the usual inherited mutability rules, as we do with
// Vec, &[] and &mut []

/// `ArrayBase` is `Sync` when the storage type is.
unsafe impl<S, D> Sync for ArrayBase<S, D>
    where S: Sync + Data, D: Sync
{ }

/// `ArrayBase` is `Send` when the storage type is.
unsafe impl<S, D> Send for ArrayBase<S, D>
    where S: Send + Data, D: Send
{ }

#[cfg(feature = "rustc-serialize")]
// Use version number so we can add a packed format later.
static ARRAY_FORMAT_VERSION: u8 = 1u8;

/// **Requires `feature = "rustc-serialize"`**
#[cfg(feature = "rustc-serialize")]
impl<A, S, D> Encodable for ArrayBase<S, D>
    where A: Encodable, D: Dimension + Encodable,
          S: Data<Elem=A>,
{
    fn encode<E: Encoder>(&self, s: &mut E) -> Result<(), E::Error>
    {
        s.emit_struct("Array", 3, |e| {
            try!(e.emit_struct_field("v", 0, |e| {
                ARRAY_FORMAT_VERSION.encode(e)
            }));
            // FIXME: Write self.dim as a slice (self.shape)
            // The problem is decoding it.
            try!(e.emit_struct_field("dim", 1,
                                           |e| self.dim.encode(e)));
            try!(e.emit_struct_field("data", 2, |e| {
                let sz = self.dim.size();
                e.emit_seq(sz, |e| {
                    for (i, elt) in self.iter().enumerate() {
                        try!(e.emit_seq_elt(i, |e| {
                            elt.encode(e)
                        }))
                    }
                    Ok(())
                })
            }));
            Ok(())
        })
    }
}

/// **Requires `feature = "rustc-serialize"`**
#[cfg(feature = "rustc-serialize")]
impl<A, S, D> Decodable for ArrayBase<S, D>
    where A: Decodable, D: Dimension + Decodable,
          S: DataOwned<Elem=A>,
{
    fn decode<E: Decoder>(d: &mut E) -> Result<ArrayBase<S, D>, E::Error>
    {
        d.read_struct("Array", 3, |d| {
            let version: u8 = try!(d.read_struct_field("v", 0, Decodable::decode));
            if version > ARRAY_FORMAT_VERSION {
                return Err(d.error("unknown array version"))
            }
            let dim: D = try!(d.read_struct_field("dim", 1, |d| {
                Decodable::decode(d)
            }));
            let elements = try!(
                d.read_struct_field("data", 2, |d| {
                    d.read_seq(|d, len| {
                        if len != dim.size() {
                            Err(d.error("data and dimension must match in size"))
                        } else {
                            let mut elements = Vec::with_capacity(len);
                            for i in 0..len {
                                elements.push(try!(d.read_seq_elt::<A, _>(i, Decodable::decode)))
                            }
                            Ok(elements)
                        }
                    })
            }));
            unsafe {
                Ok(ArrayBase::from_vec_dim(dim, elements))
            }
        })
    }
}
<|endoftext|>


<commit_before>/// Comparisons against SCCS.
///
/// The weave algorithm used comes from the the SCCS program.  This can be installed on most Linux
/// distros by installing the package "cssc".

extern crate env_logger;
#[macro_use] extern crate log;
extern crate rand;
extern crate tempdir;
extern crate weave;

use rand::{Rng, SeedableRng, StdRng};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::env;
use std::fs::{File, remove_file};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::rc::Rc;
use tempdir::TempDir;
use weave::{DeltaWriter, NewWeave, Parser, SimpleNaming, Sink, Result};

#[test]
fn sccs() {
    let _ = env_logger::init();

    if !has_sccs() {
        return;
    }

    let tdir = TempDir::new("sccstest").unwrap();
    let mut gen = Gen::new(tdir.path()).unwrap();

    // For debugging, this will cause the directory to not be removed.
    if env::var("KEEPTEMP").is_ok() {
        tdir.into_path();
    }

    gen.new_sccs();
    gen.new_weave();
    gen.weave_check();

    for i in 0 .. 100 {
        gen.shuffle();
        gen.add_sccs_delta();
        gen.add_weave_delta(i + 1);

        // Checking with sccs is very slow.  Do we want to do it?
        // gen.sccs_check();
        gen.weave_check();
    }
}

/// Determine if we have the sccs command available.  If not, show an error, and return false.
fn has_sccs() -> bool {
    match Command::new("sccs").arg("-V").output() {
        Ok(_) => true,
        Err(_) => {
            error!("'sccs' not found in path, skipping tests, install 'cssc' to fix");
            false
        }
    }
}

/// Gen synthesizes a series of deltas, and can add them using SCCS to make a weave file, and later
/// to this weave implementation to compare the results.
struct Gen {
    /// The directory to write the files into.
    tdir: PathBuf,

    /// The name of the plain file related to it.
    sccs_plain: PathBuf,

    /// The current lines.
    nums: Vec<usize>,

    /// Each delta.  Sccs numbers the deltas from 1, so these are off by one.
    deltas: Vec<Vec<usize>>,

    /// A Rng for generating the shuffles.
    rand: StdRng,
}

impl Gen {
    fn new<P: AsRef<Path>>(tdir: P) -> Result<Gen> {
        let tdir = tdir.as_ref();
        let seed: &[_] = &[1, 2, 3, 4];
        Ok(Gen {
            tdir: tdir.to_owned(),
            sccs_plain: tdir.join("tfile"),
            nums: (1..101).collect(),
            rand: SeedableRng::from_seed(seed),
            deltas: vec![],
        })
    }

    /// Perform a somewhat random modification of the data.  Choose some range of the numbers and
    /// reverse them.
    fn shuffle(&mut self) {
        let a = self.rand.gen_range(0, self.nums.len());
        let b = self.rand.gen_range(0, self.nums.len());

        let (a, b) = if a <= b { (a, b) } else { (b, a) };
        self.nums[a..b].reverse();
    }

    /// Write to a new sccs file, resulting in delta 1.
    fn new_sccs(&mut self) {
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["admin", "-itfile", "-n", "s.tfile"])
            .current_dir(&self.tdir)
            .status()
            .expect("Unable to run sccs admin")
            .expect_success("Sccs command returned error");
        remove_file(&self.sccs_plain).expect("Unable to remove data file");

        /// Consider the deltas as canonical by the SCCS command, so store the first delta.
        self.deltas.push(self.nums.clone());
    }

    /// Add a new delta to the sccs file.
    fn add_sccs_delta(&mut self) {
        Command::new("sccs").args(&["get", "-e", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs get")
            .expect_success("sccs get failed");
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["delta", "-yMessage", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs delta")
            .expect_success("sccs delta failed");

        self.deltas.push(self.nums.clone());
    }

    /// Emit the current numbers to the given name (in the temp dir).
    fn emit_to<P: AsRef<Path>>(&self, name: P) {
        let mut fd = File::create(self.tdir.join(name)).unwrap();
        for i in &self.nums {
            writeln!(&mut fd, "{}", i).unwrap();
        }
    }

    /// Check the output of "sccs get".  This is more of a sanity check.
    #[allow(dead_code)]
    fn sccs_check(&self) {
        for (i, del) in self.deltas.iter().enumerate() {
            self.sccs_check_one(i, del);
        }
    }

    #[allow(dead_code)]
    fn sccs_check_one(&self, num: usize, data: &[usize]) {
        let out = Command::new("sccs").args(&["get", &format!("-r1.{}", num+1), "-p", "s.tfile"])
            .current_dir(&self.tdir)
            .output()
            .expect("Unable to run sccs get");
        out.status.expect_success("Error running sccs get");
        let mut onums: Vec<usize> = vec![];
        for line in BufReader::new(&out.stdout[..]).lines() {
            let line = line.unwrap();
            onums.push(line.as_str().parse::<usize>().unwrap());
        }

        assert_eq!(data, &onums[..]);
    }

    /// Check that weave decodes all of the sccs files properly.
    fn weave_check(&self) {
        for (i, del) in self.deltas.iter().enumerate() {
            self.weave_sccs_check_one(i, del);
            self.weave_check_one(i, del);
        }
    }

    fn weave_sccs_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("s.tfile")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn weave_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("sample.weave")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn new_weave(&mut self) {
        let mut tags = BTreeMap::new();
        tags.insert("name", "initial");
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut nw = NewWeave::new(&nc, tags.into_iter()).unwrap();
        for i in &self.nums {
            writeln!(&mut nw, "{}", i).unwrap();
        }
        nw.close().unwrap();
    }

    fn add_weave_delta(&mut self, base: usize) {
        let name_value = format!("{}", base + 1);
        let mut tags = BTreeMap::new();
        tags.insert("name", name_value.as_str());
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut delta = DeltaWriter::new(&nc, tags.into_iter(), base).unwrap();
        for i in &self.nums {
            writeln!(&mut delta, "{}", i).unwrap();
        }
        delta.close().unwrap();
    }
}

/// A Weave Sink that just collects the numbers in the given delta.
struct DeltaSink {
    nums: Vec<usize>,
}

impl Sink for DeltaSink {
    fn plain(&mut self, text: &str, keep: bool) -> Result<()> {
        if !keep {
            return Ok(())
        }

        self.nums.push(text.parse::<usize>()?);
        Ok(())
    }
}

/// A small utility to make asserting success easier.
trait Successful {
    fn expect_success(&self, msg: &str);
}

impl Successful for ExitStatus {
    fn expect_success(&self, msg: &str) {
        if !self.success() {
            panic!(msg.to_string());
        }
    }
}
<commit_msg>weave: Parameterize the test results better<commit_after>/// Comparisons against SCCS.
///
/// The weave algorithm used comes from the the SCCS program.  This can be installed on most Linux
/// distros by installing the package "cssc".

extern crate env_logger;
#[macro_use] extern crate log;
extern crate rand;
extern crate tempdir;
extern crate weave;

use rand::{Rng, SeedableRng, StdRng};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::env;
use std::fs::{File, remove_file};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::rc::Rc;
use tempdir::TempDir;
use weave::{DeltaWriter, NewWeave, Parser, SimpleNaming, Sink, Result};

/// Number of iterations to make.  Note that the default check is greater than O(n^2) so the test
/// will run very long if this is increased too much.
const ITERATION_COUNT: usize = 100;

/// Number of lines in the files.  Affects how convoluted the diffs are.
const FILE_SIZE: usize = 100;

/// Set to true to verify all previous deltas, not just the most recent.
const VERIFY_ALL_DELTAS: bool = true;

#[test]
fn sccs() {
    let _ = env_logger::init();

    if !has_sccs() {
        return;
    }

    let tdir = TempDir::new("sccstest").unwrap();
    let mut gen = Gen::new(tdir.path()).unwrap();

    // For debugging, this will cause the directory to not be removed.
    if env::var("KEEPTEMP").is_ok() {
        tdir.into_path();
    }

    gen.new_sccs();
    gen.new_weave();
    gen.weave_check();

    for i in 0 .. ITERATION_COUNT {
        gen.shuffle();
        gen.add_sccs_delta();
        gen.add_weave_delta(i + 1);

        // Checking with sccs is very slow.  Do we want to do it?
        // gen.sccs_check();
        gen.weave_check();
    }
}

/// Determine if we have the sccs command available.  If not, show an error, and return false.
fn has_sccs() -> bool {
    match Command::new("sccs").arg("-V").output() {
        Ok(_) => true,
        Err(_) => {
            error!("'sccs' not found in path, skipping tests, install 'cssc' to fix");
            false
        }
    }
}

/// Gen synthesizes a series of deltas, and can add them using SCCS to make a weave file, and later
/// to this weave implementation to compare the results.
struct Gen {
    /// The directory to write the files into.
    tdir: PathBuf,

    /// The name of the plain file related to it.
    sccs_plain: PathBuf,

    /// The current lines.
    nums: Vec<usize>,

    /// Each delta.  Sccs numbers the deltas from 1, so these are off by one.
    deltas: Vec<Vec<usize>>,

    /// A Rng for generating the shuffles.
    rand: StdRng,
}

impl Gen {
    fn new<P: AsRef<Path>>(tdir: P) -> Result<Gen> {
        let tdir = tdir.as_ref();
        let seed: &[_] = &[1, 2, 3, 4];
        Ok(Gen {
            tdir: tdir.to_owned(),
            sccs_plain: tdir.join("tfile"),
            nums: (1..FILE_SIZE+1).collect(),
            rand: SeedableRng::from_seed(seed),
            deltas: vec![],
        })
    }

    /// Perform a somewhat random modification of the data.  Choose some range of the numbers and
    /// reverse them.
    fn shuffle(&mut self) {
        let a = self.rand.gen_range(0, self.nums.len());
        let b = self.rand.gen_range(0, self.nums.len());

        let (a, b) = if a <= b { (a, b) } else { (b, a) };
        self.nums[a..b].reverse();
    }

    /// Write to a new sccs file, resulting in delta 1.
    fn new_sccs(&mut self) {
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["admin", "-itfile", "-n", "s.tfile"])
            .current_dir(&self.tdir)
            .status()
            .expect("Unable to run sccs admin")
            .expect_success("Sccs command returned error");
        remove_file(&self.sccs_plain).expect("Unable to remove data file");

        /// Consider the deltas as canonical by the SCCS command, so store the first delta.
        self.deltas.push(self.nums.clone());
    }

    /// Add a new delta to the sccs file.
    fn add_sccs_delta(&mut self) {
        Command::new("sccs").args(&["get", "-e", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs get")
            .expect_success("sccs get failed");
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["delta", "-yMessage", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs delta")
            .expect_success("sccs delta failed");

        self.deltas.push(self.nums.clone());
    }

    /// Emit the current numbers to the given name (in the temp dir).
    fn emit_to<P: AsRef<Path>>(&self, name: P) {
        let mut fd = File::create(self.tdir.join(name)).unwrap();
        for i in &self.nums {
            writeln!(&mut fd, "{}", i).unwrap();
        }
    }

    /// Check the output of "sccs get".  This is more of a sanity check.
    #[allow(dead_code)]
    fn sccs_check(&self) {
        for (i, del) in self.deltas.iter().enumerate() {
            self.sccs_check_one(i, del);
        }
    }

    #[allow(dead_code)]
    fn sccs_check_one(&self, num: usize, data: &[usize]) {
        let out = Command::new("sccs").args(&["get", &format!("-r1.{}", num+1), "-p", "s.tfile"])
            .current_dir(&self.tdir)
            .output()
            .expect("Unable to run sccs get");
        out.status.expect_success("Error running sccs get");
        let mut onums: Vec<usize> = vec![];
        for line in BufReader::new(&out.stdout[..]).lines() {
            let line = line.unwrap();
            onums.push(line.as_str().parse::<usize>().unwrap());
        }

        assert_eq!(data, &onums[..]);
    }

    /// Check that weave decodes all of the sccs files properly.
    fn weave_check(&self) {
        if VERIFY_ALL_DELTAS {
            // Verify all of the previous deltas.
            for (i, del) in self.deltas.iter().enumerate() {
                self.weave_sccs_check_one(i, del);
                self.weave_check_one(i, del);
            }
        } else {
            // This only checks the last delta for each one.  It will miss any bugs that result in
            // earlier deltas being unreadable.
            let del = self.deltas.iter().last().unwrap();
            self.weave_sccs_check_one(self.deltas.len() - 1, del);
            self.weave_check_one(self.deltas.len() - 1, del);
        }
    }

    fn weave_sccs_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("s.tfile")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn weave_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("sample.weave")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn new_weave(&mut self) {
        let mut tags = BTreeMap::new();
        tags.insert("name", "initial");
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut nw = NewWeave::new(&nc, tags.into_iter()).unwrap();
        for i in &self.nums {
            writeln!(&mut nw, "{}", i).unwrap();
        }
        nw.close().unwrap();
    }

    fn add_weave_delta(&mut self, base: usize) {
        let name_value = format!("{}", base + 1);
        let mut tags = BTreeMap::new();
        tags.insert("name", name_value.as_str());
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut delta = DeltaWriter::new(&nc, tags.into_iter(), base).unwrap();
        for i in &self.nums {
            writeln!(&mut delta, "{}", i).unwrap();
        }
        delta.close().unwrap();
    }
}

/// A Weave Sink that just collects the numbers in the given delta.
struct DeltaSink {
    nums: Vec<usize>,
}

impl Sink for DeltaSink {
    fn plain(&mut self, text: &str, keep: bool) -> Result<()> {
        if !keep {
            return Ok(())
        }

        self.nums.push(text.parse::<usize>()?);
        Ok(())
    }
}

/// A small utility to make asserting success easier.
trait Successful {
    fn expect_success(&self, msg: &str);
}

impl Successful for ExitStatus {
    fn expect_success(&self, msg: &str) {
        if !self.success() {
            panic!(msg.to_string());
        }
    }
}
<|endoftext|>


<commit_before>extern crate gtk;
extern crate glib;

use std::rc::{Rc, Weak};
use std::cell::RefCell;

use std::thread;

use std::path::PathBuf;

use std::sync::mpsc::{channel, Sender, TryRecvError};

use gtk::prelude::*;
use gtk::{ApplicationWindow, HeaderBar, Button,
          FileChooserDialog, ResponseType, FileChooserAction};

use ::media::{Context, ContextMessage};
use ::media::ContextMessage::*;

use super::{AudioController, InfoController, MediaHandler, VideoController};

pub struct MainController {
    window: ApplicationWindow,
    header_bar: HeaderBar,
    info_ctrl: InfoController,
    video_ctrl: VideoController,
    audio_ctrl: AudioController,

    context: Option<Context>,

    self_weak: Option<Weak<RefCell<MainController>>>,
}

impl MainController {
    pub fn new(builder: gtk::Builder) -> Rc<RefCell<Self>> {
        let mc = Rc::new(RefCell::new(MainController {
            window: builder.get_object("application-window").unwrap(),
            header_bar: builder.get_object("header-bar").unwrap(),
            info_ctrl: InfoController::new(&builder),
            video_ctrl: VideoController::new(&builder),
            audio_ctrl: AudioController::new(&builder),
            context: None,
            self_weak: None,
        }));

        {
            let mut mc_mut = mc.borrow_mut();
            mc_mut.window.connect_delete_event(|_, _| {
                gtk::main_quit();
                Inhibit(false)
            });
            mc_mut.window.set_titlebar(&mc_mut.header_bar);

            let mc_weak = Rc::downgrade(&mc);
            mc_mut.self_weak = Some(mc_weak);
        }

        let open_btn: Button = builder.get_object("open-btn").unwrap();
        let mc_weak = Rc::downgrade(&mc);
        open_btn.connect_clicked(move |_|
            match mc_weak.upgrade() {
                Some(mc) => mc.borrow_mut().select_media(),
                None => panic!("Main controller is no longer available for select_media"),
            }
        );

        mc
    }

    pub fn show_all(&self) {
        self.window.show_all();
    }

    fn process_message(&mut self, message: ContextMessage) {
        println!("Processing message");
        match message {
            OpenedMedia(context) => {
                self.header_bar.set_subtitle(Some(context.file_name.as_str()));

                self.info_ctrl.new_media(&context);
                self.video_ctrl.new_media(&context);
                self.audio_ctrl.new_media(&context);

                self.context = Some(context);
            },
            FailedToOpenMedia => println!("ERROR: failed to open media"),
            _ => (),
        };
    }

    fn select_media(&mut self) {
        let file_dlg = FileChooserDialog::new(Some("Open a media file"),
                                              Some(&self.window),
                                              FileChooserAction::Open,
            );
        // Note: couldn't find equivalents for STOCK_OK
        file_dlg.add_button("Open", ResponseType::Ok.into());

        let result = file_dlg.run();

        // Note: couldn't find a way to coerce ResponseType to i32 in a match statement
        if result == ResponseType::Ok.into() {
            self.open_media(file_dlg.get_filename().unwrap());
        }
        else {
            ()
        }

        file_dlg.close();
    }

    fn open_media(&mut self, filepath: PathBuf) {
        let (ctx_tx, ctx_rx) = channel::<ContextMessage>();

        thread::spawn(move || {
            Context::open_media_path_thread(filepath, ctx_tx);
        });

        if let Some(ref self_weak) = self.self_weak {
            let self_weak = self_weak.clone();
            gtk::timeout_add(100, move || {
                let mut must_go_on = false;
                match ctx_rx.try_recv() {
                    Ok(message) => match self_weak.upgrade() {
                        Some(mc) => mc.borrow_mut().process_message(message),
                        None => panic!("Main controller is no longer available for ctx channel listener"),
                    },
                    Err(error) => match error {
                        TryRecvError::Empty => must_go_on = true,
                        error => println!("Error listening to ctx channel: {:?}", error),
                    },
                };

                glib::Continue(must_go_on)
            });
        }
    }
}
<commit_msg>Move listener register in a dedicated method (#7)<commit_after>extern crate gtk;
extern crate glib;

use std::rc::{Rc, Weak};
use std::cell::RefCell;

use std::thread;

use std::path::PathBuf;

use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};

use gtk::prelude::*;
use gtk::{ApplicationWindow, HeaderBar, Button,
          FileChooserDialog, ResponseType, FileChooserAction};

use ::media::{Context, ContextMessage};
use ::media::ContextMessage::*;

use super::{AudioController, InfoController, MediaHandler, VideoController};

pub struct MainController {
    window: ApplicationWindow,
    header_bar: HeaderBar,
    info_ctrl: InfoController,
    video_ctrl: VideoController,
    audio_ctrl: AudioController,

    context: Option<Context>,

    self_weak: Option<Weak<RefCell<MainController>>>,
}

impl MainController {
    pub fn new(builder: gtk::Builder) -> Rc<RefCell<Self>> {
        let mc = Rc::new(RefCell::new(MainController {
            window: builder.get_object("application-window").unwrap(),
            header_bar: builder.get_object("header-bar").unwrap(),
            info_ctrl: InfoController::new(&builder),
            video_ctrl: VideoController::new(&builder),
            audio_ctrl: AudioController::new(&builder),
            context: None,
            self_weak: None,
        }));

        {
            let mut mc_mut = mc.borrow_mut();
            mc_mut.window.connect_delete_event(|_, _| {
                gtk::main_quit();
                Inhibit(false)
            });
            mc_mut.window.set_titlebar(&mc_mut.header_bar);

            let mc_weak = Rc::downgrade(&mc);
            mc_mut.self_weak = Some(mc_weak);
        }

        let open_btn: Button = builder.get_object("open-btn").unwrap();
        let mc_weak = Rc::downgrade(&mc);
        open_btn.connect_clicked(move |_|
            match mc_weak.upgrade() {
                Some(mc) => mc.borrow_mut().select_media(),
                None => panic!("Main controller is no longer available for select_media"),
            }
        );

        mc
    }

    pub fn show_all(&self) {
        self.window.show_all();
    }

    fn process_message(&mut self, message: ContextMessage) {
        println!("Processing message");
        match message {
            OpenedMedia(context) => {
                self.header_bar.set_subtitle(Some(context.file_name.as_str()));

                self.info_ctrl.new_media(&context);
                self.video_ctrl.new_media(&context);
                self.audio_ctrl.new_media(&context);

                self.context = Some(context);
            },
            FailedToOpenMedia => println!("ERROR: failed to open media"),
            _ => (),
        };
    }

    fn select_media(&mut self) {
        let file_dlg = FileChooserDialog::new(Some("Open a media file"),
                                              Some(&self.window),
                                              FileChooserAction::Open,
            );
        // Note: couldn't find equivalents for STOCK_OK
        file_dlg.add_button("Open", ResponseType::Ok.into());

        let result = file_dlg.run();

        // Note: couldn't find a way to coerce ResponseType to i32 in a match statement
        if result == ResponseType::Ok.into() {
            self.open_media(file_dlg.get_filename().unwrap());
        }
        else {
            ()
        }

        file_dlg.close();
    }

    fn register_listener(&self, ctx_rx: Receiver<ContextMessage>, timeout: u32) {
        if let Some(ref self_weak) = self.self_weak {
            let self_weak = self_weak.clone();

            gtk::timeout_add(timeout, move || {
                let mut must_go_on = false;
                match ctx_rx.try_recv() {
                    Ok(message) => match self_weak.upgrade() {
                        Some(mc) => mc.borrow_mut().process_message(message),
                        None => panic!("Main controller is no longer available for ctx channel listener"),
                    },
                    Err(error) => match error {
                        TryRecvError::Empty => must_go_on = true,
                        error => println!("Error listening to ctx channel: {:?}", error),
                    },
                };

                glib::Continue(must_go_on)
            });
        }
        // FIXME: else use proper expression to panic! in
    }

    fn open_media(&mut self, filepath: PathBuf) {
        let (ctx_tx, ctx_rx) = channel::<ContextMessage>();

        self.register_listener(ctx_rx, 100);

        thread::spawn(move || {
            Context::open_media_path_thread(filepath, ctx_tx);
        });
    }
}
<|endoftext|>