<commit_before>extern crate gutenberg;
extern crate tempdir;
extern crate glob;

use std::env;
use std::path::Path;
use std::fs::File;
use std::io::prelude::*;

use tempdir::TempDir;
use gutenberg::{Site};

#[test]
fn test_can_parse_site() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();

    // Correct number of pages (sections are pages too)
    assert_eq!(site.pages.len(), 10);
    let posts_path = path.join("content").join("posts");

    // Make sure we remove all the pwd + content from the sections
    let basic = &site.pages[&posts_path.join("simple.md")];
    assert_eq!(basic.components, vec!["posts".to_string()]);

    // Make sure the page with a url doesn't have any sections
    let url_post = &site.pages[&posts_path.join("fixed-url.md")];
    assert!(url_post.components.is_empty());

    // Make sure the article in a folder with only asset doesn't get counted as a section
    let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")];
    assert_eq!(asset_folder_post.components, vec!["posts".to_string()]);

    // That we have the right number of sections
    assert_eq!(site.sections.len(), 4);

    // And that the sections are correct
    let posts_section = &site.sections[&posts_path];
    assert_eq!(posts_section.subsections.len(), 1);
    //println!("{:#?}", posts_section.pages);
    assert_eq!(posts_section.pages.len(), 5);

    let tutorials_section = &site.sections[&posts_path.join("tutorials")];
    assert_eq!(tutorials_section.subsections.len(), 2);
    assert_eq!(tutorials_section.pages.len(), 0);

    let devops_section = &site.sections[&posts_path.join("tutorials").join("devops")];
    assert_eq!(devops_section.subsections.len(), 0);
    assert_eq!(devops_section.pages.len(), 2);

    let prog_section = &site.sections[&posts_path.join("tutorials").join("programming")];
    assert_eq!(prog_section.subsections.len(), 0);
    assert_eq!(prog_section.pages.len(), 2);
}

// 2 helper macros to make all the build testing more bearable
macro_rules! file_exists {
    ($root: expr, $path: expr) => {
        {
            let mut path = $root.clone();
            for component in $path.split("/") {
                path = path.join(component);
            }
            Path::new(&path).exists()
        }
    }
}

macro_rules! file_contains {
    ($root: expr, $path: expr, $text: expr) => {
        {
            let mut path = $root.clone();
            for component in $path.split("/") {
                path = path.join(component);
            }
            let mut file = File::open(&path).unwrap();
            let mut s = String::new();
            file.read_to_string(&mut s).unwrap();
            s.contains($text)
        }
    }
}

#[test]
fn test_can_build_site_without_live_reload() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();
    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.build().unwrap();

    assert!(Path::new(&public).exists());

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));

    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // No tags or categories
    assert_eq!(file_exists!(public, "categories/index.html"), false);
    assert_eq!(file_exists!(public, "tags/index.html"), false);

    // no live reload code
    assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false);

    // Both pages and sections are in the sitemap
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/posts/simple</loc>"));
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/posts</loc>"));
}

#[test]
fn test_can_build_site_with_live_reload() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();
    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.enable_live_reload();
    site.build().unwrap();

    assert!(Path::new(&public).exists());

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));

    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // No tags or categories
    assert_eq!(file_exists!(public, "categories/index.html"), false);
    assert_eq!(file_exists!(public, "tags/index.html"), false);

    // no live reload code
    assert!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"));
}

#[test]
fn test_can_build_site_with_categories() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();

    for (i, page) in site.pages.values_mut().enumerate() {
        page.meta.category = if i % 2 == 0 {
            Some("A".to_string())
        } else {
            Some("B".to_string())
        };
    }
    site.populate_tags_and_categories();
    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.build().unwrap();

    assert!(Path::new(&public).exists());
    assert_eq!(site.categories.len(), 2);

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));

    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // Categories are there
    assert!(file_exists!(public, "categories/index.html"));
    assert!(file_exists!(public, "categories/a/index.html"));
    assert!(file_exists!(public, "categories/b/index.html"));
    // Tags aren't
    assert_eq!(file_exists!(public, "tags/index.html"), false);

    // Categories are in the sitemap
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/categories</loc>"));
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/categories/a</loc>"));
}

#[test]
fn test_can_build_site_with_tags() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();

    for (i, page) in site.pages.values_mut().enumerate() {
        page.meta.tags = if i % 2 == 0 {
            Some(vec!["tag1".to_string(), "tag2".to_string()])
        } else {
            Some(vec!["tag with space".to_string()])
        };
    }
    site.populate_tags_and_categories();

    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.build().unwrap();

    assert!(Path::new(&public).exists());
    assert_eq!(site.tags.len(), 3);

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));
    assert!(false);
    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // Tags are there
    assert!(file_exists!(public, "tags/index.html"));
    assert!(file_exists!(public, "tags/tag1/index.html"));
    assert!(file_exists!(public, "tags/tag2/index.html"));
    assert!(file_exists!(public, "tags/tag-with-space/index.html"));
    // Categories aren't
    assert_eq!(file_exists!(public, "categories/index.html"), false);
    // Tags are in the sitemap
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/tags</loc>"));
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/tags/tag-with-space</loc>"));
}
<commit_msg>Remove extra assert<commit_after>extern crate gutenberg;
extern crate tempdir;
extern crate glob;

use std::env;
use std::path::Path;
use std::fs::File;
use std::io::prelude::*;

use tempdir::TempDir;
use gutenberg::{Site};

#[test]
fn test_can_parse_site() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();

    // Correct number of pages (sections are pages too)
    assert_eq!(site.pages.len(), 10);
    let posts_path = path.join("content").join("posts");

    // Make sure we remove all the pwd + content from the sections
    let basic = &site.pages[&posts_path.join("simple.md")];
    assert_eq!(basic.components, vec!["posts".to_string()]);

    // Make sure the page with a url doesn't have any sections
    let url_post = &site.pages[&posts_path.join("fixed-url.md")];
    assert!(url_post.components.is_empty());

    // Make sure the article in a folder with only asset doesn't get counted as a section
    let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")];
    assert_eq!(asset_folder_post.components, vec!["posts".to_string()]);

    // That we have the right number of sections
    assert_eq!(site.sections.len(), 4);

    // And that the sections are correct
    let posts_section = &site.sections[&posts_path];
    assert_eq!(posts_section.subsections.len(), 1);
    //println!("{:#?}", posts_section.pages);
    assert_eq!(posts_section.pages.len(), 5);

    let tutorials_section = &site.sections[&posts_path.join("tutorials")];
    assert_eq!(tutorials_section.subsections.len(), 2);
    assert_eq!(tutorials_section.pages.len(), 0);

    let devops_section = &site.sections[&posts_path.join("tutorials").join("devops")];
    assert_eq!(devops_section.subsections.len(), 0);
    assert_eq!(devops_section.pages.len(), 2);

    let prog_section = &site.sections[&posts_path.join("tutorials").join("programming")];
    assert_eq!(prog_section.subsections.len(), 0);
    assert_eq!(prog_section.pages.len(), 2);
}

// 2 helper macros to make all the build testing more bearable
macro_rules! file_exists {
    ($root: expr, $path: expr) => {
        {
            let mut path = $root.clone();
            for component in $path.split("/") {
                path = path.join(component);
            }
            Path::new(&path).exists()
        }
    }
}

macro_rules! file_contains {
    ($root: expr, $path: expr, $text: expr) => {
        {
            let mut path = $root.clone();
            for component in $path.split("/") {
                path = path.join(component);
            }
            let mut file = File::open(&path).unwrap();
            let mut s = String::new();
            file.read_to_string(&mut s).unwrap();
            s.contains($text)
        }
    }
}

#[test]
fn test_can_build_site_without_live_reload() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();
    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.build().unwrap();

    assert!(Path::new(&public).exists());

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));

    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // No tags or categories
    assert_eq!(file_exists!(public, "categories/index.html"), false);
    assert_eq!(file_exists!(public, "tags/index.html"), false);

    // no live reload code
    assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false);

    // Both pages and sections are in the sitemap
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/posts/simple</loc>"));
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/posts</loc>"));
}

#[test]
fn test_can_build_site_with_live_reload() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();
    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.enable_live_reload();
    site.build().unwrap();

    assert!(Path::new(&public).exists());

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));

    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // No tags or categories
    assert_eq!(file_exists!(public, "categories/index.html"), false);
    assert_eq!(file_exists!(public, "tags/index.html"), false);

    // no live reload code
    assert!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"));
}

#[test]
fn test_can_build_site_with_categories() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();

    for (i, page) in site.pages.values_mut().enumerate() {
        page.meta.category = if i % 2 == 0 {
            Some("A".to_string())
        } else {
            Some("B".to_string())
        };
    }
    site.populate_tags_and_categories();
    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.build().unwrap();

    assert!(Path::new(&public).exists());
    assert_eq!(site.categories.len(), 2);

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));

    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // Categories are there
    assert!(file_exists!(public, "categories/index.html"));
    assert!(file_exists!(public, "categories/a/index.html"));
    assert!(file_exists!(public, "categories/b/index.html"));
    // Tags aren't
    assert_eq!(file_exists!(public, "tags/index.html"), false);

    // Categories are in the sitemap
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/categories</loc>"));
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/categories/a</loc>"));
}

