<commit_before>use super::AudioFilter;
use super::{Mixer, MixerConfig};
use std;
use std::error::Error;

use alsa;

#[derive(Clone)]
struct AlsaMixerVolumeParams {
    min: i64,
    max: i64,
    range: f64,
    min_db: alsa::mixer::MilliBel,
    max_db: alsa::mixer::MilliBel,
}

#[derive(Clone)]
pub struct AlsaMixer {
    config: MixerConfig,
    params: AlsaMixerVolumeParams,
}

impl AlsaMixer {
    fn pvol<T>(&self, vol: T, min: T, max: T) -> f64
    where
        T: std::ops::Sub + Copy,
        f64: std::convert::From<<T as std::ops::Sub>::Output>,
    {
        f64::from(vol - min) / f64::from(max - min)
    }

    fn init_mixer(mut config: MixerConfig) -> Result<AlsaMixer, Box<Error>> {
        let mixer = alsa::mixer::Mixer::new(&config.card, false)?;
        let sid = alsa::mixer::SelemId::new(&config.mixer, config.index);

        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
        let (min, max) = selem.get_playback_volume_range();
        let (min_db, max_db) = selem.get_playback_db_range();

        info!(
            "Alsa min: {} ({:?}[dB]) -- max: {} ({:?}[dB])",
            min, min_db, max, max_db
        );

        if config.mapped_volume && (max_db - min_db <= alsa::mixer::MilliBel(24)) {
            warn!(
                "Switching to linear volume mapping, control range: {:?}",
                max_db - min_db
            );
            config.mapped_volume = false;
        } else {
            info!("Using Alsa mapped volume: dB range: {:?}", max_db - min_db);
        }

        Ok(AlsaMixer {
            config: config,
            params: AlsaMixerVolumeParams {
                min: min,
                max: max,
                range: (max - min) as f64,
                min_db: min_db,
                max_db: max_db,
            },
        })
    }

    fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
        let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
        let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);

        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
        let cur_vol = selem
            .get_playback_volume(alsa::mixer::SelemChannelId::mono())
            .expect("Couldn't get current volume");
        let cur_vol_db = selem
            .get_playback_vol_db(alsa::mixer::SelemChannelId::mono())
            .expect("Couldn't get current volume");

        let new_vol: u16;
        debug!("Current alsa volume: {}[i64] {:?}", cur_vol, cur_vol_db);

        if let Some(vol) = set_volume {
            let alsa_volume = if self.config.mapped_volume {
                ((self.pvol(vol, 0x0000, 0xFFFF)).log10() * 6000.0).floor() as i64 + self.params.max
            } else {
                ((vol as f64 / 0xFFFF as f64) * self.params.range) as i64 + self.params.min
            };
            debug!(
                "Maping volume [{:.3}%] {:?} [u16] ->> Alsa [{:.3}%] {:?} [i64]",
                self.pvol(vol, 0x0000, 0xFFFF) * 100.0,
                vol,
                self.pvol(
                    alsa_volume as f64,
                    self.params.min as f64,
                    self.params.max as f64
                ) * 100.0,
                alsa_volume
            );
            selem
                .set_playback_volume_all(alsa_volume)
                .expect("Couldn't set alsa volume");
            new_vol = vol; // Meh
        } else {
            new_vol =
                (((cur_vol - self.params.min) as f64 / self.params.range) * 0xFFFF as f64) as u16;
            debug!(
                "Maping volume [{:.3}%] {:?} [u16] <<- Alsa [{:.3}%] {:?} [i64]",
                self.pvol(new_vol, 0x0000, 0xFFFF),
                new_vol,
                self.pvol(
                    cur_vol as f64,
                    self.params.min as f64,
                    self.params.max as f64
                ),
                cur_vol
            );
        }

        Ok(new_vol)
    }
}

impl Mixer for AlsaMixer {
    fn open(config: Option<MixerConfig>) -> AlsaMixer {
        let config = config.unwrap_or_default();
        info!(
            "Setting up new mixer: card:{} mixer:{} index:{}",
            config.card, config.mixer, config.index
        );
        AlsaMixer::init_mixer(config).expect("Error setting up mixer!")
    }

    fn start(&self) {}

    fn stop(&self) {}

    fn volume(&self) -> u16 {
        match self.map_volume(None) {
            Ok(vol) => vol,
            Err(e) => {
                error!("Error getting volume for <{}>, {:?}", self.config.card, e);
                0
            }
        }
    }

    fn set_volume(&self, volume: u16) {
        match self.map_volume(Some(volume)) {
            Ok(_) => (),
            Err(e) => error!("Error setting volume for <{}>, {:?}", self.config.card, e),
        }
    }

    fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
        None
    }
}
<commit_msg>Better `alsamixer` volume mapping for hardware mixers<commit_after>use super::AudioFilter;
use super::{Mixer, MixerConfig};
use std;
use std::error::Error;

use alsa;

#[derive(Clone)]
struct AlsaMixerVolumeParams {
    min: i64,
    max: i64,
    range: f64,
    min_db: alsa::mixer::MilliBel,
    max_db: alsa::mixer::MilliBel,
}

#[derive(Clone)]
pub struct AlsaMixer {
    config: MixerConfig,
    params: AlsaMixerVolumeParams,
}

impl AlsaMixer {
    fn pvol<T>(&self, vol: T, min: T, max: T) -> f64
    where
        T: std::ops::Sub + Copy,
        f64: std::convert::From<<T as std::ops::Sub>::Output>,
    {
        f64::from(vol - min) / f64::from(max - min)
    }

    fn init_mixer(mut config: MixerConfig) -> Result<AlsaMixer, Box<Error>> {
        let mixer = alsa::mixer::Mixer::new(&config.card, false)?;
        let sid = alsa::mixer::SelemId::new(&config.mixer, config.index);

        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
        let (min, max) = selem.get_playback_volume_range();
        let (min_db, max_db) = selem.get_playback_db_range();
        let hw_mix = selem
            .get_playback_vol_db(alsa::mixer::SelemChannelId::mono())
            .is_ok();

        info!(
            "Alsa min: {} ({:?}[dB]) -- max: {} ({:?}[dB]) HW: {:?}",
            min, min_db, max, max_db, hw_mix
        );

        if config.mapped_volume && (max_db - min_db <= alsa::mixer::MilliBel(24)) {
            warn!(
                "Switching to linear volume mapping, control range: {:?}",
                max_db - min_db
            );
            config.mapped_volume = false;
        } else if !config.mapped_volume {
            info!("Using Alsa linear volume");
        }

        Ok(AlsaMixer {
            config: config,
            params: AlsaMixerVolumeParams {
                min: min,
                max: max,
                range: (max - min) as f64,
                min_db: min_db,
                max_db: max_db,
            },
        })
    }

    fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
        let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
        let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);

        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
        let cur_vol = selem
            .get_playback_volume(alsa::mixer::SelemChannelId::mono())
            .expect("Couldn't get current volume");
        let cur_vol_db = selem
            .get_playback_vol_db(alsa::mixer::SelemChannelId::mono())
            .unwrap_or(alsa::mixer::MilliBel(9999));

        let new_vol: u16;
        info!("Current alsa volume: {}[i64] {:?}", cur_vol, cur_vol_db);

        if let Some(vol) = set_volume {
            if self.config.mapped_volume {
                info!("Using mapped_volume");
                //TODO Check if min is not mute!
                let vol_db = ((self.pvol(vol, 0x0000, 0xFFFF)).log10() * 6000.0).floor() as i64
                    + self.params.max;
                selem
                    .set_playback_volume_all(vol_db)
                    .expect("Couldn't set alsa dB volume");
                info!(
                    "Mapping volume [{:.3}%] {:?} [u16] ->> Alsa [{:.3}%] {:?} [dB]",
                    self.pvol(vol, 0x0000, 0xFFFF) * 100.0,
                    vol,
                    self.pvol(
                        vol_db as f64,
                        self.params.min_db.to_db() as f64,
                        self.params.max_db.to_db() as f64
                    ) * 100.0,
                    vol_db as f64 / 100.0,
                );
            } else {
                info!("Using linear vol");
                let alsa_volume =
                    ((vol as f64 / 0xFFFF as f64) * self.params.range) as i64 + self.params.min;
                selem
                    .set_playback_volume_all(alsa_volume)
                    .expect("Couldn't set alsa raw volume");
                info!(
                    "Mapping volume [{:.3}%] {:?} [u16] ->> Alsa [{:.3}%] {:?} [i64]",
                    self.pvol(vol, 0x0000, 0xFFFF) * 100.0,
                    vol,
                    self.pvol(
                        alsa_volume as f64,
                        self.params.min as f64,
                        self.params.max as f64
                    ) * 100.0,
                    alsa_volume
                );
            };
            new_vol = vol; // Meh
        } else {
            new_vol =
                (((cur_vol - self.params.min) as f64 / self.params.range) * 0xFFFF as f64) as u16;
            info!(
                "Mapping volume [{:.3}%] {:?} [u16] <<- Alsa [{:.3}%] {:?} [i64]",
                self.pvol(new_vol, 0x0000, 0xFFFF),
                new_vol,
                self.pvol(
                    cur_vol as f64,
                    self.params.min as f64,
                    self.params.max as f64
                ),
                cur_vol
            );
        }

        Ok(new_vol)
    }
}

