<commit_before>#[macro_use]
extern crate clap;
use clap::{App, Arg, ArgMatches};
extern crate imageflow_core;
use imageflow_core::boring::*;

use imageflow_core::ffi::{Filter, TESTED_FILTER_OPTIONS};
use std::path::{PathBuf, Path};
use std::fs::File;
use std::io::Write;
// TODO
// Disclaim use for jpeg optimization
// Disclaim use for png or gif files
// Focus on scaling
// No metadata support
// Let users adjust quality setting

fn build_app() -> App<'static, 'static> {

    App::new("flow-proto1")
        .version("0.0.1")
        .author("Email us at imageflow@imazen.io")
        .about("Throwaway prototype tool to play with downscaling jpegs via libimageflow. Not for \
                production use. Optimizations disabled on Windows; expect poor perf there. Lots \
                of things are broken, but you should still tell us about them.")
        .arg(Arg::with_name("v")
            .short("v")
            .long("verbose")
            .multiple(true)
            .help("Sets the level of verbosity"))
        .arg(Arg::with_name("input")
            .short("i")
            .long("input")
            .value_name("FILEIN")
            .takes_value(true)
            .required(true)
            .help("path to input file"))
        .arg(Arg::with_name("output")
            .short("o")
            .long("output")
            .value_name("FILEOUT")
            .takes_value(true)
            .required(true)
            .help("path to output file"))
        .arg(Arg::with_name("width")
            .short("w")
            .long("width")
            .value_name("WIDTH")
            .takes_value(true)
            .required(true)
            .help("scale to this width or smaller."))
        .arg(Arg::with_name("height")
            .short("h")
            .long("height")
            .value_name("HEIGHT")
            .takes_value(true)
            .required(true)
            .help("scale to this height or smaller."))
        .arg(Arg::with_name("jpeg-quality")
            .long("jpeg-quality")
            .value_name("0..100")
            .takes_value(true)
            .help("Jpeg compression level."))
        .arg(Arg::with_name("benchmark-threads")
            .long("benchmark-threads")
            .value_name("1..16")
            .takes_value(true)
            .help("Number of concurrent threads to use when benchmarking (one operation per \
                   thread)."))
        .arg(Arg::with_name("benchmark-count")
            .long("benchmark-count")
            .value_name("1..1000")
            .takes_value(true)
            .help("How many times to perform the specified operation."))
        .arg(Arg::with_name("sharpen")
            .long("sharpen")
            .value_name("0..100")
            .takes_value(true)
            .help("Percent sharpening to apply."))
        .arg(Arg::with_name("format")
            .long("format")
            .value_name("png | jpg | png24")
            .takes_value(true)
            .possible_values(&["png", "jpeg", "jpg", "png24"])
            .help("Output image format to use. Baseline jpeg and 32-bit PNG supported."))
        .arg(Arg::with_name("constrain")
            .long("constrain")
            .value_name("max | distort")
            .takes_value(true)
            .possible_values(&["max", "distort"])
            .help("Constrain the image within a bounding box (max) or stretch to fit (distort)"))
        .arg(Arg::with_name("down-filter")
            .long("down-filter")
            .value_name("FILTER")
            .takes_value(true)
            .possible_values(TESTED_FILTER_OPTIONS)
            .help("Filter to use when downscaling"))
        .arg(Arg::with_name("up-filter")
            .long("up-filter")
            .value_name("FILTER")
            .possible_values(TESTED_FILTER_OPTIONS)
            .takes_value(true)
            .help("Filter to use when upscaling"))
        .arg(Arg::with_name("incorrectgamma")
            .long("incorrectgamma")
            .help("Enables incorrect gamma handling (for benchmarking comparison purposes)."))
        .arg(Arg::with_name("min_precise_scaling_ratio")
            .long("min_precise_scaling_ratio")
            .short("mpsr")
            .value_name("MINRATIO")
            .takes_value(true)
            .help("Defaults to 2.1. Jpeg-integrated block scaling is permitted down to 2.1x \
                   final size"))
}

struct ParsedResult {
    input_file: PathBuf,
    output_file: PathBuf,
    commands: BoringCommands,
    bench: Option<BenchmarkOptions>,
}

fn parse(matches: ArgMatches) -> Result<ParsedResult, String> {

    let w = matches.value_of("width").and_then(|x| x.parse::<u32>().ok());
    let h = matches.value_of("height").and_then(|x| x.parse::<u32>().ok());

    let sharpen = matches.value_of("sharpen").and_then(|x| x.parse::<f32>().ok()); //.and_then(|x| Some(x / 100f32));

    let q = matches.value_of("jpeg-quality").and_then(|x| x.parse::<i32>().ok());

    let fmt = value_t!(matches, "format", ImageFormat).unwrap_or(ImageFormat::Jpeg);

    let constrain = value_t!(matches, "constrain", ConstraintMode).unwrap_or(ConstraintMode::Max);

    let down_filter = value_t!(matches, "down-filter", Filter).unwrap_or(Filter::Robidoux);
    let up_filter = value_t!(matches, "up-filter", Filter).unwrap_or(Filter::Ginseng);

    // Clap requires these to exist, thus the safe unwrap()
    let in_file = Path::new(matches.value_of("input").unwrap());
    let out_file = Path::new(matches.value_of("output").unwrap());
    let min_precise_scaling_ratio = matches.value_of("min_precise_scaling_ratio")
        .and_then(|x| x.parse::<f32>().ok());

    if w.or(h).is_none() {
        return Err("You must specified at least one of (width, height) ".to_owned());
    }
    if !in_file.is_file() {
        return Err(format!("The specified input file could not be found: {}",
                           in_file.to_str().unwrap()));
    }

    // parent() will return "", an empty path. We don't want to validate the current dir exists, that's going too far.
    let parent_exists = out_file.parent()
        .and_then(|p| match p.to_str().unwrap() {
            "" => None,
            _ => Some(p),
        })
        .map(|p| p.exists());

    // It's OK if there's no parent directory specified.
    // It's only bad if there's one specified and it doesn't exists.
    let warn_about_parent = !parent_exists.unwrap_or(true);
    if warn_about_parent {
        return Err(format!("The parent directory for the output file could not be found: {}",
                           out_file.parent().unwrap().to_str().unwrap()));
    }

    let commands = BoringCommands {
        w: w.unwrap_or(0) as i32,
        h: h.unwrap_or(0) as i32,
        sharpen: sharpen.unwrap_or(0f32) as f32,
        jpeg_quality: q.unwrap_or(100),
        fit: constrain,
        precise_scaling_ratio: min_precise_scaling_ratio.unwrap_or(2.1f32),
        luma_correct: !matches.is_present("incorrectgamma"),
        format: fmt,
        up_filter: up_filter,
        down_filter: down_filter,
    };

    let b_threads = matches.value_of("benchmark-threads").and_then(|x| x.parse::<usize>().ok());

    let b_count = matches.value_of("benchmark-count").and_then(|x| x.parse::<usize>().ok());

    let bench;
    if b_threads.is_some() || b_count.is_some() {
        bench = Some(BenchmarkOptions {
            thread_count: b_threads.unwrap_or(1),
            run_count: b_count.unwrap_or(10),
            input_path: in_file.to_path_buf(),
            commands: commands.clone(),
        });
    } else {
        bench = None;
    }

    Ok(ParsedResult {
        input_file: in_file.to_path_buf(),
        output_file: out_file.to_path_buf(),
        commands: commands,
        bench: bench,
    })

}

fn main() {
    let matches = build_app().get_matches();

    let result = match parse(matches) {
        Ok(c) => {
            match process_image_by_paths(c.input_file, c.output_file, c.commands) {
                Ok(_) if c.bench.is_some() => {
                    match benchmark(c.bench.unwrap()) {
                        Ok(results) => {
                            println!("{}", results.to_json_string());
                            Ok(())
                        }
                        Err(e) => Err(e),
                    }
                }
                Ok(res) => Ok(res),
                Err(e) => Err(e),
            }
        }
        Err(e) => Err(e),
    };

    if result.is_err() {
        println!("Failed: {}", result.err().unwrap());
        std::process::exit(1);
    }

}

#[test]
fn test_correct_parsing() {

    File::create("delete.jpg").expect("File creation should work.");

    let valid_args = vec!["flow-proto1",
                          "-i",
                          "delete.jpg",
                          "-o",
                          "b.jpg",
                          "-w",
                          "20",
                          "-h",
                          "20",
                          "--down-filter",
                          "mitchell"];

    let result = parse(build_app().get_matches_from(valid_args)).expect("To parse correctly");

    assert_eq!(result.commands.down_filter, Filter::Mitchell);

    // std::fs::remove_file("delete.jpg").unwrap();

}

#[test]
fn test_correct_execution() {

    {

        let mut f = File::create("test_input.jpg").unwrap();

        let jpeg_bytes =
            &[0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01,
              0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01,
              0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00,
              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA,
              0x00, 0x08, 0x01, 0x01, 0x00, 0x01, 0x3F, 0x10];

        f.write_all(jpeg_bytes).unwrap();
        f.sync_all().unwrap();
        drop(f);
    }

    let valid_args =
        vec!["flow-proto1", "-i", "test_input.jpg", "-o", "b.jpg", "-w", "20", "-h", "20"];

    let parsed_result = parse(build_app().get_matches_from(valid_args))
        .expect("To parse correctly");

    process_image_by_paths(parsed_result.input_file,
                           parsed_result.output_file,
                           parsed_result.commands)
        .unwrap();

    // std::fs::remove_file("delete.jpg").unwrap();

}
<commit_msg>Bump flow-proto1 version to 0.0.2<commit_after>#[macro_use]
extern crate clap;
use clap::{App, Arg, ArgMatches};
extern crate imageflow_core;
use imageflow_core::boring::*;