#[test]
fn test_can_build_site_with_tags() {
    let mut path = env::current_dir().unwrap().to_path_buf();
    path.push("test_site");
    let mut site = Site::new(&path).unwrap();
    site.load().unwrap();

    for (i, page) in site.pages.values_mut().enumerate() {
        page.meta.tags = if i % 2 == 0 {
            Some(vec!["tag1".to_string(), "tag2".to_string()])
        } else {
            Some(vec!["tag with space".to_string()])
        };
    }
    site.populate_tags_and_categories();

    let tmp_dir = TempDir::new("example").expect("create temp dir");
    let public = &tmp_dir.path().join("public");
    site.set_output_path(&public);
    site.build().unwrap();

    assert!(Path::new(&public).exists());
    assert_eq!(site.tags.len(), 3);

    assert!(file_exists!(public, "index.html"));
    assert!(file_exists!(public, "sitemap.xml"));
    assert!(file_exists!(public, "robots.txt"));
    assert!(file_exists!(public, "a-fixed-url/index.html"));
    assert!(file_exists!(public, "posts/python/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
    assert!(file_exists!(public, "posts/with-assets/index.html"));

    // Sections
    assert!(file_exists!(public, "posts/index.html"));
    assert!(file_exists!(public, "posts/tutorials/index.html"));
    assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
    assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
    // TODO: add assertion for syntax highlighting

    // Tags are there
    assert!(file_exists!(public, "tags/index.html"));
    assert!(file_exists!(public, "tags/tag1/index.html"));
    assert!(file_exists!(public, "tags/tag2/index.html"));
    assert!(file_exists!(public, "tags/tag-with-space/index.html"));
    // Categories aren't
    assert_eq!(file_exists!(public, "categories/index.html"), false);
    // Tags are in the sitemap
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/tags</loc>"));
    assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/tags/tag-with-space</loc>"));
}
<|endoftext|>


<commit_before>#![feature(inclusive_range_syntax)]

extern crate rand;

use std::ops::{Index, IndexMut};
use std::io;

use rand::distributions::{IndependentSample, Range};

const GRID_DIMENSION: usize = 4;

/// A key press.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Key {
    Up,
    Left,
    Down,
    Right,
    Quit,
}

#[derive(Debug, PartialEq)]
enum State {
    Playing,
    Won,
    Lost,
}

/// Error returned when the grid is full.
#[derive(Debug)]
struct GridFullError;

/// Error returned when no tiles were actually moved.
#[derive(Debug)]
struct NoTilesMovedError;

/// An optionally filled tile in the grid.
type Tile = Option<u32>;

/// The set of game tiles.
#[derive(Debug, PartialEq, Eq)]
struct Grid([[Tile; GRID_DIMENSION]; GRID_DIMENSION]);

impl Index<(usize, usize)> for Grid {
    type Output = Tile;

    fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
        &self.0[x][y]
    }
}

impl IndexMut<(usize, usize)> for Grid {
    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output {
        &mut self.0[x][y]
    }
}

impl Grid {
    fn new() -> Self {
        Grid([[None; GRID_DIMENSION]; GRID_DIMENSION])
    }

    fn is_move_available(&self) -> bool {
        for x in 0..4 {
            for y in 0..4 {
                if self[(x, y)].is_none() {
                    return true;
                }
                if x < 3 && self[(x + 1, y)] == self[(x, y)] {
                    return true;
                };
                if x > 0 && self[(x - 1, y)] == self[(x, y)] {
                    return true;
                };
                if y < 3 && self[(x, y + 1)] == self[(x, y)] {
                    return true;
                };
                if y > 0 && self[(x, y - 1)] == self[(x, y)] {
                    return true;
                };
            }
        }
        false
    }

    fn spawn_tile(&mut self) -> Result<(), GridFullError> {
        if !self.is_move_available() {
            return Err(GridFullError);
        }

        loop {
            let (x, y) = rand::random::<(usize, usize)>();
            let rand_tile = &mut self[(x % GRID_DIMENSION, y % GRID_DIMENSION)];

            if rand_tile.is_none() {
                let mut rng = rand::thread_rng();

                let range = Range::new(0f64, 1.);
                *rand_tile = if range.ind_sample(&mut rng) > 0.9 {
                    Some(4)
                } else {
                    Some(2)
                };
                break;
            }
        }

        Ok(())
    }

    /// Given a slice of tiles, shifts and merges the tiles towards the left end of the vector.
    ///
    /// Returns a tuple containing the new vector and the change in score.
    fn merge(vector: &[Tile]) -> (Vec<Tile>, u32) {
        // Remove intermediate empty tiles.
        let mut shifted = vector.iter().flat_map(|x| *x).collect::<Vec<_>>();

        // Merge tiles that are next to each other, leaving an empty space.
        let mut score = 0;
        for i in 0..shifted.len() {
            if i + 1 < shifted.len() && shifted[i + 1] == shifted[i] {
                shifted[i] *= 2;
                shifted[i + 1] = 0;
                score += shifted[i];
            }
        }

        // Remove intermediate empty tiles
        let mut shifted = shifted
            .into_iter()
            .filter(|x| *x != 0)
            .map(Some)
            .collect::<Vec<_>>();

        // Fill remaining array.
        while shifted.len() < GRID_DIMENSION {
            shifted.push(None);
        }

        (shifted, score)
    }

    /// Shift tiles left.
    fn shift_left(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for row in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for col in 0..GRID_DIMENSION {
                tiles.push(self[(row, col)]);
            }

            let (merged_row, row_score) = Grid::merge(&mut tiles);
            score += row_score;
            if !moved && tiles != merged_row {
                moved = true;
            }

            for (col, tile) in merged_row.iter().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }

    /// Shift tiles up.
    fn shift_up(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for col in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for row in 0..GRID_DIMENSION {
                tiles.push(self[(row, col)]);
            }

            let (merged_col, col_score) = Grid::merge(&mut tiles);
            score += col_score;
            if !moved && tiles != merged_col {
                moved = true;
            }

            for (row, tile) in merged_col.iter().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }

    /// Shift tiles down.
    fn shift_down(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for col in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for row in (0..GRID_DIMENSION).rev() {
                tiles.push(self[(row, col)]);
            }

            let (merged_col, col_score) = Grid::merge(&mut tiles);
            score += col_score;
            if !moved && tiles != merged_col {
                moved = true;
            }

            for (row, tile) in merged_col.iter().rev().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }

    /// Shift tiles down.
    fn shift_right(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for row in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for col in (0..GRID_DIMENSION).rev() {
                tiles.push(self[(row, col)]);
            }

            let (merged_row, row_score) = Grid::merge(&mut tiles);
            score += row_score;
            if !moved && tiles != merged_row {
                moved = true;
            }

            for (col, tile) in merged_row.iter().rev().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }
}

trait Ui {
    /// Wait for a key press, and report the key.
    fn wait_key(&mut self) -> Key;

    /// Draw the game.
    fn draw(&mut self, game: &Game) -> io::Result<()>;
}

struct Game {
    grid: Grid,
    state: State,
    score: u32,
}

impl Game {
    fn new() -> Self {
        let mut g = Game {
            grid: Grid::new(),
            state: State::Playing,
            score: 0,
        };

        for _ in 0..2 {
            g.grid.spawn_tile().unwrap();
        }

        g
    }

    fn step(&mut self, key: Key) {
        match key {
            key if self.state != State::Lost => {
                let move_result = match key {
                    Key::Up => self.grid.shift_up(),
                    Key::Down => self.grid.shift_down(),
                    Key::Left => self.grid.shift_left(),
                    Key::Right => self.grid.shift_right(),
                    _ => unreachable!(),
                };

                if let Ok(score) = move_result {
                    self.add_score(score);
                    self.grid.spawn_tile().unwrap();
                }

                if !self.grid.is_move_available() {
                    self.state = State::Lost;
                    return;
                }
            }
            _ => (),
        }
    }

    fn add_score(&mut self, score: u32) {
        self.score += score;
        if score == 2048 {
            self.state = State::Won;
        }
    }
}

#[cfg(unix)]
mod ui_imp {
    extern crate termion;

    use super::{Game, Key, State, Ui};

    use std::io::{self, Stdin, Stdout};
    use std::io::prelude::*;

    use self::termion::{color, cursor, style};
    use self::termion::event::Key as TermKey;
    use self::termion::input::Keys;
    use self::termion::raw::{IntoRawMode, RawTerminal};

    pub struct Termion {
        keys: Keys<Stdin>,
        stdout: RawTerminal<Stdout>,
    }

    impl Termion {
        pub fn new() -> Self {
            use std::io;
            use ui_imp::termion::input::TermRead;

            let mut stdout = io::stdout().into_raw_mode().unwrap();
            write!(stdout, "{}", termion::clear::All).unwrap();

            Termion {
                keys: io::stdin().keys(),
                stdout,
            }
        }
    }

    impl Ui for Termion {
        fn wait_key(&mut self) -> Key {
            while let Some(key) = self.keys.next() {
                let key = match key.unwrap() {
                    TermKey::Char('q') => Key::Quit,
                    TermKey::Up => Key::Up,
                    TermKey::Down => Key::Down,
                    TermKey::Left => Key::Left,
                    TermKey::Right => Key::Right,
                    _ => continue,
                };

                return key;
            }

            Key::Quit
        }

        fn draw(&mut self, game: &Game) -> io::Result<()> {
            write!(self.stdout, "{}{}", cursor::Hide, termion::clear::All)?;
            write!(self.stdout, "{}Score: {}", cursor::Goto(16, 1), game.score)?;

            const CELL_WIDTH: usize = 10;
            const CELL_HEIGHT: usize = 5;
            const GRID_X_OFFSET: usize = 0;
            const GRID_Y_OFFSET: usize = 2;

            for row in 0..4 {
                for col in 0..4 {
                    let tile = game.grid[(row, col)];
                    let x = 1 + GRID_X_OFFSET + col * CELL_WIDTH;
                    let y = GRID_Y_OFFSET + row * CELL_HEIGHT;

                    write!(
                        self.stdout,
                        "{}┌────────┐",
                        cursor::Goto(x as u16, y as u16)
                    )?;

                    for i in 1..=3 {
                        let y = y + i;
                        write!(
                            self.stdout,
                            "{}│        │",
                            cursor::Goto(x as u16, y as u16)
                        )?;
                    }

                    if let Some(value) = tile {
                        let text_x = x + CELL_WIDTH / 2 - (1 + value.to_string().len() / 3);
                        let text_y = y + CELL_HEIGHT / 2;

                        write!(
                            self.stdout,
                            "{}{}{}{}{}{}",
                            cursor::Goto(text_x as u16, text_y as u16),
                            style::Bold,
                            color::Fg(color::LightWhite),
                            value,
                            color::Fg(color::Reset),
                            style::Reset
                        )?;
                    }

                    write!(
                        self.stdout,
                        "{}└────────┘",
                        cursor::Goto(x as u16, y as u16 + 4)
                    )?;
                }
            }

            match game.state {
                State::Won => write!(self.stdout, "{}You won!", cursor::Goto(16, 12))?,
                State::Lost => write!(self.stdout, "{}You lost!", cursor::Goto(16, 12))?,
                _ => (),
            }

            write!(self.stdout, "{}←,↑,→,↓ or q", cursor::Goto(14, 22))?;

            self.stdout.flush()?;
            Ok(())
        }
    }
}