impl Mixer for AlsaMixer {
    fn open(config: Option<MixerConfig>) -> AlsaMixer {
        let config = config.unwrap_or_default();
        info!(
            "Setting up new mixer: card:{} mixer:{} index:{}",
            config.card, config.mixer, config.index
        );
        AlsaMixer::init_mixer(config).expect("Error setting up mixer!")
    }

    fn start(&self) {}

    fn stop(&self) {}

    fn volume(&self) -> u16 {
        match self.map_volume(None) {
            Ok(vol) => vol,
            Err(e) => {
                error!("Error getting volume for <{}>, {:?}", self.config.card, e);
                0
            }
        }
    }

    fn set_volume(&self, volume: u16) {
        match self.map_volume(Some(volume)) {
            Ok(_) => (),
            Err(e) => error!("Error setting volume for <{}>, {:?}", self.config.card, e),
        }
    }

    fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
        None
    }
}
<|endoftext|>


<commit_before>use std::cast;
use sync::{Arc, Mutex};
use conn::{MyInnerConn, MyOpts, MyResult, Stmt, QueryResult};

struct MyInnerPool {
    opts: MyOpts,
    pool: Vec<*()>,
    cap: uint,
    count: uint
}

impl Drop for MyInnerPool {
    fn drop(&mut self) {
        loop {
            match self.pool.pop() {
                Some(conn) => unsafe {
                    drop(cast::transmute::<*(), Box<MyInnerConn>>(conn));
                },
                None => break
            }
        }
    }
}

impl MyInnerPool {
    fn new_conn(&mut self) -> MyResult<()> {
        match MyInnerConn::new(self.opts.clone()) {
            Ok(conn) => {
                unsafe { self.pool.push(cast::transmute(box conn)) };
                Ok(())
            },
            Err(err) => Err(err)
        }
    }
}

#[deriving(Clone)]
pub struct MyPool {
    pool: Arc<Mutex<MyInnerPool>>
}

impl MyPool {
    pub fn new(cap: uint, opts: MyOpts) -> MyPool {
        let pool = MyInnerPool {
            opts: opts,
            pool: Vec::new(),
            cap: cap,
            count: 0
        };
        MyPool{ pool: Arc::new(Mutex::new(pool)) }
    }

    pub fn get_conn(&self) -> MyResult<MyPooledConn> {
        let mut pool = self.pool.lock();

        while pool.pool.is_empty() {
            if pool.count < pool.cap {
                match pool.new_conn() {
                    Ok(()) => {
                        pool.count += 1;
                        break;
                    },
                    Err(err) => return Err(err)
                }
            } else {
                pool.cond.wait();
            }
        }

        Ok(MyPooledConn {
            pool: self.clone(),
            conn: unsafe { cast::transmute(pool.pool.pop().unwrap()) }
        })
    }
}

pub struct MyPooledConn {
    pool: MyPool,
    conn: Option<Box<MyInnerConn>>
}

#[unsafe_destructor]
impl Drop for MyPooledConn {
    fn drop(&mut self) {
        let conn = unsafe { cast::transmute(self.conn.take_unwrap()) };
        let mut pool = self.pool.pool.lock();
        pool.pool.push(conn);
        pool.cond.signal();
    }
}

impl MyPooledConn {
    fn query<'a>(&'a mut self, query: &str) -> MyResult<QueryResult<'a>> {
        self.conn.get_mut_ref().query(query)
    }
    fn prepare<'a>(&'a mut self, query: &str) -> MyResult<Stmt<'a>> {
        self.conn.get_mut_ref().prepare(query)
    }
}

#[cfg(test)]
mod test {
    use conn::{MyOpts};
    use std::default::{Default};
    use super::{MyPool};

    #[test]
    fn test_query() {
        let pool = MyPool::new(3, MyOpts{user: Some("root".to_owned()),
                                         ..Default::default()});
        for _ in range(0, 10) {
            let pool = pool.clone();
            spawn(proc() {
                let conn = pool.get_conn();
                assert!(conn.is_ok());
                let mut conn = conn.unwrap();
                assert!(conn.query("SELECT 1").is_ok());
            });
        }
    }

    #[test]
    fn test_prepared_query() {
        let pool = MyPool::new(3, MyOpts{user: Some("root".to_owned()),
                                         ..Default::default()});
        for _ in range(0, 10) {
            let pool = pool.clone();
            spawn(proc() {
                let conn = pool.get_conn();
                assert!(conn.is_ok());
                let mut conn = conn.unwrap();
                let stmt = conn.prepare("SELECT 1");
                assert!(stmt.is_ok());
                let mut stmt = stmt.unwrap();
                assert!(stmt.execute([]).is_ok());
            });
        }
    }
}
<commit_msg>Treat zero pool cap as an infinite<commit_after>use std::cast;
use sync::{Arc, Mutex};
use conn::{MyInnerConn, MyOpts, MyResult, Stmt, QueryResult};

struct MyInnerPool {
    opts: MyOpts,
    pool: Vec<*()>,
    cap: uint,
    count: uint
}

impl Drop for MyInnerPool {
    fn drop(&mut self) {
        loop {
            match self.pool.pop() {
                Some(conn) => unsafe {
                    drop(cast::transmute::<*(), Box<MyInnerConn>>(conn));
                },
                None => break
            }
        }
    }
}

impl MyInnerPool {
    fn new_conn(&mut self) -> MyResult<()> {
        match MyInnerConn::new(self.opts.clone()) {
            Ok(conn) => {
                unsafe { self.pool.push(cast::transmute(box conn)) };
                Ok(())
            },
            Err(err) => Err(err)
        }
    }
}

#[deriving(Clone)]
pub struct MyPool {
    pool: Arc<Mutex<MyInnerPool>>
}

impl MyPool {
    pub fn new(cap: uint, opts: MyOpts) -> MyPool {
        let pool = MyInnerPool {
            opts: opts,
            pool: Vec::new(),
            cap: cap,
            count: 0
        };
        MyPool{ pool: Arc::new(Mutex::new(pool)) }
    }

    pub fn get_conn(&self) -> MyResult<MyPooledConn> {
        let mut pool = self.pool.lock();

        while pool.pool.is_empty() {
            if pool.cap == 0 || pool.count < pool.cap {
                match pool.new_conn() {
                    Ok(()) => {
                        pool.count += 1;
                        break;
                    },
                    Err(err) => return Err(err)
                }
            } else {
                pool.cond.wait();
            }
        }

        Ok(MyPooledConn {
            pool: self.clone(),
            conn: unsafe { cast::transmute(pool.pool.pop().unwrap()) }
        })
    }
}

pub struct MyPooledConn {
    pool: MyPool,
    conn: Option<Box<MyInnerConn>>
}

#[unsafe_destructor]
impl Drop for MyPooledConn {
    fn drop(&mut self) {
        let conn = unsafe { cast::transmute(self.conn.take_unwrap()) };
        let mut pool = self.pool.pool.lock();
        pool.pool.push(conn);
        pool.cond.signal();
    }
}

impl MyPooledConn {
    fn query<'a>(&'a mut self, query: &str) -> MyResult<QueryResult<'a>> {
        self.conn.get_mut_ref().query(query)
    }
    fn prepare<'a>(&'a mut self, query: &str) -> MyResult<Stmt<'a>> {
        self.conn.get_mut_ref().prepare(query)
    }
}

#[cfg(test)]
mod test {
    use conn::{MyOpts};
    use std::default::{Default};
    use super::{MyPool};

    #[test]
    fn test_query() {
        let pool = MyPool::new(3, MyOpts{user: Some("root".to_owned()),
                                         ..Default::default()});
        for _ in range(0, 10) {
            let pool = pool.clone();
            spawn(proc() {
                let conn = pool.get_conn();
                assert!(conn.is_ok());
                let mut conn = conn.unwrap();
                assert!(conn.query("SELECT 1").is_ok());
            });
        }
    }