use imageflow_core::ffi::{Filter, TESTED_FILTER_OPTIONS};
use std::path::{PathBuf, Path};
use std::fs::File;
use std::io::Write;
// TODO
// Disclaim use for jpeg optimization
// Disclaim use for png or gif files
// Focus on scaling
// No metadata support
// Let users adjust quality setting

fn build_app() -> App<'static, 'static> {

    App::new("flow-proto1")
        .version("0.0.2")
        .author("Email us at imageflow@imazen.io")
        .about("Throwaway prototype tool to play with downscaling jpegs via libimageflow. Not for \
                production use. Optimizations disabled on Windows; expect poor perf there. Lots \
                of things are broken, but you should still tell us about them.")
        .arg(Arg::with_name("v")
            .short("v")
            .long("verbose")
            .multiple(true)
            .help("Sets the level of verbosity"))
        .arg(Arg::with_name("input")
            .short("i")
            .long("input")
            .value_name("FILEIN")
            .takes_value(true)
            .required(true)
            .help("path to input file"))
        .arg(Arg::with_name("output")
            .short("o")
            .long("output")
            .value_name("FILEOUT")
            .takes_value(true)
            .required(true)
            .help("path to output file"))
        .arg(Arg::with_name("width")
            .short("w")
            .long("width")
            .value_name("WIDTH")
            .takes_value(true)
            .required(true)
            .help("scale to this width or smaller."))
        .arg(Arg::with_name("height")
            .short("h")
            .long("height")
            .value_name("HEIGHT")
            .takes_value(true)
            .required(true)
            .help("scale to this height or smaller."))
        .arg(Arg::with_name("jpeg-quality")
            .long("jpeg-quality")
            .value_name("0..100")
            .takes_value(true)
            .help("Jpeg compression level."))
        .arg(Arg::with_name("benchmark-threads")
            .long("benchmark-threads")
            .value_name("1..16")
            .takes_value(true)
            .help("Number of concurrent threads to use when benchmarking (one operation per \
                   thread)."))
        .arg(Arg::with_name("benchmark-count")
            .long("benchmark-count")
            .value_name("1..1000")
            .takes_value(true)
            .help("How many times to perform the specified operation."))
        .arg(Arg::with_name("sharpen")
            .long("sharpen")
            .value_name("0..100")
            .takes_value(true)
            .help("Percent sharpening to apply."))
        .arg(Arg::with_name("format")
            .long("format")
            .value_name("png | jpg | png24")
            .takes_value(true)
            .possible_values(&["png", "jpeg", "jpg", "png24"])
            .help("Output image format to use. Baseline jpeg and 32-bit PNG supported."))
        .arg(Arg::with_name("constrain")
            .long("constrain")
            .value_name("max | distort")
            .takes_value(true)
            .possible_values(&["max", "distort"])
            .help("Constrain the image within a bounding box (max) or stretch to fit (distort)"))
        .arg(Arg::with_name("down-filter")
            .long("down-filter")
            .value_name("FILTER")
            .takes_value(true)
            .possible_values(TESTED_FILTER_OPTIONS)
            .help("Filter to use when downscaling"))
        .arg(Arg::with_name("up-filter")
            .long("up-filter")
            .value_name("FILTER")
            .possible_values(TESTED_FILTER_OPTIONS)
            .takes_value(true)
            .help("Filter to use when upscaling"))
        .arg(Arg::with_name("incorrectgamma")
            .long("incorrectgamma")
            .help("Enables incorrect gamma handling (for benchmarking comparison purposes)."))
        .arg(Arg::with_name("min_precise_scaling_ratio")
            .long("min_precise_scaling_ratio")
            .short("mpsr")
            .value_name("MINRATIO")
            .takes_value(true)
            .help("Defaults to 2.1. Jpeg-integrated block scaling is permitted down to 2.1x \
                   final size"))
}

struct ParsedResult {
    input_file: PathBuf,
    output_file: PathBuf,
    commands: BoringCommands,
    bench: Option<BenchmarkOptions>,
}

fn parse(matches: ArgMatches) -> Result<ParsedResult, String> {

    let w = matches.value_of("width").and_then(|x| x.parse::<u32>().ok());
    let h = matches.value_of("height").and_then(|x| x.parse::<u32>().ok());

    let sharpen = matches.value_of("sharpen").and_then(|x| x.parse::<f32>().ok()); //.and_then(|x| Some(x / 100f32));

    let q = matches.value_of("jpeg-quality").and_then(|x| x.parse::<i32>().ok());

    let fmt = value_t!(matches, "format", ImageFormat).unwrap_or(ImageFormat::Jpeg);

    let constrain = value_t!(matches, "constrain", ConstraintMode).unwrap_or(ConstraintMode::Max);

    let down_filter = value_t!(matches, "down-filter", Filter).unwrap_or(Filter::Robidoux);
    let up_filter = value_t!(matches, "up-filter", Filter).unwrap_or(Filter::Ginseng);

    // Clap requires these to exist, thus the safe unwrap()
    let in_file = Path::new(matches.value_of("input").unwrap());
    let out_file = Path::new(matches.value_of("output").unwrap());
    let min_precise_scaling_ratio = matches.value_of("min_precise_scaling_ratio")
        .and_then(|x| x.parse::<f32>().ok());

    if w.or(h).is_none() {
        return Err("You must specified at least one of (width, height) ".to_owned());
    }
    if !in_file.is_file() {
        return Err(format!("The specified input file could not be found: {}",
                           in_file.to_str().unwrap()));
    }

    // parent() will return "", an empty path. We don't want to validate the current dir exists, that's going too far.
    let parent_exists = out_file.parent()
        .and_then(|p| match p.to_str().unwrap() {
            "" => None,
            _ => Some(p),
        })
        .map(|p| p.exists());

    // It's OK if there's no parent directory specified.
    // It's only bad if there's one specified and it doesn't exists.
    let warn_about_parent = !parent_exists.unwrap_or(true);
    if warn_about_parent {
        return Err(format!("The parent directory for the output file could not be found: {}",
                           out_file.parent().unwrap().to_str().unwrap()));
    }

    let commands = BoringCommands {
        w: w.unwrap_or(0) as i32,
        h: h.unwrap_or(0) as i32,
        sharpen: sharpen.unwrap_or(0f32) as f32,
        jpeg_quality: q.unwrap_or(100),
        fit: constrain,
        precise_scaling_ratio: min_precise_scaling_ratio.unwrap_or(2.1f32),
        luma_correct: !matches.is_present("incorrectgamma"),
        format: fmt,
        up_filter: up_filter,
        down_filter: down_filter,
    };

    let b_threads = matches.value_of("benchmark-threads").and_then(|x| x.parse::<usize>().ok());

    let b_count = matches.value_of("benchmark-count").and_then(|x| x.parse::<usize>().ok());

    let bench;
    if b_threads.is_some() || b_count.is_some() {
        bench = Some(BenchmarkOptions {
            thread_count: b_threads.unwrap_or(1),
            run_count: b_count.unwrap_or(10),
            input_path: in_file.to_path_buf(),
            commands: commands.clone(),
        });
    } else {
        bench = None;
    }

    Ok(ParsedResult {
        input_file: in_file.to_path_buf(),
        output_file: out_file.to_path_buf(),
        commands: commands,
        bench: bench,
    })

}

fn main() {
    let matches = build_app().get_matches();

    let result = match parse(matches) {
        Ok(c) => {
            match process_image_by_paths(c.input_file, c.output_file, c.commands) {
                Ok(_) if c.bench.is_some() => {
                    match benchmark(c.bench.unwrap()) {
                        Ok(results) => {
                            println!("{}", results.to_json_string());
                            Ok(())
                        }
                        Err(e) => Err(e),
                    }
                }
                Ok(res) => Ok(res),
                Err(e) => Err(e),
            }
        }
        Err(e) => Err(e),
    };

    if result.is_err() {
        println!("Failed: {}", result.err().unwrap());
        std::process::exit(1);
    }

}

#[test]
fn test_correct_parsing() {

    File::create("delete.jpg").expect("File creation should work.");

    let valid_args = vec!["flow-proto1",
                          "-i",
                          "delete.jpg",
                          "-o",
                          "b.jpg",
                          "-w",
                          "20",
                          "-h",
                          "20",
                          "--down-filter",
                          "mitchell"];

    let result = parse(build_app().get_matches_from(valid_args)).expect("To parse correctly");

    assert_eq!(result.commands.down_filter, Filter::Mitchell);

    // std::fs::remove_file("delete.jpg").unwrap();

}

#[test]
fn test_correct_execution() {

    {

        let mut f = File::create("test_input.jpg").unwrap();

        let jpeg_bytes =
            &[0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01,
              0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01,
              0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00,
              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA,
              0x00, 0x08, 0x01, 0x01, 0x00, 0x01, 0x3F, 0x10];

        f.write_all(jpeg_bytes).unwrap();
        f.sync_all().unwrap();
        drop(f);
    }

    let valid_args =
        vec!["flow-proto1", "-i", "test_input.jpg", "-o", "b.jpg", "-w", "20", "-h", "20"];

    let parsed_result = parse(build_app().get_matches_from(valid_args))
        .expect("To parse correctly");

    process_image_by_paths(parsed_result.input_file,
                           parsed_result.output_file,
                           parsed_result.commands)
        .unwrap();

    // std::fs::remove_file("delete.jpg").unwrap();

}
<|endoftext|>


<commit_before><commit_msg>Typed actor refs derive Clone<commit_after><|endoftext|>


