<commit_before>#![recursion_limit = "1024"]

extern crate cargo_metadata;
extern crate chrono;
#[macro_use]
extern crate clap;
extern crate collector;
extern crate env_logger;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate rust_sysroot;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tempdir;

use failure::{Error, ResultExt, SyncFailure};

use std::fs;
use std::process;
use std::str;
use std::path::{Path, PathBuf};
use std::io::{stderr, stdout, Write};
use std::collections::BTreeMap;

use chrono::{DateTime, Utc};

use collector::{Commit, CommitData, Date};
use rust_sysroot::git::Commit as GitCommit;
use rust_sysroot::sysroot::Sysroot;

mod git;
mod execute;
mod outrepo;

use execute::Benchmark;

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Mode {
    Test,
    Normal,
}

fn bench_commit(
    commit: &GitCommit,
    repo: Option<&outrepo::Repo>,
    sysroot: Sysroot,
    benchmarks: &[Benchmark],
    iterations: usize,
    mode: Mode,
) -> CommitData {
    info!(
        "benchmarking commit {} ({}) for triple {}",
        commit.sha, commit.date, sysroot.triple
    );

    let existing_data = repo.and_then(|r| r.load_commit_data(&commit, &sysroot.triple).ok());

    let mut results = BTreeMap::new();
    if let Some(ref data) = existing_data {
        for benchmark in benchmarks {
            if let Some(result) = data.benchmarks.get(&benchmark.name) {
                results.insert(benchmark.name.clone(), result.clone());
            }
        }
    }
    for benchmark in benchmarks {
        if results.contains_key(&benchmark.name) {
            continue;
        }

        let result = benchmark.run(&sysroot, iterations, mode);

        if let Err(ref s) = result {
            info!("failure to benchmark {}, recorded: {}", benchmark.name, s);
        }

        results.insert(
            benchmark.name.clone(),
            result.map_err(|e| format!("{:?}", e)),
        );
        info!("{} benchmarks left", benchmarks.len() - results.len());
    }

    CommitData {
        commit: Commit {
            sha: commit.sha.clone(),
            date: Date(commit.date),
        },
        triple: sysroot.triple.clone(),
        benchmarks: results,
    }
}

fn get_benchmarks(
    benchmark_dir: &Path,
    filter: Option<&str>,
    exclude: Option<&str>,
) -> Result<Vec<Benchmark>, Error> {
    let mut benchmarks = Vec::new();
    for entry in fs::read_dir(benchmark_dir).context("failed to list benchmarks")? {
        let entry = entry?;
        let path = entry.path();
        let name = match entry.file_name().into_string() {
            Ok(s) => s,
            Err(e) => bail!("non-utf8 benchmark name: {:?}", e),
        };

        if path.ends_with(".git") || path.ends_with("scripts") || !entry.file_type()?.is_dir() {
            debug!("benchmark {} - ignored", name);
            continue;
        }

        if let Some(filter) = filter {
            if !name.contains(filter) {
                debug!("benchmark {} - filtered", name);
                continue;
            }
        }

        if let Some(exclude) = exclude {
            if name.contains(exclude) {
                debug!("benchmark {} - filtered", name);
                continue;
            }
        }

        debug!("benchmark `{}`- registered", name);
        benchmarks.push(Benchmark::new(name, path)?);
    }
    benchmarks.sort_by_key(|benchmark| benchmark.name.clone());
    Ok(benchmarks)
}

fn process_commit(
    repo: &outrepo::Repo,
    commit: &GitCommit,
    benchmarks: &[Benchmark],
) -> Result<(), Error> {
    let sysroot = Sysroot::install(commit, "x86_64-unknown-linux-gnu", false, false)
        .map_err(SyncFailure::new)?;
    repo.success(&bench_commit(
        commit,
        Some(repo),
        sysroot,
        benchmarks,
        3,
        Mode::Normal,
    ))
}

fn process_retries(
    commits: &[GitCommit],
    repo: &mut outrepo::Repo,
    benchmarks: &[Benchmark],
) -> Result<(), Error> {
    while let Some(retry) = repo.next_retry() {
        info!("retrying {}", retry);
        let commit = commits.iter().find(|commit| commit.sha == retry).unwrap();
        process_commit(repo, commit, benchmarks)?;
    }
    Ok(())
}

fn process_commits(
    commits: &[GitCommit],
    repo: &outrepo::Repo,
    benchmarks: &[Benchmark],
) -> Result<(), Error> {
    println!("processing commits");
    if !commits.is_empty() {
        let to_process =
            repo.find_missing_commits(commits, benchmarks, "x86_64-unknown-linux-gnu")?;
        // take 3 from the end -- this means that for each bors commit (which takes ~3 hours) we
        // test 3, which should allow us to eventually test all commits, but also keep up with the
        // latest rustc
        for commit in to_process.iter().rev().take(3) {
            if let Err(err) = process_commit(repo, &commit, &benchmarks) {
                repo.write_broken_commit(commit, err)?;
            }
        }
    } else {
        info!("Nothing to do; no commits.");
    }
    Ok(())
}

fn main() {
    process::exit(run().unwrap())
}

fn run() -> Result<i32, Error> {
    env_logger::init();
    git::fetch_rust(Path::new("rust.git"))?;

    let matches = clap_app!(rustc_perf_collector =>
       (version: "0.1")
       (author: "The Rust Compiler Team")
       (about: "Collects Rust performance data")
       (@arg filter: --filter +takes_value "Run only benchmarks that contain this")
       (@arg sync_git: --("sync-git") "Synchronize repository with remote")
       (@arg output_repo: --("output-repo") +required +takes_value "Repository to output to")
       (@subcommand process =>
           (about: "syncs to git and collects performance data for all versions")
       )
       (@subcommand bench_commit =>
           (about: "benchmark a bors merge from AWS and output data to stdout")
           (@arg COMMIT: +required +takes_value "Commit hash to bench")
       )
       (@subcommand bench_local =>
           (about: "benchmark a bors merge from AWS and output data to stdout")
           (@arg COMMIT: --commit +required +takes_value "Commit hash to associate benchmark results with")
           (@arg DATE: --date +required +takes_value "Date to associate benchmark result with, in the RFC3339 \"YYYY-MM-DDTHH:MM:SS-HH:MM\" format.")
           (@arg RUSTC: +required +takes_value "the path to the local rustc to benchmark")
       )
       (@subcommand remove_errs =>
           (about: "remove errored data")
       )
       (@subcommand remove_benchmark =>
           (about: "remove data for a benchmark")
           (@arg BENCHMARK: --benchmark +required +takes_value "benchmark name to remove data for")
       )
       (@subcommand test_benchmarks =>
           (about: "test some set of benchmarks, controlled by --filter")
       )
    ).get_matches();
    let benchmark_dir = PathBuf::from("collector/benchmarks");
    let filter = matches.value_of("filter");
    let benchmarks = get_benchmarks(
        &benchmark_dir,
        filter,
        if matches.subcommand().0 == "test_benchmarks" {
            Some("servo")
        } else {
            None
        },
    )?;
    let use_remote = matches.is_present("sync_git");
    let out_repo = PathBuf::from(matches.value_of_os("output_repo").unwrap());
    let mut out_repo = outrepo::Repo::open(out_repo, use_remote)?;

    let commits =
        rust_sysroot::get_commits(rust_sysroot::EPOCH_COMMIT, "master").map_err(SyncFailure::new)?;

    match matches.subcommand() {
        ("test_benchmarks", Some(_)) => {
            if let Some(commit) = commits.last() {
                let sysroot = Sysroot::install(commit, "x86_64-unknown-linux-gnu", false, false)
                    .map_err(SyncFailure::new)?;
                // filter out servo benchmarks as they simple take too long
                bench_commit(commit, None, sysroot, &benchmarks, 1, Mode::Test);
            } else {
                panic!("no commits");
            }
            Ok(0)
        }
        ("process", Some(_)) => {
            process_retries(&commits, &mut out_repo, &benchmarks)?;
            process_commits(&commits, &out_repo, &benchmarks)?;
            Ok(0)
        }
        ("bench_commit", Some(sub_m)) => {
            let commit = sub_m.value_of("COMMIT").unwrap();
            let commit = commits
                .iter()
                .find(|c| c.sha == commit)
                .cloned()
                .unwrap_or_else(|| {
                    warn!("utilizing fake commit!");
                    rust_sysroot::git::Commit {
                        sha: commit.to_string(),
                        date: Date::ymd_hms(2000, 01, 01, 0, 0, 0).0,
                        summary: String::new(),
                    }
                });
            process_commit(&out_repo, &commit, &benchmarks)?;
            Ok(0)
        }
        ("bench_local", Some(sub_m)) => {
            let commit = sub_m.value_of("COMMIT").unwrap();
            let date = sub_m.value_of("DATE").unwrap();
            let rustc = sub_m.value_of("RUSTC").unwrap();
            let commit = GitCommit {
                sha: commit.to_string(),
                date: DateTime::parse_from_rfc3339(date)?.with_timezone(&Utc),
                summary: String::new(),
            };
            let sysroot =
                Sysroot::with_local_rustc(&commit, rustc, "x86_64-unknown-linux-gnu", false, false)
                    .map_err(SyncFailure::new)?;
            let result = bench_commit(&commit, None, sysroot, &benchmarks, 3, Mode::Normal);
            serde_json::to_writer(&mut stdout(), &result)?;
            Ok(0)
        }
        ("remove_errs", Some(_)) => {
            for commit in &commits {
                if let Ok(mut data) = out_repo.load_commit_data(&commit, "x86_64-unknown-linux-gnu")
                {
                    let benchmarks = data.benchmarks
                        .into_iter()
                        .filter(|&(_, ref v)| v.is_ok())
                        .collect();
                    data.benchmarks = benchmarks;
                    out_repo.add_commit_data(&data)?;
                }
            }
            Ok(0)
        }
        ("remove_benchmark", Some(sub_m)) => {
            let benchmark = sub_m.value_of("BENCHMARK").unwrap();
            for commit in &commits {
                if let Ok(mut data) = out_repo.load_commit_data(&commit, "x86_64-unknown-linux-gnu")
                {
                    if data.benchmarks.remove(&*benchmark).is_none() {
                        warn!("could not remove {} from {}", benchmark, commit.sha);
                    }
                    out_repo.add_commit_data(&data)?;
                }
            }
            Ok(0)
        }
        _ => {
            let _ = writeln!(stderr(), "{}", matches.usage());
            Ok(2)
        }
    }
}
<commit_msg>Only get commits for subcommands that need them.<commit_after>#![recursion_limit = "1024"]