    #[test]
    fn test_prepared_query() {
        let pool = MyPool::new(3, MyOpts{user: Some("root".to_owned()),
                                         ..Default::default()});
        for _ in range(0, 10) {
            let pool = pool.clone();
            spawn(proc() {
                let conn = pool.get_conn();
                assert!(conn.is_ok());
                let mut conn = conn.unwrap();
                let stmt = conn.prepare("SELECT 1");
                assert!(stmt.is_ok());
                let mut stmt = stmt.unwrap();
                assert!(stmt.execute([]).is_ok());
            });
        }
    }

    #[test]
    fn test_zero_cap() {
        let pool = MyPool::new(0, MyOpts{user: Some("root".to_owned()),
                                         ..Default::default()});
        for _ in range(0, 10) {
            let pool = pool.clone();
            spawn(proc() {
                let conn = pool.get_conn();
                assert!(conn.is_ok());
                let mut conn = conn.unwrap();
                assert!(conn.query("SELECT 1").is_ok());
            });
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>flatten tt<commit_after><|endoftext|>


<commit_before><commit_msg>Try not to invalidate our state when the proc macro preference didn't change<commit_after><|endoftext|>


<commit_before>use std::time::{SystemTime, UNIX_EPOCH};
use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::io::Cursor;
use std::collections::HashSet;
use quick_xml::writer::Writer;
use quick_xml::events::{Event, BytesEnd, BytesStart, BytesDecl};
use quick_xml::errors::Result;
use traces::{TraceMap, CoverageStat};
use config::Config;

fn write_header<T:Write>(writer: &mut Writer<T>, config: &Config) -> Result<usize> {

    writer.write_event(Event::Start(BytesStart::borrowed(b"sources", b"sources".len())))?;
    writer.write_event(Event::Start(BytesStart::borrowed(b"source", b"source".len())))?;

    let parent_folder = match config.manifest.parent() {
        Some(s) => s.to_str().unwrap_or_default(),
        None => "",
    };
    writer.write(parent_folder.as_bytes()).unwrap();
    writer.write_event(Event::End(BytesEnd::borrowed(b"source")))?;
    writer.write_event(Event::End(BytesEnd::borrowed(b"sources")))
}

/// Input only from single source file
fn write_class<T:Write>(writer: &mut Writer<T>,
                        manifest_path: &Path,
                        filename: &Path,
                        coverage: &TraceMap) ->Result<usize> {
    if !coverage.is_empty() {
        let covered = coverage.covered_in_path(filename);
        let covered = (covered as f64)/(coverage.coverable_in_path(filename) as f64);

        let tidy_filename = match filename.strip_prefix(manifest_path) {
            Ok(p) => p,
            _ => filename,
        };
        let name = filename.file_stem().unwrap_or_default().to_str().unwrap_or_default();

        let mut class = BytesStart::owned(b"class".to_vec(), b"class".len());
        class.push_attribute(("name", name));
        class.push_attribute(("filename", tidy_filename.to_str().unwrap_or_default()));
        class.push_attribute(("line-rate", covered.to_string().as_ref()));
        class.push_attribute(("branch-rate", "1.0"));
        class.push_attribute(("complexity", "0.0"));
        writer.write_event(Event::Start(class))?;
        writer.write_event(Event::Empty(BytesStart::owned(b"methods".to_vec(), b"methods".len())))?;
        writer.write_event(Event::Start(BytesStart::borrowed(b"lines", b"lines".len())))?;
        for trace in coverage.get_child_traces(filename) {
            let mut line = BytesStart::owned(b"line".to_vec(), b"line".len());
            line.push_attribute(("number", trace.line.to_string().as_ref()));
            match trace.stats {
                CoverageStat::Line(hit) => {
                    line.push_attribute(("hits", hit.to_string().as_ref()));
                },
                _ => {
                    println!("Coverage statistic currently not implemented for cobertura");
                },
            }
            writer.write_event(Event::Empty(line))?;
        }
        writer.write_event(Event::End(BytesEnd::borrowed(b"lines")))?;
        writer.write_event(Event::End(BytesEnd::borrowed(b"class")))
    } else {
        Ok(0)
    }
}

/// Input only tracer data from a single source folder
fn write_package<T:Write>(mut writer: &mut Writer<T>,
                          package: &Path,
                          package_name: &str,
                          coverage: &TraceMap) -> Result<usize> {
    let covered = coverage.covered_in_path(package);
    let covered = (covered as f64)/(coverage.coverable_in_path(package) as f64);

    let mut pack = BytesStart::owned(b"package".to_vec(), b"package".len());
    pack.push_attribute(("name", package_name));
    pack.push_attribute(("line-rate", covered.to_string().as_ref()));
    pack.push_attribute(("branch-rate", "1.0"));
    pack.push_attribute(("complexity", "0.0"));
    writer.write_event(Event::Start(pack))?;
    writer.write_event(Event::Start(BytesStart::borrowed(b"classes", b"classes".len())))?;

    for file in &coverage.files() {
        if file.starts_with(package) {
            write_class(&mut writer, package, file, coverage)?;
        }
    }

    writer.write_event(Event::End(BytesEnd::borrowed(b"classes")))?;
    writer.write_event(Event::End(BytesEnd::borrowed(b"package")))
}

pub fn export(coverage_data: &TraceMap, config: &Config) {
    let mut file = File::create("cobertura.xml").unwrap();
    let mut writer = Writer::new(Cursor::new(Vec::new()));
    writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None))).unwrap();
    // Construct cobertura xml
    let line_rate = coverage_data.coverage_percentage();
    let mut cov = BytesStart::owned(b"coverage".to_vec(), b"coverage".len());
    cov.push_attribute(("line-rate", line_rate.to_string().as_ref()));
    cov.push_attribute(("branch-rate", "1.0"));
    cov.push_attribute(("version", "1.9"));

    if let Ok(s) = SystemTime::now().duration_since(UNIX_EPOCH) {
        cov.push_attribute(("timestamp", s.as_secs().to_string().as_ref()));
    } else {
        cov.push_attribute(("timestamp", "0"));
    }

    writer.write_event(Event::Start(cov)).unwrap();
    let _ = write_header(&mut writer, &config);
    // other data
    writer.write_event(Event::Start(BytesStart::borrowed(b"packages", b"packages".len()))).unwrap();

    let mut folder_set: HashSet<&Path> = HashSet::new();
    for t in &coverage_data.files() {
        let parent = match t.parent() {
            Some(x) => x,
            None => continue,
        };
        if !folder_set.contains(&parent) {
            folder_set.insert(parent);
            let manifest_path = config.manifest.parent().unwrap_or(&config.manifest);
            let package_name = match parent.strip_prefix(manifest_path) {
                Ok(p) => p,
                _ => manifest_path,
            };
            let package_name = package_name.to_str().unwrap_or_default();
            let _ = write_package(&mut writer, &manifest_path, package_name, &coverage_data);
        }
    }

    writer.write_event(Event::End(BytesEnd::borrowed(b"packages"))).unwrap();
    writer.write_event(Event::End(BytesEnd::borrowed(b"coverage"))).unwrap();
    let result = writer.into_inner().into_inner();
    file.write_all(&result).unwrap();
}
<commit_msg>Moved cobertura back to f32.<commit_after>use std::time::{SystemTime, UNIX_EPOCH};
use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::io::Cursor;
use std::collections::HashSet;
use quick_xml::writer::Writer;
use quick_xml::events::{Event, BytesEnd, BytesStart, BytesDecl};
use quick_xml::errors::Result;
use traces::{TraceMap, CoverageStat};
use config::Config;

fn write_header<T:Write>(writer: &mut Writer<T>, config: &Config) -> Result<usize> {

    writer.write_event(Event::Start(BytesStart::borrowed(b"sources", b"sources".len())))?;
    writer.write_event(Event::Start(BytesStart::borrowed(b"source", b"source".len())))?;

    let parent_folder = match config.manifest.parent() {
        Some(s) => s.to_str().unwrap_or_default(),
        None => "",
    };
    writer.write(parent_folder.as_bytes()).unwrap();
    writer.write_event(Event::End(BytesEnd::borrowed(b"source")))?;
    writer.write_event(Event::End(BytesEnd::borrowed(b"sources")))
}