#[cfg(unix)]
fn main() {
    use ui_imp::Termion;

    let mut game = Game::new();
    let mut ui = Termion::new();

    loop {
        ui.draw(&game).unwrap();
        let key = ui.wait_key();
        if let Key::Quit = key {
            break;
        }
        game.step(key);
    }
}

#[cfg(not(unix))]
fn main() {
    println!("This solution is not supported on Windows.");
}

#[cfg(test)]
mod tests {
    use super::{Grid, GRID_DIMENSION};

    #[test]
    fn merge() {
        let test_cases = [
            ([None, None, None, None], [None, None, None, None]),
            ([None, None, None, Some(2)], [Some(2), None, None, None]),
            ([None, None, Some(2), Some(2)], [Some(4), None, None, None]),
            ([None, Some(2), None, Some(2)], [Some(4), None, None, None]),
            ([Some(2), None, None, Some(2)], [Some(4), None, None, None]),
            ([Some(2), None, Some(2), None], [Some(4), None, None, None]),
            (
                [Some(2), Some(2), Some(2), None],
                [Some(4), Some(2), None, None],
            ),
            (
                [Some(2), None, Some(2), Some(2)],
                [Some(4), Some(2), None, None],
            ),
            (
                [Some(2), Some(2), None, Some(2)],
                [Some(4), Some(2), None, None],
            ),
            (
                [Some(2), Some(2), Some(2), Some(2)],
                [Some(4), Some(4), None, None],
            ),
            (
                [Some(4), Some(4), Some(2), Some(2)],
                [Some(8), Some(4), None, None],
            ),
            (
                [Some(2), Some(2), Some(4), Some(4)],
                [Some(4), Some(8), None, None],
            ),
            (
                [Some(8), None, Some(2), Some(2)],
                [Some(8), Some(4), None, None],
            ),
            (
                [Some(4), None, Some(2), Some(2)],
                [Some(4), Some(4), None, None],
            ),
        ];

        for &(candidate, expected) in &test_cases {
            let (merged, _) = Grid::merge(&candidate);
            assert_eq!(&merged, &expected);
        }
    }

    #[test]
    fn directions() {
        let mut grid = Grid::new();
        grid[(0, 0)] = Some(2);

        grid.shift_right().unwrap();
        assert_eq!(grid[(0, GRID_DIMENSION - 1)], Some(2));
        println!("{:?}", grid);

        grid.shift_down().unwrap();
        assert_eq!(grid[(GRID_DIMENSION - 1, GRID_DIMENSION - 1)], Some(2));
        println!("{:?}", grid);

        grid.shift_left().unwrap();
        println!("{:?}", grid);
        assert_eq!(grid[(GRID_DIMENSION - 1, 0)], Some(2));

        grid.shift_up().unwrap();
        assert_eq!(grid[(0, 0)], Some(2));
    }

    #[test]
    fn non_greedy_movement() {
        let mut grid = Grid::new();
        for i in 0..4 {
            grid[(0, i)] = Some(2);
        }

        grid.shift_right().unwrap();

        let mut expected_grid = Grid::new();
        expected_grid[(0, 2)] = Some(4);
        expected_grid[(0, 3)] = Some(4);
        assert_eq!(grid, expected_grid);
    }

    #[test]
    fn move_direction_priority() {
        let mut grid = Grid::new();
        for i in 1..4 {
            grid[(0, i)] = Some(2);
        }

        grid.shift_right().unwrap();

        let mut expected_grid = Grid::new();
        expected_grid[(0, 2)] = Some(2);
        expected_grid[(0, 3)] = Some(4);
        assert_eq!(grid, expected_grid);
    }
}
<commit_msg>simplify choosing random tile value<commit_after>#![feature(inclusive_range_syntax)]

extern crate rand;

use std::ops::{Index, IndexMut};
use std::io;

use rand::Rng;

const GRID_DIMENSION: usize = 4;

/// A key press.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Key {
    Up,
    Left,
    Down,
    Right,
    Quit,
}

#[derive(Debug, PartialEq)]
enum State {
    Playing,
    Won,
    Lost,
}

/// Error returned when the grid is full.
#[derive(Debug)]
struct GridFullError;

/// Error returned when no tiles were actually moved.
#[derive(Debug)]
struct NoTilesMovedError;

/// An optionally filled tile in the grid.
type Tile = Option<u32>;

/// The set of game tiles.
#[derive(Debug, PartialEq, Eq)]
struct Grid([[Tile; GRID_DIMENSION]; GRID_DIMENSION]);

impl Index<(usize, usize)> for Grid {
    type Output = Tile;

    fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
        &self.0[x][y]
    }
}

impl IndexMut<(usize, usize)> for Grid {
    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output {
        &mut self.0[x][y]
    }
}

impl Grid {
    fn new() -> Self {
        Grid([[None; GRID_DIMENSION]; GRID_DIMENSION])
    }

    fn is_move_available(&self) -> bool {
        for x in 0..4 {
            for y in 0..4 {
                if self[(x, y)].is_none() {
                    return true;
                }
                if x < 3 && self[(x + 1, y)] == self[(x, y)] {
                    return true;
                };
                if x > 0 && self[(x - 1, y)] == self[(x, y)] {
                    return true;
                };
                if y < 3 && self[(x, y + 1)] == self[(x, y)] {
                    return true;
                };
                if y > 0 && self[(x, y - 1)] == self[(x, y)] {
                    return true;
                };
            }
        }
        false
    }

    fn spawn_tile(&mut self) -> Result<(), GridFullError> {
        if !self.is_move_available() {
            return Err(GridFullError);
        }

        let mut rng = rand::thread_rng();

        loop {
            let (x, y) = rand::random::<(usize, usize)>();
            let rand_tile = &mut self[(x % GRID_DIMENSION, y % GRID_DIMENSION)];

            if rand_tile.is_none() {
                *rand_tile = if rng.gen_weighted_bool(10) {
                    Some(4)
                } else {
                    Some(2)
                };
                break;
            }
        }

        Ok(())
    }

    /// Given a slice of tiles, shifts and merges the tiles towards the left end of the vector.
    ///
    /// Returns a tuple containing the new vector and the change in score.
    fn merge(vector: &[Tile]) -> (Vec<Tile>, u32) {
        // Remove intermediate empty tiles.
        let mut shifted = vector.iter().flat_map(|x| *x).collect::<Vec<_>>();

        // Merge tiles that are next to each other, leaving an empty space.
        let mut score = 0;
        for i in 0..shifted.len() {
            if i + 1 < shifted.len() && shifted[i + 1] == shifted[i] {
                shifted[i] *= 2;
                shifted[i + 1] = 0;
                score += shifted[i];
            }
        }

        // Remove intermediate empty tiles
        let mut shifted = shifted
            .into_iter()
            .filter(|x| *x != 0)
            .map(Some)
            .collect::<Vec<_>>();

        // Fill remaining array.
        while shifted.len() < GRID_DIMENSION {
            shifted.push(None);
        }

        (shifted, score)
    }

    /// Shift tiles left.
    fn shift_left(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for row in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for col in 0..GRID_DIMENSION {
                tiles.push(self[(row, col)]);
            }

            let (merged_row, row_score) = Grid::merge(&mut tiles);
            score += row_score;
            if !moved && tiles != merged_row {
                moved = true;
            }

            for (col, tile) in merged_row.iter().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }

    /// Shift tiles up.
    fn shift_up(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for col in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for row in 0..GRID_DIMENSION {
                tiles.push(self[(row, col)]);
            }

            let (merged_col, col_score) = Grid::merge(&mut tiles);
            score += col_score;
            if !moved && tiles != merged_col {
                moved = true;
            }

            for (row, tile) in merged_col.iter().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }

    /// Shift tiles down.
    fn shift_down(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for col in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for row in (0..GRID_DIMENSION).rev() {
                tiles.push(self[(row, col)]);
            }

            let (merged_col, col_score) = Grid::merge(&mut tiles);
            score += col_score;
            if !moved && tiles != merged_col {
                moved = true;
            }

            for (row, tile) in merged_col.iter().rev().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }

    /// Shift tiles down.
    fn shift_right(&mut self) -> Result<u32, NoTilesMovedError> {
        let mut moved = false;
        let mut score = 0;
        for row in 0..GRID_DIMENSION {
            let mut tiles = vec![];

            for col in (0..GRID_DIMENSION).rev() {
                tiles.push(self[(row, col)]);
            }

            let (merged_row, row_score) = Grid::merge(&mut tiles);
            score += row_score;
            if !moved && tiles != merged_row {
                moved = true;
            }

            for (col, tile) in merged_row.iter().rev().enumerate() {
                self[(row, col)] = *tile;
            }
        }

        if moved {
            Ok(score)
        } else {
            Err(NoTilesMovedError)
        }
    }
}