extern crate cargo_metadata;
extern crate chrono;
#[macro_use]
extern crate clap;
extern crate collector;
extern crate env_logger;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate rust_sysroot;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tempdir;

use failure::{Error, ResultExt, SyncFailure};

use std::fs;
use std::process;
use std::str;
use std::path::{Path, PathBuf};
use std::io::{stderr, stdout, Write};
use std::collections::BTreeMap;

use chrono::{DateTime, Utc};

use collector::{Commit, CommitData, Date};
use rust_sysroot::git::Commit as GitCommit;
use rust_sysroot::sysroot::Sysroot;

mod git;
mod execute;
mod outrepo;

use execute::Benchmark;

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Mode {
    Test,
    Normal,
}

fn bench_commit(
    commit: &GitCommit,
    repo: Option<&outrepo::Repo>,
    sysroot: Sysroot,
    benchmarks: &[Benchmark],
    iterations: usize,
    mode: Mode,
) -> CommitData {
    info!(
        "benchmarking commit {} ({}) for triple {}",
        commit.sha, commit.date, sysroot.triple
    );

    let existing_data = repo.and_then(|r| r.load_commit_data(&commit, &sysroot.triple).ok());

    let mut results = BTreeMap::new();
    if let Some(ref data) = existing_data {
        for benchmark in benchmarks {
            if let Some(result) = data.benchmarks.get(&benchmark.name) {
                results.insert(benchmark.name.clone(), result.clone());
            }
        }
    }
    for benchmark in benchmarks {
        if results.contains_key(&benchmark.name) {
            continue;
        }

        let result = benchmark.run(&sysroot, iterations, mode);

        if let Err(ref s) = result {
            info!("failure to benchmark {}, recorded: {}", benchmark.name, s);
        }

        results.insert(
            benchmark.name.clone(),
            result.map_err(|e| format!("{:?}", e)),
        );
        info!("{} benchmarks left", benchmarks.len() - results.len());
    }

    CommitData {
        commit: Commit {
            sha: commit.sha.clone(),
            date: Date(commit.date),
        },
        triple: sysroot.triple.clone(),
        benchmarks: results,
    }
}

fn get_benchmarks(
    benchmark_dir: &Path,
    filter: Option<&str>,
    exclude: Option<&str>,
) -> Result<Vec<Benchmark>, Error> {
    let mut benchmarks = Vec::new();
    for entry in fs::read_dir(benchmark_dir).context("failed to list benchmarks")? {
        let entry = entry?;
        let path = entry.path();
        let name = match entry.file_name().into_string() {
            Ok(s) => s,
            Err(e) => bail!("non-utf8 benchmark name: {:?}", e),
        };

        if path.ends_with(".git") || path.ends_with("scripts") || !entry.file_type()?.is_dir() {
            debug!("benchmark {} - ignored", name);
            continue;
        }

        if let Some(filter) = filter {
            if !name.contains(filter) {
                debug!("benchmark {} - filtered", name);
                continue;
            }
        }

        if let Some(exclude) = exclude {
            if name.contains(exclude) {
                debug!("benchmark {} - filtered", name);
                continue;
            }
        }

        debug!("benchmark `{}`- registered", name);
        benchmarks.push(Benchmark::new(name, path)?);
    }
    benchmarks.sort_by_key(|benchmark| benchmark.name.clone());
    Ok(benchmarks)
}

fn process_commit(
    repo: &outrepo::Repo,
    commit: &GitCommit,
    benchmarks: &[Benchmark],
) -> Result<(), Error> {
    let sysroot = Sysroot::install(commit, "x86_64-unknown-linux-gnu", false, false)
        .map_err(SyncFailure::new)?;
    repo.success(&bench_commit(
        commit,
        Some(repo),
        sysroot,
        benchmarks,
        3,
        Mode::Normal,
    ))
}

fn process_retries(
    commits: &[GitCommit],
    repo: &mut outrepo::Repo,
    benchmarks: &[Benchmark],
) -> Result<(), Error> {
    while let Some(retry) = repo.next_retry() {
        info!("retrying {}", retry);
        let commit = commits.iter().find(|commit| commit.sha == retry).unwrap();
        process_commit(repo, commit, benchmarks)?;
    }
    Ok(())
}

fn process_commits(
    commits: &[GitCommit],
    repo: &outrepo::Repo,
    benchmarks: &[Benchmark],
) -> Result<(), Error> {
    println!("processing commits");
    if !commits.is_empty() {
        let to_process =
            repo.find_missing_commits(commits, benchmarks, "x86_64-unknown-linux-gnu")?;
        // take 3 from the end -- this means that for each bors commit (which takes ~3 hours) we
        // test 3, which should allow us to eventually test all commits, but also keep up with the
        // latest rustc
        for commit in to_process.iter().rev().take(3) {
            if let Err(err) = process_commit(repo, &commit, &benchmarks) {
                repo.write_broken_commit(commit, err)?;
            }
        }
    } else {
        info!("Nothing to do; no commits.");
    }
    Ok(())
}

fn main() {
    process::exit(run().unwrap())
}