/// Input only from single source file
fn write_class<T:Write>(writer: &mut Writer<T>,
                        manifest_path: &Path,
                        filename: &Path,
                        coverage: &TraceMap) ->Result<usize> {
    if !coverage.is_empty() {
        let covered = coverage.covered_in_path(filename);
        let covered = (covered as f32)/(coverage.coverable_in_path(filename) as f32);

        let tidy_filename = match filename.strip_prefix(manifest_path) {
            Ok(p) => p,
            _ => filename,
        };
        let name = filename.file_stem().unwrap_or_default().to_str().unwrap_or_default();

        let mut class = BytesStart::owned(b"class".to_vec(), b"class".len());
        class.push_attribute(("name", name));
        class.push_attribute(("filename", tidy_filename.to_str().unwrap_or_default()));
        class.push_attribute(("line-rate", covered.to_string().as_ref()));
        class.push_attribute(("branch-rate", "1.0"));
        class.push_attribute(("complexity", "0.0"));
        writer.write_event(Event::Start(class))?;
        writer.write_event(Event::Empty(BytesStart::owned(b"methods".to_vec(), b"methods".len())))?;
        writer.write_event(Event::Start(BytesStart::borrowed(b"lines", b"lines".len())))?;
        for trace in coverage.get_child_traces(filename) {
            let mut line = BytesStart::owned(b"line".to_vec(), b"line".len());
            line.push_attribute(("number", trace.line.to_string().as_ref()));
            match trace.stats {
                CoverageStat::Line(hit) => {
                    line.push_attribute(("hits", hit.to_string().as_ref()));
                },
                _ => {
                    println!("Coverage statistic currently not implemented for cobertura");
                },
            }
            writer.write_event(Event::Empty(line))?;
        }
        writer.write_event(Event::End(BytesEnd::borrowed(b"lines")))?;
        writer.write_event(Event::End(BytesEnd::borrowed(b"class")))
    } else {
        Ok(0)
    }
}

/// Input only tracer data from a single source folder
fn write_package<T:Write>(mut writer: &mut Writer<T>,
                          package: &Path,
                          package_name: &str,
                          coverage: &TraceMap) -> Result<usize> {
    let covered = coverage.covered_in_path(package);
    let covered = (covered as f32)/(coverage.coverable_in_path(package) as f32);

    let mut pack = BytesStart::owned(b"package".to_vec(), b"package".len());
    pack.push_attribute(("name", package_name));
    pack.push_attribute(("line-rate", covered.to_string().as_ref()));
    pack.push_attribute(("branch-rate", "1.0"));
    pack.push_attribute(("complexity", "0.0"));
    writer.write_event(Event::Start(pack))?;
    writer.write_event(Event::Start(BytesStart::borrowed(b"classes", b"classes".len())))?;

    for file in &coverage.files() {
        if file.starts_with(package) {
            write_class(&mut writer, package, file, coverage)?;
        }
    }

    writer.write_event(Event::End(BytesEnd::borrowed(b"classes")))?;
    writer.write_event(Event::End(BytesEnd::borrowed(b"package")))
}

pub fn export(coverage_data: &TraceMap, config: &Config) {
    let mut file = File::create("cobertura.xml").unwrap();
    let mut writer = Writer::new(Cursor::new(Vec::new()));
    writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None))).unwrap();
    // Construct cobertura xml
    let line_rate = coverage_data.coverage_percentage();
    let mut cov = BytesStart::owned(b"coverage".to_vec(), b"coverage".len());
    cov.push_attribute(("line-rate", line_rate.to_string().as_ref()));
    cov.push_attribute(("branch-rate", "1.0"));
    cov.push_attribute(("version", "1.9"));

    if let Ok(s) = SystemTime::now().duration_since(UNIX_EPOCH) {
        cov.push_attribute(("timestamp", s.as_secs().to_string().as_ref()));
    } else {
        cov.push_attribute(("timestamp", "0"));
    }

    writer.write_event(Event::Start(cov)).unwrap();
    let _ = write_header(&mut writer, &config);
    // other data
    writer.write_event(Event::Start(BytesStart::borrowed(b"packages", b"packages".len()))).unwrap();

    let mut folder_set: HashSet<&Path> = HashSet::new();
    for t in &coverage_data.files() {
        let parent = match t.parent() {
            Some(x) => x,
            None => continue,
        };
        if !folder_set.contains(&parent) {
            folder_set.insert(parent);
            let manifest_path = config.manifest.parent().unwrap_or(&config.manifest);
            let package_name = match parent.strip_prefix(manifest_path) {
                Ok(p) => p,
                _ => manifest_path,
            };
            let package_name = package_name.to_str().unwrap_or_default();
            let _ = write_package(&mut writer, &manifest_path, package_name, &coverage_data);
        }
    }

    writer.write_event(Event::End(BytesEnd::borrowed(b"packages"))).unwrap();
    writer.write_event(Event::End(BytesEnd::borrowed(b"coverage"))).unwrap();
    let result = writer.into_inner().into_inner();
    file.write_all(&result).unwrap();
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use rustc::ty::layout::{Size, Align};
use rustc_mir::interpret::{Pointer, Memory};
use crate::stacked_borrows::Tag;
use crate::*;

#[derive(Default)]
pub struct EnvVars {
    /// Stores pointers to the environment variables. These variables must be stored as
    /// null-terminated C strings with the `"{name}={value}"` format.
    map: HashMap<Vec<u8>, Pointer<Tag>>,
}

impl EnvVars {
    pub(crate) fn init<'mir, 'tcx>(
        ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
        excluded_env_vars: Vec<String>,
    ) {
        if ecx.machine.communicate {
            for (name, value) in std::env::vars() {
                if !excluded_env_vars.contains(&name) {
                    let var_ptr = alloc_env_var(name.as_bytes(), value.as_bytes(), ecx.memory_mut());
                    ecx.machine.env_vars.map.insert(name.into_bytes(), var_ptr);
                }
            }
        }
    }
}

fn alloc_env_var<'mir, 'tcx>(
    name: &[u8],
    value: &[u8],
    memory: &mut Memory<'mir, 'tcx, Evaluator<'tcx>>,
) -> Pointer<Tag> {
    let bytes = [name, b"=", value].concat();
    let tcx = {memory.tcx.tcx};
    let length = bytes.len() as u64;
    // `+1` for the null terminator.
    let ptr = memory.allocate(
        Size::from_bytes(length + 1),
        Align::from_bytes(1).unwrap(),
        MiriMemoryKind::Env.into(),
    );
    // We just allocated these, so the write cannot fail.
    let alloc = memory.get_mut(ptr.alloc_id).unwrap();
    alloc.write_bytes(&tcx, ptr, &bytes).unwrap();
    let trailing_zero_ptr = ptr.offset(
        Size::from_bytes(length),
        &tcx,
    ).unwrap();
    alloc.write_bytes(&tcx, trailing_zero_ptr, &[0]).unwrap();
    ptr
}

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
    fn getenv(
        &mut self,
        name_op: OpTy<'tcx, Tag>,
    ) -> InterpResult<'tcx, Scalar<Tag>> {
        let this = self.eval_context_mut();

        let name_ptr = this.read_scalar(name_op)?.not_undef()?;
        let name = this.memory().read_c_str(name_ptr)?;
        Ok(match this.machine.env_vars.map.get(name) {
            // The offset is used to strip the "{name}=" part of the string.
            Some(var_ptr) => Scalar::Ptr(var_ptr.offset(Size::from_bytes(name.len() as u64 + 1), this)?),
            None => Scalar::ptr_null(&*this.tcx),
        })
    }

    fn setenv(
        &mut self,
        name_op: OpTy<'tcx, Tag>,
        value_op: OpTy<'tcx, Tag>,
    ) -> InterpResult<'tcx, i32> {
        let this = self.eval_context_mut();

        let name_ptr = this.read_scalar(name_op)?.not_undef()?;
        let value_ptr = this.read_scalar(value_op)?.not_undef()?;
        let value = this.memory().read_c_str(value_ptr)?;
        let mut new = None;
        if !this.is_null(name_ptr)? {
            let name = this.memory().read_c_str(name_ptr)?;
            if !name.is_empty() && !name.contains(&b'=') {
                new = Some((name.to_owned(), value.to_owned()));
            }
        }
        if let Some((name, value)) = new {
            let var_ptr = alloc_env_var(&name, &value, this.memory_mut());
            if let Some(var) = this.machine.env_vars.map.insert(name.to_owned(), var_ptr) {
                this.memory_mut().deallocate(var, None, MiriMemoryKind::Env.into())?;
            }
            Ok(0)
        } else {
            Ok(-1)
        }
    }

    fn unsetenv(
        &mut self,
        name_op: OpTy<'tcx, Tag>,
    ) -> InterpResult<'tcx, i32> {
        let this = self.eval_context_mut();

        let name_ptr = this.read_scalar(name_op)?.not_undef()?;
        let mut success = None;
        if !this.is_null(name_ptr)? {
            let name = this.memory().read_c_str(name_ptr)?.to_owned();
            if !name.is_empty() && !name.contains(&b'=') {
                success = Some(this.machine.env_vars.map.remove(&name));
            }
        }
        if let Some(old) = success {
            if let Some(var) = old {
                this.memory_mut().deallocate(var, None, MiriMemoryKind::Env.into())?;
            }
            Ok(0)
        } else {
            Ok(-1)
        }
    }
}
<commit_msg>Exclude TERM env var by default<commit_after>use std::collections::HashMap;