<commit_before>use crate::{config::Config, env::Env, file_saver::save_to_file, nameutil, version::Version};
use log::info;
use std::{fs::File, io::prelude::*};
use toml::{self, value::Table, Value};

pub fn generate(env: &Env) -> String {
    info!("Generating sys Cargo.toml for {}", env.config.library_name);

    let path = env.config.target_path.join("Cargo.toml");

    let mut toml_str = String::new();
    if let Ok(mut file) = File::open(&path) {
        file.read_to_string(&mut toml_str).unwrap();
    }
    let empty = toml_str.trim().is_empty();
    let mut root_table = toml::from_str(&toml_str).unwrap_or_else(|_| Table::new());
    let crate_name = get_crate_name(&env.config, &root_table);

    if empty {
        fill_empty(&mut root_table, env, &crate_name);
    }
    fill_in(&mut root_table, env);

    save_to_file(&path, env.config.make_backup, |w| {
        w.write_all(toml::to_string(&root_table).unwrap().as_bytes())
    });

    crate_name
}

fn fill_empty(root: &mut Table, env: &Env, crate_name: &str) {
    let package_name = nameutil::exported_crate_name(crate_name);

    {
        let package = upsert_table(root, "package");
        set_string(package, "name", package_name);
        set_string(package, "version", "0.0.1");
        set_string(
            package,
            "links",
            nameutil::crate_name(&env.config.library_name),
        );
    }

    {
        let lib = upsert_table(root, "lib");
        set_string(lib, "name", crate_name);
    }

    let deps = upsert_table(root, "dependencies");
    for ext_lib in &env.config.external_libraries {
        let ext_package = if ext_lib.crate_name == "cairo" {
            format!("{}-sys-rs", ext_lib.crate_name)
        } else if ext_lib.crate_name == "gdk_pixbuf" {
            "gdk-pixbuf-sys".into()
        } else {
            format!("{}-sys", ext_lib.crate_name)
        };
        let dep = upsert_table(deps, &*ext_package);
        if ext_lib.crate_name == "cairo" {
            set_string(dep, "git", "https://github.com/gtk-rs/cairo");
        } else if ext_package.starts_with("sourceview") {
            set_string(dep, "git", "https://github.com/gtk-rs/sourceview");
        } else {
            set_string(dep, "git", "https://github.com/gtk-rs/sys");
        }
    }
}

fn fill_in(root: &mut Table, env: &Env) {
    {
        let package = upsert_table(root, "package");
        set_string(package, "build", "build.rs");
        //set_string(package, "version", "0.2.0");
    }

    {
        let deps = upsert_table(root, "dependencies");
        set_string(deps, "libc", "0.2");
    }

    {
        let build_deps = upsert_table(root, "build-dependencies");
        set_string(build_deps, "pkg-config", "0.3.7");
    }

    {
        let dev_deps = upsert_table(root, "dev-dependencies");
        set_string(dev_deps, "shell-words", "0.1.0");
        set_string(dev_deps, "tempdir", "0.3");
    }

    {
        let features = upsert_table(root, "features");
        let versions = env
            .namespaces
            .main()
            .versions
            .iter()
            .filter(|&&v| v > env.config.min_cfg_version);
        versions.fold(None::<Version>, |prev, &version| {
            let prev_array: Vec<Value> =
                prev.iter().map(|v| Value::String(v.to_feature())).collect();
            features.insert(version.to_feature(), Value::Array(prev_array));
            Some(version)
        });
        features.insert("dox".to_string(), Value::Array(Vec::new()));
    }

    {
        let docs_rs_metadata = upsert_table(root, "package.metadata.docs.rs");
        let mut docs_rs_features = env.config.docs_rs_features.clone();
        docs_rs_features.push("dox".to_owned());
        docs_rs_metadata.insert(
            "features".to_string(),
            Value::Array(
                docs_rs_features
                    .into_iter()
                    .map(Value::String)
                    .collect::<Vec<_>>(),
            ),
        );
    }
}

/// Returns the name of crate being currently generated.
fn get_crate_name(config: &Config, root: &Table) -> String {
    if let Some(&Value::Table(ref lib)) = root.get("lib") {
        if let Some(&Value::String(ref lib_name)) = lib.get("name") {
            //Converting don't needed as library target names cannot contain hyphens
            return lib_name.to_owned();
        }
    }
    if let Some(&Value::Table(ref package)) = root.get("package") {
        if let Some(&Value::String(ref package_name)) = package.get("name") {
            return nameutil::crate_name(package_name);
        }
    }
    return format!("{}_sys", nameutil::crate_name(&config.library_name));
}

fn set_string<S: Into<String>>(table: &mut Table, name: &str, new_value: S) {
    if let Some(v) = table.get_mut(name) {
        *v = Value::String(new_value.into());
        return;
    }
    table.insert(name.into(), Value::String(new_value.into()));
}

fn upsert_table<S: Into<String>>(parent: &mut Table, name: S) -> &mut Table {
    if let Value::Table(ref mut table) = *parent
        .entry(name.into())
        .or_insert_with(|| Value::Table(toml::map::Map::new()))
    {
        table
    } else {
        unreachable!()
    }
}
<commit_msg>Fix invalid double quotes generation for docs.rs feature<commit_after>use crate::{config::Config, env::Env, file_saver::save_to_file, nameutil, version::Version};
use log::info;
use std::{fs::File, io::prelude::*};
use toml::{self, value::Table, Value};

pub fn generate(env: &Env) -> String {
    info!("Generating sys Cargo.toml for {}", env.config.library_name);

    let path = env.config.target_path.join("Cargo.toml");

    let mut toml_str = String::new();
    if let Ok(mut file) = File::open(&path) {
        file.read_to_string(&mut toml_str).unwrap();
    }
    let empty = toml_str.trim().is_empty();
    let mut root_table = toml::from_str(&toml_str).unwrap_or_else(|_| Table::new());
    let crate_name = get_crate_name(&env.config, &root_table);

    if empty {
        fill_empty(&mut root_table, env, &crate_name);
    }
    fill_in(&mut root_table, env);

    save_to_file(&path, env.config.make_backup, |w| {
        w.write_all(toml::to_string(&root_table).unwrap().as_bytes())
    });

    crate_name
}

fn fill_empty(root: &mut Table, env: &Env, crate_name: &str) {
    let package_name = nameutil::exported_crate_name(crate_name);

    {
        let package = upsert_table(root, "package");
        set_string(package, "name", package_name);
        set_string(package, "version", "0.0.1");
        set_string(
            package,
            "links",
            nameutil::crate_name(&env.config.library_name),
        );
    }

    {
        let lib = upsert_table(root, "lib");
        set_string(lib, "name", crate_name);
    }

    let deps = upsert_table(root, "dependencies");
    for ext_lib in &env.config.external_libraries {
        let ext_package = if ext_lib.crate_name == "cairo" {
            format!("{}-sys-rs", ext_lib.crate_name)
        } else if ext_lib.crate_name == "gdk_pixbuf" {
            "gdk-pixbuf-sys".into()
        } else {
            format!("{}-sys", ext_lib.crate_name)
        };
        let dep = upsert_table(deps, &*ext_package);
        if ext_lib.crate_name == "cairo" {
            set_string(dep, "git", "https://github.com/gtk-rs/cairo");
        } else if ext_package.starts_with("sourceview") {
            set_string(dep, "git", "https://github.com/gtk-rs/sourceview");
        } else {
            set_string(dep, "git", "https://github.com/gtk-rs/sys");
        }
    }
}

fn fill_in(root: &mut Table, env: &Env) {
    {
        let package = upsert_table(root, "package");
        set_string(package, "build", "build.rs");
        //set_string(package, "version", "0.2.0");
    }

    {
        let deps = upsert_table(root, "dependencies");
        set_string(deps, "libc", "0.2");
    }

    {
        let build_deps = upsert_table(root, "build-dependencies");
        set_string(build_deps, "pkg-config", "0.3.7");
    }

    {
        let dev_deps = upsert_table(root, "dev-dependencies");
        set_string(dev_deps, "shell-words", "0.1.0");
        set_string(dev_deps, "tempdir", "0.3");
    }

    {
        let features = upsert_table(root, "features");
        let versions = env
            .namespaces
            .main()
            .versions
            .iter()
            .filter(|&&v| v > env.config.min_cfg_version);
        versions.fold(None::<Version>, |prev, &version| {
            let prev_array: Vec<Value> =
                prev.iter().map(|v| Value::String(v.to_feature())).collect();
            features.insert(version.to_feature(), Value::Array(prev_array));
            Some(version)
        });
        features.insert("dox".to_string(), Value::Array(Vec::new()));
    }

    {
        // Small trick to prevent having double quotes around it since toml doesn't like having '.'
        let docs_rs_metadata = upsert_table(root, "package");
        let docs_rs_metadata = upsert_table(docs_rs_metadata, "metadata");
        let docs_rs_metadata = upsert_table(docs_rs_metadata, "docs");
        let docs_rs_metadata = upsert_table(docs_rs_metadata, "rs");
        let mut docs_rs_features = env.config.docs_rs_features.clone();
        docs_rs_features.push("dox".to_owned());
        docs_rs_metadata.insert(
            "features".to_string(),
            Value::Array(
                docs_rs_features
                    .into_iter()
                    .map(Value::String)
                    .collect::<Vec<_>>(),
            ),
        );
    }
}

/// Returns the name of crate being currently generated.
fn get_crate_name(config: &Config, root: &Table) -> String {
    if let Some(&Value::Table(ref lib)) = root.get("lib") {
        if let Some(&Value::String(ref lib_name)) = lib.get("name") {
            //Converting don't needed as library target names cannot contain hyphens
            return lib_name.to_owned();
        }
    }
    if let Some(&Value::Table(ref package)) = root.get("package") {
        if let Some(&Value::String(ref package_name)) = package.get("name") {
            return nameutil::crate_name(package_name);
        }
    }
    return format!("{}_sys", nameutil::crate_name(&config.library_name));
}