fn run() -> Result<i32, Error> {
    env_logger::init();
    git::fetch_rust(Path::new("rust.git"))?;

    let matches = clap_app!(rustc_perf_collector =>
       (version: "0.1")
       (author: "The Rust Compiler Team")
       (about: "Collects Rust performance data")
       (@arg filter: --filter +takes_value "Run only benchmarks that contain this")
       (@arg sync_git: --("sync-git") "Synchronize repository with remote")
       (@arg output_repo: --("output-repo") +required +takes_value "Repository to output to")
       (@subcommand process =>
           (about: "syncs to git and collects performance data for all versions")
       )
       (@subcommand bench_commit =>
           (about: "benchmark a bors merge from AWS and output data to stdout")
           (@arg COMMIT: +required +takes_value "Commit hash to bench")
       )
       (@subcommand bench_local =>
           (about: "benchmark a bors merge from AWS and output data to stdout")
           (@arg COMMIT: --commit +required +takes_value "Commit hash to associate benchmark results with")
           (@arg DATE: --date +required +takes_value "Date to associate benchmark result with, in the RFC3339 \"YYYY-MM-DDTHH:MM:SS-HH:MM\" format.")
           (@arg RUSTC: +required +takes_value "the path to the local rustc to benchmark")
       )
       (@subcommand remove_errs =>
           (about: "remove errored data")
       )
       (@subcommand remove_benchmark =>
           (about: "remove data for a benchmark")
           (@arg BENCHMARK: --benchmark +required +takes_value "benchmark name to remove data for")
       )
       (@subcommand test_benchmarks =>
           (about: "test some set of benchmarks, controlled by --filter")
       )
    ).get_matches();
    let benchmark_dir = PathBuf::from("collector/benchmarks");
    let filter = matches.value_of("filter");
    let benchmarks = get_benchmarks(
        &benchmark_dir,
        filter,
        if matches.subcommand().0 == "test_benchmarks" {
            Some("servo")
        } else {
            None
        },
    )?;
    let use_remote = matches.is_present("sync_git");
    let out_repo = PathBuf::from(matches.value_of_os("output_repo").unwrap());
    let mut out_repo = outrepo::Repo::open(out_repo, use_remote)?;

    let get_commits = || {
        rust_sysroot::get_commits(rust_sysroot::EPOCH_COMMIT, "master").map_err(SyncFailure::new)
    };

    match matches.subcommand() {
        ("test_benchmarks", Some(_)) => {
            if let Some(commit) = get_commits()?.last() {
                let sysroot = Sysroot::install(commit, "x86_64-unknown-linux-gnu", false, false)
                    .map_err(SyncFailure::new)?;
                // filter out servo benchmarks as they simple take too long
                bench_commit(commit, None, sysroot, &benchmarks, 1, Mode::Test);
            } else {
                panic!("no commits");
            }
            Ok(0)
        }
        ("process", Some(_)) => {
            let commits = get_commits()?;
            process_retries(&commits, &mut out_repo, &benchmarks)?;
            process_commits(&commits, &out_repo, &benchmarks)?;
            Ok(0)
        }
        ("bench_commit", Some(sub_m)) => {
            let commit = sub_m.value_of("COMMIT").unwrap();
            let commit = get_commits()?
                .iter()
                .find(|c| c.sha == commit)
                .cloned()
                .unwrap_or_else(|| {
                    warn!("utilizing fake commit!");
                    rust_sysroot::git::Commit {
                        sha: commit.to_string(),
                        date: Date::ymd_hms(2000, 01, 01, 0, 0, 0).0,
                        summary: String::new(),
                    }
                });
            process_commit(&out_repo, &commit, &benchmarks)?;
            Ok(0)
        }
        ("bench_local", Some(sub_m)) => {
            let commit = sub_m.value_of("COMMIT").unwrap();
            let date = sub_m.value_of("DATE").unwrap();
            let rustc = sub_m.value_of("RUSTC").unwrap();
            let commit = GitCommit {
                sha: commit.to_string(),
                date: DateTime::parse_from_rfc3339(date)?.with_timezone(&Utc),
                summary: String::new(),
            };
            let sysroot =
                Sysroot::with_local_rustc(&commit, rustc, "x86_64-unknown-linux-gnu", false, false)
                    .map_err(SyncFailure::new)?;
            let result = bench_commit(&commit, None, sysroot, &benchmarks, 3, Mode::Normal);
            serde_json::to_writer(&mut stdout(), &result)?;
            Ok(0)
        }
        ("remove_errs", Some(_)) => {
            for commit in &get_commits()? {
                if let Ok(mut data) = out_repo.load_commit_data(&commit, "x86_64-unknown-linux-gnu")
                {
                    let benchmarks = data.benchmarks
                        .into_iter()
                        .filter(|&(_, ref v)| v.is_ok())
                        .collect();
                    data.benchmarks = benchmarks;
                    out_repo.add_commit_data(&data)?;
                }
            }
            Ok(0)
        }
        ("remove_benchmark", Some(sub_m)) => {
            let benchmark = sub_m.value_of("BENCHMARK").unwrap();
            for commit in &get_commits()? {
                if let Ok(mut data) = out_repo.load_commit_data(&commit, "x86_64-unknown-linux-gnu")
                {
                    if data.benchmarks.remove(&*benchmark).is_none() {
                        warn!("could not remove {} from {}", benchmark, commit.sha);
                    }
                    out_repo.add_commit_data(&data)?;
                }
            }
            Ok(0)
        }
        _ => {
            let _ = writeln!(stderr(), "{}", matches.usage());
            Ok(2)
        }
    }
}
<|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/. */

use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::CGFont;
use core_text::font::CTFont;
use core_text;

use std::borrow::ToOwned;

/// Platform specific font representation for mac.
/// The identifier is a PostScript font name. The
/// CTFont object is cached here for use by the
/// paint functions that create CGFont references.
pub struct FontTemplateData {
    pub ctfont: Option<CTFont>,
    pub identifier: String,
}

unsafe impl Send for FontTemplateData {}
unsafe impl Sync for FontTemplateData {}

impl FontTemplateData {
    pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
        let ctfont = match font_data {
            Some(bytes) => {
                let fontprov = CGDataProvider::from_buffer(bytes.as_slice());
                let cgfont_result = CGFont::from_data_provider(fontprov);
                match cgfont_result {
                    Ok(cgfont) => Some(core_text::font::new_from_CGFont(&cgfont, 0.0)),
                    Err(_) => None
                }
            },
            None => {
                Some(core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap())
            }
        };

        FontTemplateData {
            ctfont: ctfont,
            identifier: identifier.to_owned(),
        }
    }
}
<commit_msg>Retain buffer for CGDataProviderCreateWithData. Fixes #5084.<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/. */

use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::CGFont;
use core_text::font::CTFont;
use core_text;

use std::borrow::ToOwned;

/// Platform specific font representation for mac.
/// The identifier is a PostScript font name. The
/// CTFont object is cached here for use by the
/// paint functions that create CGFont references.
pub struct FontTemplateData {
    pub ctfont: Option<CTFont>,
    pub identifier: String,
    pub font_data: Option<Vec<u8>>
}

unsafe impl Send for FontTemplateData {}
unsafe impl Sync for FontTemplateData {}

impl FontTemplateData {
    pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
        let ctfont = match font_data {
            Some(ref bytes) => {
                let fontprov = CGDataProvider::from_buffer(bytes.as_slice());
                let cgfont_result = CGFont::from_data_provider(fontprov);
                match cgfont_result {
                    Ok(cgfont) => Some(core_text::font::new_from_CGFont(&cgfont, 0.0)),
                    Err(_) => None
                }
            },
            None => {
                Some(core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap())
            }
        };