use rustc::ty::layout::{Size, Align};
use rustc_mir::interpret::{Pointer, Memory};
use crate::stacked_borrows::Tag;
use crate::*;

#[derive(Default)]
pub struct EnvVars {
    /// Stores pointers to the environment variables. These variables must be stored as
    /// null-terminated C strings with the `"{name}={value}"` format.
    map: HashMap<Vec<u8>, Pointer<Tag>>,
}

impl EnvVars {
    pub(crate) fn init<'mir, 'tcx>(
        ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
        mut excluded_env_vars: Vec<String>,
    ) {
        // Exclude TERM var to avoid calls to the file system
        excluded_env_vars.push("TERM".to_owned());
        if ecx.machine.communicate {
            for (name, value) in std::env::vars() {
                if !excluded_env_vars.contains(&name) {
                    let var_ptr = alloc_env_var(name.as_bytes(), value.as_bytes(), ecx.memory_mut());
                    ecx.machine.env_vars.map.insert(name.into_bytes(), var_ptr);
                }
            }
        }
    }
}

fn alloc_env_var<'mir, 'tcx>(
    name: &[u8],
    value: &[u8],
    memory: &mut Memory<'mir, 'tcx, Evaluator<'tcx>>,
) -> Pointer<Tag> {
    let bytes = [name, b"=", value].concat();
    let tcx = {memory.tcx.tcx};
    let length = bytes.len() as u64;
    // `+1` for the null terminator.
    let ptr = memory.allocate(
        Size::from_bytes(length + 1),
        Align::from_bytes(1).unwrap(),
        MiriMemoryKind::Env.into(),
    );
    // We just allocated these, so the write cannot fail.
    let alloc = memory.get_mut(ptr.alloc_id).unwrap();
    alloc.write_bytes(&tcx, ptr, &bytes).unwrap();
    let trailing_zero_ptr = ptr.offset(
        Size::from_bytes(length),
        &tcx,
    ).unwrap();
    alloc.write_bytes(&tcx, trailing_zero_ptr, &[0]).unwrap();
    ptr
}

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
    fn getenv(
        &mut self,
        name_op: OpTy<'tcx, Tag>,
    ) -> InterpResult<'tcx, Scalar<Tag>> {
        let this = self.eval_context_mut();

        let name_ptr = this.read_scalar(name_op)?.not_undef()?;
        let name = this.memory().read_c_str(name_ptr)?;
        Ok(match this.machine.env_vars.map.get(name) {
            // The offset is used to strip the "{name}=" part of the string.
            Some(var_ptr) => Scalar::Ptr(var_ptr.offset(Size::from_bytes(name.len() as u64 + 1), this)?),
            None => Scalar::ptr_null(&*this.tcx),
        })
    }

    fn setenv(
        &mut self,
        name_op: OpTy<'tcx, Tag>,
        value_op: OpTy<'tcx, Tag>,
    ) -> InterpResult<'tcx, i32> {
        let this = self.eval_context_mut();

        let name_ptr = this.read_scalar(name_op)?.not_undef()?;
        let value_ptr = this.read_scalar(value_op)?.not_undef()?;
        let value = this.memory().read_c_str(value_ptr)?;
        let mut new = None;
        if !this.is_null(name_ptr)? {
            let name = this.memory().read_c_str(name_ptr)?;
            if !name.is_empty() && !name.contains(&b'=') {
                new = Some((name.to_owned(), value.to_owned()));
            }
        }
        if let Some((name, value)) = new {
            let var_ptr = alloc_env_var(&name, &value, this.memory_mut());
            if let Some(var) = this.machine.env_vars.map.insert(name.to_owned(), var_ptr) {
                this.memory_mut().deallocate(var, None, MiriMemoryKind::Env.into())?;
            }
            Ok(0)
        } else {
            Ok(-1)
        }
    }

    fn unsetenv(
        &mut self,
        name_op: OpTy<'tcx, Tag>,
    ) -> InterpResult<'tcx, i32> {
        let this = self.eval_context_mut();

        let name_ptr = this.read_scalar(name_op)?.not_undef()?;
        let mut success = None;
        if !this.is_null(name_ptr)? {
            let name = this.memory().read_c_str(name_ptr)?.to_owned();
            if !name.is_empty() && !name.contains(&b'=') {
                success = Some(this.machine.env_vars.map.remove(&name));
            }
        }
        if let Some(old) = success {
            if let Some(var) = old {
                this.memory_mut().deallocate(var, None, MiriMemoryKind::Env.into())?;
            }
            Ok(0)
        } else {
            Ok(-1)
        }
    }
}
<|endoftext|>


<commit_before>//! Implement thread-local storage.

use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::collections::HashSet;

use log::trace;

use rustc_index::vec::Idx;
use rustc_middle::ty;
use rustc_target::abi::{Size, HasDataLayout};

use crate::{
    HelpersEvalContextExt, InterpResult, MPlaceTy, Scalar, StackPopCleanup, Tag, ThreadId,
    ThreadsEvalContextExt,
};

pub type TlsKey = u128;

#[derive(Clone, Debug)]
pub struct TlsEntry<'tcx> {
    /// The data for this key. None is used to represent NULL.
    /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
    /// Will eventually become a map from thread IDs to `Scalar`s, if we ever support more than one thread.
    data: BTreeMap<ThreadId, Scalar<Tag>>,
    dtor: Option<ty::Instance<'tcx>>,
}

#[derive(Debug)]
pub struct TlsData<'tcx> {
    /// The Key to use for the next thread-local allocation.
    next_key: TlsKey,

    /// pthreads-style thread-local storage.
    keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,

    /// A single global per thread dtor (that's how things work on macOS) with a data argument.
    global_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar<Tag>)>,

    /// Whether we are in the "destruct" phase, during which some operations are UB.
    dtors_running: HashSet<ThreadId>,
}

impl<'tcx> Default for TlsData<'tcx> {
    fn default() -> Self {
        TlsData {
            next_key: 1, // start with 1 as we must not use 0 on Windows
            keys: Default::default(),
            global_dtors: Default::default(),
            dtors_running: Default::default(),
        }
    }
}