fn set_string<S: Into<String>>(table: &mut Table, name: &str, new_value: S) {
    if let Some(v) = table.get_mut(name) {
        *v = Value::String(new_value.into());
        return;
    }
    table.insert(name.into(), Value::String(new_value.into()));
}

fn upsert_table<S: Into<String>>(parent: &mut Table, name: S) -> &mut Table {
    if let Value::Table(ref mut table) = *parent
        .entry(name.into())
        .or_insert_with(|| Value::Table(toml::map::Map::new()))
    {
        table
    } else {
        unreachable!()
    }
}
<|endoftext|>


<commit_before>//! Assorted testing utilities.
//!
//! Most notable things are:
//!
//! * Rich text comparison, which outputs a diff.
//! * Extracting markup (mainly, `<|>` markers) out of fixture strings.
//! * marks (see the eponymous module).

#[macro_use]
pub mod marks;

use std::{
    fs,
    path::{Path, PathBuf},
};

use serde_json::Value;
use text_unit::{TextRange, TextUnit};

pub use difference::Changeset as __Changeset;

pub const CURSOR_MARKER: &str = "<|>";

/// Asserts that two strings are equal, otherwise displays a rich diff between them.
///
/// The diff shows changes from the "original" left string to the "actual" right string.
///
/// All arguments starting from and including the 3rd one are passed to
/// `eprintln!()` macro in case of text inequality.
#[macro_export]
macro_rules! assert_eq_text {
    ($left:expr, $right:expr) => {
        assert_eq_text!($left, $right,)
    };
    ($left:expr, $right:expr, $($tt:tt)*) => {{
        let left = $left;
        let right = $right;
        if left != right {
            if left.trim() == right.trim() {
                eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right);
            } else {
                let changeset = $crate::__Changeset::new(right, left, "\n");
                eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, changeset);
            }
            eprintln!($($tt)*);
            panic!("text differs");
        }
    }};
}

/// Infallible version of `try_extract_offset()`.
pub fn extract_offset(text: &str) -> (TextUnit, String) {
    match try_extract_offset(text) {
        None => panic!("text should contain cursor marker"),
        Some(result) => result,
    }
}

/// Returns the offset of the first occurence of `<|>` marker and the copy of `text`
/// without the marker.
fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> {
    let cursor_pos = text.find(CURSOR_MARKER)?;
    let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len());
    new_text.push_str(&text[..cursor_pos]);
    new_text.push_str(&text[cursor_pos + CURSOR_MARKER.len()..]);
    let cursor_pos = TextUnit::from(cursor_pos as u32);
    Some((cursor_pos, new_text))
}

/// Infallible version of `try_extract_range()`.
pub fn extract_range(text: &str) -> (TextRange, String) {
    match try_extract_range(text) {
        None => panic!("text should contain cursor marker"),
        Some(result) => result,
    }
}

/// Returns `TextRange` between the first two markers `<|>...<|>` and the copy
/// of `text` without both of these markers.
fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
    let (start, text) = try_extract_offset(text)?;
    let (end, text) = try_extract_offset(&text)?;
    Some((TextRange::from_to(start, end), text))
}

#[derive(Clone, Copy)]
pub enum RangeOrOffset {
    Range(TextRange),
    Offset(TextUnit),
}

impl From<RangeOrOffset> for TextRange {
    fn from(selection: RangeOrOffset) -> Self {
        match selection {
            RangeOrOffset::Range(it) => it,
            RangeOrOffset::Offset(it) => TextRange::from_to(it, it),
        }
    }
}

/// Extracts `TextRange` or `TextUnit` depending on the amount of `<|>` markers
/// found in `text`.
///
/// # Panics
/// Panics if no `<|>` marker is present in the `text`.
pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) {
    if let Some((range, text)) = try_extract_range(text) {
        return (RangeOrOffset::Range(range), text);
    }
    let (offset, text) = extract_offset(text);
    (RangeOrOffset::Offset(offset), text)
}

/// Extracts ranges, marked with `<tag> </tag>` pairs from the `text`
pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) {
    let open = format!("<{}>", tag);
    let close = format!("</{}>", tag);
    let mut ranges = Vec::new();
    let mut res = String::new();
    let mut stack = Vec::new();
    loop {
        match text.find('<') {
            None => {
                res.push_str(text);
                break;
            }
            Some(i) => {
                res.push_str(&text[..i]);
                text = &text[i..];
                if text.starts_with(&open) {
                    text = &text[open.len()..];
                    let from = TextUnit::of_str(&res);
                    stack.push(from);
                } else if text.starts_with(&close) {
                    text = &text[close.len()..];
                    let from = stack.pop().unwrap_or_else(|| panic!("unmatched </{}>", tag));
                    let to = TextUnit::of_str(&res);
                    ranges.push(TextRange::from_to(from, to));
                }
            }
        }
    }
    assert!(stack.is_empty(), "unmatched <{}>", tag);
    ranges.sort_by_key(|r| (r.start(), r.end()));
    (ranges, res)
}

/// Inserts `<|>` marker into the `text` at `offset`.
pub fn add_cursor(text: &str, offset: TextUnit) -> String {
    let offset: usize = offset.to_usize();
    let mut res = String::new();
    res.push_str(&text[..offset]);
    res.push_str("<|>");
    res.push_str(&text[offset..]);
    res
}

#[derive(Debug)]
pub struct FixtureEntry {
    pub meta: String,
    pub text: String,
}

/// Parses text which looks like this:
///
///  ```not_rust
///  //- some meta
///  line 1
///  line 2
///  // - other meta
///  ```
pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
    let margin = fixture
        .lines()
        .filter(|it| it.trim_start().starts_with("//-"))
        .map(|it| it.len() - it.trim_start().len())
        .next()
        .expect("empty fixture");

    let mut lines = fixture
        .split('\n') // don't use `.lines` to not drop `\r\n`
        .filter_map(|line| {
            if line.len() >= margin {
                assert!(line[..margin].trim().is_empty());
                Some(&line[margin..])
            } else {
                assert!(line.trim().is_empty());
                None
            }
        });

    let mut res: Vec<FixtureEntry> = Vec::new();
    for line in lines.by_ref() {
        if line.starts_with("//-") {
            let meta = line["//-".len()..].trim().to_string();
            res.push(FixtureEntry { meta, text: String::new() })
        } else if let Some(entry) = res.last_mut() {
            entry.text.push_str(line);
            entry.text.push('\n');
        }
    }
    res
}

/// Same as `parse_fixture`, except it allow empty fixture
pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> {
    if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) {
        return None;
    }
    parse_fixture(fixture).into_iter().nth(0)
}

// Comparison functionality borrowed from cargo:

/// Compare a line with an expected pattern.
/// - Use `[..]` as a wildcard to match 0 or more characters on the same line
///   (similar to `.*` in a regex).
pub fn lines_match(expected: &str, actual: &str) -> bool {
    // Let's not deal with / vs \ (windows...)
    // First replace backslash-escaped backslashes with forward slashes
    // which can occur in, for example, JSON output
    let expected = expected.replace(r"\\", "/").replace(r"\", "/");
    let mut actual: &str = &actual.replace(r"\\", "/").replace(r"\", "/");
    for (i, part) in expected.split("[..]").enumerate() {
        match actual.find(part) {
            Some(j) => {
                if i == 0 && j != 0 {
                    return false;
                }
                actual = &actual[j + part.len()..];
            }
            None => return false,
        }
    }
    actual.is_empty() || expected.ends_with("[..]")
}

#[test]
fn lines_match_works() {
    assert!(lines_match("a b", "a b"));
    assert!(lines_match("a[..]b", "a b"));
    assert!(lines_match("a[..]", "a b"));
    assert!(lines_match("[..]", "a b"));
    assert!(lines_match("[..]b", "a b"));

    assert!(!lines_match("[..]b", "c"));
    assert!(!lines_match("b", "c"));
    assert!(!lines_match("b", "cb"));
}

/// Compares JSON object for approximate equality.
/// You can use `[..]` wildcard in strings (useful for OS dependent things such
/// as paths). You can use a `"{...}"` string literal as a wildcard for
/// arbitrary nested JSON. Arrays are sorted before comparison.
pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> {
    use serde_json::Value::*;
    match (expected, actual) {
        (&Number(ref l), &Number(ref r)) if l == r => None,
        (&Bool(l), &Bool(r)) if l == r => None,
        (&String(ref l), &String(ref r)) if lines_match(l, r) => None,
        (&Array(ref l), &Array(ref r)) => {
            if l.len() != r.len() {
                return Some((expected, actual));
            }

            let mut l = l.iter().collect::<Vec<_>>();
            let mut r = r.iter().collect::<Vec<_>>();

            l.retain(|l| match r.iter().position(|r| find_mismatch(l, r).is_none()) {
                Some(i) => {
                    r.remove(i);
                    false
                }
                None => true,
            });

            if !l.is_empty() {
                assert!(!r.is_empty());
                Some((&l[0], &r[0]))
            } else {
                assert_eq!(r.len(), 0);
                None
            }
        }
        (&Object(ref l), &Object(ref r)) => {
            let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
            if !same_keys {
                return Some((expected, actual));
            }

            l.values().zip(r.values()).filter_map(|(l, r)| find_mismatch(l, r)).next()
        }
        (&Null, &Null) => None,
        // magic string literal "{...}" acts as wildcard for any sub-JSON
        (&String(ref l), _) if l == "{...}" => None,
        _ => Some((expected, actual)),
    }
}

/// Calls callback `f` with input code and file paths of all `.rs` files from `test_data_dir`
/// subdirectories defined by `paths`.
///
/// If the content of the matching `.txt` file differs from the output of `f()`
/// the test will fail.
///
/// If there is no matching `.txt` file it will be created and filled with the
/// output of `f()`, but the test will fail.
pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], f: F)
where
    F: Fn(&str, &Path) -> String,
{
    for (path, input_code) in collect_tests(test_data_dir, paths) {
        let parse_tree = f(&input_code, &path);
        let path = path.with_extension("txt");
        if !path.exists() {
            println!("\nfile: {}", path.display());
            println!("No .txt file with expected result, creating...\n");
            println!("{}\n{}", input_code, parse_tree);
            fs::write(&path, &parse_tree).unwrap();
            panic!("No expected result")
        }
        let expected = read_text(&path);
        let expected = expected.as_str();
        let parse_tree = parse_tree.as_str();
        assert_equal_text(expected, parse_tree, &path);
    }
}