        FontTemplateData {
            ctfont: ctfont,
            identifier: identifier.to_owned(),
            font_data: font_data
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2021, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use serde::Deserialize;
use serde::Serialize;

use super::Bytes;

#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum KeyType {
    ServerInitialSecret,
    ClientInitialSecret,

    ServerHandshakeSecret,
    ClientHandshakeSecret,

    Server0RttSecret,
    Client0RttSecret,

    Server1RttSecret,
    Client1RttSecret,
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct KeyUpdated {
    key_type: KeyType,
    old: Option<Bytes>,
    new: Bytes,
    generation: Option<u32>,
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct KeyRetired {
    key_type: KeyType,
    key: Option<Bytes>,
    generation: Option<u32>,
}
<commit_msg>qlog: fix invalid auto snake case of KeyType<commit_after>// Copyright (C) 2021, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use serde::Deserialize;
use serde::Serialize;

use super::Bytes;

#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum KeyType {
    ServerInitialSecret,
    ClientInitialSecret,

    ServerHandshakeSecret,
    ClientHandshakeSecret,

    #[serde(rename = "server_0rtt_secret")]
    Server0RttSecret,
    #[serde(rename = "client_0rtt_secret")]
    Client0RttSecret,
    #[serde(rename = "server_1rtt_secret")]
    Server1RttSecret,
    #[serde(rename = "client_1rtt_secret")]
    Client1RttSecret,
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct KeyUpdated {
    key_type: KeyType,
    old: Option<Bytes>,
    new: Bytes,
    generation: Option<u32>,
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct KeyRetired {
    key_type: KeyType,
    key: Option<Bytes>,
    generation: Option<u32>,
}
<|endoftext|>


<commit_before>use regex::Regex;
use std::collections::HashSet;
use std::fmt;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
use std::str;
use tempfile::NamedTempFile;

use protobuf::descriptor::field_descriptor_proto;
use protobuf::descriptor::DescriptorProto;
use protobuf::descriptor::EnumDescriptorProto;
use protobuf::descriptor::FieldDescriptorProto;
use protobuf::descriptor::FileDescriptorProto;
use protobuf::descriptor::FileDescriptorSet;
use protobuf::descriptor::OneofDescriptorProto;
use protobuf::text_format::lexer::float::parse_protobuf_float;
use protobuf::RepeatedField;
use protobuf_test_common::build::copy_tests_v2_v3;
use protobuf_test_common::build::glob_simple;
use std::process::Command;
use std::process::Stdio;

fn to_paths(iter: impl IntoIterator<Item = impl Into<String>>) -> Vec<PathBuf> {
    iter.into_iter()
        .map(|item| item.into().replace(MAIN_SEPARATOR, "/").into())
        .collect()
}

#[derive(Default, Debug)]
struct TestStats {
    passed: u32,
    passed_marked_skip: u32,
    skipped: u32,
    failed: u32,
}

fn normalize_generated_file(contents: &str) -> String {
    let parsed_by = Regex::new("^// \\.proto file is parsed by.*").unwrap();

    let mut r = String::new();
    let mut inside_descriptor_data = false;
    for line in contents.lines() {
        let line = if inside_descriptor_data {
            if line == "\";" {
                inside_descriptor_data = false;
                line.to_owned()
            } else {
                continue;
            }
        } else if line.starts_with("static file_descriptor_proto_data") {
            inside_descriptor_data = true;
            line.to_owned()
        } else {
            parsed_by.replace(line, "").into_owned()
        };

        writeln!(&mut r, "{}", line).unwrap();
    }

    // sanity check
    assert!(!inside_descriptor_data);

    r
}

fn normalize_generated_file_in_place(path: &Path) {
    let contents = fs::read_to_string(path).unwrap();
    let contents = normalize_generated_file(&contents);
    fs::write(path, &contents).unwrap();
}

fn print_diff(dir: &Path, a: &Path, b: &Path) {
    if cfg!(windows) {
        // likely we don't have `diff` command on Windows
        return;
    }

    // sanity check
    let mut a_full = dir.to_path_buf();
    a_full.push(a);
    assert!(a_full.exists());
    let mut b_full = dir.to_path_buf();
    b_full.push(b);
    assert!(b_full.exists());

    // Not using difference crate because it's slow for large files
    let output = Command::new("diff")
        .current_dir(dir)
        .arg("-u")
        .arg(a)
        .arg(b)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    print!("{}", str::from_utf8(&output.stdout).unwrap());
    print!("{}", str::from_utf8(&output.stderr).unwrap());
}

fn protoc_descriptor_set(includes: &[PathBuf], inputs: &[PathBuf]) -> FileDescriptorSet {
    let mut temp_file = NamedTempFile::new().unwrap();
    protoc::Protoc::from_env_path()
        .descriptor_set_out_args()
        .out(temp_file.path())
        .includes(includes)
        .inputs(inputs)
        .write_descriptor_set()
        .unwrap();
    protobuf::parse_from_reader(&mut temp_file).unwrap()
}

// TODO: expose this utility from protobuf-codegen-pure crate.
fn pure_descriptor_set(includes: &[PathBuf], inputs: &[PathBuf]) -> FileDescriptorSet {
    let mut codegen = protobuf_codegen_pure::parse_and_typecheck(includes, inputs).unwrap();
    let relative_paths: HashSet<_> = codegen
        .relative_paths
        .iter()
        .map(|path| path.to_string_lossy())
        .collect();
    codegen
        .file_descriptors
        .retain(|fd| relative_paths.contains(fd.get_name()));
    let mut fds = FileDescriptorSet::new();
    fds.file = RepeatedField::from_vec(codegen.file_descriptors);
    fds
}

fn normalize_descriptor_set(fds: &mut FileDescriptorSet) {
    for desc in &mut fds.file {
        normalize_file_descriptor(desc)
    }
}

fn normalize_file_descriptor(desc: &mut FileDescriptorProto) {
    if !desc.has_syntax() {
        desc.set_syntax("proto2".into())
    }
    for desc in &mut *desc.message_type {
        normalize_descriptor(desc)
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc)
    }

    for desc in &mut desc.extension {
        desc.options.mut_or_default();
    }
    desc.options.mut_or_default();

    // TODO: don't clear services.
    desc.service.clear();

    // for unittest_custom_options.proto where a custom option
    // is an extension. Probably nobody outside of Google uses it.
    desc.options
        .mut_or_default()
        .unknown_fields
        .remove(15478479);
}

fn normalize_enum_descriptor(desc: &mut EnumDescriptorProto) {
    desc.options.mut_or_default();

    for value in &mut desc.value {
        value.options.mut_or_default();
    }
}

fn normalize_oneof_descriptor(desc: &mut OneofDescriptorProto) {
    desc.options.mut_or_default();
}

fn normalize_descriptor(desc: &mut DescriptorProto) {
    for desc in &mut desc.nested_type {
        normalize_descriptor(desc);
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc);
    }
    for desc in &mut desc.oneof_decl {
        normalize_oneof_descriptor(desc);
    }

    // TODO: don't clear options.
    desc.options.clear();

    for field in &mut desc.field {
        normalize_field(field);
    }

    for ext in &mut desc.extension {
        ext.options.mut_or_default();
    }

    for ext in &mut desc.extension_range {
        // If ext range end exceeds max field number,
        // the actual upper limit does not matter.
        // protoc is not consistent in behavior thus flush
        // the value to some arbitrary compatible value.
        if ext.has_end() && ext.get_end() >= 0x20000000 {
            ext.set_end(0x20000000);
        }
    }
}

fn normalize_field(field: &mut FieldDescriptorProto) {
    field.options.mut_or_default();

    if field.has_default_value() {
        if field.get_field_type() == field_descriptor_proto::Type::TYPE_FLOAT {
            field.set_default_value(format!(
                "{}",
                parse_protobuf_float(field.get_default_value()).unwrap()
            ));
        }
    }
}

fn pretty_message<M: protobuf::Message>(message: &M) -> String {
    // TODO: pull this handy utility into the protobuf crate.
    struct FormatMessage<'a, M>(&'a M);

    impl<M: protobuf::Message> fmt::Display for FormatMessage<'_, M> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            protobuf::text_format::fmt(self.0, f)
        }
    }

    format!("{:#}", FormatMessage(message))
}

fn descriptor_for_file<'a>(fds: &'a FileDescriptorSet, file_name: &str) -> &'a FileDescriptorProto {
    for file in &fds.file {
        if Path::new(file.get_name()).file_name().unwrap()
            == Path::new(file_name).file_name().unwrap()
        {
            return file;
        }
    }
    panic!(
        "file not found: {}; all files: {}",
        file_name,
        fds.file
            .iter()
            .map(|f| f.get_name())
            .collect::<Vec<_>>()
            .join(", ")
    );
}

fn test_diff_in<F>(root: &str, sources_dir: &str, include: &str, should_skip: F)
where
    F: Fn(&str) -> bool,
{
    let mut stats = TestStats {
        passed: 0,
        passed_marked_skip: 0,
        skipped: 0,
        failed: 0,
    };

    let mut include_root = Path::new(root).to_path_buf();
    include_root.push(include);

    let include_full = format!("{}/{}", root, include);
    let s_full = format!("{}/{}", root, sources_dir);

    let inputs_glob = format!("{}/*.proto", s_full);
    let inputs = to_paths(glob_simple(&inputs_glob));
    assert!(!inputs.is_empty(), "glob is empty: {}", inputs_glob);
    let includes = to_paths(vec![include_full.as_str(), "../proto"]);

    let temp_dir = tempfile::Builder::new()
        .prefix("protobuf-codegen-identical-test")
        .tempdir()
        .unwrap();

    let protoc_dir = format!("{}/protoc", temp_dir.path().to_str().unwrap());
    let pure_dir = format!("{}/pure", temp_dir.path().to_str().unwrap());

    fs::create_dir(&protoc_dir).unwrap();
    fs::create_dir(&pure_dir).unwrap();

    protoc_rust::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&protoc_dir)
        .run()
        .unwrap();

    protobuf_codegen_pure::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&pure_dir)
        .run()
        .unwrap();

    let mut protoc_descriptors = protoc_descriptor_set(&includes, &inputs);
    let mut pure_descriptors = pure_descriptor_set(&includes, &inputs);
    normalize_descriptor_set(&mut protoc_descriptors);
    normalize_descriptor_set(&mut pure_descriptors);

    for input in &inputs {
        let label = input.strip_prefix(root).unwrap().to_str().unwrap();
        let proto_file_name = input.strip_prefix(&include_root).unwrap().to_str().unwrap();
        let proto_name = input.file_name().unwrap().to_str().unwrap();
        let rs_name = protobuf_codegen::proto_name_to_rs(proto_name);
        let protoc_rs = format!("{}/{}", protoc_dir, rs_name);
        let pure_rs = format!("{}/{}", pure_dir, rs_name);

        normalize_generated_file_in_place(Path::new(&protoc_rs));
        normalize_generated_file_in_place(Path::new(&pure_rs));

        let protoc_rs_contents =
            fs::read_to_string(&protoc_rs).expect(&format!("while reading {}", protoc_rs));
        let pure_rs_contents =
            fs::read_to_string(&pure_rs).expect(&format!("while reading {}", pure_rs));

        let protoc_descriptor_for_file = descriptor_for_file(&protoc_descriptors, &proto_file_name);
        let pure_descriptor_for_file = descriptor_for_file(&pure_descriptors, &proto_file_name);

        let skip = should_skip(input.to_str().unwrap());
        if protoc_rs_contents == pure_rs_contents
            && protoc_descriptor_for_file == pure_descriptor_for_file
        {
            if !skip {
                stats.passed += 1;
                println!("{}: PASSED", label);
            } else {
                stats.passed_marked_skip += 1;
                println!("{}: PASSED BUT MARKED SKIP", label);
            }
        } else {
            if skip {
                stats.skipped += 1;
                println!("{} SKIPPED", label);
            } else {
                stats.failed += 1;
                println!("{} FAILED", label);
            }

            print_diff(
                temp_dir.path(),
                Path::new(&protoc_rs).strip_prefix(temp_dir.path()).unwrap(),
                Path::new(&pure_rs).strip_prefix(temp_dir.path()).unwrap(),
            );

            fs::write(
                format!("{}/{}.descriptors", protoc_dir, proto_name),
                pretty_message(protoc_descriptor_for_file),
            )
            .unwrap();
            fs::write(
                format!("{}/{}.descriptors", pure_dir, proto_name),
                pretty_message(pure_descriptor_for_file),
            )
            .unwrap();
            print_diff(
                temp_dir.path(),
                Path::new(&format!("protoc/{}.descriptors", proto_name)),
                Path::new(&format!("pure/{}.descriptors", proto_name)),
            );
        }
    }

    println!("{:?}", stats);
    assert!(
        stats.passed != 0 || sources_dir == "src/google/protobuf",
        "sanity check"
    );
    assert!(stats.failed == 0, "at least one test failed");
}