impl<'tcx> TlsData<'tcx> {
    /// Generate a new TLS key with the given destructor.
    /// `max_size` determines the integer size the key has to fit in.
    pub fn create_tls_key(&mut self, dtor: Option<ty::Instance<'tcx>>, max_size: Size) -> InterpResult<'tcx, TlsKey> {
        let new_key = self.next_key;
        self.next_key += 1;
        self.keys.insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap_none();
        trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);

        if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits() as u128) {
            throw_unsup_format!("we ran out of TLS key space");
        }
        Ok(new_key)
    }

    pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
        match self.keys.remove(&key) {
            Some(_) => {
                trace!("TLS key {} removed", key);
                Ok(())
            }
            None => throw_ub_format!("removing a non-existig TLS key: {}", key),
        }
    }

    pub fn load_tls(
        &self,
        key: TlsKey,
        thread_id: ThreadId,
        cx: &impl HasDataLayout,
    ) -> InterpResult<'tcx, Scalar<Tag>> {
        match self.keys.get(&key) {
            Some(TlsEntry { data, .. }) => {
                let value = data.get(&thread_id).cloned();
                trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value);
                Ok(value.unwrap_or_else(|| Scalar::null_ptr(cx).into()))
            }
            None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
        }
    }

    pub fn store_tls(
        &mut self,
         key: TlsKey, thread_id: ThreadId, new_data: Option<Scalar<Tag>>) -> InterpResult<'tcx> {
        match self.keys.get_mut(&key) {
            Some(TlsEntry { data, .. }) => {
                match new_data {
                    Some(ptr) => {
                        trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, ptr);
                        data.insert(thread_id, ptr);
                    }
                    None => {
                        trace!("TLS key {} for thread {:?} removed", key, thread_id);
                        data.remove(&thread_id);
                    }
                }
                Ok(())
            }
            None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
        }
    }

    /// Set global dtor for the given thread.
    pub fn set_global_dtor(&mut self, thread: ThreadId, dtor: ty::Instance<'tcx>, data: Scalar<Tag>) -> InterpResult<'tcx> {
        if self.dtors_running.contains(&thread) {
            // UB, according to libstd docs.
            throw_ub_format!("setting global destructor while destructors are already running");
        }
        if self.global_dtors.insert(thread, (dtor, data)).is_some() {
            throw_unsup_format!("setting more than one global destructor for the same thread is not supported");
        }
        Ok(())
    }

    /// Returns a dtor, its argument and its index, if one is supposed to run.
    /// `key` is the last dtors that was run; we return the *next* one after that.
    ///
    /// An optional destructor function may be associated with each key value.
    /// At thread exit, if a key value has a non-NULL destructor pointer,
    /// and the thread has a non-NULL value associated with that key,
    /// the value of the key is set to NULL, and then the function pointed
    /// to is called with the previously associated value as its sole argument.
    /// The order of destructor calls is unspecified if more than one destructor
    /// exists for a thread when it exits.
    ///
    /// If, after all the destructors have been called for all non-NULL values
    /// with associated destructors, there are still some non-NULL values with
    /// associated destructors, then the process is repeated.
    /// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor
    /// calls for outstanding non-NULL values, there are still some non-NULL values
    /// with associated destructors, implementations may stop calling destructors,
    /// or they may continue calling destructors until no non-NULL values with
    /// associated destructors exist, even though this might result in an infinite loop.
    fn fetch_tls_dtor(
        &mut self,
        key: Option<TlsKey>,
        thread_id: ThreadId,
    ) -> Option<(ty::Instance<'tcx>, Scalar<Tag>, TlsKey)> {
        use std::collections::Bound::*;

        let thread_local = &mut self.keys;
        let start = match key {
            Some(key) => Excluded(key),
            None => Unbounded,
        };
        for (&key, TlsEntry { data, dtor }) in
            thread_local.range_mut((start, Unbounded))
        {
            match data.entry(thread_id) {
                Entry::Occupied(entry) => {
                    let data_scalar = entry.remove();
                    if let Some(dtor) = dtor {
                        let ret = Some((*dtor, data_scalar, key));
                        return ret;
                    }
                }
                Entry::Vacant(_) => {}
            }
        }
        None
    }
}

impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {

    /// Run TLS destructors for the main thread on Windows. The implementation
    /// assumes that we do not support concurrency on Windows yet.
    ///
    /// Note: on non-Windows OS this function is a no-op.
    fn run_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
        let this = self.eval_context_mut();
        if this.tcx.sess.target.target.target_os != "windows" {
            return Ok(());
        }
        let active_thread = this.get_active_thread()?;
        assert_eq!(active_thread.index(), 0, "concurrency on Windows not supported");
        assert!(!this.machine.tls.dtors_running.contains(&active_thread), "running TLS dtors twice");
        this.machine.tls.dtors_running.insert(active_thread);
        // Windows has a special magic linker section that is run on certain events.
        // Instead of searching for that section and supporting arbitrary hooks in there
        // (that would be basically https://github.com/rust-lang/miri/issues/450),
        // we specifically look up the static in libstd that we know is placed
        // in that section.
        let thread_callback = this.eval_path_scalar(&["std", "sys", "windows", "thread_local", "p_thread_callback"])?;
        let thread_callback = this.memory.get_fn(thread_callback.not_undef()?)?.as_instance()?;

        // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
        let reason = this.eval_path_scalar(&["std", "sys", "windows", "c", "DLL_PROCESS_DETACH"])?;
        let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
        this.call_function(
            thread_callback,
            &[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()],
            Some(ret_place),
            StackPopCleanup::None { cleanup: true },
        )?;

        // step until out of stackframes
        this.run()?;

        // Windows doesn't have other destructors.
        Ok(())
    }

    /// Run TLS destructors for the active thread.
    ///
    /// Note: on Windows OS this function is a no-op because we do not support
    /// concurrency on Windows yet.
    fn run_tls_dtors_for_active_thread(&mut self) -> InterpResult<'tcx> {
        let this = self.eval_context_mut();
        if this.tcx.sess.target.target.target_os == "windows" {
            return Ok(());
        }
        let thread_id = this.get_active_thread()?;
        assert!(!this.machine.tls.dtors_running.contains(&thread_id), "running TLS dtors twice");
        this.machine.tls.dtors_running.insert(thread_id);

        // The macOS global dtor runs "before any TLS slots get freed", so do that first.
        if let Some(&(instance, data)) = this.machine.tls.global_dtors.get(&thread_id) {
            trace!("Running global dtor {:?} on {:?} at {:?}", instance, data, thread_id);

            let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
            this.call_function(
                instance,
                &[data.into()],
                Some(ret_place),
                StackPopCleanup::None { cleanup: true },
            )?;

            // step until out of stackframes
            this.run()?;
        }

        assert!(this.has_terminated(thread_id)?, "running TLS dtors for non-terminated thread");
        let mut dtor = this.machine.tls.fetch_tls_dtor(None, thread_id);
        while let Some((instance, ptr, key)) = dtor {
            trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, thread_id);
            assert!(!this.is_null(ptr).unwrap(), "Data can't be NULL when dtor is called!");

            let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
            this.call_function(
                instance,
                &[ptr.into()],
                Some(ret_place),
                StackPopCleanup::None { cleanup: true },
            )?;

            // step until out of stackframes
            this.run()?;

            // Fetch next dtor after `key`.
            dtor = match this.machine.tls.fetch_tls_dtor(Some(key), thread_id) {
                dtor @ Some(_) => dtor,
                // We ran each dtor once, start over from the beginning.
                None => this.machine.tls.fetch_tls_dtor(None, thread_id),
            };
        }

        Ok(())
    }
}
<commit_msg>Add a comment explaining global destructors on MacOS.<commit_after>//! Implement thread-local storage.

use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::collections::HashSet;

use log::trace;

use rustc_index::vec::Idx;
use rustc_middle::ty;
use rustc_target::abi::{Size, HasDataLayout};

use crate::{
    HelpersEvalContextExt, InterpResult, MPlaceTy, Scalar, StackPopCleanup, Tag, ThreadId,
    ThreadsEvalContextExt,
};

pub type TlsKey = u128;

#[derive(Clone, Debug)]
pub struct TlsEntry<'tcx> {
    /// The data for this key. None is used to represent NULL.
    /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
    /// Will eventually become a map from thread IDs to `Scalar`s, if we ever support more than one thread.
    data: BTreeMap<ThreadId, Scalar<Tag>>,
    dtor: Option<ty::Instance<'tcx>>,
}

#[derive(Debug)]
pub struct TlsData<'tcx> {
    /// The Key to use for the next thread-local allocation.
    next_key: TlsKey,

    /// pthreads-style thread-local storage.
    keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,

    /// A single global per thread dtor (that's how things work on macOS) with a data argument.
    global_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar<Tag>)>,

    /// Whether we are in the "destruct" phase, during which some operations are UB.
    dtors_running: HashSet<ThreadId>,
}

impl<'tcx> Default for TlsData<'tcx> {
    fn default() -> Self {
        TlsData {
            next_key: 1, // start with 1 as we must not use 0 on Windows
            keys: Default::default(),
            global_dtors: Default::default(),
            dtors_running: Default::default(),
        }
    }
}