trait Ui {
    /// Wait for a key press, and report the key.
    fn wait_key(&mut self) -> Key;

    /// Draw the game.
    fn draw(&mut self, game: &Game) -> io::Result<()>;
}

struct Game {
    grid: Grid,
    state: State,
    score: u32,
}

impl Game {
    fn new() -> Self {
        let mut g = Game {
            grid: Grid::new(),
            state: State::Playing,
            score: 0,
        };

        for _ in 0..2 {
            g.grid.spawn_tile().unwrap();
        }

        g
    }

    fn step(&mut self, key: Key) {
        match key {
            key if self.state != State::Lost => {
                let move_result = match key {
                    Key::Up => self.grid.shift_up(),
                    Key::Down => self.grid.shift_down(),
                    Key::Left => self.grid.shift_left(),
                    Key::Right => self.grid.shift_right(),
                    _ => unreachable!(),
                };

                if let Ok(score) = move_result {
                    self.add_score(score);
                    self.grid.spawn_tile().unwrap();
                }

                if !self.grid.is_move_available() {
                    self.state = State::Lost;
                    return;
                }
            }
            _ => (),
        }
    }

    fn add_score(&mut self, score: u32) {
        self.score += score;
        if score == 2048 {
            self.state = State::Won;
        }
    }
}

#[cfg(unix)]
mod ui_imp {
    extern crate termion;

    use super::{Game, Key, State, Ui};

    use std::io::{self, Stdin, Stdout};
    use std::io::prelude::*;

    use self::termion::{color, cursor, style};
    use self::termion::event::Key as TermKey;
    use self::termion::input::Keys;
    use self::termion::raw::{IntoRawMode, RawTerminal};

    pub struct Termion {
        keys: Keys<Stdin>,
        stdout: RawTerminal<Stdout>,
    }

    impl Termion {
        pub fn new() -> Self {
            use std::io;
            use ui_imp::termion::input::TermRead;

            let mut stdout = io::stdout().into_raw_mode().unwrap();
            write!(stdout, "{}", termion::clear::All).unwrap();

            Termion {
                keys: io::stdin().keys(),
                stdout,
            }
        }
    }

    impl Ui for Termion {
        fn wait_key(&mut self) -> Key {
            while let Some(key) = self.keys.next() {
                let key = match key.unwrap() {
                    TermKey::Char('q') => Key::Quit,
                    TermKey::Up => Key::Up,
                    TermKey::Down => Key::Down,
                    TermKey::Left => Key::Left,
                    TermKey::Right => Key::Right,
                    _ => continue,
                };

                return key;
            }

            Key::Quit
        }

        fn draw(&mut self, game: &Game) -> io::Result<()> {
            write!(self.stdout, "{}{}", cursor::Hide, termion::clear::All)?;
            write!(self.stdout, "{}Score: {}", cursor::Goto(16, 1), game.score)?;

            const CELL_WIDTH: usize = 10;
            const CELL_HEIGHT: usize = 5;
            const GRID_X_OFFSET: usize = 0;
            const GRID_Y_OFFSET: usize = 2;

            for row in 0..4 {
                for col in 0..4 {
                    let tile = game.grid[(row, col)];
                    let x = 1 + GRID_X_OFFSET + col * CELL_WIDTH;
                    let y = GRID_Y_OFFSET + row * CELL_HEIGHT;

                    write!(
                        self.stdout,
                        "{}┌────────┐",
                        cursor::Goto(x as u16, y as u16)
                    )?;

                    for i in 1..=3 {
                        let y = y + i;
                        write!(
                            self.stdout,
                            "{}│        │",
                            cursor::Goto(x as u16, y as u16)
                        )?;
                    }

                    if let Some(value) = tile {
                        let text_x = x + CELL_WIDTH / 2 - (1 + value.to_string().len() / 3);
                        let text_y = y + CELL_HEIGHT / 2;

                        write!(
                            self.stdout,
                            "{}{}{}{}{}{}",
                            cursor::Goto(text_x as u16, text_y as u16),
                            style::Bold,
                            color::Fg(color::LightWhite),
                            value,
                            color::Fg(color::Reset),
                            style::Reset
                        )?;
                    }

                    write!(
                        self.stdout,
                        "{}└────────┘",
                        cursor::Goto(x as u16, y as u16 + 4)
                    )?;
                }
            }

            match game.state {
                State::Won => write!(self.stdout, "{}You won!", cursor::Goto(16, 12))?,
                State::Lost => write!(self.stdout, "{}You lost!", cursor::Goto(16, 12))?,
                _ => (),
            }

            write!(self.stdout, "{}←,↑,→,↓ or q", cursor::Goto(14, 22))?;

            self.stdout.flush()?;
            Ok(())
        }
    }
}

#[cfg(unix)]
fn main() {
    use ui_imp::Termion;

    let mut game = Game::new();
    let mut ui = Termion::new();

    loop {
        ui.draw(&game).unwrap();
        let key = ui.wait_key();
        if let Key::Quit = key {
            break;
        }
        game.step(key);
    }
}

#[cfg(not(unix))]
fn main() {
    println!("This solution is not supported on Windows.");
}

#[cfg(test)]
mod tests {
    use super::{Grid, GRID_DIMENSION};

    #[test]
    fn merge() {
        let test_cases = [
            ([None, None, None, None], [None, None, None, None]),
            ([None, None, None, Some(2)], [Some(2), None, None, None]),
            ([None, None, Some(2), Some(2)], [Some(4), None, None, None]),
            ([None, Some(2), None, Some(2)], [Some(4), None, None, None]),
            ([Some(2), None, None, Some(2)], [Some(4), None, None, None]),
            ([Some(2), None, Some(2), None], [Some(4), None, None, None]),
            (
                [Some(2), Some(2), Some(2), None],
                [Some(4), Some(2), None, None],
            ),
            (
                [Some(2), None, Some(2), Some(2)],
                [Some(4), Some(2), None, None],
            ),
            (
                [Some(2), Some(2), None, Some(2)],
                [Some(4), Some(2), None, None],
            ),
            (
                [Some(2), Some(2), Some(2), Some(2)],
                [Some(4), Some(4), None, None],
            ),
            (
                [Some(4), Some(4), Some(2), Some(2)],
                [Some(8), Some(4), None, None],
            ),
            (
                [Some(2), Some(2), Some(4), Some(4)],
                [Some(4), Some(8), None, None],
            ),
            (
                [Some(8), None, Some(2), Some(2)],
                [Some(8), Some(4), None, None],
            ),
            (
                [Some(4), None, Some(2), Some(2)],
                [Some(4), Some(4), None, None],
            ),
        ];

        for &(candidate, expected) in &test_cases {
            let (merged, _) = Grid::merge(&candidate);
            assert_eq!(&merged, &expected);
        }
    }

    #[test]
    fn directions() {
        let mut grid = Grid::new();
        grid[(0, 0)] = Some(2);

        grid.shift_right().unwrap();
        assert_eq!(grid[(0, GRID_DIMENSION - 1)], Some(2));
        println!("{:?}", grid);

        grid.shift_down().unwrap();
        assert_eq!(grid[(GRID_DIMENSION - 1, GRID_DIMENSION - 1)], Some(2));
        println!("{:?}", grid);

        grid.shift_left().unwrap();
        println!("{:?}", grid);
        assert_eq!(grid[(GRID_DIMENSION - 1, 0)], Some(2));

        grid.shift_up().unwrap();
        assert_eq!(grid[(0, 0)], Some(2));
    }

    #[test]
    fn non_greedy_movement() {
        let mut grid = Grid::new();
        for i in 0..4 {
            grid[(0, i)] = Some(2);
        }

        grid.shift_right().unwrap();

        let mut expected_grid = Grid::new();
        expected_grid[(0, 2)] = Some(4);
        expected_grid[(0, 3)] = Some(4);
        assert_eq!(grid, expected_grid);
    }

    #[test]
    fn move_direction_priority() {
        let mut grid = Grid::new();
        for i in 1..4 {
            grid[(0, i)] = Some(2);
        }

        grid.shift_right().unwrap();

        let mut expected_grid = Grid::new();
        expected_grid[(0, 2)] = Some(2);
        expected_grid[(0, 3)] = Some(4);
        assert_eq!(grid, expected_grid);
    }
}
<|endoftext|>


<commit_before>use std::fs::{read_dir, read_to_string};
use std::path::Path;
use std::error::Error;
use wasmbin::io::Decode;
use wasmbin::module::Module;
use wabt::script::{Command, CommandKind, ScriptParser};

fn fmt_wabt_error(err: wabt::Error) -> String {
    use wabt::ErrorKind;

    match err.kind() {
        ErrorKind::Nul => "string contained nul-byte".to_owned(),
        ErrorKind::Deserialize(err) => format!("failed to deserialize:\n{:#}", err),
        ErrorKind::Parse(_) => "failed to parse".to_owned(),
        ErrorKind::WriteText => "failed to write text".to_owned(),
        ErrorKind::NonUtf8Result => "result is not a valid utf8".to_owned(),
        ErrorKind::WriteBinary => "failed to write binary".to_owned(),
        ErrorKind::ResolveNames(err) => format!("failed to resolve names:\n{:#}", err),
        ErrorKind::Validate(err) => format!("failed to validate:\n{:#}", err),
    }
}

fn fmt_wabt_script_error(err: wabt::script::Error) -> String {
    match err {
        wabt::script::Error::IoError(err) => format!("{}", err),
        wabt::script::Error::WabtError(err) => format!("Wabt: {}", fmt_wabt_error(err)),
        wabt::script::Error::Other(err) => format!("{}", err),
        wabt::script::Error::WithLineInfo { line, error } => format!("at line {}: {}", line, fmt_wabt_script_error(*error)),
    }
}