fn should_skip_with_marker(path: &str) -> bool {
    fs::read_to_string(path)
        .unwrap()
        .contains("@skip-codegen-identical-test")
}

#[test]
fn common_v2() {
    test_diff_in(
        "../protobuf-test",
        "src/common/v2",
        "src/common/v2",
        should_skip_with_marker,
    );
}

#[test]
fn common_v3() {
    let common_v3_root = tempfile::Builder::new()
        .prefix("common-v3")
        .tempdir()
        .unwrap();
    fs::create_dir_all(format!(
        "{}/src/common/v3",
        common_v3_root.path().to_str().unwrap()
    ))
    .unwrap();

    copy_tests_v2_v3(
        "../protobuf-test/src/common/v2",
        &format!("{}/src/common/v3", common_v3_root.path().to_str().unwrap()),
    );

    test_diff_in(
        common_v3_root.path().to_str().unwrap(),
        "src/common/v3",
        "src/common/v3",
        should_skip_with_marker,
    );
}

#[test]
fn v2() {
    test_diff_in(
        "../protobuf-test",
        "src/v2",
        "src/v2",
        should_skip_with_marker,
    );
}

#[test]
fn v3() {
    test_diff_in(
        "../protobuf-test",
        "src/v3",
        "src/v3",
        should_skip_with_marker,
    );
}

#[test]
fn interop() {
    test_diff_in(
        "../protobuf-test",
        "../interop/cxx",
        "../interop/cxx",
        should_skip_with_marker,
    );
}

#[test]
fn google() {
    test_diff_in("../protobuf-test", "src/google/protobuf", "src", |_| true);
}
<commit_msg>No longer skip<commit_after>use regex::Regex;
use std::collections::HashSet;
use std::fmt;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
use std::str;
use tempfile::NamedTempFile;

use protobuf::descriptor::field_descriptor_proto;
use protobuf::descriptor::DescriptorProto;
use protobuf::descriptor::EnumDescriptorProto;
use protobuf::descriptor::FieldDescriptorProto;
use protobuf::descriptor::FileDescriptorProto;
use protobuf::descriptor::FileDescriptorSet;
use protobuf::descriptor::OneofDescriptorProto;
use protobuf::text_format::lexer::float::parse_protobuf_float;
use protobuf::RepeatedField;
use protobuf_test_common::build::copy_tests_v2_v3;
use protobuf_test_common::build::glob_simple;
use std::process::Command;
use std::process::Stdio;

fn to_paths(iter: impl IntoIterator<Item = impl Into<String>>) -> Vec<PathBuf> {
    iter.into_iter()
        .map(|item| item.into().replace(MAIN_SEPARATOR, "/").into())
        .collect()
}

#[derive(Default, Debug)]
struct TestStats {
    passed: u32,
    passed_marked_skip: u32,
    skipped: u32,
    failed: u32,
}

fn normalize_generated_file(contents: &str) -> String {
    let parsed_by = Regex::new("^// \\.proto file is parsed by.*").unwrap();

    let mut r = String::new();
    let mut inside_descriptor_data = false;
    for line in contents.lines() {
        let line = if inside_descriptor_data {
            if line == "\";" {
                inside_descriptor_data = false;
                line.to_owned()
            } else {
                continue;
            }
        } else if line.starts_with("static file_descriptor_proto_data") {
            inside_descriptor_data = true;
            line.to_owned()
        } else {
            parsed_by.replace(line, "").into_owned()
        };

        writeln!(&mut r, "{}", line).unwrap();
    }

    // sanity check
    assert!(!inside_descriptor_data);

    r
}

fn normalize_generated_file_in_place(path: &Path) {
    let contents = fs::read_to_string(path).unwrap();
    let contents = normalize_generated_file(&contents);
    fs::write(path, &contents).unwrap();
}

fn print_diff(dir: &Path, a: &Path, b: &Path) {
    if cfg!(windows) {
        // likely we don't have `diff` command on Windows
        return;
    }

    // sanity check
    let mut a_full = dir.to_path_buf();
    a_full.push(a);
    assert!(a_full.exists());
    let mut b_full = dir.to_path_buf();
    b_full.push(b);
    assert!(b_full.exists());

    // Not using difference crate because it's slow for large files
    let output = Command::new("diff")
        .current_dir(dir)
        .arg("-u")
        .arg(a)
        .arg(b)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    print!("{}", str::from_utf8(&output.stdout).unwrap());
    print!("{}", str::from_utf8(&output.stderr).unwrap());
}

fn protoc_descriptor_set(includes: &[PathBuf], inputs: &[PathBuf]) -> FileDescriptorSet {
    let mut temp_file = NamedTempFile::new().unwrap();
    protoc::Protoc::from_env_path()
        .descriptor_set_out_args()
        .out(temp_file.path())
        .includes(includes)
        .inputs(inputs)
        .write_descriptor_set()
        .unwrap();
    protobuf::parse_from_reader(&mut temp_file).unwrap()
}

// TODO: expose this utility from protobuf-codegen-pure crate.
fn pure_descriptor_set(includes: &[PathBuf], inputs: &[PathBuf]) -> FileDescriptorSet {
    let mut codegen = protobuf_codegen_pure::parse_and_typecheck(includes, inputs).unwrap();
    let relative_paths: HashSet<_> = codegen
        .relative_paths
        .iter()
        .map(|path| path.to_string_lossy())
        .collect();
    codegen
        .file_descriptors
        .retain(|fd| relative_paths.contains(fd.get_name()));
    let mut fds = FileDescriptorSet::new();
    fds.file = RepeatedField::from_vec(codegen.file_descriptors);
    fds
}

fn normalize_descriptor_set(fds: &mut FileDescriptorSet) {
    for desc in &mut fds.file {
        normalize_file_descriptor(desc)
    }
}

fn normalize_file_descriptor(desc: &mut FileDescriptorProto) {
    if !desc.has_syntax() {
        desc.set_syntax("proto2".into())
    }
    for desc in &mut *desc.message_type {
        normalize_descriptor(desc)
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc)
    }

    for desc in &mut desc.extension {
        desc.options.mut_or_default();
    }
    desc.options.mut_or_default();

    // TODO: don't clear services.
    desc.service.clear();

    // for unittest_custom_options.proto where a custom option
    // is an extension. Probably nobody outside of Google uses it.
    desc.options
        .mut_or_default()
        .unknown_fields
        .remove(15478479);
}

fn normalize_enum_descriptor(desc: &mut EnumDescriptorProto) {
    desc.options.mut_or_default();

    for value in &mut desc.value {
        value.options.mut_or_default();
    }
}

fn normalize_oneof_descriptor(desc: &mut OneofDescriptorProto) {
    desc.options.mut_or_default();
}

fn normalize_descriptor(desc: &mut DescriptorProto) {
    for desc in &mut desc.nested_type {
        normalize_descriptor(desc);
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc);
    }
    for desc in &mut desc.oneof_decl {
        normalize_oneof_descriptor(desc);
    }

    // TODO: don't clear options.
    desc.options.clear();

    for field in &mut desc.field {
        normalize_field(field);
    }

    for ext in &mut desc.extension {
        ext.options.mut_or_default();
    }

    for ext in &mut desc.extension_range {
        // If ext range end exceeds max field number,
        // the actual upper limit does not matter.
        // protoc is not consistent in behavior thus flush
        // the value to some arbitrary compatible value.
        if ext.has_end() && ext.get_end() >= 0x20000000 {
            ext.set_end(0x20000000);
        }
    }
}

fn normalize_field(field: &mut FieldDescriptorProto) {
    field.options.mut_or_default();

    if field.has_default_value() {
        if field.get_field_type() == field_descriptor_proto::Type::TYPE_FLOAT {
            field.set_default_value(format!(
                "{}",
                parse_protobuf_float(field.get_default_value()).unwrap()
            ));
        }
    }
}

fn pretty_message<M: protobuf::Message>(message: &M) -> String {
    // TODO: pull this handy utility into the protobuf crate.
    struct FormatMessage<'a, M>(&'a M);

    impl<M: protobuf::Message> fmt::Display for FormatMessage<'_, M> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            protobuf::text_format::fmt(self.0, f)
        }
    }

    format!("{:#}", FormatMessage(message))
}