impl<'tcx> TlsData<'tcx> {
    /// Generate a new TLS key with the given destructor.
    /// `max_size` determines the integer size the key has to fit in.
    pub fn create_tls_key(&mut self, dtor: Option<ty::Instance<'tcx>>, max_size: Size) -> InterpResult<'tcx, TlsKey> {
        let new_key = self.next_key;
        self.next_key += 1;
        self.keys.insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap_none();
        trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);

        if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits() as u128) {
            throw_unsup_format!("we ran out of TLS key space");
        }
        Ok(new_key)
    }

    pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
        match self.keys.remove(&key) {
            Some(_) => {
                trace!("TLS key {} removed", key);
                Ok(())
            }
            None => throw_ub_format!("removing a non-existig TLS key: {}", key),
        }
    }

    pub fn load_tls(
        &self,
        key: TlsKey,
        thread_id: ThreadId,
        cx: &impl HasDataLayout,
    ) -> InterpResult<'tcx, Scalar<Tag>> {
        match self.keys.get(&key) {
            Some(TlsEntry { data, .. }) => {
                let value = data.get(&thread_id).cloned();
                trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value);
                Ok(value.unwrap_or_else(|| Scalar::null_ptr(cx).into()))
            }
            None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
        }
    }

    pub fn store_tls(
        &mut self,
         key: TlsKey, thread_id: ThreadId, new_data: Option<Scalar<Tag>>) -> InterpResult<'tcx> {
        match self.keys.get_mut(&key) {
            Some(TlsEntry { data, .. }) => {
                match new_data {
                    Some(ptr) => {
                        trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, ptr);
                        data.insert(thread_id, ptr);
                    }
                    None => {
                        trace!("TLS key {} for thread {:?} removed", key, thread_id);
                        data.remove(&thread_id);
                    }
                }
                Ok(())
            }
            None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
        }
    }

    /// Set global dtor for the given thread. This function is used to implement
    /// `_tlv_atexit` shim on MacOS.
    ///
    /// Global destructors are available only on MacOS and (potentially
    /// confusingly) they seem to be still per thread as can be guessed from the
    /// following comment in the [`_tlv_atexit`
    /// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389):
    ///
    ///     // NOTE: this does not need locks because it only operates on current thread data
    pub fn set_global_dtor(
        &mut self,
        thread: ThreadId,
        dtor: ty::Instance<'tcx>,
        data: Scalar<Tag>
    ) -> InterpResult<'tcx> {
        if self.dtors_running.contains(&thread) {
            // UB, according to libstd docs.
            throw_ub_format!("setting global destructor while destructors are already running");
        }
        if self.global_dtors.insert(thread, (dtor, data)).is_some() {
            throw_unsup_format!("setting more than one global destructor for the same thread is not supported");
        }
        Ok(())
    }

    /// Returns a dtor, its argument and its index, if one is supposed to run.
    /// `key` is the last dtors that was run; we return the *next* one after that.
    ///
    /// An optional destructor function may be associated with each key value.
    /// At thread exit, if a key value has a non-NULL destructor pointer,
    /// and the thread has a non-NULL value associated with that key,
    /// the value of the key is set to NULL, and then the function pointed
    /// to is called with the previously associated value as its sole argument.
    /// The order of destructor calls is unspecified if more than one destructor
    /// exists for a thread when it exits.
    ///
    /// If, after all the destructors have been called for all non-NULL values
    /// with associated destructors, there are still some non-NULL values with
    /// associated destructors, then the process is repeated.
    /// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor
    /// calls for outstanding non-NULL values, there are still some non-NULL values
    /// with associated destructors, implementations may stop calling destructors,
    /// or they may continue calling destructors until no non-NULL values with
    /// associated destructors exist, even though this might result in an infinite loop.
    fn fetch_tls_dtor(
        &mut self,
        key: Option<TlsKey>,
        thread_id: ThreadId,
    ) -> Option<(ty::Instance<'tcx>, Scalar<Tag>, TlsKey)> {
        use std::collections::Bound::*;

        let thread_local = &mut self.keys;
        let start = match key {
            Some(key) => Excluded(key),
            None => Unbounded,
        };
        for (&key, TlsEntry { data, dtor }) in
            thread_local.range_mut((start, Unbounded))
        {
            match data.entry(thread_id) {
                Entry::Occupied(entry) => {
                    let data_scalar = entry.remove();
                    if let Some(dtor) = dtor {
                        let ret = Some((*dtor, data_scalar, key));
                        return ret;
                    }
                }
                Entry::Vacant(_) => {}
            }
        }
        None
    }
}

impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {

    /// Run TLS destructors for the main thread on Windows. The implementation
    /// assumes that we do not support concurrency on Windows yet.
    ///
    /// Note: on non-Windows OS this function is a no-op.
    fn run_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
        let this = self.eval_context_mut();
        if this.tcx.sess.target.target.target_os != "windows" {
            return Ok(());
        }
        let active_thread = this.get_active_thread()?;
        assert_eq!(active_thread.index(), 0, "concurrency on Windows not supported");
        assert!(!this.machine.tls.dtors_running.contains(&active_thread), "running TLS dtors twice");
        this.machine.tls.dtors_running.insert(active_thread);
        // Windows has a special magic linker section that is run on certain events.
        // Instead of searching for that section and supporting arbitrary hooks in there
        // (that would be basically https://github.com/rust-lang/miri/issues/450),
        // we specifically look up the static in libstd that we know is placed
        // in that section.
        let thread_callback = this.eval_path_scalar(&["std", "sys", "windows", "thread_local", "p_thread_callback"])?;
        let thread_callback = this.memory.get_fn(thread_callback.not_undef()?)?.as_instance()?;

        // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
        let reason = this.eval_path_scalar(&["std", "sys", "windows", "c", "DLL_PROCESS_DETACH"])?;
        let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
        this.call_function(
            thread_callback,
            &[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()],
            Some(ret_place),
            StackPopCleanup::None { cleanup: true },
        )?;

        // step until out of stackframes
        this.run()?;

        // Windows doesn't have other destructors.
        Ok(())
    }

    /// Run TLS destructors for the active thread.
    ///
    /// Note: on Windows OS this function is a no-op because we do not support
    /// concurrency on Windows yet.
    fn run_tls_dtors_for_active_thread(&mut self) -> InterpResult<'tcx> {
        let this = self.eval_context_mut();
        if this.tcx.sess.target.target.target_os == "windows" {
            return Ok(());
        }
        let thread_id = this.get_active_thread()?;
        assert!(!this.machine.tls.dtors_running.contains(&thread_id), "running TLS dtors twice");
        this.machine.tls.dtors_running.insert(thread_id);

        // The macOS global dtor runs "before any TLS slots get freed", so do that first.
        if let Some(&(instance, data)) = this.machine.tls.global_dtors.get(&thread_id) {
            trace!("Running global dtor {:?} on {:?} at {:?}", instance, data, thread_id);

            let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
            this.call_function(
                instance,
                &[data.into()],
                Some(ret_place),
                StackPopCleanup::None { cleanup: true },
            )?;

            // step until out of stackframes
            this.run()?;
        }

        assert!(this.has_terminated(thread_id)?, "running TLS dtors for non-terminated thread");
        let mut dtor = this.machine.tls.fetch_tls_dtor(None, thread_id);
        while let Some((instance, ptr, key)) = dtor {
            trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, thread_id);
            assert!(!this.is_null(ptr).unwrap(), "Data can't be NULL when dtor is called!");

            let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
            this.call_function(
                instance,
                &[ptr.into()],
                Some(ret_place),
                StackPopCleanup::None { cleanup: true },
            )?;

            // step until out of stackframes
            this.run()?;

            // Fetch next dtor after `key`.
            dtor = match this.machine.tls.fetch_tls_dtor(Some(key), thread_id) {
                dtor @ Some(_) => dtor,
                // We ran each dtor once, start over from the beginning.
                None => this.machine.tls.fetch_tls_dtor(None, thread_id),
            };
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>use piston_window::{Context, G2d};
use piston_window::types::Color;
use rand::{Rng, thread_rng};

use colors::*;
use game::{Renderer, render_square_in_grid};
use settings::WIDTH_IN_BLOCKS;

pub fn create_random_piece() -> Piece {
    let all_tetrominos = [&I, &J, &L, &O, &S, &T, &Z];
    let ptype = thread_rng().choose(&all_tetrominos).unwrap();
    Piece::create(*ptype)
}

pub static I: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:3, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:2, y:2}, Block{x:2, y:3}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:2, y:2}, Block{x:3, y:2}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:1, y:3}]
    ],
    color: CYAN
};

pub static J: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: BLUE
};

pub static L: Tetromino = Tetromino {
    configurations: [
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: ORANGE
};

pub static O: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}]
    ],
    color: YELLOW
};

pub static S: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}, Block{x:2, y:0}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:2, y:2}],
        [Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:0, y:2}],
        [Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}, Block{x:0, y:0}]
    ],
    color: LIME
};

pub static T: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:1, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: PURPLE
};

pub static Z: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: RED
};

pub type Configuration = [Block; 4];
type Rotation = usize;

pub struct Piece {
    // TODO: Replace with position
    // TODO: Make this private again
    pub x: i32,
    pub y: i32,
    ptype: &'static Tetromino,
    rotation: Rotation
}
impl Piece {
    pub fn create(ptype: &'static Tetromino) -> Piece {
        let x = (WIDTH_IN_BLOCKS as i32 / 2) - 2;
        Piece::new(x, 0, ptype, 0)
    }