fn check_wast(path: &Path) -> Result<(), Box<dyn Error>> {
    let path = path.as_os_str().to_str().ok_or("Invalid UTF-8 path")?;
    let src = read_to_string(path)?;
    let mut parser = ScriptParser::<f32, f64>::from_str(&src).map_err(fmt_wabt_script_error)?;
    while let Some(Command { kind, line }) = parser.next().map_err(fmt_wabt_script_error)? {
        let (module, expect_res) = match kind {
            CommandKind::AssertMalformed { module, message } => {
                (module, Err(message))
            }
            CommandKind::Module { module, .. } => {
                (module, Ok(()))
            }
            _ => {
                // Skipping interpreted
                continue
            }
        };
        println!("Test {}:{}", path, line);
        println!("---");
        match (Module::decode(&mut module.into_vec().as_slice()), expect_res) {
            (Ok(_), Err(err)) => println!("Expected an invalid module definition at line {} with an error: {}", line, err),
            (Err(err), Ok(())) => println!("Expected a valid module definition at line {}. Error:\n{:#}", line, err),
            _ => println!("OK"),
        }
        println!("===");
    }
    Ok(())
}

fn main() -> Result<(), Box<dyn Error>> {
    for file in read_dir("tests/testsuite")? {
        let path = file?.path();
        if !path.extension().map_or(false, |ext| ext == "wast") {
            continue;
        }
        match check_wast(&path) {
            Ok(()) => {},
            Err(err) => {
                // If an error comes from the Wabt, ignore for now - most likely it uses a new feature we don't support yet.
                let _ = err;
                /*
                println!("Suite {}", path.display());
                println!("---");
                println!("{}", err);
                println!("===");
                */
            }
        }
    }
    Ok(())
}
<commit_msg>Make sure that tests run w/o lazy blobs<commit_after>#[cfg(feature = "lazy-blob")]
compile_error!("Tests must be run without lazy blobs.");

use std::fs::{read_dir, read_to_string};
use std::path::Path;
use std::error::Error;
use wasmbin::io::Decode;
use wasmbin::module::Module;
use wabt::script::{Command, CommandKind, ScriptParser};

fn fmt_wabt_error(err: wabt::Error) -> String {
    use wabt::ErrorKind;

    match err.kind() {
        ErrorKind::Nul => "string contained nul-byte".to_owned(),
        ErrorKind::Deserialize(err) => format!("failed to deserialize:\n{:#}", err),
        ErrorKind::Parse(_) => "failed to parse".to_owned(),
        ErrorKind::WriteText => "failed to write text".to_owned(),
        ErrorKind::NonUtf8Result => "result is not a valid utf8".to_owned(),
        ErrorKind::WriteBinary => "failed to write binary".to_owned(),
        ErrorKind::ResolveNames(err) => format!("failed to resolve names:\n{:#}", err),
        ErrorKind::Validate(err) => format!("failed to validate:\n{:#}", err),
    }
}

fn fmt_wabt_script_error(err: wabt::script::Error) -> String {
    match err {
        wabt::script::Error::IoError(err) => format!("{}", err),
        wabt::script::Error::WabtError(err) => format!("Wabt: {}", fmt_wabt_error(err)),
        wabt::script::Error::Other(err) => format!("{}", err),
        wabt::script::Error::WithLineInfo { line, error } => format!("at line {}: {}", line, fmt_wabt_script_error(*error)),
    }
}

fn check_wast(path: &Path) -> Result<(), Box<dyn Error>> {
    let path = path.as_os_str().to_str().ok_or("Invalid UTF-8 path")?;
    let src = read_to_string(path)?;
    let mut parser = ScriptParser::<f32, f64>::from_str(&src).map_err(fmt_wabt_script_error)?;
    while let Some(Command { kind, line }) = parser.next().map_err(fmt_wabt_script_error)? {
        let (module, expect_res) = match kind {
            CommandKind::AssertMalformed { module, message } => {
                (module, Err(message))
            }
            CommandKind::Module { module, .. } => {
                (module, Ok(()))
            }
            _ => {
                // Skipping interpreted
                continue
            }
        };
        println!("Test {}:{}", path, line);
        println!("---");
        match (Module::decode(&mut module.into_vec().as_slice()), expect_res) {
            (Ok(_), Err(err)) => println!("Expected an invalid module definition at line {} with an error: {}", line, err),
            (Err(err), Ok(())) => println!("Expected a valid module definition at line {}. Error:\n{:#}", line, err),
            _ => println!("OK"),
        }
        println!("===");
    }
    Ok(())
}

fn main() -> Result<(), Box<dyn Error>> {
    for file in read_dir("tests/testsuite")? {
        let path = file?.path();
        if !path.extension().map_or(false, |ext| ext == "wast") {
            continue;
        }
        match check_wast(&path) {
            Ok(()) => {},
            Err(err) => {
                // If an error comes from the Wabt, ignore for now - most likely it uses a new feature we don't support yet.
                let _ = err;
                /*
                println!("Suite {}", path.display());
                println!("---");
                println!("{}", err);
                println!("===");
                */
            }
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    access_path::AccessPath,
    account_address::AccountAddress,
    account_config::CORE_CODE_ADDRESS,
    event::{EventHandle, EventKey},
};
use anyhow::{format_err, Result};
use move_core_types::{
    identifier::Identifier,
    language_storage::{StructTag, TypeTag},
    move_resource::MoveResource,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc};

mod libra_version;
mod registered_currencies;
mod validator_set;
mod vm_config;
mod vm_publishing_option;

pub use self::{
    libra_version::LibraVersion, registered_currencies::RegisteredCurrencies,
    validator_set::ValidatorSet, vm_config::VMConfig, vm_publishing_option::VMPublishingOption,
};

/// To register an on-chain config in Rust:
/// 1. Implement the `OnChainConfig` trait for the Rust representation of the config
/// 2. Add the config's `ConfigID` to `ON_CHAIN_CONFIG_REGISTRY`

#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct ConfigID(&'static str, &'static str);

const CONFIG_ADDRESS_STR: &str = "0xA550C18";

pub fn config_address() -> AccountAddress {
    AccountAddress::from_hex_literal(CONFIG_ADDRESS_STR).expect("failed to get address")
}

impl ConfigID {
    pub fn access_path(self) -> AccessPath {
        access_path_for_config(
            AccountAddress::from_hex_literal(self.0).expect("failed to get address"),
            Identifier::new(self.1).expect("failed to get Identifier"),
        )
    }
}

/// State sync will panic if the value of any config in this registry is uninitialized
pub const ON_CHAIN_CONFIG_REGISTRY: &[ConfigID] = &[
    VMConfig::CONFIG_ID,
    VMPublishingOption::CONFIG_ID,
    LibraVersion::CONFIG_ID,
    ValidatorSet::CONFIG_ID,
    RegisteredCurrencies::CONFIG_ID,
];

#[derive(Clone, Debug, PartialEq)]
pub struct OnChainConfigPayload {
    epoch: u64,
    configs: Arc<HashMap<ConfigID, Vec<u8>>>,
}

impl OnChainConfigPayload {
    pub fn new(epoch: u64, configs: Arc<HashMap<ConfigID, Vec<u8>>>) -> Self {
        Self { epoch, configs }
    }

    pub fn epoch(&self) -> u64 {
        self.epoch
    }

    pub fn get<T: OnChainConfig>(&self) -> Result<T> {
        let bytes = self
            .configs
            .get(&T::CONFIG_ID)
            .ok_or_else(|| format_err!("[on-chain cfg] config not in payload"))?;
        T::deserialize_into_config(bytes)
    }

    pub fn configs(&self) -> &HashMap<ConfigID, Vec<u8>> {
        &self.configs
    }
}

/// Trait to be implemented by a storage type from which to read on-chain configs
pub trait ConfigStorage {
    fn fetch_config(&self, access_path: AccessPath) -> Option<Vec<u8>>;
}

/// Trait to be implemented by a Rust struct representation of an on-chain config
/// that is stored in storage as a serialized byte array
pub trait OnChainConfig: Send + Sync + DeserializeOwned {
    // libra_root_address
    const ADDRESS: &'static str = CONFIG_ADDRESS_STR;
    const IDENTIFIER: &'static str;
    const CONFIG_ID: ConfigID = ConfigID(Self::ADDRESS, Self::IDENTIFIER);

    // Single-round LCS deserialization from bytes to `Self`
    // This is the expected deserialization pattern for most Rust representations,
    // but sometimes `deserialize_into_config` may need an extra customized round of deserialization
    // (e.g. enums like `VMPublishingOption`)
    // In the override, we can reuse this default logic via this function
    // Note: we cannot directly call the default `deserialize_into_config` implementation
    // in its override - this will just refer to the override implementation itself
    fn deserialize_default_impl(bytes: &[u8]) -> Result<Self> {
        lcs::from_bytes::<Self>(&bytes)
            .map_err(|e| format_err!("[on-chain config] Failed to deserialize into config: {}", e))
    }

    // Function for deserializing bytes to `Self`
    // It will by default try one round of LCS deserialization directly to `Self`
    // The implementation for the concrete type should override this function if this
    // logic needs to be customized
    fn deserialize_into_config(bytes: &[u8]) -> Result<Self> {
        Self::deserialize_default_impl(bytes)
    }

    fn fetch_config<T>(storage: &T) -> Option<Self>
    where
        T: ConfigStorage,
    {
        storage
            .fetch_config(Self::CONFIG_ID.access_path())
            .and_then(|bytes| Self::deserialize_into_config(&bytes).ok())
    }
}

pub fn new_epoch_event_key() -> EventKey {
    EventKey::new_from_address(&config_address(), 4)
}

pub fn access_path_for_config(address: AccountAddress, config_name: Identifier) -> AccessPath {
    AccessPath::new(
        address,
        AccessPath::resource_access_vec(&StructTag {
            address: CORE_CODE_ADDRESS,
            module: Identifier::new("LibraConfig").unwrap(),
            name: Identifier::new("LibraConfig").unwrap(),
            type_params: vec![TypeTag::Struct(StructTag {
                address: CORE_CODE_ADDRESS,
                module: config_name.clone(),
                name: config_name,
                type_params: vec![],
            })],
        }),
    )
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ConfigurationResource {
    epoch: u64,
    last_reconfiguration_time: u64,
    events: EventHandle,
}

impl ConfigurationResource {
    pub fn epoch(&self) -> u64 {
        self.epoch
    }

    pub fn last_reconfiguration_time(&self) -> u64 {
        self.last_reconfiguration_time
    }

    pub fn events(&self) -> &EventHandle {
        &self.events
    }

    #[cfg(feature = "fuzzing")]
    pub fn bump_epoch_for_test(&self) -> Self {
        let epoch = self.epoch + 1;
        let last_reconfiguration_time = self.last_reconfiguration_time + 1;
        let mut events = self.events.clone();
        *events.count_mut() += 1;

        Self {
            epoch,
            last_reconfiguration_time,
            events,
        }
    }
}

#[cfg(feature = "fuzzing")]
impl Default for ConfigurationResource {
    fn default() -> Self {
        Self {
            epoch: 0,
            last_reconfiguration_time: 0,
            events: EventHandle::new_from_address(&crate::account_config::libra_root_address(), 16),
        }
    }
}

impl MoveResource for ConfigurationResource {
    const MODULE_NAME: &'static str = "LibraConfig";
    const STRUCT_NAME: &'static str = "Configuration";
}
<commit_msg>[types] add display impl for OnChainConfigPayload<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    access_path::AccessPath,
    account_address::AccountAddress,
    account_config::CORE_CODE_ADDRESS,
    event::{EventHandle, EventKey},
};
use anyhow::{format_err, Result};
use move_core_types::{
    identifier::Identifier,
    language_storage::{StructTag, TypeTag},
    move_resource::MoveResource,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{collections::HashMap, fmt, sync::Arc};

mod libra_version;
mod registered_currencies;
mod validator_set;
mod vm_config;
mod vm_publishing_option;

pub use self::{
    libra_version::LibraVersion, registered_currencies::RegisteredCurrencies,
    validator_set::ValidatorSet, vm_config::VMConfig, vm_publishing_option::VMPublishingOption,
};

/// To register an on-chain config in Rust:
/// 1. Implement the `OnChainConfig` trait for the Rust representation of the config
/// 2. Add the config's `ConfigID` to `ON_CHAIN_CONFIG_REGISTRY`

#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct ConfigID(&'static str, &'static str);

const CONFIG_ADDRESS_STR: &str = "0xA550C18";

pub fn config_address() -> AccountAddress {
    AccountAddress::from_hex_literal(CONFIG_ADDRESS_STR).expect("failed to get address")
}

impl ConfigID {
    pub fn access_path(self) -> AccessPath {
        access_path_for_config(
            AccountAddress::from_hex_literal(self.0).expect("failed to get address"),
            Identifier::new(self.1).expect("failed to get Identifier"),
        )
    }
}

impl fmt::Display for ConfigID {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "OnChain config ID [address: {}, identifier: {}]",
            self.0, self.1
        )
    }
}

/// State sync will panic if the value of any config in this registry is uninitialized
pub const ON_CHAIN_CONFIG_REGISTRY: &[ConfigID] = &[
    VMConfig::CONFIG_ID,
    VMPublishingOption::CONFIG_ID,
    LibraVersion::CONFIG_ID,
    ValidatorSet::CONFIG_ID,
    RegisteredCurrencies::CONFIG_ID,
];

#[derive(Clone, Debug, PartialEq)]
pub struct OnChainConfigPayload {
    epoch: u64,
    configs: Arc<HashMap<ConfigID, Vec<u8>>>,
}

impl OnChainConfigPayload {
    pub fn new(epoch: u64, configs: Arc<HashMap<ConfigID, Vec<u8>>>) -> Self {
        Self { epoch, configs }
    }

    pub fn epoch(&self) -> u64 {
        self.epoch
    }

    pub fn get<T: OnChainConfig>(&self) -> Result<T> {
        let bytes = self
            .configs
            .get(&T::CONFIG_ID)
            .ok_or_else(|| format_err!("[on-chain cfg] config not in payload"))?;
        T::deserialize_into_config(bytes)
    }

    pub fn configs(&self) -> &HashMap<ConfigID, Vec<u8>> {
        &self.configs
    }
}

impl fmt::Display for OnChainConfigPayload {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut config_ids = "".to_string();
        for id in self.configs.keys() {
            config_ids += &id.to_string();
        }
        write!(
            f,
            "OnChainConfigPayload [epoch: {}, configs: {}]",
            self.epoch, config_ids
        )
    }
}