fn descriptor_for_file<'a>(fds: &'a FileDescriptorSet, file_name: &str) -> &'a FileDescriptorProto {
    for file in &fds.file {
        if Path::new(file.get_name()).file_name().unwrap()
            == Path::new(file_name).file_name().unwrap()
        {
            return file;
        }
    }
    panic!(
        "file not found: {}; all files: {}",
        file_name,
        fds.file
            .iter()
            .map(|f| f.get_name())
            .collect::<Vec<_>>()
            .join(", ")
    );
}

fn test_diff_in<F>(root: &str, sources_dir: &str, include: &str, should_skip: F)
where
    F: Fn(&str) -> bool,
{
    let mut stats = TestStats {
        passed: 0,
        passed_marked_skip: 0,
        skipped: 0,
        failed: 0,
    };

    let mut include_root = Path::new(root).to_path_buf();
    include_root.push(include);

    let include_full = format!("{}/{}", root, include);
    let s_full = format!("{}/{}", root, sources_dir);

    let inputs_glob = format!("{}/*.proto", s_full);
    let inputs = to_paths(glob_simple(&inputs_glob));
    assert!(!inputs.is_empty(), "glob is empty: {}", inputs_glob);
    let includes = to_paths(vec![include_full.as_str(), "../proto"]);

    let temp_dir = tempfile::Builder::new()
        .prefix("protobuf-codegen-identical-test")
        .tempdir()
        .unwrap();

    let protoc_dir = format!("{}/protoc", temp_dir.path().to_str().unwrap());
    let pure_dir = format!("{}/pure", temp_dir.path().to_str().unwrap());

    fs::create_dir(&protoc_dir).unwrap();
    fs::create_dir(&pure_dir).unwrap();

    protoc_rust::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&protoc_dir)
        .run()
        .unwrap();

    protobuf_codegen_pure::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&pure_dir)
        .run()
        .unwrap();

    let mut protoc_descriptors = protoc_descriptor_set(&includes, &inputs);
    let mut pure_descriptors = pure_descriptor_set(&includes, &inputs);
    normalize_descriptor_set(&mut protoc_descriptors);
    normalize_descriptor_set(&mut pure_descriptors);

    for input in &inputs {
        let label = input.strip_prefix(root).unwrap().to_str().unwrap();
        let proto_file_name = input.strip_prefix(&include_root).unwrap().to_str().unwrap();
        let proto_name = input.file_name().unwrap().to_str().unwrap();
        let rs_name = protobuf_codegen::proto_name_to_rs(proto_name);
        let protoc_rs = format!("{}/{}", protoc_dir, rs_name);
        let pure_rs = format!("{}/{}", pure_dir, rs_name);

        normalize_generated_file_in_place(Path::new(&protoc_rs));
        normalize_generated_file_in_place(Path::new(&pure_rs));

        let protoc_rs_contents =
            fs::read_to_string(&protoc_rs).expect(&format!("while reading {}", protoc_rs));
        let pure_rs_contents =
            fs::read_to_string(&pure_rs).expect(&format!("while reading {}", pure_rs));

        let protoc_descriptor_for_file = descriptor_for_file(&protoc_descriptors, &proto_file_name);
        let pure_descriptor_for_file = descriptor_for_file(&pure_descriptors, &proto_file_name);

        let skip = should_skip(input.to_str().unwrap());
        if protoc_rs_contents == pure_rs_contents
            && protoc_descriptor_for_file == pure_descriptor_for_file
        {
            if !skip {
                stats.passed += 1;
                println!("{}: PASSED", label);
            } else {
                stats.passed_marked_skip += 1;
                println!("{}: PASSED BUT MARKED SKIP", label);
            }
        } else {
            if skip {
                stats.skipped += 1;
                println!("{} SKIPPED", label);
            } else {
                stats.failed += 1;
                println!("{} FAILED", label);
            }

            print_diff(
                temp_dir.path(),
                Path::new(&protoc_rs).strip_prefix(temp_dir.path()).unwrap(),
                Path::new(&pure_rs).strip_prefix(temp_dir.path()).unwrap(),
            );

            fs::write(
                format!("{}/{}.descriptors", protoc_dir, proto_name),
                pretty_message(protoc_descriptor_for_file),
            )
            .unwrap();
            fs::write(
                format!("{}/{}.descriptors", pure_dir, proto_name),
                pretty_message(pure_descriptor_for_file),
            )
            .unwrap();
            print_diff(
                temp_dir.path(),
                Path::new(&format!("protoc/{}.descriptors", proto_name)),
                Path::new(&format!("pure/{}.descriptors", proto_name)),
            );
        }
    }

    println!("{:?}", stats);
    assert!(stats.failed == 0, "at least one test failed");
}

fn should_skip_with_marker(path: &str) -> bool {
    fs::read_to_string(path)
        .unwrap()
        .contains("@skip-codegen-identical-test")
}

#[test]
fn common_v2() {
    test_diff_in(
        "../protobuf-test",
        "src/common/v2",
        "src/common/v2",
        should_skip_with_marker,
    );
}

#[test]
fn common_v3() {
    let common_v3_root = tempfile::Builder::new()
        .prefix("common-v3")
        .tempdir()
        .unwrap();
    fs::create_dir_all(format!(
        "{}/src/common/v3",
        common_v3_root.path().to_str().unwrap()
    ))
    .unwrap();

    copy_tests_v2_v3(
        "../protobuf-test/src/common/v2",
        &format!("{}/src/common/v3", common_v3_root.path().to_str().unwrap()),
    );

    test_diff_in(
        common_v3_root.path().to_str().unwrap(),
        "src/common/v3",
        "src/common/v3",
        should_skip_with_marker,
    );
}

#[test]
fn v2() {
    test_diff_in(
        "../protobuf-test",
        "src/v2",
        "src/v2",
        should_skip_with_marker,
    );
}

#[test]
fn v3() {
    test_diff_in(
        "../protobuf-test",
        "src/v3",
        "src/v3",
        should_skip_with_marker,
    );
}

#[test]
fn interop() {
    test_diff_in(
        "../protobuf-test",
        "../interop/cxx",
        "../interop/cxx",
        should_skip_with_marker,
    );
}

#[test]
fn google() {
    test_diff_in("../protobuf-test", "src/google/protobuf", "src", |_| false);
}
<|endoftext|>


<commit_before>use super::{Syllable,SyllableOptions,Subtitles,SubtitlesOptions};
use super::pos::RowPosition;
use std::ops::Deref;

#[derive(Debug,Serialize,Deserialize,Clone)]
pub struct Sentence {
    pub syllables: Vec<Syllable>,
    #[serde(skip_serializing,skip_deserializing)]
    pub position: RowPosition,
    #[serde(skip_serializing_if="Option::is_none")]
    pub sentence_options: Option<SentenceOptions>,
}

pub trait AsSentenceOptions {
    fn as_sentence_options(&self) -> Option<&SentenceOptions>;
    fn or_sentence_options(&self,other:Option<&SentenceOptions>) -> Option<SentenceOptions> {
        match (self.as_sentence_options(),other) {
            (Some(s),Some(other)) => Some(SentenceOptions {
                syllable_options: s.syllable_options.or(other.syllable_options),
                display_logo: s.display_logo.or(other.display_logo),
                transition_time_after: s.transition_time_after.or(other.transition_time_after),
                fade_time_after: s.fade_time_after.or(other.fade_time_after),
                transition_time_before: s.transition_time_before.or(other.transition_time_before),
                fade_time_before: s.fade_time_before.or(other.fade_time_before),
                row_position: s.row_position.or(other.row_position),
            }),
            (Some(s),None) => Some(s.clone()),
            (None,Some(other)) => Some(other.clone()),
            (None,None) => None
        }
    }
}

impl AsSentenceOptions for Sentence {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        self.sentence_options.as_ref()
    }
}

impl AsSentenceOptions for Subtitles {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        self.subtitles_options.sentence_options.as_ref()
    }
}

impl AsSentenceOptions for SentenceOptions {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        Some(self)
    }
}

impl AsSentenceOptions for SubtitlesOptions {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        self.sentence_options.as_ref()
    }
}

impl<T:AsSentenceOptions> AsSentenceOptions for Option<T> {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        match *self {
            Some(ref t) => t.as_sentence_options(),
            None => None
        }
    }
}

impl Sentence {
    /// used mainly for debugging purposes
    #[allow(dead_code)]
    pub fn text(&self) -> String {
        self.syllables.iter().fold(String::new(),|mut string,syllable| {
            string.push_str(&*syllable.text);
            string
        })
    }
}