/// Collects all `.rs` files from `test_data_dir` subdirectories defined by `paths`.
pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
    paths
        .iter()
        .flat_map(|path| {
            let path = test_data_dir.to_owned().join(path);
            test_from_dir(&path).into_iter()
        })
        .map(|path| {
            let text = read_text(&path);
            (path, text)
        })
        .collect()
}

/// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`.
fn test_from_dir(dir: &Path) -> Vec<PathBuf> {
    let mut acc = Vec::new();
    for file in fs::read_dir(&dir).unwrap() {
        let file = file.unwrap();
        let path = file.path();
        if path.extension().unwrap_or_default() == "rs" {
            acc.push(path);
        }
    }
    acc.sort();
    acc
}

/// Returns the path to the root directory of `rust-analyzer` project.
pub fn project_dir() -> PathBuf {
    let dir = env!("CARGO_MANIFEST_DIR");
    PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned()
}

/// Read file and normalize newlines.
///
/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
///
/// ```
/// let s = "
/// ";
/// assert_eq!(s.as_bytes(), &[10]);
/// ```
///
/// so this should always be correct.
pub fn read_text(path: &Path) -> String {
    fs::read_to_string(path)
        .unwrap_or_else(|_| panic!("File at {:?} should be valid", path))
        .replace("\r\n", "\n")
}

/// Returns `false` if slow tests should not run, otherwise returns `true` and
/// also creates a file at `./target/.slow_tests_cookie` which serves as a flag
/// that slow tests did run.
pub fn skip_slow_tests() -> bool {
    let should_skip = std::env::var("CI").is_err() && std::env::var("RUN_SLOW_TESTS").is_err();
    if should_skip {
        eprintln!("ignoring slow test")
    } else {
        let path = project_dir().join("./target/.slow_tests_cookie");
        fs::write(&path, ".").unwrap();
    }
    should_skip
}

/// Asserts that `expected` and `actual` strings are equal. If they differ only
/// in trailing or leading whitespace the test won't fail and
/// the contents of `actual` will be written to the file located at `path`.
fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
    if expected == actual {
        return;
    }
    let dir = project_dir();
    let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
    if expected.trim() == actual.trim() {
        println!("whitespace difference, rewriting");
        println!("file: {}\n", pretty_path.display());
        fs::write(path, actual).unwrap();
        return;
    }
    const REWRITE: bool = false;
    if REWRITE {
        println!("rewriting {}", pretty_path.display());
        fs::write(path, actual).unwrap();
        return;
    }
    assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
}
<commit_msg>Check only one fixture<commit_after>//! Assorted testing utilities.
//!
//! Most notable things are:
//!
//! * Rich text comparison, which outputs a diff.
//! * Extracting markup (mainly, `<|>` markers) out of fixture strings.
//! * marks (see the eponymous module).

#[macro_use]
pub mod marks;

use std::{
    fs,
    path::{Path, PathBuf},
};

use serde_json::Value;
use text_unit::{TextRange, TextUnit};

pub use difference::Changeset as __Changeset;

pub const CURSOR_MARKER: &str = "<|>";

/// Asserts that two strings are equal, otherwise displays a rich diff between them.
///
/// The diff shows changes from the "original" left string to the "actual" right string.
///
/// All arguments starting from and including the 3rd one are passed to
/// `eprintln!()` macro in case of text inequality.
#[macro_export]
macro_rules! assert_eq_text {
    ($left:expr, $right:expr) => {
        assert_eq_text!($left, $right,)
    };
    ($left:expr, $right:expr, $($tt:tt)*) => {{
        let left = $left;
        let right = $right;
        if left != right {
            if left.trim() == right.trim() {
                eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right);
            } else {
                let changeset = $crate::__Changeset::new(right, left, "\n");
                eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, changeset);
            }
            eprintln!($($tt)*);
            panic!("text differs");
        }
    }};
}

/// Infallible version of `try_extract_offset()`.
pub fn extract_offset(text: &str) -> (TextUnit, String) {
    match try_extract_offset(text) {
        None => panic!("text should contain cursor marker"),
        Some(result) => result,
    }
}

/// Returns the offset of the first occurence of `<|>` marker and the copy of `text`
/// without the marker.
fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> {
    let cursor_pos = text.find(CURSOR_MARKER)?;
    let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len());
    new_text.push_str(&text[..cursor_pos]);
    new_text.push_str(&text[cursor_pos + CURSOR_MARKER.len()..]);
    let cursor_pos = TextUnit::from(cursor_pos as u32);
    Some((cursor_pos, new_text))
}

/// Infallible version of `try_extract_range()`.
pub fn extract_range(text: &str) -> (TextRange, String) {
    match try_extract_range(text) {
        None => panic!("text should contain cursor marker"),
        Some(result) => result,
    }
}

/// Returns `TextRange` between the first two markers `<|>...<|>` and the copy
/// of `text` without both of these markers.
fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
    let (start, text) = try_extract_offset(text)?;
    let (end, text) = try_extract_offset(&text)?;
    Some((TextRange::from_to(start, end), text))
}

#[derive(Clone, Copy)]
pub enum RangeOrOffset {
    Range(TextRange),
    Offset(TextUnit),
}

impl From<RangeOrOffset> for TextRange {
    fn from(selection: RangeOrOffset) -> Self {
        match selection {
            RangeOrOffset::Range(it) => it,
            RangeOrOffset::Offset(it) => TextRange::from_to(it, it),
        }
    }
}

/// Extracts `TextRange` or `TextUnit` depending on the amount of `<|>` markers
/// found in `text`.
///
/// # Panics
/// Panics if no `<|>` marker is present in the `text`.
pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) {
    if let Some((range, text)) = try_extract_range(text) {
        return (RangeOrOffset::Range(range), text);
    }
    let (offset, text) = extract_offset(text);
    (RangeOrOffset::Offset(offset), text)
}

/// Extracts ranges, marked with `<tag> </tag>` pairs from the `text`
pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) {
    let open = format!("<{}>", tag);
    let close = format!("</{}>", tag);
    let mut ranges = Vec::new();
    let mut res = String::new();
    let mut stack = Vec::new();
    loop {
        match text.find('<') {
            None => {
                res.push_str(text);
                break;
            }
            Some(i) => {
                res.push_str(&text[..i]);
                text = &text[i..];
                if text.starts_with(&open) {
                    text = &text[open.len()..];
                    let from = TextUnit::of_str(&res);
                    stack.push(from);
                } else if text.starts_with(&close) {
                    text = &text[close.len()..];
                    let from = stack.pop().unwrap_or_else(|| panic!("unmatched </{}>", tag));
                    let to = TextUnit::of_str(&res);
                    ranges.push(TextRange::from_to(from, to));
                }
            }
        }
    }
    assert!(stack.is_empty(), "unmatched <{}>", tag);
    ranges.sort_by_key(|r| (r.start(), r.end()));
    (ranges, res)
}

/// Inserts `<|>` marker into the `text` at `offset`.
pub fn add_cursor(text: &str, offset: TextUnit) -> String {
    let offset: usize = offset.to_usize();
    let mut res = String::new();
    res.push_str(&text[..offset]);
    res.push_str("<|>");
    res.push_str(&text[offset..]);
    res
}

#[derive(Debug)]
pub struct FixtureEntry {
    pub meta: String,
    pub text: String,
}

/// Parses text which looks like this:
///
///  ```not_rust
///  //- some meta
///  line 1
///  line 2
///  // - other meta
///  ```
pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
    let margin = fixture
        .lines()
        .filter(|it| it.trim_start().starts_with("//-"))
        .map(|it| it.len() - it.trim_start().len())
        .next()
        .expect("empty fixture");

    let mut lines = fixture
        .split('\n') // don't use `.lines` to not drop `\r\n`
        .filter_map(|line| {
            if line.len() >= margin {
                assert!(line[..margin].trim().is_empty());
                Some(&line[margin..])
            } else {
                assert!(line.trim().is_empty());
                None
            }
        });

    let mut res: Vec<FixtureEntry> = Vec::new();
    for line in lines.by_ref() {
        if line.starts_with("//-") {
            let meta = line["//-".len()..].trim().to_string();
            res.push(FixtureEntry { meta, text: String::new() })
        } else if let Some(entry) = res.last_mut() {
            entry.text.push_str(line);
            entry.text.push('\n');
        }
    }
    res
}

/// Same as `parse_fixture`, except it allow empty fixture
pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> {
    if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) {
        return None;
    }

    let fixtures = parse_fixture(fixture);
    if fixtures.len() > 1 {
        panic!("too many fixtures");
    }
    fixtures.into_iter().nth(0)
}

// Comparison functionality borrowed from cargo:

/// Compare a line with an expected pattern.
/// - Use `[..]` as a wildcard to match 0 or more characters on the same line
///   (similar to `.*` in a regex).
pub fn lines_match(expected: &str, actual: &str) -> bool {
    // Let's not deal with / vs \ (windows...)
    // First replace backslash-escaped backslashes with forward slashes
    // which can occur in, for example, JSON output
    let expected = expected.replace(r"\\", "/").replace(r"\", "/");
    let mut actual: &str = &actual.replace(r"\\", "/").replace(r"\", "/");
    for (i, part) in expected.split("[..]").enumerate() {
        match actual.find(part) {
            Some(j) => {
                if i == 0 && j != 0 {
                    return false;
                }
                actual = &actual[j + part.len()..];
            }
            None => return false,
        }
    }
    actual.is_empty() || expected.ends_with("[..]")
}

#[test]
fn lines_match_works() {
    assert!(lines_match("a b", "a b"));
    assert!(lines_match("a[..]b", "a b"));
    assert!(lines_match("a[..]", "a b"));
    assert!(lines_match("[..]", "a b"));
    assert!(lines_match("[..]b", "a b"));

    assert!(!lines_match("[..]b", "c"));
    assert!(!lines_match("b", "c"));
    assert!(!lines_match("b", "cb"));
}

/// Compares JSON object for approximate equality.
/// You can use `[..]` wildcard in strings (useful for OS dependent things such
/// as paths). You can use a `"{...}"` string literal as a wildcard for
/// arbitrary nested JSON. Arrays are sorted before comparison.
pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> {
    use serde_json::Value::*;
    match (expected, actual) {
        (&Number(ref l), &Number(ref r)) if l == r => None,
        (&Bool(l), &Bool(r)) if l == r => None,
        (&String(ref l), &String(ref r)) if lines_match(l, r) => None,
        (&Array(ref l), &Array(ref r)) => {
            if l.len() != r.len() {
                return Some((expected, actual));
            }

            let mut l = l.iter().collect::<Vec<_>>();
            let mut r = r.iter().collect::<Vec<_>>();

            l.retain(|l| match r.iter().position(|r| find_mismatch(l, r).is_none()) {
                Some(i) => {
                    r.remove(i);
                    false
                }
                None => true,
            });

            if !l.is_empty() {
                assert!(!r.is_empty());
                Some((&l[0], &r[0]))
            } else {
                assert_eq!(r.len(), 0);
                None
            }
        }
        (&Object(ref l), &Object(ref r)) => {
            let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
            if !same_keys {
                return Some((expected, actual));
            }

            l.values().zip(r.values()).filter_map(|(l, r)| find_mismatch(l, r)).next()
        }
        (&Null, &Null) => None,
        // magic string literal "{...}" acts as wildcard for any sub-JSON
        (&String(ref l), _) if l == "{...}" => None,
        _ => Some((expected, actual)),
    }
}

/// Calls callback `f` with input code and file paths of all `.rs` files from `test_data_dir`
/// subdirectories defined by `paths`.
///
/// If the content of the matching `.txt` file differs from the output of `f()`
/// the test will fail.
///
/// If there is no matching `.txt` file it will be created and filled with the
/// output of `f()`, but the test will fail.
pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], f: F)
where
    F: Fn(&str, &Path) -> String,
{
    for (path, input_code) in collect_tests(test_data_dir, paths) {
        let parse_tree = f(&input_code, &path);
        let path = path.with_extension("txt");
        if !path.exists() {
            println!("\nfile: {}", path.display());
            println!("No .txt file with expected result, creating...\n");
            println!("{}\n{}", input_code, parse_tree);
            fs::write(&path, &parse_tree).unwrap();
            panic!("No expected result")
        }
        let expected = read_text(&path);
        let expected = expected.as_str();
        let parse_tree = parse_tree.as_str();
        assert_equal_text(expected, parse_tree, &path);
    }
}

/// Collects all `.rs` files from `test_data_dir` subdirectories defined by `paths`.
pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
    paths
        .iter()
        .flat_map(|path| {
            let path = test_data_dir.to_owned().join(path);
            test_from_dir(&path).into_iter()
        })
        .map(|path| {
            let text = read_text(&path);
            (path, text)
        })
        .collect()
}

/// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`.
fn test_from_dir(dir: &Path) -> Vec<PathBuf> {
    let mut acc = Vec::new();
    for file in fs::read_dir(&dir).unwrap() {
        let file = file.unwrap();
        let path = file.path();
        if path.extension().unwrap_or_default() == "rs" {
            acc.push(path);
        }
    }
    acc.sort();
    acc
}

/// Returns the path to the root directory of `rust-analyzer` project.
pub fn project_dir() -> PathBuf {
    let dir = env!("CARGO_MANIFEST_DIR");
    PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned()
}

/// Read file and normalize newlines.
///
/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
///
/// ```
/// let s = "
/// ";
/// assert_eq!(s.as_bytes(), &[10]);
/// ```
///
/// so this should always be correct.
pub fn read_text(path: &Path) -> String {
    fs::read_to_string(path)
        .unwrap_or_else(|_| panic!("File at {:?} should be valid", path))
        .replace("\r\n", "\n")
}

/// Returns `false` if slow tests should not run, otherwise returns `true` and
/// also creates a file at `./target/.slow_tests_cookie` which serves as a flag
/// that slow tests did run.
pub fn skip_slow_tests() -> bool {
    let should_skip = std::env::var("CI").is_err() && std::env::var("RUN_SLOW_TESTS").is_err();
    if should_skip {
        eprintln!("ignoring slow test")
    } else {
        let path = project_dir().join("./target/.slow_tests_cookie");
        fs::write(&path, ".").unwrap();
    }
    should_skip
}

/// Asserts that `expected` and `actual` strings are equal. If they differ only
/// in trailing or leading whitespace the test won't fail and
/// the contents of `actual` will be written to the file located at `path`.
fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
    if expected == actual {
        return;
    }
    let dir = project_dir();
    let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
    if expected.trim() == actual.trim() {
        println!("whitespace difference, rewriting");
        println!("file: {}\n", pretty_path.display());
        fs::write(path, actual).unwrap();
        return;
    }
    const REWRITE: bool = false;
    if REWRITE {
        println!("rewriting {}", pretty_path.display());
        fs::write(path, actual).unwrap();
        return;
    }
    assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
}
<|endoftext|>


<commit_before>use distribution::Distribution;
use generator::Generator;

/// A Bernoulli distribution.
#[derive(Clone, Copy)]
pub struct Bernoulli {
    /// The probability of success.
    pub p: f64,
    /// The probability of failure.
    pub q: f64,

    pq: f64,
}

impl Bernoulli {
    /// Create a Bernoulli distribution with success probability `p`.
    ///
    /// It should hold that `p > 0` and `p < 1`.
    #[inline]
    pub fn new(p: f64) -> Bernoulli {
        should!(p > 0.0 && p < 1.0);
        Bernoulli { p: p, q: 1.0 - p, pq: p * (1.0 - p) }
    }

    /// Create a Bernoulli distribution with failure probability `q`.
    ///
    /// It should hold that `q > 0` and `q < 1`. The constructor is preferable
    /// when `q` is very small.
    #[inline]
    pub fn new_failprob(q: f64) -> Bernoulli {
        should!(q > 0.0 && q < 1.0);
        Bernoulli { p: 1.0 - q, q: q, pq: (1.0 - q) * q }
    }
}

impl Distribution for Bernoulli {
    type Value = u8;

    #[inline] fn mean(&self) -> f64 { self.p }
    #[inline] fn var(&self) -> f64 { self.pq }
    #[inline] fn skewness(&self) -> f64 { (1.0 - 2.0 * self.p) / (self.pq).sqrt() }
    #[inline] fn kurtosis(&self) -> f64 { (1.0 - 6.0 * self.pq) / (self.pq) }

    #[inline]
    fn median(&self) -> f64 {
        use std::cmp::Ordering::*;
        match self.p.partial_cmp(&self.q) {
            Some(Less) => 0.0,
            Some(Equal) => 0.5,
            Some(Greater) => 1.0,
            None => unreachable!(),
        }
    }

    #[inline]
    fn modes(&self) -> Vec<u8> {
        use std::cmp::Ordering::*;
        match self.p.partial_cmp(&self.q) {
            Some(Less) => vec![0],
            Some(Equal) => vec![0, 1],
            Some(Greater) => vec![1],
            None => unreachable!(),
        }
    }

    #[inline] fn entropy(&self) -> f64 { -self.q * self.q.ln() - self.p * self.p.ln() }
    #[inline] fn cdf(&self, x: u8) -> f64 { if x == 0 { self.q } else { 1.0 } }

    #[inline]
    fn inv_cdf(&self, p: f64) -> u8 {
        should!(0.0 <= p && p <= 1.0);
        if p <= self.q { 0 } else { 1 }
    }

    #[inline]
    fn pdf(&self, x: u8) -> f64 {
        if x == 0 { self.q } else if x == 1 { self.p } else { 0.0 }
    }

    #[inline(always)]
    fn sample<G: Generator>(&self, generator: &mut G) -> u8 {
        if generator.next::<f64>() < self.q { 0 } else { 1 }
    }
}

#[cfg(test)]
mod tests {
    use assert;
    use prelude::*;

    macro_rules! new(
        (failure $q:expr) => (Bernoulli::new_failprob($q));
        ($p:expr) => (Bernoulli::new($p));
    );

    #[test]
    fn mean() {
        assert_eq!(new!(0.5).mean(), 0.5);
    }

    #[test]
    fn var() {
        assert_eq!(new!(0.25).var(), 0.1875);
    }