/// Trait to be implemented by a storage type from which to read on-chain configs
pub trait ConfigStorage {
    fn fetch_config(&self, access_path: AccessPath) -> Option<Vec<u8>>;
}

/// Trait to be implemented by a Rust struct representation of an on-chain config
/// that is stored in storage as a serialized byte array
pub trait OnChainConfig: Send + Sync + DeserializeOwned {
    // libra_root_address
    const ADDRESS: &'static str = CONFIG_ADDRESS_STR;
    const IDENTIFIER: &'static str;
    const CONFIG_ID: ConfigID = ConfigID(Self::ADDRESS, Self::IDENTIFIER);

    // Single-round LCS deserialization from bytes to `Self`
    // This is the expected deserialization pattern for most Rust representations,
    // but sometimes `deserialize_into_config` may need an extra customized round of deserialization
    // (e.g. enums like `VMPublishingOption`)
    // In the override, we can reuse this default logic via this function
    // Note: we cannot directly call the default `deserialize_into_config` implementation
    // in its override - this will just refer to the override implementation itself
    fn deserialize_default_impl(bytes: &[u8]) -> Result<Self> {
        lcs::from_bytes::<Self>(&bytes)
            .map_err(|e| format_err!("[on-chain config] Failed to deserialize into config: {}", e))
    }

    // Function for deserializing bytes to `Self`
    // It will by default try one round of LCS deserialization directly to `Self`
    // The implementation for the concrete type should override this function if this
    // logic needs to be customized
    fn deserialize_into_config(bytes: &[u8]) -> Result<Self> {
        Self::deserialize_default_impl(bytes)
    }

    fn fetch_config<T>(storage: &T) -> Option<Self>
    where
        T: ConfigStorage,
    {
        storage
            .fetch_config(Self::CONFIG_ID.access_path())
            .and_then(|bytes| Self::deserialize_into_config(&bytes).ok())
    }
}

pub fn new_epoch_event_key() -> EventKey {
    EventKey::new_from_address(&config_address(), 4)
}

pub fn access_path_for_config(address: AccountAddress, config_name: Identifier) -> AccessPath {
    AccessPath::new(
        address,
        AccessPath::resource_access_vec(&StructTag {
            address: CORE_CODE_ADDRESS,
            module: Identifier::new("LibraConfig").unwrap(),
            name: Identifier::new("LibraConfig").unwrap(),
            type_params: vec![TypeTag::Struct(StructTag {
                address: CORE_CODE_ADDRESS,
                module: config_name.clone(),
                name: config_name,
                type_params: vec![],
            })],
        }),
    )
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ConfigurationResource {
    epoch: u64,
    last_reconfiguration_time: u64,
    events: EventHandle,
}

impl ConfigurationResource {
    pub fn epoch(&self) -> u64 {
        self.epoch
    }

    pub fn last_reconfiguration_time(&self) -> u64 {
        self.last_reconfiguration_time
    }

    pub fn events(&self) -> &EventHandle {
        &self.events
    }

    #[cfg(feature = "fuzzing")]
    pub fn bump_epoch_for_test(&self) -> Self {
        let epoch = self.epoch + 1;
        let last_reconfiguration_time = self.last_reconfiguration_time + 1;
        let mut events = self.events.clone();
        *events.count_mut() += 1;

        Self {
            epoch,
            last_reconfiguration_time,
            events,
        }
    }
}

#[cfg(feature = "fuzzing")]
impl Default for ConfigurationResource {
    fn default() -> Self {
        Self {
            epoch: 0,
            last_reconfiguration_time: 0,
            events: EventHandle::new_from_address(&crate::account_config::libra_root_address(), 16),
        }
    }
}

impl MoveResource for ConfigurationResource {
    const MODULE_NAME: &'static str = "LibraConfig";
    const STRUCT_NAME: &'static str = "Configuration";
}
<|endoftext|>


<commit_before>use std::{
    collections::BTreeMap,
    env,
    fmt::{self, Write as _},
    io::Write as _,
    time::{Instant, SystemTime, UNIX_EPOCH},
};

use anyhow::{bail, format_err, Result};

use crate::not_bash::{fs2, pushd, rm_rf, run};

type Unit = &'static str;

pub fn run_metrics() -> Result<()> {
    let mut metrics = Metrics::new()?;
    metrics.measure_build()?;

    {
        let _d = pushd("target");
        let api_token = env::var("METRICS_TOKEN").unwrap();
        let repo = format!("https://{}@github.com/rust-analyzer/metrics.git", api_token);
        run!("git clone --depth 1 {}", repo)?;
        let _d = pushd("metrics");

        let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
        writeln!(file, "{}", metrics.json())?;
        run!("git add .")?;
        run!("git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈")?;
        run!("git push origin master")?;
    }
    eprintln!("{:#?}", metrics);
    Ok(())
}

impl Metrics {
    fn measure_build(&mut self) -> Result<()> {
        run!("cargo fetch")?;
        rm_rf("./target/release")?;

        let build = Instant::now();
        // run!("cargo build --release --package rust-analyzer --bin rust-analyzer")?;
        let build = build.elapsed();
        self.report("build", build.as_millis() as u64, "ms");
        Ok(())
    }
}

#[derive(Debug)]
struct Metrics {
    host: Host,
    timestamp: SystemTime,
    revision: String,
    metrics: BTreeMap<String, (u64, Unit)>,
}

#[derive(Debug)]
struct Host {
    os: String,
    cpu: String,
    mem: String,
}

impl Metrics {
    fn new() -> Result<Metrics> {
        let host = Host::new()?;
        let timestamp = SystemTime::now();
        let revision = run!("git rev-parse HEAD")?;
        Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() })
    }

    fn report(&mut self, name: &str, value: u64, unit: Unit) {
        self.metrics.insert(name.into(), (value, unit));
    }

    fn json(&self) -> Json {
        let mut json = Json::default();
        self.to_json(&mut json);
        json
    }
    fn to_json(&self, json: &mut Json) {
        json.begin_object();
        {
            json.field("host");
            self.host.to_json(json);

            json.field("timestamp");
            let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap();
            json.number(timestamp.as_secs() as f64);

            json.field("revision");
            json.string(&self.revision);

            json.field("metrics");
            json.begin_object();
            {
                for (k, &(value, unit)) in &self.metrics {
                    json.field(k);
                    json.begin_array();
                    {
                        json.number(value as f64);
                        json.string(unit);
                    }
                    json.end_array();
                }
            }
            json.end_object()
        }
        json.end_object();
    }
}