#[derive(Debug,Clone,Copy,Default,Serialize,Deserialize)]
pub struct SentenceOptions {
    /// Global SyllableOptions
    #[serde(skip_serializing_if="Option::is_none")]
    pub syllable_options: Option<SyllableOptions>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub display_logo: Option<bool>,
    /// Total time where subtitles start appearing, before first syllable start playing
    #[serde(skip_serializing_if="Option::is_none")]
    pub transition_time_before: Option<u16>,
    /// Span where subtitles start appearing
    #[serde(skip_serializing_if="Option::is_none")]
    pub fade_time_before: Option<u16>,
    /// Total time where subtitles are disappearing, before first syllable start playing
    #[serde(skip_serializing_if="Option::is_none")]
    pub transition_time_after: Option<u16>,
    /// Span where subtitles start disappearing
    #[serde(skip_serializing_if="Option::is_none")]
    pub fade_time_after: Option<u16>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub row_position: Option<RowPosition>,
}

impl Deref for SentenceOptions {
    type Target = Option<SyllableOptions>;
    fn deref(&self) -> &Option<SyllableOptions> {
        &self.syllable_options
    }
}

#[derive(Debug,Clone)]
pub struct SentenceParameters {
    pub display_logo: bool,
    pub transition_time_before: u16,
    pub fade_time_before: u16,
    pub transition_time_after: u16,
    pub fade_time_after: u16,
    pub row_position: Option<RowPosition>,
}

impl From<SentenceOptions> for SentenceParameters {
    fn from(sentence_options: SentenceOptions) -> Self {
        SentenceParameters {
            display_logo: sentence_options.display_logo.unwrap_or(true),
            transition_time_before: sentence_options.transition_time_before.unwrap_or(800),
            fade_time_before: sentence_options.fade_time_before.unwrap_or(200),
            transition_time_after: sentence_options.transition_time_after.unwrap_or(500),
            fade_time_after: sentence_options.fade_time_after.unwrap_or(200),
            row_position: sentence_options.row_position,
        }
    }
}

#[test]
fn test_sentence_options_propagation(){
    use super::*;
    use utils::RGB;
    let default_options = SentenceOptions {
        syllable_options: Some(SyllableOptions {
            alive_color: Some(Color::new(128,0,0)),
            dead_color: Some(Color::new(0,128,0)),
            transition_color: None,
            outline: None
        }),
        ..SentenceOptions::default()
    };
    let syllable_options = SyllableOptions {
        alive_color: Some(Color::new(172,172,172)),
        ..SyllableOptions::default()
    };
    let syllable_parameters = SyllableParameters::from(syllable_options.or_syllable_options(default_options.as_syllable_options()).unwrap());
    assert_eq!(syllable_parameters.alive_color,Color::new(172,172,172));
    assert_eq!(syllable_parameters.dead_color,Color::new(0,128,0));
    assert_eq!(syllable_parameters.transition_color,Color::new(255,0,0));
}
<commit_msg>fixed bug in sub option propagation<commit_after>use super::{Syllable,SyllableOptions,Subtitles,SubtitlesOptions,AsSyllableOptions};
use super::pos::RowPosition;
use std::ops::Deref;

#[derive(Debug,Serialize,Deserialize,Clone)]
pub struct Sentence {
    pub syllables: Vec<Syllable>,
    #[serde(skip_serializing,skip_deserializing)]
    pub position: RowPosition,
    #[serde(skip_serializing_if="Option::is_none")]
    pub sentence_options: Option<SentenceOptions>,
}

pub trait AsSentenceOptions {
    fn as_sentence_options(&self) -> Option<&SentenceOptions>;
    fn or_sentence_options(&self,other:Option<&SentenceOptions>) -> Option<SentenceOptions> {
        match (self.as_sentence_options(),other) {
            (Some(s),Some(other)) => Some(SentenceOptions {
                syllable_options: s.syllable_options.or_syllable_options(other.syllable_options.as_ref()),
                display_logo: s.display_logo.or(other.display_logo),
                transition_time_after: s.transition_time_after.or(other.transition_time_after),
                fade_time_after: s.fade_time_after.or(other.fade_time_after),
                transition_time_before: s.transition_time_before.or(other.transition_time_before),
                fade_time_before: s.fade_time_before.or(other.fade_time_before),
                row_position: s.row_position.or(other.row_position),
            }),
            (Some(s),None) => Some(s.clone()),
            (None,Some(other)) => Some(other.clone()),
            (None,None) => None
        }
    }
}

impl AsSentenceOptions for Sentence {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        self.sentence_options.as_ref()
    }
}

impl AsSentenceOptions for Subtitles {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        self.subtitles_options.sentence_options.as_ref()
    }
}

impl AsSentenceOptions for SentenceOptions {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        Some(self)
    }
}

impl AsSentenceOptions for SubtitlesOptions {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        self.sentence_options.as_ref()
    }
}

impl<T:AsSentenceOptions> AsSentenceOptions for Option<T> {
    fn as_sentence_options(&self) -> Option<&SentenceOptions> {
        match *self {
            Some(ref t) => t.as_sentence_options(),
            None => None
        }
    }
}

impl Sentence {
    /// used mainly for debugging purposes
    #[allow(dead_code)]
    pub fn text(&self) -> String {
        self.syllables.iter().fold(String::new(),|mut string,syllable| {
            string.push_str(&*syllable.text);
            string
        })
    }
}

#[derive(Debug,Clone,Copy,Default,Serialize,Deserialize)]
pub struct SentenceOptions {
    /// Global SyllableOptions
    #[serde(skip_serializing_if="Option::is_none")]
    pub syllable_options: Option<SyllableOptions>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub display_logo: Option<bool>,
    /// Total time where subtitles start appearing, before first syllable start playing
    #[serde(skip_serializing_if="Option::is_none")]
    pub transition_time_before: Option<u16>,
    /// Span where subtitles start appearing
    #[serde(skip_serializing_if="Option::is_none")]
    pub fade_time_before: Option<u16>,
    /// Total time where subtitles are disappearing, before first syllable start playing
    #[serde(skip_serializing_if="Option::is_none")]
    pub transition_time_after: Option<u16>,
    /// Span where subtitles start disappearing
    #[serde(skip_serializing_if="Option::is_none")]
    pub fade_time_after: Option<u16>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub row_position: Option<RowPosition>,
}

impl Deref for SentenceOptions {
    type Target = Option<SyllableOptions>;
    fn deref(&self) -> &Option<SyllableOptions> {
        &self.syllable_options
    }
}

#[derive(Debug,Clone)]
pub struct SentenceParameters {
    pub display_logo: bool,
    pub transition_time_before: u16,
    pub fade_time_before: u16,
    pub transition_time_after: u16,
    pub fade_time_after: u16,
    pub row_position: Option<RowPosition>,
}

impl From<SentenceOptions> for SentenceParameters {
    fn from(sentence_options: SentenceOptions) -> Self {
        SentenceParameters {
            display_logo: sentence_options.display_logo.unwrap_or(true),
            transition_time_before: sentence_options.transition_time_before.unwrap_or(800),
            fade_time_before: sentence_options.fade_time_before.unwrap_or(200),
            transition_time_after: sentence_options.transition_time_after.unwrap_or(500),
            fade_time_after: sentence_options.fade_time_after.unwrap_or(200),
            row_position: sentence_options.row_position,
        }
    }
}

#[test]
fn test_sentence_options_propagation(){
    use super::*;
    use utils::RGB;
    let default_options = SentenceOptions {
        syllable_options: Some(SyllableOptions {
            alive_color: Some(Color::new(128,0,0)),
            dead_color: Some(Color::new(0,128,0)),
            transition_color: None,
            outline: None
        }),
        ..SentenceOptions::default()
    };
    let syllable_options = SyllableOptions {
        alive_color: Some(Color::new(172,172,172)),
        ..SyllableOptions::default()
    };
    let syllable_parameters = SyllableParameters::from(syllable_options.or_syllable_options(default_options.as_syllable_options()).unwrap());
    assert_eq!(syllable_parameters.alive_color,Color::new(172,172,172));
    assert_eq!(syllable_parameters.dead_color,Color::new(0,128,0));
    assert_eq!(syllable_parameters.transition_color,Color::new(255,0,0));
}
<|endoftext|>


<commit_before>use itertools::Itertools;
use syntax::{
    ast::{self, AstNode},
    Direction, SmolStr,
    SyntaxKind::{IDENT, WHITESPACE},
    TextRange, TextSize,
};

use crate::{
    assist_context::{AssistContext, Assists},
    AssistId, AssistKind,
};