    #[test]
    fn sd() {
        assert_eq!(new!(0.5).sd(), 0.5);
    }

    #[test]
    fn skewness() {
        assert_eq!(new!(0.5).skewness(), 0.0);
    }

    #[test]
    fn kurtosis() {
        assert_eq!(new!(0.5).kurtosis(), -2.0);
    }

    #[test]
    fn median() {
        assert_eq!(new!(0.25).median(), 0.0);
        assert_eq!(new!(0.5).median(), 0.5);
        assert_eq!(new!(0.75).median(), 1.0);
    }

    #[test]
    fn modes() {
        assert_eq!(new!(0.25).modes(), vec![0]);
        assert_eq!(new!(0.5).modes(), vec![0, 1]);
        assert_eq!(new!(0.75).modes(), vec![1]);
    }

    #[test]
    fn entropy() {
        let bernoullies = vec![new!(0.25), new!(0.5), new!(0.75)];
        assert::close(&bernoullies.iter().map(|d| d.entropy()).collect::<Vec<_>>(),
                      &vec![0.5623351446188083, 0.6931471805599453, 0.5623351446188083], 1e-16);
    }

    #[test]
    fn pdf() {
        let bernoulli = new!(0.25);
        assert_eq!(&(0..3).map(|x| bernoulli.pdf(x)).collect::<Vec<_>>(), &[0.75, 0.25, 0.0]);
    }

    #[test]
    fn cdf() {
        let bernoulli = new!(0.25);
        assert_eq!(&(0..3).map(|x| bernoulli.cdf(x)).collect::<Vec<_>>(), &[0.75, 1.0, 1.0]);
    }

    #[test]
    fn inv_cdf() {
        let bernoulli = new!(0.25);
        let p = vec![0.0, 0.25, 0.5, 0.75, 0.75000000001, 1.0];
        let x = vec![0, 0, 0, 0, 1, 1];
        assert_eq!(&p.iter().map(|&p| bernoulli.inv_cdf(p)).collect::<Vec<_>>(), &x);
    }

    #[test]
    fn sample() {
        assert!(Sampler(&new!(0.25), &mut generator()).take(100).fold(0, |a, b| a + b) <= 100);
    }
}
<commit_msg>bernoulli: adjust the description of new_failprob<commit_after>use distribution::Distribution;
use generator::Generator;

/// A Bernoulli distribution.
#[derive(Clone, Copy)]
pub struct Bernoulli {
    /// The probability of success.
    pub p: f64,
    /// The probability of failure.
    pub q: f64,

    pq: f64,
}

impl Bernoulli {
    /// Create a Bernoulli distribution with success probability `p`.
    ///
    /// It should hold that `p > 0` and `p < 1`.
    #[inline]
    pub fn new(p: f64) -> Bernoulli {
        should!(p > 0.0 && p < 1.0);
        Bernoulli { p: p, q: 1.0 - p, pq: p * (1.0 - p) }
    }

    /// Create a Bernoulli distribution with failure probability `q`.
    ///
    /// It should hold that `q > 0` and `q < 1`. This constructor is preferable
    /// when `q` is very small.
    #[inline]
    pub fn new_failprob(q: f64) -> Bernoulli {
        should!(q > 0.0 && q < 1.0);
        Bernoulli { p: 1.0 - q, q: q, pq: (1.0 - q) * q }
    }
}

impl Distribution for Bernoulli {
    type Value = u8;

    #[inline] fn mean(&self) -> f64 { self.p }
    #[inline] fn var(&self) -> f64 { self.pq }
    #[inline] fn skewness(&self) -> f64 { (1.0 - 2.0 * self.p) / (self.pq).sqrt() }
    #[inline] fn kurtosis(&self) -> f64 { (1.0 - 6.0 * self.pq) / (self.pq) }

    #[inline]
    fn median(&self) -> f64 {
        use std::cmp::Ordering::*;
        match self.p.partial_cmp(&self.q) {
            Some(Less) => 0.0,
            Some(Equal) => 0.5,
            Some(Greater) => 1.0,
            None => unreachable!(),
        }
    }

    #[inline]
    fn modes(&self) -> Vec<u8> {
        use std::cmp::Ordering::*;
        match self.p.partial_cmp(&self.q) {
            Some(Less) => vec![0],
            Some(Equal) => vec![0, 1],
            Some(Greater) => vec![1],
            None => unreachable!(),
        }
    }

    #[inline] fn entropy(&self) -> f64 { -self.q * self.q.ln() - self.p * self.p.ln() }
    #[inline] fn cdf(&self, x: u8) -> f64 { if x == 0 { self.q } else { 1.0 } }

    #[inline]
    fn inv_cdf(&self, p: f64) -> u8 {
        should!(0.0 <= p && p <= 1.0);
        if p <= self.q { 0 } else { 1 }
    }

    #[inline]
    fn pdf(&self, x: u8) -> f64 {
        if x == 0 { self.q } else if x == 1 { self.p } else { 0.0 }
    }

    #[inline(always)]
    fn sample<G: Generator>(&self, generator: &mut G) -> u8 {
        if generator.next::<f64>() < self.q { 0 } else { 1 }
    }
}

#[cfg(test)]
mod tests {
    use assert;
    use prelude::*;

    macro_rules! new(
        (failure $q:expr) => (Bernoulli::new_failprob($q));
        ($p:expr) => (Bernoulli::new($p));
    );

    #[test]
    fn mean() {
        assert_eq!(new!(0.5).mean(), 0.5);
    }

    #[test]
    fn var() {
        assert_eq!(new!(0.25).var(), 0.1875);
    }

    #[test]
    fn sd() {
        assert_eq!(new!(0.5).sd(), 0.5);
    }

    #[test]
    fn skewness() {
        assert_eq!(new!(0.5).skewness(), 0.0);
    }

    #[test]
    fn kurtosis() {
        assert_eq!(new!(0.5).kurtosis(), -2.0);
    }

    #[test]
    fn median() {
        assert_eq!(new!(0.25).median(), 0.0);
        assert_eq!(new!(0.5).median(), 0.5);
        assert_eq!(new!(0.75).median(), 1.0);
    }

    #[test]
    fn modes() {
        assert_eq!(new!(0.25).modes(), vec![0]);
        assert_eq!(new!(0.5).modes(), vec![0, 1]);
        assert_eq!(new!(0.75).modes(), vec![1]);
    }

    #[test]
    fn entropy() {
        let bernoullies = vec![new!(0.25), new!(0.5), new!(0.75)];
        assert::close(&bernoullies.iter().map(|d| d.entropy()).collect::<Vec<_>>(),
                      &vec![0.5623351446188083, 0.6931471805599453, 0.5623351446188083], 1e-16);
    }

    #[test]
    fn pdf() {
        let bernoulli = new!(0.25);
        assert_eq!(&(0..3).map(|x| bernoulli.pdf(x)).collect::<Vec<_>>(), &[0.75, 0.25, 0.0]);
    }

    #[test]
    fn cdf() {
        let bernoulli = new!(0.25);
        assert_eq!(&(0..3).map(|x| bernoulli.cdf(x)).collect::<Vec<_>>(), &[0.75, 1.0, 1.0]);
    }

    #[test]
    fn inv_cdf() {
        let bernoulli = new!(0.25);
        let p = vec![0.0, 0.25, 0.5, 0.75, 0.75000000001, 1.0];
        let x = vec![0, 0, 0, 0, 1, 1];
        assert_eq!(&p.iter().map(|&p| bernoulli.inv_cdf(p)).collect::<Vec<_>>(), &x);
    }

    #[test]
    fn sample() {
        assert!(Sampler(&new!(0.25), &mut generator()).take(100).fold(0, |a, b| a + b) <= 100);
    }
}
<|endoftext|>


<commit_before>// This integration test suite relies heavily on the string
// representation of the Violations struct, as that's the final
// user-visible output that the zakocmp binary presents.
extern crate indoc;

use indoc::indoc;
use libzakocmp::config::Config;
use libzakocmp::snapshot::snapshot_string_for_testing;
use libzakocmp::snapshot::Snapshot;

#[test]
fn test_basic_default_immutability() {
    let config: Config = Config::new(r#"default-policy: immutable"#).unwrap();

    // Verifies that empty snapshots never turn up violations.
    let empty_older = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_newer = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_violations = libzakocmp::enter(&config, &empty_older, &empty_newer);
    assert_eq!(empty_violations.to_string(), "");

    // Verifies that disjoint snapshots also violate this policy.
    let disjoint_older = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./a/b/c",
    ))
    .unwrap();
    let disjoint_newer = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./x/y/z",
    ))
    .unwrap();
    let disjoint_violations = libzakocmp::enter(&config, &disjoint_older, &disjoint_newer);
    // From zakocmp's point of view, ``./a/b/c'' was deleted and
    // ``./x/y/z'' was added.
    assert_eq!(
        disjoint_violations.to_string(),
        indoc!(
            r#"
            - ./a/b/c
            + ./x/y/z
            "#
        )
    );

    // Verifies that zakocmp catches the two changed files interspersed
    // with others that did not.
    let shifty_older = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_newer = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_violations = libzakocmp::enter(&config, &shifty_older, &shifty_newer);
    assert_eq!(
        shifty_violations.to_string(),
        indoc!(
            r#"
            ! ./a/b/changed
            ! ./i/j/changed
            "#
        )
    );

    // As an additional sanity check, verifies that zakocmp snapshots
    // are not sensitive to the ordering of input files.
    let shifty_newer_shuffled = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        "#
    )))
    .unwrap();
    let the_same_shifty_violations =
        libzakocmp::enter(&config, &shifty_older, &shifty_newer_shuffled);
    assert_eq!(
        shifty_violations.to_string(),
        the_same_shifty_violations.to_string()
    );
}
<commit_msg>zakocmp: add involved integration test<commit_after>// This integration test suite relies heavily on the string
// representation of the Violations struct, as that's the final
// user-visible output that the zakocmp binary presents.
extern crate indoc;