impl Host {
    fn new() -> Result<Host> {
        if cfg!(not(target_os = "linux")) {
            bail!("can only collect metrics on Linux ");
        }

        let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();

        let cpu =
            read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();

        let mem = read_field("/proc/meminfo", "MemTotal:")?;

        return Ok(Host { os, cpu, mem });

        fn read_field<'a>(path: &str, field: &str) -> Result<String> {
            let text = fs2::read_to_string(path)?;

            let line = text
                .lines()
                .find(|it| it.starts_with(field))
                .ok_or_else(|| format_err!("can't parse {}", path))?;
            Ok(line[field.len()..].trim().to_string())
        }
    }
    fn to_json(&self, json: &mut Json) {
        json.begin_object();
        {
            json.field("os");
            json.string(&self.os);

            json.field("cpu");
            json.string(&self.cpu);

            json.field("mem");
            json.string(&self.mem);
        }
        json.end_object();
    }
}

struct State {
    obj: bool,
    first: bool,
}

#[derive(Default)]
struct Json {
    stack: Vec<State>,
    buf: String,
}

impl Json {
    fn begin_object(&mut self) {
        self.stack.push(State { obj: true, first: true });
        self.buf.push('{');
    }
    fn end_object(&mut self) {
        self.stack.pop();
        self.buf.push('}')
    }
    fn begin_array(&mut self) {
        self.stack.push(State { obj: false, first: true });
        self.buf.push('[');
    }
    fn end_array(&mut self) {
        self.stack.pop();
        self.buf.push(']')
    }
    fn field(&mut self, name: &str) {
        self.object_comma();
        self.string_token(name);
        self.buf.push(':');
    }
    fn string(&mut self, value: &str) {
        self.array_comma();
        self.string_token(value);
    }
    fn string_token(&mut self, value: &str) {
        self.buf.push('"');
        self.buf.extend(value.escape_default());
        self.buf.push('"');
    }
    fn number(&mut self, value: f64) {
        self.array_comma();
        write!(self.buf, "{}", value).unwrap();
    }

    fn array_comma(&mut self) {
        let state = self.stack.last_mut().unwrap();
        if state.obj {
            return;
        }
        if !state.first {
            self.buf.push(',');
        }
        state.first = false;
    }

    fn object_comma(&mut self) {
        let state = self.stack.last_mut().unwrap();
        if !state.first {
            self.buf.push(',');
        }
        state.first = false;
    }
}

impl fmt::Display for Json {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.buf)
    }
}
<commit_msg>Real metrics<commit_after>use std::{
    collections::BTreeMap,
    env,
    fmt::{self, Write as _},
    io::Write as _,
    time::{Instant, SystemTime, UNIX_EPOCH},
};

use anyhow::{bail, format_err, Result};

use crate::not_bash::{fs2, pushd, rm_rf, run};

type Unit = &'static str;

pub fn run_metrics() -> Result<()> {
    let mut metrics = Metrics::new()?;
    metrics.measure_build()?;

    {
        let _d = pushd("target");
        let metrics_token = env::var("METRICS_TOKEN").unwrap();
        let repo = format!("https://{}@github.com/rust-analyzer/metrics.git", metrics_token);
        run!("git clone --depth 1 {}", repo)?;
        let _d = pushd("metrics");

        let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
        writeln!(file, "{}", metrics.json())?;
        run!("git add .")?;
        run!("git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈")?;
        run!("git push origin master")?;
    }
    eprintln!("{:#?}", metrics);
    Ok(())
}

impl Metrics {
    fn measure_build(&mut self) -> Result<()> {
        run!("cargo fetch")?;
        rm_rf("./target/release")?;

        let build = Instant::now();
        run!("cargo build --release --package rust-analyzer --bin rust-analyzer")?;
        let build = build.elapsed();
        self.report("build", build.as_millis() as u64, "ms");
        Ok(())
    }
}

#[derive(Debug)]
struct Metrics {
    host: Host,
    timestamp: SystemTime,
    revision: String,
    metrics: BTreeMap<String, (u64, Unit)>,
}

#[derive(Debug)]
struct Host {
    os: String,
    cpu: String,
    mem: String,
}

impl Metrics {
    fn new() -> Result<Metrics> {
        let host = Host::new()?;
        let timestamp = SystemTime::now();
        let revision = run!("git rev-parse HEAD")?;
        Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() })
    }

    fn report(&mut self, name: &str, value: u64, unit: Unit) {
        self.metrics.insert(name.into(), (value, unit));
    }

    fn json(&self) -> Json {
        let mut json = Json::default();
        self.to_json(&mut json);
        json
    }
    fn to_json(&self, json: &mut Json) {
        json.begin_object();
        {
            json.field("host");
            self.host.to_json(json);

            json.field("timestamp");
            let timestamp = self.timestamp.duration_since(UNIX_EPOCH).unwrap();
            json.number(timestamp.as_secs() as f64);

            json.field("revision");
            json.string(&self.revision);

            json.field("metrics");
            json.begin_object();
            {
                for (k, &(value, unit)) in &self.metrics {
                    json.field(k);
                    json.begin_array();
                    {
                        json.number(value as f64);
                        json.string(unit);
                    }
                    json.end_array();
                }
            }
            json.end_object()
        }
        json.end_object();
    }
}

impl Host {
    fn new() -> Result<Host> {
        if cfg!(not(target_os = "linux")) {
            bail!("can only collect metrics on Linux ");
        }

        let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();

        let cpu =
            read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();

        let mem = read_field("/proc/meminfo", "MemTotal:")?;

        return Ok(Host { os, cpu, mem });

        fn read_field<'a>(path: &str, field: &str) -> Result<String> {
            let text = fs2::read_to_string(path)?;

            let line = text
                .lines()
                .find(|it| it.starts_with(field))
                .ok_or_else(|| format_err!("can't parse {}", path))?;
            Ok(line[field.len()..].trim().to_string())
        }
    }
    fn to_json(&self, json: &mut Json) {
        json.begin_object();
        {
            json.field("os");
            json.string(&self.os);

            json.field("cpu");
            json.string(&self.cpu);

            json.field("mem");
            json.string(&self.mem);
        }
        json.end_object();
    }
}

struct State {
    obj: bool,
    first: bool,
}

#[derive(Default)]
struct Json {
    stack: Vec<State>,
    buf: String,
}

impl Json {
    fn begin_object(&mut self) {
        self.stack.push(State { obj: true, first: true });
        self.buf.push('{');
    }
    fn end_object(&mut self) {
        self.stack.pop();
        self.buf.push('}')
    }
    fn begin_array(&mut self) {
        self.stack.push(State { obj: false, first: true });
        self.buf.push('[');
    }
    fn end_array(&mut self) {
        self.stack.pop();
        self.buf.push(']')
    }
    fn field(&mut self, name: &str) {
        self.object_comma();
        self.string_token(name);
        self.buf.push(':');
    }
    fn string(&mut self, value: &str) {
        self.array_comma();
        self.string_token(value);
    }
    fn string_token(&mut self, value: &str) {
        self.buf.push('"');
        self.buf.extend(value.escape_default());
        self.buf.push('"');
    }
    fn number(&mut self, value: f64) {
        self.array_comma();
        write!(self.buf, "{}", value).unwrap();
    }

    fn array_comma(&mut self) {
        let state = self.stack.last_mut().unwrap();
        if state.obj {
            return;
        }
        if !state.first {
            self.buf.push(',');
        }
        state.first = false;
    }

    fn object_comma(&mut self) {
        let state = self.stack.last_mut().unwrap();
        if !state.first {
            self.buf.push(',');
        }
        state.first = false;
    }
}

impl fmt::Display for Json {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.buf)
    }
}
<|endoftext|>


<commit_before>// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

mod dist_regs;
mod gicv2;
mod gicv3;
mod icc_regs;
mod redist_regs;

use std::{boxed::Box, result};

use kvm_ioctls::{DeviceFd, VmFd};

pub use self::dist_regs::{get_dist_regs, set_dist_regs};
pub use self::icc_regs::{get_icc_regs, set_icc_regs};
pub use self::redist_regs::{get_redist_regs, set_redist_regs};
use {super::layout, gicv2::GICv2, gicv3::GICv3};