// Assist: add_custom_impl
//
// Adds impl block for derived trait.
//
// ```
// #[derive(Deb<|>ug, Display)]
// struct S;
// ```
// ->
// ```
// #[derive(Display)]
// struct S;
//
// impl Debug for S {
//     $0
// }
// ```
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let attr = ctx.find_node_at_offset::<ast::Attr>()?;
    let input = attr.token_tree()?;

    let attr_name = attr
        .syntax()
        .descendants_with_tokens()
        .filter(|t| t.kind() == IDENT)
        .find_map(|i| i.into_token())
        .filter(|t| *t.text() == "derive")?
        .text()
        .clone();

    let trait_token =
        ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;

    let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
    let annotated_name = annotated.syntax().text().to_string();
    let start_offset = annotated.syntax().parent()?.text_range().end();

    let label =
        format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);

    let target = attr.syntax().text_range();
    acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
        let new_attr_input = input
            .syntax()
            .descendants_with_tokens()
            .filter(|t| t.kind() == IDENT)
            .filter_map(|t| t.into_token().map(|t| t.text().clone()))
            .filter(|t| t != trait_token.text())
            .collect::<Vec<SmolStr>>();
        let has_more_derives = !new_attr_input.is_empty();

        if has_more_derives {
            let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
            builder.replace(input.syntax().text_range(), new_attr_input);
        } else {
            let attr_range = attr.syntax().text_range();
            builder.delete(attr_range);

            let line_break_range = attr
                .syntax()
                .next_sibling_or_token()
                .filter(|t| t.kind() == WHITESPACE)
                .map(|t| t.text_range())
                .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
            builder.delete(line_break_range);
        }

        match ctx.config.snippet_cap {
            Some(cap) => {
                builder.insert_snippet(
                    cap,
                    start_offset,
                    format!("\n\nimpl {} for {} {{\n    $0\n}}", trait_token, annotated_name),
                );
            }
            None => {
                builder.insert(
                    start_offset,
                    format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
                );
            }
        }
    })
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn add_custom_impl_for_unique_input() {
        check_assist(
            add_custom_impl,
            "
#[derive(Debu<|>g)]
struct Foo {
    bar: String,
}
            ",
            "
struct Foo {
    bar: String,
}

impl Debug for Foo {
    $0
}
            ",
        )
    }

    #[test]
    fn add_custom_impl_for_with_visibility_modifier() {
        check_assist(
            add_custom_impl,
            "
#[derive(Debug<|>)]
pub struct Foo {
    bar: String,
}
            ",
            "
pub struct Foo {
    bar: String,
}

impl Debug for Foo {
    $0
}
            ",
        )
    }

    #[test]
    fn add_custom_impl_when_multiple_inputs() {
        check_assist(
            add_custom_impl,
            "
#[derive(Display, Debug<|>, Serialize)]
struct Foo {}
            ",
            "
#[derive(Display, Serialize)]
struct Foo {}

impl Debug for Foo {
    $0
}
            ",
        )
    }

    #[test]
    fn test_ignore_derive_macro_without_input() {
        check_assist_not_applicable(
            add_custom_impl,
            "
#[derive(<|>)]
struct Foo {}
            ",
        )
    }

    #[test]
    fn test_ignore_if_cursor_on_param() {
        check_assist_not_applicable(
            add_custom_impl,
            "
#[derive<|>(Debug)]
struct Foo {}
            ",
        );

        check_assist_not_applicable(
            add_custom_impl,
            "
#[derive(Debug)<|>]
struct Foo {}
            ",
        )
    }

    #[test]
    fn test_ignore_if_not_derive() {
        check_assist_not_applicable(
            add_custom_impl,
            "
#[allow(non_camel_<|>case_types)]
struct Foo {}
            ",
        )
    }
}
<commit_msg>Qualify trait impl created by add_custom_impl assist<commit_after>use ide_db::imports_locator;
use itertools::Itertools;
use syntax::{
    ast::{self, make, AstNode},
    Direction, SmolStr,
    SyntaxKind::{IDENT, WHITESPACE},
    TextRange, TextSize,
};

use crate::{
    assist_config::SnippetCap,
    assist_context::{AssistBuilder, AssistContext, Assists},
    utils::mod_path_to_ast,
    AssistId, AssistKind,
};

// Assist: add_custom_impl
//
// Adds impl block for derived trait.
//
// ```
// #[derive(Deb<|>ug, Display)]
// struct S;
// ```
// ->
// ```
// #[derive(Display)]
// struct S;
//
// impl Debug for S {
//     $0
// }
// ```
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let attr = ctx.find_node_at_offset::<ast::Attr>()?;

    let attr_name = attr
        .syntax()
        .descendants_with_tokens()
        .filter(|t| t.kind() == IDENT)
        .find_map(syntax::NodeOrToken::into_token)
        .filter(|t| t.text() == "derive")?
        .text()
        .clone();

    let trait_token =
        ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
    let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));

    let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
    let annotated_name = annotated.syntax().text().to_string();
    let insert_pos = annotated.syntax().parent()?.text_range().end();

    let current_module = ctx.sema.scope(annotated.syntax()).module()?;
    let current_crate = current_module.krate();

    let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
        .into_iter()
        .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
            either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
            _ => None,
        })
        .flat_map(|trait_| {
            current_module
                .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
                .as_ref()
                .map(mod_path_to_ast)
                .zip(Some(trait_))
        });

    let mut no_traits_found = true;
    for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
        add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
    }
    if no_traits_found {
        add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
    }
    Some(())
}

fn add_assist(
    acc: &mut Assists,
    snippet_cap: Option<SnippetCap>,
    attr: &ast::Attr,
    trait_path: &ast::Path,
    annotated_name: &str,
    insert_pos: TextSize,
) -> Option<()> {
    let target = attr.syntax().text_range();
    let input = attr.token_tree()?;
    let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
    let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;

    acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
        update_attribute(builder, &input, &trait_name, &attr);
        match snippet_cap {
            Some(cap) => {
                builder.insert_snippet(
                    cap,
                    insert_pos,
                    format!("\n\nimpl {} for {} {{\n    $0\n}}", trait_path, annotated_name),
                );
            }
            None => {
                builder.insert(
                    insert_pos,
                    format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
                );
            }
        }
    })
}

fn update_attribute(
    builder: &mut AssistBuilder,
    input: &ast::TokenTree,
    trait_name: &ast::NameRef,
    attr: &ast::Attr,
) {
    let new_attr_input = input
        .syntax()
        .descendants_with_tokens()
        .filter(|t| t.kind() == IDENT)
        .filter_map(|t| t.into_token().map(|t| t.text().clone()))
        .filter(|t| t != trait_name.text())
        .collect::<Vec<SmolStr>>();
    let has_more_derives = !new_attr_input.is_empty();

    if has_more_derives {
        let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
        builder.replace(input.syntax().text_range(), new_attr_input);
    } else {
        let attr_range = attr.syntax().text_range();
        builder.delete(attr_range);

        let line_break_range = attr
            .syntax()
            .next_sibling_or_token()
            .filter(|t| t.kind() == WHITESPACE)
            .map(|t| t.text_range())
            .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
        builder.delete(line_break_range);
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn add_custom_impl_qualified() {
        check_assist(
            add_custom_impl,
            "
mod fmt {
    pub trait Debug {}
}

#[derive(Debu<|>g)]
struct Foo {
    bar: String,
}
",
            "
mod fmt {
    pub trait Debug {}
}

struct Foo {
    bar: String,
}

impl fmt::Debug for Foo {
    $0
}
",
        )
    }
    #[test]
    fn add_custom_impl_for_unique_input() {
        check_assist(
            add_custom_impl,
            "
#[derive(Debu<|>g)]
struct Foo {
    bar: String,
}
            ",
            "
struct Foo {
    bar: String,
}

impl Debug for Foo {
    $0
}
            ",
        )
    }

    #[test]
    fn add_custom_impl_for_with_visibility_modifier() {
        check_assist(
            add_custom_impl,
            "
#[derive(Debug<|>)]
pub struct Foo {
    bar: String,
}
            ",
            "
pub struct Foo {
    bar: String,
}

impl Debug for Foo {
    $0
}
            ",
        )
    }

    #[test]
    fn add_custom_impl_when_multiple_inputs() {
        check_assist(
            add_custom_impl,
            "
#[derive(Display, Debug<|>, Serialize)]
struct Foo {}
            ",
            "
#[derive(Display, Serialize)]
struct Foo {}

impl Debug for Foo {
    $0
}
            ",
        )
    }

    #[test]
    fn test_ignore_derive_macro_without_input() {
        check_assist_not_applicable(
            add_custom_impl,
            "
#[derive(<|>)]
struct Foo {}
            ",
        )
    }

    #[test]
    fn test_ignore_if_cursor_on_param() {
        check_assist_not_applicable(
            add_custom_impl,
            "
#[derive<|>(Debug)]
struct Foo {}
            ",
        );

        check_assist_not_applicable(
            add_custom_impl,
            "
#[derive(Debug)<|>]
struct Foo {}
            ",
        )
    }

    #[test]
    fn test_ignore_if_not_derive() {
        check_assist_not_applicable(
            add_custom_impl,
            "
#[allow(non_camel_<|>case_types)]
struct Foo {}
            ",
        )
    }
}
<|endoftext|>