    fn new(x: i32, y: i32, ptype: &'static Tetromino, rotation: Rotation) -> Piece {
        Piece {
            x: x,
            y: y,
            ptype: ptype,
            rotation: rotation
        }
    }

    pub fn get_blocks(&self) -> &Configuration {
        self.ptype.get_configuration(self.rotation)
    }

    pub fn rotated(&self) -> Self {
        let rotation = (self.rotation + 1) % self.ptype.configurations.len();
        Self::new(self.x, self.y, self.ptype, rotation)
    }

    pub fn moved(&self, direction: Direction) -> Self {
        let translated_x = match direction {
            Direction::Left => self.x - 1,
            Direction::Right => self.x + 1
        };
        Self::new(translated_x, self.y, self.ptype, self.rotation)
    }

    pub fn is_out_of_bounds(&self) -> bool {
        self.get_blocks().iter().any(|block| {
            let x = self.x + block.x;
            x < 0 || x >= WIDTH_IN_BLOCKS as i32
        })
    }

    pub fn wall_kick(&self) -> Self {
        let mut translation = 0;
        let min_block = self.get_blocks().iter().min_by_key(|block| self.x + block.x).unwrap();
        let max_block = self.get_blocks().iter().max_by_key(|block| self.x + block.x).unwrap();
        if self.x + min_block.x < 0 {
            translation = self.x + min_block.x;
        } else if self.x + max_block.x >= WIDTH_IN_BLOCKS as i32 {
            translation = (self.x + max_block.x) - (WIDTH_IN_BLOCKS - 1) as i32;
        }
        Self::new(self.x - translation, self.y, self.ptype, self.rotation)
    }

    fn get_color(&self) -> Color {
        self.ptype.color
    }
}
impl Renderer for Piece {
    fn render(&self, context: Context, graphics: &mut G2d) {
        for block in self.get_blocks() {
            let x = self.x + block.x;
            let y = self.y + block.y;
            render_square_in_grid(x, y, self.get_color(), context, graphics);
        }
    }
}

pub enum Direction {
    Left,
    Right
}

#[derive(Debug)]
pub struct Tetromino {
    configurations: [Configuration; 4],
    color: Color
}
impl Tetromino {
    fn get_configuration(&self, rotation: Rotation) -> &Configuration {
        &self.configurations[rotation]
    }
}

#[derive(Debug)]
pub struct Block {
    pub x: i32,
    pub y: i32
}

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

    #[test]
    fn test_piece_rotated() {
        let mut p = Piece::create(&I);
        assert_eq!(p.rotation, 0);
        p = p.rotated();
        assert_eq!(p.rotation, 1);
        p = p.rotated();
        assert_eq!(p.rotation, 2);
        p = p.rotated();
        assert_eq!(p.rotation, 3);
        p = p.rotated();
        assert_eq!(p.rotation, 0);
    }

    #[test]
    fn test_piece_moved_left() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Left);
        assert_eq!(result.x, p.x - 1);
    }

    #[test]
    fn test_piece_moved_right() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Right);
        assert_eq!(result.x, p.x + 1);
    }
}
<commit_msg>added some more tests to Piece struct<commit_after>use piston_window::{Context, G2d};
use piston_window::types::Color;
use rand::{Rng, thread_rng};

use colors::*;
use game::{Renderer, render_square_in_grid};
use settings::WIDTH_IN_BLOCKS;

pub fn create_random_piece() -> Piece {
    let all_tetrominos = [&I, &J, &L, &O, &S, &T, &Z];
    let ptype = thread_rng().choose(&all_tetrominos).unwrap();
    Piece::create(*ptype)
}

pub static I: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:3, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:2, y:2}, Block{x:2, y:3}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:2, y:2}, Block{x:3, y:2}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:1, y:3}]
    ],
    color: CYAN
};

pub static J: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: BLUE
};

pub static L: Tetromino = Tetromino {
    configurations: [
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: ORANGE
};

pub static O: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}]
    ],
    color: YELLOW
};

pub static S: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}, Block{x:2, y:0}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:2, y:2}],
        [Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:0, y:2}],
        [Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}, Block{x:0, y:0}]
    ],
    color: LIME
};

pub static T: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:1, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: PURPLE
};

pub static Z: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: RED
};

pub type Configuration = [Block; 4];
type Rotation = usize;

pub struct Piece {
    // TODO: Replace with position
    // TODO: Make this private again
    pub x: i32,
    pub y: i32,
    ptype: &'static Tetromino,
    rotation: Rotation
}
impl Piece {
    pub fn create(ptype: &'static Tetromino) -> Piece {
        let x = (WIDTH_IN_BLOCKS as i32 / 2) - 2;
        Piece::new(x, 0, ptype, 0)
    }

    fn new(x: i32, y: i32, ptype: &'static Tetromino, rotation: Rotation) -> Piece {
        Piece {
            x: x,
            y: y,
            ptype: ptype,
            rotation: rotation
        }
    }

    pub fn get_blocks(&self) -> &Configuration {
        self.ptype.get_configuration(self.rotation)
    }

    pub fn rotated(&self) -> Self {
        let rotation = (self.rotation + 1) % self.ptype.configurations.len();
        Self::new(self.x, self.y, self.ptype, rotation)
    }

    pub fn moved(&self, direction: Direction) -> Self {
        let translated_x = match direction {
            Direction::Left => self.x - 1,
            Direction::Right => self.x + 1
        };
        Self::new(translated_x, self.y, self.ptype, self.rotation)
    }

    pub fn is_out_of_bounds(&self) -> bool {
        self.get_blocks().iter().any(|block| {
            let x = self.x + block.x;
            x < 0 || x >= WIDTH_IN_BLOCKS as i32
        })
    }

    pub fn wall_kick(&self) -> Self {
        let mut translation = 0;
        let min_block = self.get_blocks().iter().min_by_key(|block| self.x + block.x).unwrap();
        let max_block = self.get_blocks().iter().max_by_key(|block| self.x + block.x).unwrap();
        if self.x + min_block.x < 0 {
            translation = self.x + min_block.x;
        } else if self.x + max_block.x >= WIDTH_IN_BLOCKS as i32 {
            translation = (self.x + max_block.x) - (WIDTH_IN_BLOCKS - 1) as i32;
        }
        Self::new(self.x - translation, self.y, self.ptype, self.rotation)
    }

    fn get_color(&self) -> Color {
        self.ptype.color
    }
}
impl Renderer for Piece {
    fn render(&self, context: Context, graphics: &mut G2d) {
        for block in self.get_blocks() {
            let x = self.x + block.x;
            let y = self.y + block.y;
            render_square_in_grid(x, y, self.get_color(), context, graphics);
        }
    }
}

pub enum Direction {
    Left,
    Right
}

#[derive(Debug)]
pub struct Tetromino {
    configurations: [Configuration; 4],
    color: Color
}
impl Tetromino {
    fn get_configuration(&self, rotation: Rotation) -> &Configuration {
        &self.configurations[rotation]
    }
}

#[derive(Debug)]
pub struct Block {
    pub x: i32,
    pub y: i32
}

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

    #[test]
    fn test_piece_rotated() {
        let mut p = Piece::create(&I);
        assert_eq!(p.rotation, 0);
        p = p.rotated();
        assert_eq!(p.rotation, 1);
        p = p.rotated();
        assert_eq!(p.rotation, 2);
        p = p.rotated();
        assert_eq!(p.rotation, 3);
        p = p.rotated();
        assert_eq!(p.rotation, 0);
    }

    #[test]
    fn test_piece_moved_left() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Left);
        assert_eq!(result.x, p.x - 1);
    }

    #[test]
    fn test_piece_moved_right() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Right);
        assert_eq!(result.x, p.x + 1);
    }

    #[test]
    fn test_piece_out_of_bounds_simple() {
        let mut p = Piece::create(&I);
        p.x += 10;
        assert_eq!(p.is_out_of_bounds(), true);
    }

    #[test]
    fn test_piece_out_of_bounds_at_boundary() {
        let mut p = Piece::create(&I);
        p.x = 6;
        assert_eq!(p.is_out_of_bounds(), false);
        p.x = 7;
        assert_eq!(p.is_out_of_bounds(), true);
        p.x = 0;
        assert_eq!(p.is_out_of_bounds(), false);
        p.x = -1;
        assert_eq!(p.is_out_of_bounds(), true);
    }

    #[test]
    fn test_wall_kick() {
        assert_eq!(false, true)
    }
}
<|endoftext|>