use indoc::indoc;
use libzakocmp::config::Config;
use libzakocmp::snapshot::snapshot_string_for_testing;
use libzakocmp::snapshot::Snapshot;

#[test]
fn test_basic_default_immutability() {
    let config: Config = Config::new(r#"default-policy: immutable"#).unwrap();

    // Verifies that empty snapshots never turn up violations.
    let empty_older = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_newer = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_violations = libzakocmp::enter(&config, &empty_older, &empty_newer);
    assert_eq!(empty_violations.to_string(), "");

    // Verifies that disjoint snapshots also violate this policy.
    let disjoint_older = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./a/b/c",
    ))
    .unwrap();
    let disjoint_newer = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./x/y/z",
    ))
    .unwrap();
    let disjoint_violations = libzakocmp::enter(&config, &disjoint_older, &disjoint_newer);
    // From zakocmp's point of view, ``./a/b/c'' was deleted and
    // ``./x/y/z'' was added.
    assert_eq!(
        disjoint_violations.to_string(),
        indoc!(
            r#"
            - ./a/b/c
            + ./x/y/z
            "#
        )
    );

    // Verifies that zakocmp catches the two changed files interspersed
    // with others that did not.
    let shifty_older = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_newer = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_violations = libzakocmp::enter(&config, &shifty_older, &shifty_newer);
    assert_eq!(
        shifty_violations.to_string(),
        indoc!(
            r#"
            ! ./a/b/changed
            ! ./i/j/changed
            "#
        )
    );

    // As an additional sanity check, verifies that zakocmp snapshots
    // are not sensitive to the ordering of input files.
    let shifty_newer_shuffled = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        "#
    )))
    .unwrap();
    let the_same_shifty_violations =
        libzakocmp::enter(&config, &shifty_older, &shifty_newer_shuffled);
    assert_eq!(
        shifty_violations.to_string(),
        the_same_shifty_violations.to_string()
    );
}

#[test]
fn test_overlapping_prefixes() {
    let config = Config::new(
        r#"
    default-policy: immutable
    policies:
        ./Pictures/: immutable
        ./Pictures/2020/: ignore
        ./Documents/: nodelete
        "#,
    )
    .unwrap();
    let snapshot_older = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./Music/hello-there.mp3
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/general-kenobi.gif
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/2020/you-are.gif
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/2020/a-bold-one.gif
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/2019/something-immutable.jpg
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/2019/something-supposedly-immutable.jpg
        0000000000000000000000000000000000000000000000000000000000000000  ./Documents/nodelete-1.txt
        0000000000000000000000000000000000000000000000000000000000000000  ./Documents/nodelete-2.txt
        "#
    )))
    .unwrap();
    let snapshot_newer = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./Music/hello-there.mp3
        0000000000000000000000000000000000000000000000000000000000000000  ./Music/copy-of-hello-there.mp3
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./Pictures/general-kenobi.gif
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/copy-of-general-kenobi.gif
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/2020/copy-of-you-are.gif
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./Pictures/2020/a-bold-one.gif
        0000000000000000000000000000000000000000000000000000000000000000  ./Pictures/2019/something-immutable.jpg
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./Pictures/2019/something-supposedly-immutable.jpg
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./Documents/nodelete-2.txt
        "#
    )))
    .unwrap();

    let violations = libzakocmp::enter(&config, &snapshot_older, &snapshot_newer);
    assert_eq!(
        violations.to_string(),
        indoc!(
            r#"
            - ./Documents/nodelete-1.txt
            + ./Music/copy-of-hello-there.mp3
            ! ./Music/hello-there.mp3
            ! ./Pictures/2019/something-supposedly-immutable.jpg
            + ./Pictures/copy-of-general-kenobi.gif
            ! ./Pictures/general-kenobi.gif
            "#
        )
    );
}
<|endoftext|>


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

extern crate regex;

use regex::Regex;
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

fn main() {
    let root_path = Path::new("../../../");
    let bindings_file = root_path.join("components/style/gecko/generated/bindings.rs");
    let glue_file = root_path.join("ports/geckolib/glue.rs");

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed={}", glue_file.display());
    println!("cargo:rerun-if-changed={}", bindings_file.display());

    let env_out_dir = env::var("OUT_DIR").unwrap();
    let out_dir = Path::new(&env_out_dir);

    {
        let output = out_dir.join("check_bindings.rs");
        let r = BufReader::new(File::open(bindings_file).unwrap());
        let mut w = File::create(output).unwrap();

        w.write_all(b"fn assert_types() {\n").unwrap();

        let matcher = Regex::new(r"fn\s*Servo_([a-zA-Z0-9_]+)\s*\(").unwrap();

        for line in r.lines() {
            let s = line.unwrap();
            for cap in matcher.captures_iter(&s) {
                // This causes a mismatch in old libclangs (the ones that are
                // used in linux32 mozilla-central) because it generates:
                //
                //   *const nsTArray<*const RawServoStyleSet>
                //
                // Instead of:
                //
                //   *const nsTArray<RawServoStyleSetBorrowed>
                //
                // Which is not a problem, but would cause this to not compile.
                //
                // Skip this until libclang is updated there.
                if &cap[1] == "InvalidateStyleForDocStateChanges" {
                    continue;
                }
                w.write_all(format!("    [ Servo_{0}, bindings::Servo_{0} ];\n", &cap[1]).as_bytes()).unwrap();
            }
        }

        w.write_all(b"}\n").unwrap();
    }

    {
        let output = out_dir.join("glue.rs");
        let r = BufReader::new(File::open(glue_file).unwrap());
        let mut w = File::create(output).unwrap();

        w.write_all(b"pub use style::gecko::arc_types::*;\n").unwrap();

        for line in r.lines() {
            let s = line.unwrap().replace("pub extern \"C\" fn", "pub unsafe extern \"C\" fn");
            w.write_all(s.as_bytes()).unwrap();
            w.write_all(b"\n").unwrap();
        }
    }

    // https://github.com/rust-lang/cargo/issues/3544
    let style_out_dir = env::var_os("DEP_FOR SOME REASON THE LINKS KEY IS REQUIRED \
                                     TO PASS DATA AROUND BETWEEN BUILD SCRIPTS_OUT_DIR").unwrap();
    File::create(out_dir.join("bindings.rs"))
        .unwrap()
        .write_all(format!("include!(concat!({:?}, \"/gecko/structs.rs\"));",
                           style_out_dir).as_bytes())
        .unwrap();

    if env::var_os("MOZ_SRC").is_some() {
        println!("cargo:rustc-cfg=linking_with_gecko")
    }
}
<commit_msg>style: Switch stylo function signature checking to use generated bindings.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate regex;

use regex::Regex;
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

fn main() {
    // https://github.com/rust-lang/cargo/issues/3544
    let style_out_dir = env::var_os("DEP_FOR SOME REASON THE LINKS KEY IS REQUIRED \
                                     TO PASS DATA AROUND BETWEEN BUILD SCRIPTS_OUT_DIR").unwrap();
    let root_path = Path::new("../../../");
    let bindings_file = Path::new(&style_out_dir).join("gecko/bindings.rs");
    let glue_file = root_path.join("ports/geckolib/glue.rs");

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed={}", glue_file.display());
    println!("cargo:rerun-if-changed={}", bindings_file.display());

    let env_out_dir = env::var("OUT_DIR").unwrap();
    let out_dir = Path::new(&env_out_dir);

    {
        let output = out_dir.join("check_bindings.rs");
        let r = BufReader::new(File::open(bindings_file).unwrap());
        let mut w = File::create(output).unwrap();

        w.write_all(b"fn assert_types() {\n").unwrap();

        let matcher = Regex::new(r"fn\s*Servo_([a-zA-Z0-9_]+)\s*\(").unwrap();

        for line in r.lines() {
            let s = line.unwrap();
            for cap in matcher.captures_iter(&s) {
                // This causes a mismatch in old libclangs (the ones that are
                // used in linux32 mozilla-central) because it generates:
                //
                //   *const nsTArray<*const RawServoStyleSet>
                //
                // Instead of:
                //
                //   *const nsTArray<RawServoStyleSetBorrowed>
                //
                // Which is not a problem, but would cause this to not compile.
                //
                // Skip this until libclang is updated there.
                if &cap[1] == "InvalidateStyleForDocStateChanges" {
                    continue;
                }
                w.write_all(format!("    [ Servo_{0}, bindings::Servo_{0} ];\n", &cap[1]).as_bytes()).unwrap();
            }
        }

        w.write_all(b"}\n").unwrap();
    }

    {
        let output = out_dir.join("glue.rs");
        let r = BufReader::new(File::open(glue_file).unwrap());
        let mut w = File::create(output).unwrap();

        w.write_all(b"pub use style::gecko::arc_types::*;\n").unwrap();

        for line in r.lines() {
            let s = line.unwrap().replace("pub extern \"C\" fn", "pub unsafe extern \"C\" fn");
            w.write_all(s.as_bytes()).unwrap();
            w.write_all(b"\n").unwrap();
        }
    }

    File::create(out_dir.join("bindings.rs"))
        .unwrap()
        .write_all(format!("include!(concat!({:?}, \"/gecko/structs.rs\"));",
                           style_out_dir).as_bytes())
        .unwrap();

    if env::var_os("MOZ_SRC").is_some() {
        println!("cargo:rustc-cfg=linking_with_gecko")
    }
}
<|endoftext|>