/// Errors thrown while setting up the GIC.
#[derive(Debug)]
pub enum Error {
    /// Error while calling KVM ioctl for setting up the global interrupt controller.
    CreateGIC(kvm_ioctls::Error),
    /// Error while setting or getting device attributes for the GIC.
    DeviceAttribute(kvm_ioctls::Error, bool, u32),
}
type Result<T> = result::Result<T, Error>;

/// Trait for GIC devices.
pub trait GICDevice {
    /// Returns the file descriptor of the GIC device
    fn device_fd(&self) -> &DeviceFd;

    /// Returns an array with GIC device properties
    fn device_properties(&self) -> &[u64];

    /// Returns the number of vCPUs this GIC handles
    fn vcpu_count(&self) -> u64;

    /// Returns the fdt compatibility property of the device
    fn fdt_compatibility(&self) -> &str;

    /// Returns the maint_irq fdt property of the device
    fn fdt_maint_irq(&self) -> u32;

    /// Returns the GIC version of the device
    fn version() -> u32
    where
        Self: Sized;

    /// Create the GIC device object
    fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice>
    where
        Self: Sized;

    /// Setup the device-specific attributes
    fn init_device_attributes(gic_device: &dyn GICDevice) -> Result<()>
    where
        Self: Sized;

    /// Initialize a GIC device
    fn init_device(vm: &VmFd) -> Result<DeviceFd>
    where
        Self: Sized,
    {
        let mut gic_device = kvm_bindings::kvm_create_device {
            type_: Self::version(),
            fd: 0,
            flags: 0,
        };

        vm.create_device(&mut gic_device).map_err(Error::CreateGIC)
    }

    /// Set a GIC device attribute
    fn set_device_attribute(
        fd: &DeviceFd,
        group: u32,
        attr: u64,
        addr: u64,
        flags: u32,
    ) -> Result<()>
    where
        Self: Sized,
    {
        let attr = kvm_bindings::kvm_device_attr {
            group,
            attr,
            addr,
            flags,
        };
        fd.set_device_attr(&attr)
            .map_err(|e| Error::DeviceAttribute(e, true, group))?;

        Ok(())
    }

    /// Finalize the setup of a GIC device
    fn finalize_device(gic_device: &dyn GICDevice) -> Result<()>
    where
        Self: Sized,
    {
        /* On arm there are 3 types of interrupts: SGI (0-15), PPI (16-31), SPI (32-1020).
         * SPIs are used to signal interrupts from various peripherals accessible across
         * the whole system so these are the ones that we increment when adding a new virtio device.
         * KVM_DEV_ARM_VGIC_GRP_NR_IRQS sets the highest SPI number. Consequently, we will have a total
         * of `super::layout::IRQ_MAX - 32` usable SPIs in our microVM.
         */
        let nr_irqs: u32 = super::layout::IRQ_MAX;
        let nr_irqs_ptr = &nr_irqs as *const u32;
        Self::set_device_attribute(
            gic_device.device_fd(),
            kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
            0,
            nr_irqs_ptr as u64,
            0,
        )?;

        /* Finalize the GIC.
         * See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
         */
        Self::set_device_attribute(
            gic_device.device_fd(),
            kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
            u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
            0,
            0,
        )?;

        Ok(())
    }

    /// Method to initialize the GIC device
    fn new(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>>
    where
        Self: Sized,
    {
        let vgic_fd = Self::init_device(vm)?;

        let device = Self::create_device(vgic_fd, vcpu_count);

        Self::init_device_attributes(device.as_ref())?;

        Self::finalize_device(device.as_ref())?;

        Ok(device)
    }
}

/// Create a GIC device.
///
/// It will try to create by default a GICv3 device. If that fails it will try
/// to fall-back to a GICv2 device.
pub fn create_gic(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
    GICv3::new(vm, vcpu_count).or_else(|_| GICv2::new(vm, vcpu_count))
}

#[cfg(test)]
mod tests {

    use super::*;
    use kvm_ioctls::Kvm;

    #[test]
    fn test_create_gic() {
        let kvm = Kvm::new().unwrap();
        let vm = kvm.create_vm().unwrap();
        assert!(create_gic(&vm, 1).is_ok());
    }
}
<commit_msg>aarch64: support for flushing rdist tables<commit_after>// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

mod dist_regs;
mod gicv2;
mod gicv3;
mod icc_regs;
mod redist_regs;

use std::{boxed::Box, result};

use kvm_ioctls::{DeviceFd, VmFd};

pub use self::dist_regs::{get_dist_regs, set_dist_regs};
pub use self::icc_regs::{get_icc_regs, set_icc_regs};
pub use self::redist_regs::{get_redist_regs, set_redist_regs};
use {super::layout, gicv2::GICv2, gicv3::GICv3};

/// Errors thrown while setting up the GIC.
#[derive(Debug)]
pub enum Error {
    /// Error while calling KVM ioctl for setting up the global interrupt controller.
    CreateGIC(kvm_ioctls::Error),
    /// Error while setting or getting device attributes for the GIC.
    DeviceAttribute(kvm_ioctls::Error, bool, u32),
}
type Result<T> = result::Result<T, Error>;

/// Trait for GIC devices.
pub trait GICDevice {
    /// Returns the file descriptor of the GIC device
    fn device_fd(&self) -> &DeviceFd;

    /// Returns an array with GIC device properties
    fn device_properties(&self) -> &[u64];

    /// Returns the number of vCPUs this GIC handles
    fn vcpu_count(&self) -> u64;

    /// Returns the fdt compatibility property of the device
    fn fdt_compatibility(&self) -> &str;

    /// Returns the maint_irq fdt property of the device
    fn fdt_maint_irq(&self) -> u32;

    /// Returns the GIC version of the device
    fn version() -> u32
    where
        Self: Sized;

    /// Create the GIC device object
    fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice>
    where
        Self: Sized;

    /// Setup the device-specific attributes
    fn init_device_attributes(gic_device: &dyn GICDevice) -> Result<()>
    where
        Self: Sized;

    /// Initialize a GIC device
    fn init_device(vm: &VmFd) -> Result<DeviceFd>
    where
        Self: Sized,
    {
        let mut gic_device = kvm_bindings::kvm_create_device {
            type_: Self::version(),
            fd: 0,
            flags: 0,
        };

        vm.create_device(&mut gic_device).map_err(Error::CreateGIC)
    }

    /// Set a GIC device attribute
    fn set_device_attribute(
        fd: &DeviceFd,
        group: u32,
        attr: u64,
        addr: u64,
        flags: u32,
    ) -> Result<()>
    where
        Self: Sized,
    {
        let attr = kvm_bindings::kvm_device_attr {
            group,
            attr,
            addr,
            flags,
        };
        fd.set_device_attr(&attr)
            .map_err(|e| Error::DeviceAttribute(e, true, group))?;

        Ok(())
    }

    /// Finalize the setup of a GIC device
    fn finalize_device(gic_device: &dyn GICDevice) -> Result<()>
    where
        Self: Sized,
    {
        /* On arm there are 3 types of interrupts: SGI (0-15), PPI (16-31), SPI (32-1020).
         * SPIs are used to signal interrupts from various peripherals accessible across
         * the whole system so these are the ones that we increment when adding a new virtio device.
         * KVM_DEV_ARM_VGIC_GRP_NR_IRQS sets the highest SPI number. Consequently, we will have a total
         * of `super::layout::IRQ_MAX - 32` usable SPIs in our microVM.
         */
        let nr_irqs: u32 = super::layout::IRQ_MAX;
        let nr_irqs_ptr = &nr_irqs as *const u32;
        Self::set_device_attribute(
            gic_device.device_fd(),
            kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
            0,
            nr_irqs_ptr as u64,
            0,
        )?;

        /* Finalize the GIC.
         * See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
         */
        Self::set_device_attribute(
            gic_device.device_fd(),
            kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
            u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
            0,
            0,
        )?;

        Ok(())
    }

    /// Method to initialize the GIC device
    fn new(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>>
    where
        Self: Sized,
    {
        let vgic_fd = Self::init_device(vm)?;

        let device = Self::create_device(vgic_fd, vcpu_count);

        Self::init_device_attributes(device.as_ref())?;

        Self::finalize_device(device.as_ref())?;

        Ok(device)
    }
}

/// Create a GIC device.
///
/// It will try to create by default a GICv3 device. If that fails it will try
/// to fall-back to a GICv2 device.
pub fn create_gic(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
    GICv3::new(vm, vcpu_count).or_else(|_| GICv2::new(vm, vcpu_count))
}

/// Function that flushes
/// RDIST pending tables into guest RAM.
///
/// The tables get flushed to guest RAM whenever the VM gets stopped.
pub fn save_pending_tables(fd: &DeviceFd) -> Result<()> {
    let init_gic_attr = kvm_bindings::kvm_device_attr {
        group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
        attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
        addr: 0,
        flags: 0,
    };
    fd.set_device_attr(&init_gic_attr)
        .map_err(|e| Error::DeviceAttribute(e, true, kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL))
}

#[cfg(test)]
mod tests {

    use super::*;
    use kvm_ioctls::Kvm;

    #[test]
    fn test_create_gic() {
        let kvm = Kvm::new().unwrap();
        let vm = kvm.create_vm().unwrap();
        assert!(create_gic(&vm, 1).is_ok());
    }

    #[test]
    fn test_save_pending_tables() {
        use std::os::unix::io::AsRawFd;

        let kvm = Kvm::new().unwrap();
        let vm = kvm.create_vm().unwrap();
        let gic = create_gic(&vm, 1).expect("Cannot create gic");
        assert!(save_pending_tables(&gic.device_fd()).is_ok());

        unsafe { libc::close(gic.device_fd().as_raw_fd()) };

        let res = save_pending_tables(&gic.device_fd());
        assert!(res.is_err());
        assert_eq!(
            format!("{:?}", res.unwrap_err()),
            "DeviceAttribute(Error(9), true, 4)"
        );
    }
}
<|endoftext|>