<commit_before>// Rust Bitcoin Library
// Written in 2014 by
//     Andrew Poelstra <apoelstra@wpsoftware.net>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! # Floating-point decimal type
//!
//! `i64`-based floating-point decimal type designed to hold Bitcoin
//! amounts. For satoshi amounts (8 decimal places) the maximum
//! amounts that can be represented is ~92.25bn, well over the 21m
//! maximum number of bitcoin in existence. Be aware that some
//! altcoins with different granularity may require a wider type.
//!

use std::{fmt, ops};

use serde::{ser, de};
use strason::Json;

/// A fixed-point decimal type
#[derive(Copy, Clone, Debug, Eq, Ord)]
pub struct Decimal {
    mantissa: i64,
    exponent: usize,
}

impl PartialEq<Decimal> for Decimal {
    fn eq(&self, other: &Decimal) -> bool {
        use std::cmp::max;
        let exp = max(self.exponent(), other.exponent());
        self.integer_value(exp) == other.integer_value(exp)
    }
}

impl PartialOrd<Decimal> for Decimal {
    fn partial_cmp(&self, other: &Decimal) -> Option<::std::cmp::Ordering> {
        use std::cmp::max;
        let exp = max(self.exponent(), other.exponent());
        self.integer_value(exp).partial_cmp(&other.integer_value(exp))
    }
}

impl fmt::Display for Decimal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let ten = 10i64.pow(self.exponent as u32);
        let int_part = self.mantissa / ten;
        let dec_part = (self.mantissa % ten).abs();
        write!(f, "{}.{:02$}", int_part, dec_part, self.exponent)
    }
}

impl ops::Add for Decimal {
    type Output = Decimal;

    #[inline]
    fn add(self, other: Decimal) -> Decimal {
        if self.exponent > other.exponent {
            Decimal {
                mantissa: other.mantissa * 10i64.pow((self.exponent - other.exponent) as u32) + self.mantissa,
                exponent: self.exponent
            }
        } else {
            Decimal {
                mantissa: self.mantissa * 10i64.pow((other.exponent - self.exponent) as u32) + other.mantissa,
                exponent: other.exponent
            }
        }
    }
}

impl ops::Neg for Decimal {
    type Output = Decimal;
    #[inline]
    fn neg(self) -> Decimal { Decimal { mantissa: -self.mantissa, exponent: self.exponent } }
}

impl ops::Sub for Decimal {
    type Output = Decimal;
    #[inline]
    fn sub(self, other: Decimal) -> Decimal { self + (-other) }
}

impl Decimal {
    /// Creates a new Decimal
    pub fn new(mantissa: i64, exponent: usize) -> Decimal {
        Decimal {
            mantissa: mantissa,
            exponent: exponent
        }
    }

    /// Returns the mantissa
    #[inline]
    pub fn mantissa(&self) -> i64 { self.mantissa }
    /// Returns the exponent
    #[inline]
    pub fn exponent(&self) -> usize { self.exponent }

    /// Get the decimal's value in an integer type, by multiplying
    /// by some power of ten to ensure the returned value is 10 **
    /// `exponent` types the actual value.
    pub fn integer_value(&self, exponent: usize) -> i64 {
        if exponent < self.exponent {
            self.mantissa / 10i64.pow((self.exponent - exponent) as u32)
        } else {
            self.mantissa * 10i64.pow((exponent - self.exponent) as u32)
        }
    }

    /// Returns whether or not the number is nonnegative
    #[inline]
    pub fn nonnegative(&self) -> bool { self.mantissa >= 0 }
}

impl ser::Serialize for Decimal {
    // Serialize through strason since it will not lose precision (when serializing
    // to strason itself, the value will be passed through; otherwise it will be
    // encoded as a string)
    fn serialize<S: ser::Serializer>(&self, s: &mut S) -> Result<(), S::Error> {
        let json = Json::from_str(&self.to_string()).unwrap();
        ser::Serialize::serialize(&json, s)
    }
}

impl de::Deserialize for Decimal {
    // Deserialize through strason for the same reason as in `Serialize`
    fn deserialize<D: de::Deserializer>(d: &mut D) -> Result<Decimal, D::Error> {
        let json: Json = try!(de::Deserialize::deserialize(d));
        match json.num() {
            Some(s) => {
                 // We know this will be a well-formed Json number, so we can
                 // be pretty lax about parsing
                 let mut negative = false;
                 let mut past_dec = false;
                 let mut exponent = 0;
                 let mut mantissa = 0i64;

                 for b in s.as_bytes() {
                     match *b {
                         b'-' => { negative = true; }
                         b'0'...b'9' => {
                             mantissa = 10 * mantissa + (b - b'0') as i64;
                             if past_dec { exponent += 1; }
                         }
                         b'.' => { past_dec = true; }
                         _ => { /* whitespace or something, just ignore it */ }
                     }
                 }
                 if negative { mantissa *= -1; }
                 Ok(Decimal {
                     mantissa: mantissa,
                     exponent: exponent,
                 })
            }
            None => Err(de::Error::syntax("expected decimal, got non-numeric"))
        }
    }
}

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

    #[test]
    fn integer_value() {
        let d = Decimal::new(12345678, 4);
        assert_eq!(d.mantissa(), 12345678);
        assert_eq!(d.exponent(), 4);

        assert_eq!(d.integer_value(0), 1234);
        assert_eq!(d.integer_value(1), 12345);
        assert_eq!(d.integer_value(2), 123456);
        assert_eq!(d.integer_value(3), 1234567);
        assert_eq!(d.integer_value(4), 12345678);
        assert_eq!(d.integer_value(5), 123456780);
        assert_eq!(d.integer_value(6), 1234567800);
        assert_eq!(d.integer_value(7), 12345678000);
        assert_eq!(d.integer_value(8), 123456780000);
    }

    macro_rules! deserialize_round_trip(
        ($dec:expr, $s:expr) => ({
            let d = $dec;
            let encoded = Json::from_serialize(&d).unwrap();
            assert_eq!(encoded, Json::from_reader(&$s[..]).unwrap());
            assert_eq!(encoded.to_bytes(), &$s[..]);

            let decoded: Decimal = encoded.into_deserialize().unwrap();
            assert_eq!(decoded, d);
        })
    );

    #[test]
    fn deserialize() {
        deserialize_round_trip!(Decimal::new(0, 0), b"0.0");

        deserialize_round_trip!(Decimal::new(123456789001, 8), b"1234.56789001");
        deserialize_round_trip!(Decimal::new(-123456789001, 8), b"-1234.56789001");
        deserialize_round_trip!(Decimal::new(123456789001, 1), b"12345678900.1");
        deserialize_round_trip!(Decimal::new(-123456789001, 1), b"-12345678900.1");
        deserialize_round_trip!(Decimal::new(123456789001, 0), b"123456789001.0");
        deserialize_round_trip!(Decimal::new(-123456789001, 0), b"-123456789001.0");

        deserialize_round_trip!(Decimal::new(123400000001, 8), b"1234.00000001");
        deserialize_round_trip!(Decimal::new(-123400000001, 8), b"-1234.00000001");
    }

    #[test]
    fn equality() {
        let d1 = Decimal::new(1234, 8);
        let d2 = Decimal::new(12340, 9);
        let d3 = Decimal::new(12340, 8);
        assert_eq!(d1, d1);
        assert_eq!(d1, d2);
        assert!(d1 != d3);
        assert!(d2 != d3);

        assert!(d1 <= d1);
        assert!(d2 <= d2);
        assert!(d3 <= d3);
        assert!(d1 <= d2);
        assert!(d1 <= d3);
        assert!(d3 > d1);
        assert!(d3 > d2);
    }

    #[test]
    fn arithmetic() {
        let d1 = Decimal::new(5, 1);   //  0.5
        let d2 = Decimal::new(-2, 2);  // -0.02
        let d3 = Decimal::new(3, 0);   //  3.0
        let d4 = Decimal::new(0, 5);  //   0.00000

        assert!(d1.nonnegative());
        assert!(!d2.nonnegative());
        assert!(d3.nonnegative());
        assert!(d4.nonnegative());

        assert_eq!(d1 + d2, Decimal::new(48, 2));
        assert_eq!(d1 - d2, Decimal::new(52, 2));
        assert_eq!(d1 + d3, Decimal::new(35, 1));
        assert_eq!(d1 - d3, Decimal::new(-25, 1));
        assert_eq!(d2 + d3, Decimal::new(298, 2));
        assert_eq!(d2 - d3, Decimal::new(-302, 2));

        assert_eq!(d1 + d4, d1);
        assert_eq!(d1 - d4, d1);
        assert_eq!(d1 + d4, d1 - d4);
        assert_eq!(d4 + d4, d4);
    }

    #[test]
    fn json_parse() {
        let json = Json::from_str("0.00980000").unwrap();
        assert_eq!(json.to_bytes(), b"0.00980000");
        let dec: Decimal = json.into_deserialize().unwrap();
        assert_eq!(dec, Decimal::new(980000, 8));

        let json = Json::from_str("0.00980").unwrap();
        assert_eq!(json.to_bytes(), b"0.00980");
        let dec: Decimal = json.into_deserialize().unwrap();
        assert_eq!(dec, Decimal::new(98000, 7));
    }
}

<commit_msg>Add unsigned Decimal type<commit_after>// Rust Bitcoin Library
// Written in 2014 by
//     Andrew Poelstra <apoelstra@wpsoftware.net>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! # Floating-point decimal type
//!
//! `i64`-based floating-point decimal type designed to hold Bitcoin
//! amounts. For satoshi amounts (8 decimal places) the maximum
//! amounts that can be represented is ~92.25bn, well over the 21m
//! maximum number of bitcoin in existence. Be aware that some
//! altcoins with different granularity may require a wider type.
//!

use std::{fmt, ops};

use serde::{ser, de};
use strason::Json;

/// A fixed-point decimal type
#[derive(Copy, Clone, Debug, Eq, Ord)]
pub struct Decimal {
    mantissa: i64,
    exponent: usize,
}

/// Unsigned fixed-point decimal type
#[derive(Copy, Clone, Debug, Eq, Ord)]
pub struct UDecimal {
    mantissa: u64,
    exponent: usize,
}

impl PartialEq<Decimal> for Decimal {
    fn eq(&self, other: &Decimal) -> bool {
        use std::cmp::max;
        let exp = max(self.exponent(), other.exponent());
        self.integer_value(exp) == other.integer_value(exp)
    }
}

impl PartialOrd<Decimal> for Decimal {
    fn partial_cmp(&self, other: &Decimal) -> Option<::std::cmp::Ordering> {
        use std::cmp::max;
        let exp = max(self.exponent(), other.exponent());
        self.integer_value(exp).partial_cmp(&other.integer_value(exp))
    }
}

impl fmt::Display for Decimal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let ten = 10i64.pow(self.exponent as u32);
        let int_part = self.mantissa / ten;
        let dec_part = (self.mantissa % ten).abs();
        write!(f, "{}.{:02$}", int_part, dec_part, self.exponent)
    }
}

impl ops::Add for Decimal {
    type Output = Decimal;

    #[inline]
    fn add(self, other: Decimal) -> Decimal {
        if self.exponent > other.exponent {
            Decimal {
                mantissa: other.mantissa * 10i64.pow((self.exponent - other.exponent) as u32) + self.mantissa,
                exponent: self.exponent
            }
        } else {
            Decimal {
                mantissa: self.mantissa * 10i64.pow((other.exponent - self.exponent) as u32) + other.mantissa,
                exponent: other.exponent
            }
        }
    }
}

impl ops::Neg for Decimal {
    type Output = Decimal;
    #[inline]
    fn neg(self) -> Decimal { Decimal { mantissa: -self.mantissa, exponent: self.exponent } }
}

impl ops::Sub for Decimal {
    type Output = Decimal;
    #[inline]
    fn sub(self, other: Decimal) -> Decimal { self + (-other) }
}

impl Decimal {
    /// Creates a new Decimal
    pub fn new(mantissa: i64, exponent: usize) -> Decimal {
        Decimal {
            mantissa: mantissa,
            exponent: exponent
        }
    }

    /// Returns the mantissa
    #[inline]
    pub fn mantissa(&self) -> i64 { self.mantissa }
    /// Returns the exponent
    #[inline]
    pub fn exponent(&self) -> usize { self.exponent }

    /// Get the decimal's value in an integer type, by multiplying
    /// by some power of ten to ensure the returned value is 10 **
    /// `exponent` types the actual value.
    pub fn integer_value(&self, exponent: usize) -> i64 {
        if exponent < self.exponent {
            self.mantissa / 10i64.pow((self.exponent - exponent) as u32)
        } else {
            self.mantissa * 10i64.pow((exponent - self.exponent) as u32)
        }
    }

    /// Returns whether or not the number is nonnegative
    #[inline]
    pub fn nonnegative(&self) -> bool { self.mantissa >= 0 }
}

impl ser::Serialize for Decimal {
    // Serialize through strason since it will not lose precision (when serializing
    // to strason itself, the value will be passed through; otherwise it will be
    // encoded as a string)
    fn serialize<S: ser::Serializer>(&self, s: &mut S) -> Result<(), S::Error> {
        let json = Json::from_str(&self.to_string()).unwrap();
        ser::Serialize::serialize(&json, s)
    }
}

impl de::Deserialize for Decimal {
    // Deserialize through strason for the same reason as in `Serialize`
    fn deserialize<D: de::Deserializer>(d: &mut D) -> Result<Decimal, D::Error> {
        let json: Json = try!(de::Deserialize::deserialize(d));
        match json.num() {
            Some(s) => {
                 // We know this will be a well-formed Json number, so we can
                 // be pretty lax about parsing
                 let mut negative = false;
                 let mut past_dec = false;
                 let mut exponent = 0;
                 let mut mantissa = 0i64;

                 for b in s.as_bytes() {
                     match *b {
                         b'-' => { negative = true; }
                         b'0'...b'9' => {
                             mantissa = 10 * mantissa + (b - b'0') as i64;
                             if past_dec { exponent += 1; }
                         }
                         b'.' => { past_dec = true; }
                         _ => { /* whitespace or something, just ignore it */ }
                     }
                 }
                 if negative { mantissa *= -1; }
                 Ok(Decimal {
                     mantissa: mantissa,
                     exponent: exponent,
                 })
            }
            None => Err(de::Error::syntax("expected decimal, got non-numeric"))
        }
    }
}

impl PartialEq<UDecimal> for UDecimal {
    fn eq(&self, other: &UDecimal) -> bool {
        use std::cmp::max;
        let exp = max(self.exponent(), other.exponent());
        self.integer_value(exp) == other.integer_value(exp)
    }
}

impl PartialOrd<UDecimal> for UDecimal {
    fn partial_cmp(&self, other: &UDecimal) -> Option<::std::cmp::Ordering> {
        use std::cmp::max;
        let exp = max(self.exponent(), other.exponent());
        self.integer_value(exp).partial_cmp(&other.integer_value(exp))
    }
}

impl fmt::Display for UDecimal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let ten = 10u64.pow(self.exponent as u32);
        let int_part = self.mantissa / ten;
        let dec_part = self.mantissa % ten;
        write!(f, "{}.{:02$}", int_part, dec_part, self.exponent)
    }
}

impl ops::Add for UDecimal {
    type Output = UDecimal;

    #[inline]
    fn add(self, other: UDecimal) -> UDecimal {
        if self.exponent > other.exponent {
            UDecimal {
                mantissa: other.mantissa * 10u64.pow((self.exponent - other.exponent) as u32) + self.mantissa,
                exponent: self.exponent
            }
        } else {
            UDecimal {
                mantissa: self.mantissa * 10u64.pow((other.exponent - self.exponent) as u32) + other.mantissa,
                exponent: other.exponent
            }
        }
    }
}

impl UDecimal {
    /// Creates a new Decimal
    pub fn new(mantissa: u64, exponent: usize) -> UDecimal {
        UDecimal {
            mantissa: mantissa,
            exponent: exponent
        }
    }

    /// Returns the mantissa
    #[inline]
    pub fn mantissa(&self) -> u64 { self.mantissa }
    /// Returns the exponent
    #[inline]
    pub fn exponent(&self) -> usize { self.exponent }

    /// Get the decimal's value in an integer type, by multiplying
    /// by some power of ten to ensure the returned value is 10 **
    /// `exponent` types the actual value.
    pub fn integer_value(&self, exponent: usize) -> u64 {
        if exponent < self.exponent {
            self.mantissa / 10u64.pow((self.exponent - exponent) as u32)
        } else {
            self.mantissa * 10u64.pow((exponent - self.exponent) as u32)
        }
    }
}

impl ser::Serialize for UDecimal {
    // Serialize through strason since it will not lose precision (when serializing
    // to strason itself, the value will be passed through; otherwise it will be
    // encoded as a string)
    fn serialize<S: ser::Serializer>(&self, s: &mut S) -> Result<(), S::Error> {
        let json = Json::from_str(&self.to_string()).unwrap();
        ser::Serialize::serialize(&json, s)
    }
}

impl de::Deserialize for UDecimal {
    // Deserialize through strason for the same reason as in `Serialize`
    fn deserialize<D: de::Deserializer>(d: &mut D) -> Result<UDecimal, D::Error> {
        let json: Json = try!(de::Deserialize::deserialize(d));
        match json.num() {
            Some(s) => {
                 // We know this will be a well-formed Json number, so we can
                 // be pretty lax about parsing
                 let mut past_dec = false;
                 let mut exponent = 0;
                 let mut mantissa = 0u64;

                 for b in s.as_bytes() {
                     match *b {
                         b'0'...b'9' => {
                             mantissa = 10 * mantissa + (b - b'0') as u64;
                             if past_dec { exponent += 1; }
                         }
                         b'.' => { past_dec = true; }
                         _ => { /* whitespace or something, just ignore it */ }
                     }
                 }
                 Ok(UDecimal {
                     mantissa: mantissa,
                     exponent: exponent,
                 })
            }
            None => Err(de::Error::syntax("expected decimal, got non-numeric"))
        }
    }
}

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

    #[test]
    fn integer_value() {
        let d = Decimal::new(12345678, 4);
        assert_eq!(d.mantissa(), 12345678);
        assert_eq!(d.exponent(), 4);

        assert_eq!(d.integer_value(0), 1234);
        assert_eq!(d.integer_value(1), 12345);
        assert_eq!(d.integer_value(2), 123456);
        assert_eq!(d.integer_value(3), 1234567);
        assert_eq!(d.integer_value(4), 12345678);
        assert_eq!(d.integer_value(5), 123456780);
        assert_eq!(d.integer_value(6), 1234567800);
        assert_eq!(d.integer_value(7), 12345678000);
        assert_eq!(d.integer_value(8), 123456780000);

        let u = UDecimal::new(12345678, 4);
        assert_eq!(u.mantissa(), 12345678);
        assert_eq!(u.exponent(), 4);

        assert_eq!(u.integer_value(0), 1234);
        assert_eq!(u.integer_value(1), 12345);
        assert_eq!(u.integer_value(2), 123456);
        assert_eq!(u.integer_value(3), 1234567);
        assert_eq!(u.integer_value(4), 12345678);
        assert_eq!(u.integer_value(5), 123456780);
        assert_eq!(u.integer_value(6), 1234567800);
        assert_eq!(u.integer_value(7), 12345678000);
        assert_eq!(u.integer_value(8), 123456780000);
    }

    macro_rules! deserialize_round_trip(
        ($dec:expr, $s:expr) => ({
            let d = $dec;
            let encoded = Json::from_serialize(&d).unwrap();
            assert_eq!(encoded, Json::from_reader(&$s[..]).unwrap());
            assert_eq!(encoded.to_bytes(), &$s[..]);

            // hack to force type inference
            let mut decoded_res = encoded.into_deserialize();
            if false { decoded_res = Ok($dec); }
            let decoded = decoded_res.unwrap();
            assert_eq!(decoded, d);
        })
    );

    #[test]
    fn deserialize() {
        deserialize_round_trip!(Decimal::new(0, 0), b"0.0");
        deserialize_round_trip!(UDecimal::new(0, 0), b"0.0");

        deserialize_round_trip!(Decimal::new(123456789001, 8), b"1234.56789001");
        deserialize_round_trip!(UDecimal::new(123456789001, 8), b"1234.56789001");
        deserialize_round_trip!(Decimal::new(-123456789001, 8), b"-1234.56789001");
        deserialize_round_trip!(Decimal::new(123456789001, 1), b"12345678900.1");
        deserialize_round_trip!(UDecimal::new(123456789001, 1), b"12345678900.1");
        deserialize_round_trip!(Decimal::new(-123456789001, 1), b"-12345678900.1");
        deserialize_round_trip!(Decimal::new(123456789001, 0), b"123456789001.0");
        deserialize_round_trip!(UDecimal::new(123456789001, 0), b"123456789001.0");
        deserialize_round_trip!(Decimal::new(-123456789001, 0), b"-123456789001.0");

        deserialize_round_trip!(Decimal::new(123400000001, 8), b"1234.00000001");
        deserialize_round_trip!(UDecimal::new(123400000001, 8), b"1234.00000001");
        deserialize_round_trip!(Decimal::new(-123400000001, 8), b"-1234.00000001");
    }

    #[test]
    fn equality() {
        let d1 = Decimal::new(1234, 8);
        let d2 = Decimal::new(12340, 9);
        let d3 = Decimal::new(12340, 8);
        assert_eq!(d1, d1);
        assert_eq!(d1, d2);
        assert!(d1 != d3);
        assert!(d2 != d3);

        assert!(d1 <= d1);
        assert!(d2 <= d2);
        assert!(d3 <= d3);
        assert!(d1 <= d2);
        assert!(d1 <= d3);
        assert!(d3 > d1);
        assert!(d3 > d2);
    }

    #[test]
    fn arithmetic() {
        let d1 = Decimal::new(5, 1);   //  0.5
        let d2 = Decimal::new(-2, 2);  // -0.02
        let d3 = Decimal::new(3, 0);   //  3.0
        let d4 = Decimal::new(0, 5);  //   0.00000
        let u1 = UDecimal::new(5, 1);   //  0.5
        let u3 = UDecimal::new(3, 0);   //  3.0
        let u4 = UDecimal::new(0, 5);  //   0.00000

        assert!(d1.nonnegative());
        assert!(!d2.nonnegative());
        assert!(d3.nonnegative());
        assert!(d4.nonnegative());

        assert_eq!(d1 + d2, Decimal::new(48, 2));
        assert_eq!(d1 - d2, Decimal::new(52, 2));
        assert_eq!(d1 + d3, Decimal::new(35, 1));
        assert_eq!(u1 + u3, UDecimal::new(35, 1));
        assert_eq!(d1 - d3, Decimal::new(-25, 1));
        assert_eq!(d2 + d3, Decimal::new(298, 2));
        assert_eq!(d2 - d3, Decimal::new(-302, 2));

        assert_eq!(d1 + d4, d1);
        assert_eq!(u1 + u4, u1);
        assert_eq!(d1 - d4, d1);
        assert_eq!(d1 + d4, d1 - d4);
        assert_eq!(d4 + d4, d4);
        assert_eq!(u4 + u4, u4);
    }

    #[test]
    fn json_parse() {
        let json = Json::from_str("0.00980000").unwrap();
        assert_eq!(json.to_bytes(), b"0.00980000");
        let dec: Decimal = json.into_deserialize().unwrap();
        assert_eq!(dec, Decimal::new(980000, 8));

        let json = Json::from_str("0.00980000").unwrap();
        assert_eq!(json.to_bytes(), b"0.00980000");
        let dec: UDecimal = json.into_deserialize().unwrap();
        assert_eq!(dec, UDecimal::new(980000, 8));

        let json = Json::from_str("0.00980").unwrap();
        assert_eq!(json.to_bytes(), b"0.00980");
        let dec: Decimal = json.into_deserialize().unwrap();
        assert_eq!(dec, Decimal::new(98000, 7));

        let json = Json::from_str("0.00980").unwrap();
        assert_eq!(json.to_bytes(), b"0.00980");
        let dec: UDecimal = json.into_deserialize().unwrap();
        assert_eq!(dec, UDecimal::new(98000, 7));
    }
}

<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Fangxu Hu <framlog@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
mod table;

use uucore::error::UResult;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};

use clap::{crate_version, App, AppSettings, Arg, ArgMatches};

use std::collections::HashSet;
#[cfg(unix)]
use std::ffi::CString;
use std::iter::FromIterator;
#[cfg(unix)]
use std::mem;

#[cfg(windows)]
use std::path::Path;

use crate::table::{DisplayRow, Header, Row};

static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
                      or all file systems by default.";

static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
static OPT_TOTAL: &str = "total";
static OPT_HUMAN_READABLE: &str = "human-readable";
static OPT_HUMAN_READABLE_2: &str = "human-readable-2";
static OPT_INODES: &str = "inodes";
static OPT_KILO: &str = "kilo";
static OPT_LOCAL: &str = "local";
static OPT_NO_SYNC: &str = "no-sync";
static OPT_OUTPUT: &str = "output";
static OPT_PATHS: &str = "paths";
static OPT_PORTABILITY: &str = "portability";
static OPT_SYNC: &str = "sync";
static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";

/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
#[derive(Default)]
struct FsSelector {
    include: HashSet<String>,
    exclude: HashSet<String>,
}

#[derive(Default)]
struct Options {
    show_local_fs: bool,
    show_all_fs: bool,
    show_listed_fs: bool,
    show_fs_type: bool,
    show_inode_instead: bool,
    // block_size: usize,
    human_readable_base: i64,
    fs_selector: FsSelector,
}

impl Options {
    /// Convert command-line arguments into [`Options`].
    fn from(matches: &ArgMatches) -> Self {
        Self {
            show_local_fs: matches.is_present(OPT_LOCAL),
            show_all_fs: matches.is_present(OPT_ALL),
            show_listed_fs: false,
            show_fs_type: matches.is_present(OPT_PRINT_TYPE),
            show_inode_instead: matches.is_present(OPT_INODES),
            human_readable_base: if matches.is_present(OPT_HUMAN_READABLE) {
                1024
            } else if matches.is_present(OPT_HUMAN_READABLE_2) {
                1000
            } else {
                -1
            },
            fs_selector: FsSelector::from(matches),
        }
    }
}

#[derive(Debug, Clone)]
struct Filesystem {
    mount_info: MountInfo,
    usage: FsUsage,
}

fn usage() -> String {
    format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
}

impl FsSelector {
    /// Convert command-line arguments into a [`FsSelector`].
    ///
    /// This function reads the include and exclude sets from
    /// [`ArgMatches`] and returns the corresponding [`FsSelector`]
    /// instance.
    fn from(matches: &ArgMatches) -> Self {
        let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default());
        let exclude = HashSet::from_iter(
            matches
                .values_of_lossy(OPT_EXCLUDE_TYPE)
                .unwrap_or_default(),
        );
        Self { include, exclude }
    }

    fn should_select(&self, fs_type: &str) -> bool {
        if self.exclude.contains(fs_type) {
            return false;
        }
        self.include.is_empty() || self.include.contains(fs_type)
    }
}

impl Filesystem {
    // TODO: resolve uuid in `mount_info.dev_name` if exists
    fn new(mount_info: MountInfo) -> Option<Self> {
        let _stat_path = if !mount_info.mount_dir.is_empty() {
            mount_info.mount_dir.clone()
        } else {
            #[cfg(unix)]
            {
                mount_info.dev_name.clone()
            }
            #[cfg(windows)]
            {
                // On windows, we expect the volume id
                mount_info.dev_id.clone()
            }
        };
        #[cfg(unix)]
        unsafe {
            let path = CString::new(_stat_path).unwrap();
            let mut statvfs = mem::zeroed();
            if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
                None
            } else {
                Some(Self {
                    mount_info,
                    usage: FsUsage::new(statvfs),
                })
            }
        }
        #[cfg(windows)]
        Some(Self {
            mount_info,
            usage: FsUsage::new(Path::new(&_stat_path)),
        })
    }
}

/// Whether to display the mount info given the inclusion settings.
fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool {
    // Don't show remote filesystems if `--local` has been given.
    if mi.remote && opt.show_local_fs {
        return false;
    }

    // Don't show pseudo filesystems unless `--all` has been given.
    if mi.dummy && !opt.show_all_fs && !opt.show_listed_fs {
        return false;
    }

    // Don't show filesystems if they have been explicitly excluded.
    if !opt.fs_selector.should_select(&mi.fs_type) {
        return false;
    }

    // Don't show filesystems other than the ones specified on the
    // command line, if any.
    if !paths.is_empty() && !paths.contains(&mi.mount_dir) {
        return false;
    }

    true
}

/// Whether the mount info in `m2` should be prioritized over `m1`.
///
/// The "lt" in the function name is in analogy to the
/// [`std::cmp::PartialOrd::lt`].
fn mount_info_lt(m1: &MountInfo, m2: &MountInfo) -> bool {
    // let "real" devices with '/' in the name win.
    if m1.dev_name.starts_with('/') && !m2.dev_name.starts_with('/') {
        return false;
    }

    let m1_nearer_root = m1.mount_dir.len() < m2.mount_dir.len();
    // With bind mounts, prefer items nearer the root of the source
    let m2_below_root = !m1.mount_root.is_empty()
        && !m2.mount_root.is_empty()
        && m1.mount_root.len() > m2.mount_root.len();
    // let points towards the root of the device win.
    if m1_nearer_root && !m2_below_root {
        return false;
    }

    // let an entry over-mounted on a new device win, but only when
    // matching an existing mnt point, to avoid problematic
    // replacement when given inaccurate mount lists, seen with some
    // chroot environments for example.
    if m1.dev_name != m2.dev_name && m1.mount_dir == m2.mount_dir {
        return false;
    }

    true
}

/// Whether to prioritize given mount info over all others on the same device.
///
/// This function decides whether the mount info `mi` is better than
/// all others in `previous` that mount the same device as `mi`.
fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool {
    for seen in previous {
        if seen.dev_id == mi.dev_id && mount_info_lt(mi, seen) {
            return false;
        }
    }
    true
}

/// Keep only the specified subset of [`MountInfo`] instances.
///
/// If `paths` is non-empty, this function excludes any [`MountInfo`]
/// that is not mounted at the specified path.
///
/// The `opt` argument specifies a variety of ways of excluding
/// [`MountInfo`] instances; see [`Options`] for more information.
///
/// Finally, if there are duplicate entries, the one with the shorter
/// path is kept.
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
    let mut result = vec![];
    for mi in vmi {
        // TODO The running time of the `is_best()` function is linear
        // in the length of `result`. That makes the running time of
        // this loop quadratic in the length of `vmi`. This could be
        // improved by a more efficient implementation of `is_best()`,
        // but `vmi` is probably not very long in practice.
        if is_included(&mi, paths, opt) && is_best(&result, &mi) {
            result.push(mi);
        }
    }
    result
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let usage = usage();
    let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);

    let paths: Vec<String> = matches
        .values_of(OPT_PATHS)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    #[cfg(windows)]
    {
        if matches.is_present(OPT_INODES) {
            println!("{}: doesn't support -i option", uucore::util_name());
            return Ok(());
        }
    }

    let opt = Options::from(&matches);

    let mounts = read_fs_list();
    let data: Vec<Row> = filter_mount_list(mounts, &paths, &opt)
        .into_iter()
        .filter_map(Filesystem::new)
        .filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
        .map(Into::into)
        .collect();
    println!("{}", Header::new(&opt));
    for row in data {
        println!("{}", DisplayRow::new(row, &opt));
    }

    Ok(())
}

pub fn uu_app<'a>() -> App<'a> {
    App::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .setting(AppSettings::InferLongArgs)
        .arg(
            Arg::new(OPT_ALL)
                .short('a')
                .long("all")
                .help("include dummy file systems"),
        )
        .arg(
            Arg::new(OPT_BLOCKSIZE)
                .short('B')
                .long("block-size")
                .takes_value(true)
                .help(
                    "scale sizes by SIZE before printing them; e.g.\
                     '-BM' prints sizes in units of 1,048,576 bytes",
                ),
        )
        .arg(
            Arg::new(OPT_DIRECT)
                .long("direct")
                .help("show statistics for a file instead of mount point"),
        )
        .arg(
            Arg::new(OPT_TOTAL)
                .long("total")
                .help("produce a grand total"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE)
                .short('h')
                .long("human-readable")
                .conflicts_with(OPT_HUMAN_READABLE_2)
                .help("print sizes in human readable format (e.g., 1K 234M 2G)"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE_2)
                .short('H')
                .long("si")
                .conflicts_with(OPT_HUMAN_READABLE)
                .help("likewise, but use powers of 1000 not 1024"),
        )
        .arg(
            Arg::new(OPT_INODES)
                .short('i')
                .long("inodes")
                .help("list inode information instead of block usage"),
        )
        .arg(Arg::new(OPT_KILO).short('k').help("like --block-size=1K"))
        .arg(
            Arg::new(OPT_LOCAL)
                .short('l')
                .long("local")
                .help("limit listing to local file systems"),
        )
        .arg(
            Arg::new(OPT_NO_SYNC)
                .long("no-sync")
                .conflicts_with(OPT_SYNC)
                .help("do not invoke sync before getting usage info (default)"),
        )
        .arg(
            Arg::new(OPT_OUTPUT)
                .long("output")
                .takes_value(true)
                .use_delimiter(true)
                .help(
                    "use the output format defined by FIELD_LIST,\
                     or print all fields if FIELD_LIST is omitted.",
                ),
        )
        .arg(
            Arg::new(OPT_PORTABILITY)
                .short('P')
                .long("portability")
                .help("use the POSIX output format"),
        )
        .arg(
            Arg::new(OPT_SYNC)
                .long("sync")
                .conflicts_with(OPT_NO_SYNC)
                .help("invoke sync before getting usage info"),
        )
        .arg(
            Arg::new(OPT_TYPE)
                .short('t')
                .long("type")
                .allow_invalid_utf8(true)
                .takes_value(true)
                .use_delimiter(true)
                .help("limit listing to file systems of type TYPE"),
        )
        .arg(
            Arg::new(OPT_PRINT_TYPE)
                .short('T')
                .long("print-type")
                .help("print file system type"),
        )
        .arg(
            Arg::new(OPT_EXCLUDE_TYPE)
                .short('x')
                .long("exclude-type")
                .takes_value(true)
                .use_delimiter(true)
                .help("limit listing to file systems not of type TYPE"),
        )
        .arg(Arg::new(OPT_PATHS).multiple_occurrences(true))
}
<commit_msg>df: `-t` may appear more than once and doesn't support delimiter<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Fangxu Hu <framlog@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
mod table;

use uucore::error::UResult;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};

use clap::{crate_version, App, AppSettings, Arg, ArgMatches};

use std::collections::HashSet;
#[cfg(unix)]
use std::ffi::CString;
use std::iter::FromIterator;
#[cfg(unix)]
use std::mem;

#[cfg(windows)]
use std::path::Path;

use crate::table::{DisplayRow, Header, Row};

static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
                      or all file systems by default.";

static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
static OPT_TOTAL: &str = "total";
static OPT_HUMAN_READABLE: &str = "human-readable";
static OPT_HUMAN_READABLE_2: &str = "human-readable-2";
static OPT_INODES: &str = "inodes";
static OPT_KILO: &str = "kilo";
static OPT_LOCAL: &str = "local";
static OPT_NO_SYNC: &str = "no-sync";
static OPT_OUTPUT: &str = "output";
static OPT_PATHS: &str = "paths";
static OPT_PORTABILITY: &str = "portability";
static OPT_SYNC: &str = "sync";
static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";

/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
#[derive(Default)]
struct FsSelector {
    include: HashSet<String>,
    exclude: HashSet<String>,
}

#[derive(Default)]
struct Options {
    show_local_fs: bool,
    show_all_fs: bool,
    show_listed_fs: bool,
    show_fs_type: bool,
    show_inode_instead: bool,
    // block_size: usize,
    human_readable_base: i64,
    fs_selector: FsSelector,
}

impl Options {
    /// Convert command-line arguments into [`Options`].
    fn from(matches: &ArgMatches) -> Self {
        Self {
            show_local_fs: matches.is_present(OPT_LOCAL),
            show_all_fs: matches.is_present(OPT_ALL),
            show_listed_fs: false,
            show_fs_type: matches.is_present(OPT_PRINT_TYPE),
            show_inode_instead: matches.is_present(OPT_INODES),
            human_readable_base: if matches.is_present(OPT_HUMAN_READABLE) {
                1024
            } else if matches.is_present(OPT_HUMAN_READABLE_2) {
                1000
            } else {
                -1
            },
            fs_selector: FsSelector::from(matches),
        }
    }
}

#[derive(Debug, Clone)]
struct Filesystem {
    mount_info: MountInfo,
    usage: FsUsage,
}

fn usage() -> String {
    format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
}

impl FsSelector {
    /// Convert command-line arguments into a [`FsSelector`].
    ///
    /// This function reads the include and exclude sets from
    /// [`ArgMatches`] and returns the corresponding [`FsSelector`]
    /// instance.
    fn from(matches: &ArgMatches) -> Self {
        let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default());
        let exclude = HashSet::from_iter(
            matches
                .values_of_lossy(OPT_EXCLUDE_TYPE)
                .unwrap_or_default(),
        );
        Self { include, exclude }
    }

    fn should_select(&self, fs_type: &str) -> bool {
        if self.exclude.contains(fs_type) {
            return false;
        }
        self.include.is_empty() || self.include.contains(fs_type)
    }
}

impl Filesystem {
    // TODO: resolve uuid in `mount_info.dev_name` if exists
    fn new(mount_info: MountInfo) -> Option<Self> {
        let _stat_path = if !mount_info.mount_dir.is_empty() {
            mount_info.mount_dir.clone()
        } else {
            #[cfg(unix)]
            {
                mount_info.dev_name.clone()
            }
            #[cfg(windows)]
            {
                // On windows, we expect the volume id
                mount_info.dev_id.clone()
            }
        };
        #[cfg(unix)]
        unsafe {
            let path = CString::new(_stat_path).unwrap();
            let mut statvfs = mem::zeroed();
            if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
                None
            } else {
                Some(Self {
                    mount_info,
                    usage: FsUsage::new(statvfs),
                })
            }
        }
        #[cfg(windows)]
        Some(Self {
            mount_info,
            usage: FsUsage::new(Path::new(&_stat_path)),
        })
    }
}

/// Whether to display the mount info given the inclusion settings.
fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool {
    // Don't show remote filesystems if `--local` has been given.
    if mi.remote && opt.show_local_fs {
        return false;
    }

    // Don't show pseudo filesystems unless `--all` has been given.
    if mi.dummy && !opt.show_all_fs && !opt.show_listed_fs {
        return false;
    }

    // Don't show filesystems if they have been explicitly excluded.
    if !opt.fs_selector.should_select(&mi.fs_type) {
        return false;
    }

    // Don't show filesystems other than the ones specified on the
    // command line, if any.
    if !paths.is_empty() && !paths.contains(&mi.mount_dir) {
        return false;
    }

    true
}

/// Whether the mount info in `m2` should be prioritized over `m1`.
///
/// The "lt" in the function name is in analogy to the
/// [`std::cmp::PartialOrd::lt`].
fn mount_info_lt(m1: &MountInfo, m2: &MountInfo) -> bool {
    // let "real" devices with '/' in the name win.
    if m1.dev_name.starts_with('/') && !m2.dev_name.starts_with('/') {
        return false;
    }

    let m1_nearer_root = m1.mount_dir.len() < m2.mount_dir.len();
    // With bind mounts, prefer items nearer the root of the source
    let m2_below_root = !m1.mount_root.is_empty()
        && !m2.mount_root.is_empty()
        && m1.mount_root.len() > m2.mount_root.len();
    // let points towards the root of the device win.
    if m1_nearer_root && !m2_below_root {
        return false;
    }

    // let an entry over-mounted on a new device win, but only when
    // matching an existing mnt point, to avoid problematic
    // replacement when given inaccurate mount lists, seen with some
    // chroot environments for example.
    if m1.dev_name != m2.dev_name && m1.mount_dir == m2.mount_dir {
        return false;
    }

    true
}

/// Whether to prioritize given mount info over all others on the same device.
///
/// This function decides whether the mount info `mi` is better than
/// all others in `previous` that mount the same device as `mi`.
fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool {
    for seen in previous {
        if seen.dev_id == mi.dev_id && mount_info_lt(mi, seen) {
            return false;
        }
    }
    true
}

/// Keep only the specified subset of [`MountInfo`] instances.
///
/// If `paths` is non-empty, this function excludes any [`MountInfo`]
/// that is not mounted at the specified path.
///
/// The `opt` argument specifies a variety of ways of excluding
/// [`MountInfo`] instances; see [`Options`] for more information.
///
/// Finally, if there are duplicate entries, the one with the shorter
/// path is kept.
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
    let mut result = vec![];
    for mi in vmi {
        // TODO The running time of the `is_best()` function is linear
        // in the length of `result`. That makes the running time of
        // this loop quadratic in the length of `vmi`. This could be
        // improved by a more efficient implementation of `is_best()`,
        // but `vmi` is probably not very long in practice.
        if is_included(&mi, paths, opt) && is_best(&result, &mi) {
            result.push(mi);
        }
    }
    result
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let usage = usage();
    let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);

    let paths: Vec<String> = matches
        .values_of(OPT_PATHS)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    #[cfg(windows)]
    {
        if matches.is_present(OPT_INODES) {
            println!("{}: doesn't support -i option", uucore::util_name());
            return Ok(());
        }
    }

    let opt = Options::from(&matches);

    let mounts = read_fs_list();
    let data: Vec<Row> = filter_mount_list(mounts, &paths, &opt)
        .into_iter()
        .filter_map(Filesystem::new)
        .filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
        .map(Into::into)
        .collect();
    println!("{}", Header::new(&opt));
    for row in data {
        println!("{}", DisplayRow::new(row, &opt));
    }

    Ok(())
}

pub fn uu_app<'a>() -> App<'a> {
    App::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .setting(AppSettings::InferLongArgs)
        .arg(
            Arg::new(OPT_ALL)
                .short('a')
                .long("all")
                .help("include dummy file systems"),
        )
        .arg(
            Arg::new(OPT_BLOCKSIZE)
                .short('B')
                .long("block-size")
                .takes_value(true)
                .help(
                    "scale sizes by SIZE before printing them; e.g.\
                     '-BM' prints sizes in units of 1,048,576 bytes",
                ),
        )
        .arg(
            Arg::new(OPT_DIRECT)
                .long("direct")
                .help("show statistics for a file instead of mount point"),
        )
        .arg(
            Arg::new(OPT_TOTAL)
                .long("total")
                .help("produce a grand total"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE)
                .short('h')
                .long("human-readable")
                .conflicts_with(OPT_HUMAN_READABLE_2)
                .help("print sizes in human readable format (e.g., 1K 234M 2G)"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE_2)
                .short('H')
                .long("si")
                .conflicts_with(OPT_HUMAN_READABLE)
                .help("likewise, but use powers of 1000 not 1024"),
        )
        .arg(
            Arg::new(OPT_INODES)
                .short('i')
                .long("inodes")
                .help("list inode information instead of block usage"),
        )
        .arg(Arg::new(OPT_KILO).short('k').help("like --block-size=1K"))
        .arg(
            Arg::new(OPT_LOCAL)
                .short('l')
                .long("local")
                .help("limit listing to local file systems"),
        )
        .arg(
            Arg::new(OPT_NO_SYNC)
                .long("no-sync")
                .conflicts_with(OPT_SYNC)
                .help("do not invoke sync before getting usage info (default)"),
        )
        .arg(
            Arg::new(OPT_OUTPUT)
                .long("output")
                .takes_value(true)
                .use_delimiter(true)
                .help(
                    "use the output format defined by FIELD_LIST,\
                     or print all fields if FIELD_LIST is omitted.",
                ),
        )
        .arg(
            Arg::new(OPT_PORTABILITY)
                .short('P')
                .long("portability")
                .help("use the POSIX output format"),
        )
        .arg(
            Arg::new(OPT_SYNC)
                .long("sync")
                .conflicts_with(OPT_NO_SYNC)
                .help("invoke sync before getting usage info"),
        )
        .arg(
            Arg::new(OPT_TYPE)
                .short('t')
                .long("type")
                .allow_invalid_utf8(true)
                .takes_value(true)
                .multiple_occurrences(true)
                .help("limit listing to file systems of type TYPE"),
        )
        .arg(
            Arg::new(OPT_PRINT_TYPE)
                .short('T')
                .long("print-type")
                .help("print file system type"),
        )
        .arg(
            Arg::new(OPT_EXCLUDE_TYPE)
                .short('x')
                .long("exclude-type")
                .takes_value(true)
                .use_delimiter(true)
                .help("limit listing to file systems not of type TYPE"),
        )
        .arg(Arg::new(OPT_PATHS).multiple_occurrences(true))
}
<|endoftext|>


<commit_before>// Copyright 2014 The GLFW-RS Developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[cfg(target_os="windows")]
#[link(name = "opengl32")]
#[link(name = "gdi32")]
extern {}

#[cfg(feature = "glfw-sys")]
#[link(name = "glfw3", kind = "static")]
extern {}

#[cfg(not(feature = "glfw-sys"))]
// leaving off `kind = static` allows for the specification of a dynamic library if desired
#[link(name = "glfw3")]
extern {}

#[cfg(target_os="linux")]
#[link(name = "X11")]
#[link(name = "GL")]
#[link(name = "Xxf86vm")]
#[link(name = "Xrandr")]
#[link(name = "Xi")]
extern {}

#[cfg(target_os="macos")]
#[link(name = "Cocoa", kind = "framework")]
#[link(name = "OpenGL", kind = "framework")]
#[link(name = "IOKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "QuartzCore", kind = "framework")]
extern {}
<commit_msg>Fix building errors on x86_64-w64-mingw32 due to wrong linking order.<commit_after>// Copyright 2014 The GLFW-RS Developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[cfg(feature = "glfw-sys")]
#[link(name = "glfw3", kind = "static")]
extern {}

#[cfg(not(feature = "glfw-sys"))]
// leaving off `kind = static` allows for the specification of a dynamic library if desired
#[link(name = "glfw3")]
extern {}

#[cfg(target_os="windows")]
#[link(name = "opengl32")]
#[link(name = "gdi32")]
extern {}

#[cfg(target_os="linux")]
#[link(name = "X11")]
#[link(name = "GL")]
#[link(name = "Xxf86vm")]
#[link(name = "Xrandr")]
#[link(name = "Xi")]
extern {}

#[cfg(target_os="macos")]
#[link(name = "Cocoa", kind = "framework")]
#[link(name = "OpenGL", kind = "framework")]
#[link(name = "IOKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "QuartzCore", kind = "framework")]
extern {}
<|endoftext|>


<commit_before>/*
Copyright ⓒ 2016 Daniel Keep.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
#[macro_use] extern crate scan_rules;
#[macro_use] mod util;

use scan_rules::ScanError as SE;
use scan_rules::ScanErrorKind as SEK;
use scan_rules::input::{StrCursor, ExactCompare, IgnoreAsciiCase};

#[test]
fn test_scan_space() {
    let inp = "UPPERCASE lowercase mIxeDcAsE TitleCase";

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCaSE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 0
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowerCase", "mIxeDcAsE", "TitleCase") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 10
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowercase", "mIxEdcAsE", "TitleCase") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 20
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitLecAse") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 30
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCaSE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowerCase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowercase", "mIxEdcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitLecAse") => ()),
        Ok(())
    );
}
<commit_msg>Updated case_match test to also test `Option<T>`.<commit_after>/*
Copyright ⓒ 2016 Daniel Keep.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
#[macro_use] extern crate scan_rules;
#[macro_use] mod util;

use scan_rules::ScanError as SE;
use scan_rules::ScanErrorKind as SEK;
use scan_rules::input::{StrCursor, ExactCompare, IgnoreAsciiCase};

#[test]
fn test_case_match() {
    let inp = "UPPERCASE lowercase mIxeDcAsE TitleCase";

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCaSE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 0
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowerCase", "mIxeDcAsE", "TitleCase") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 10
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowercase", "mIxEdcAsE", "TitleCase") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 20
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitLecAse") => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 30
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCaSE", "lowercase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowerCase", "mIxeDcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowercase", "mIxEdcAsE", "TitleCase") => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new(inp);
            ("UPPERCASE", "lowercase", "mIxeDcAsE", "TitLecAse") => ()),
        Ok(())
    );
}

/**
This makes sure that case sensitivity works for the std enums, if nothing else.
*/
#[test]
fn test_case_match_option() {
    assert_match!(
        scan!(StrCursor::<ExactCompare>::new("Some(42)");
            (let _: Option<i32>) => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<ExactCompare>::new("some(42)");
            (let _: Option<i32>) => ()),
        Err(SE { ref at, kind: SEK::LiteralMismatch, .. }) if at.offset() == 0
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new("Some(42)");
            (let _: Option<i32>) => ()),
        Ok(())
    );

    assert_match!(
        scan!(StrCursor::<IgnoreAsciiCase>::new("some(42)");
            (let _: Option<i32>) => ()),
        Ok(())
    );
}
<|endoftext|>


<commit_before>use colored::*;
use regex::Regex;
use std::path::{Path, PathBuf};
use std::{env, process::Command};
use ui_test::{color_eyre::Result, Config, Mode, OutputConflictHandling};

fn miri_path() -> PathBuf {
    PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
}

// Build the shared object file for testing external C function calls.
fn build_so_for_c_ffi_tests() -> PathBuf {
    let cc = option_env!("CC").unwrap_or("cc");
    // Target directory that we can write to.
    let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so");
    // Create the directory if it does not already exist.
    std::fs::create_dir_all(&so_target_dir)
        .expect("Failed to create directory for shared object file");
    let so_file_path = so_target_dir.join("libtestlib.so");
    let cc_output = Command::new(cc)
        .args([
            "-shared",
            "-o",
            so_file_path.to_str().unwrap(),
            "tests/extern-so/test.c",
            // Only add the functions specified in libcode.version to the shared object file.
            // This is to avoid automatically adding `malloc`, etc.
            // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
            "-fPIC",
            "-Wl,--version-script=tests/extern-so/libcode.version",
        ])
        .output()
        .expect("failed to generate shared object file for testing external C function calls");
    if !cc_output.status.success() {
        panic!("error in generating shared object file for testing external C function calls");
    }
    so_file_path
}

fn run_tests(
    mode: Mode,
    path: &str,
    target: Option<String>,
    with_dependencies: bool,
) -> Result<()> {
    let mut config = Config {
        target,
        stderr_filters: STDERR.clone(),
        stdout_filters: STDOUT.clone(),
        root_dir: PathBuf::from(path),
        mode,
        program: miri_path(),
        quiet: false,
        ..Config::default()
    };

    let in_rustc_test_suite = option_env!("RUSTC_STAGE").is_some();

    // Add some flags we always want.
    config.args.push("--edition".into());
    config.args.push("2018".into());
    if in_rustc_test_suite {
        // Less aggressive warnings to make the rustc toolstate management less painful.
        // (We often get warnings when e.g. a feature gets stabilized or some lint gets added/improved.)
        config.args.push("-Astable-features".into());
        config.args.push("-Aunused".into());
    } else {
        config.args.push("-Dwarnings".into());
        config.args.push("-Dunused".into());
    }
    if let Ok(extra_flags) = env::var("MIRIFLAGS") {
        for flag in extra_flags.split_whitespace() {
            config.args.push(flag.into());
        }
    }
    config.args.push("-Zui-testing".into());
    if let Some(target) = &config.target {
        config.args.push("--target".into());
        config.args.push(target.into());
    }

    // If we're on linux, and we're testing the extern-so functionality,
    // then build the shared object file for testing external C function calls
    // and push the relevant compiler flag.
    if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") {
        let so_file_path = build_so_for_c_ffi_tests();
        let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
        flag.push(so_file_path.into_os_string());
        config.args.push(flag);
    }

    let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();

    config.output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
        (false, false) => OutputConflictHandling::Error,
        (true, false) => OutputConflictHandling::Bless,
        (false, true) => OutputConflictHandling::Ignore,
        (true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),
    };

    // Handle command-line arguments.
    config.path_filter.extend(std::env::args().skip(1).filter(|arg| {
        match &**arg {
            "--quiet" => {
                config.quiet = true;
                false
            }
            _ => true,
        }
    }));

    let use_std = env::var_os("MIRI_NO_STD").is_none();

    if with_dependencies && use_std {
        config.dependencies_crate_manifest_path =
            Some(Path::new("test_dependencies").join("Cargo.toml"));
        config.dependency_builder.args = vec![
            "run".into(),
            "--manifest-path".into(),
            "cargo-miri/Cargo.toml".into(),
            "--".into(),
            "miri".into(),
            "run".into(),
        ];
    }
    ui_test::run_tests(config)
}

macro_rules! regexes {
    ($name:ident: $($regex:expr => $replacement:expr,)*) => {lazy_static::lazy_static! {
        static ref $name: Vec<(Regex, &'static str)> = vec![
            $((Regex::new($regex).unwrap(), $replacement),)*
        ];
    }};
}

regexes! {
    STDOUT:
    // Windows file paths
    r"\\"                           => "/",
}

regexes! {
    STDERR:
    // erase line and column info
    r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC",
    // erase alloc ids
    "alloc[0-9]+"                    => "ALLOC",
    // erase Stacked Borrows tags
    "<[0-9]+>"                       => "<TAG>",
    // erase whitespace that differs between platforms
    r" +at (.*\.rs)"                 => " at $1",
    // erase generics in backtraces
    "([0-9]+: .*)::<.*>"             => "$1",
    // erase addresses in backtraces
    "([0-9]+: ) +0x[0-9a-f]+ - (.*)" => "$1$2",
    // erase long hexadecimals
    r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX",
    // erase specific alignments
    "alignment [0-9]+"               => "alignment ALIGN",
    // erase thread caller ids
    r"call [0-9]+"                  => "call ID",
    // erase platform module paths
    "sys::[a-z]+::"                  => "sys::PLATFORM::",
    // Windows file paths
    r"\\"                           => "/",
    // erase Rust stdlib path
    "[^ `]*/(rust[^/]*|checkout)/library/" => "RUSTLIB/",
    // erase platform file paths
    "sys/[a-z]+/"                    => "sys/PLATFORM/",
    // erase paths into the crate registry
    r"[^ ]*/\.?cargo/registry/.*/(.*\.rs)"  => "CARGO_REGISTRY/.../$1",
}

enum Dependencies {
    WithDependencies,
    WithoutDependencies,
}

use Dependencies::*;

fn ui(mode: Mode, path: &str, with_dependencies: Dependencies) -> Result<()> {
    let target = get_target();

    let msg = format!(
        "## Running ui tests in {path} against miri for {}",
        target.as_deref().unwrap_or("host")
    );
    eprintln!("{}", msg.green().bold());

    let with_dependencies = match with_dependencies {
        WithDependencies => true,
        WithoutDependencies => false,
    };
    run_tests(mode, path, target, with_dependencies)
}

fn get_target() -> Option<String> {
    env::var("MIRI_TEST_TARGET").ok()
}

fn main() -> Result<()> {
    ui_test::color_eyre::install()?;

    // Add a test env var to do environment communication tests.
    env::set_var("MIRI_ENV_VAR_TEST", "0");
    // Let the tests know where to store temp files (they might run for a different target, which can make this hard to find).
    env::set_var("MIRI_TEMP", env::temp_dir());

    ui(Mode::Pass, "tests/pass", WithoutDependencies)?;
    ui(Mode::Pass, "tests/pass-dep", WithDependencies)?;
    ui(Mode::Panic, "tests/panic", WithDependencies)?;
    ui(Mode::Fail { require_patterns: true }, "tests/fail", WithDependencies)?;
    if cfg!(target_os = "linux") {
        ui(Mode::Pass, "tests/extern-so/pass", WithoutDependencies)?;
        ui(Mode::Fail { require_patterns: true }, "tests/extern-so/fail", WithDependencies)?;
    }

    Ok(())
}
<commit_msg>add comment<commit_after>use colored::*;
use regex::Regex;
use std::path::{Path, PathBuf};
use std::{env, process::Command};
use ui_test::{color_eyre::Result, Config, Mode, OutputConflictHandling};

fn miri_path() -> PathBuf {
    PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
}

// Build the shared object file for testing external C function calls.
fn build_so_for_c_ffi_tests() -> PathBuf {
    let cc = option_env!("CC").unwrap_or("cc");
    // Target directory that we can write to.
    let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so");
    // Create the directory if it does not already exist.
    std::fs::create_dir_all(&so_target_dir)
        .expect("Failed to create directory for shared object file");
    let so_file_path = so_target_dir.join("libtestlib.so");
    let cc_output = Command::new(cc)
        .args([
            "-shared",
            "-o",
            so_file_path.to_str().unwrap(),
            "tests/extern-so/test.c",
            // Only add the functions specified in libcode.version to the shared object file.
            // This is to avoid automatically adding `malloc`, etc.
            // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
            "-fPIC",
            "-Wl,--version-script=tests/extern-so/libcode.version",
        ])
        .output()
        .expect("failed to generate shared object file for testing external C function calls");
    if !cc_output.status.success() {
        panic!("error in generating shared object file for testing external C function calls");
    }
    so_file_path
}

fn run_tests(
    mode: Mode,
    path: &str,
    target: Option<String>,
    with_dependencies: bool,
) -> Result<()> {
    let mut config = Config {
        target,
        stderr_filters: STDERR.clone(),
        stdout_filters: STDOUT.clone(),
        root_dir: PathBuf::from(path),
        mode,
        program: miri_path(),
        quiet: false,
        ..Config::default()
    };

    let in_rustc_test_suite = option_env!("RUSTC_STAGE").is_some();

    // Add some flags we always want.
    config.args.push("--edition".into());
    config.args.push("2018".into());
    if in_rustc_test_suite {
        // Less aggressive warnings to make the rustc toolstate management less painful.
        // (We often get warnings when e.g. a feature gets stabilized or some lint gets added/improved.)
        config.args.push("-Astable-features".into());
        config.args.push("-Aunused".into());
    } else {
        config.args.push("-Dwarnings".into());
        config.args.push("-Dunused".into());
    }
    if let Ok(extra_flags) = env::var("MIRIFLAGS") {
        for flag in extra_flags.split_whitespace() {
            config.args.push(flag.into());
        }
    }
    config.args.push("-Zui-testing".into());
    if let Some(target) = &config.target {
        config.args.push("--target".into());
        config.args.push(target.into());
    }

    // If we're on linux, and we're testing the extern-so functionality,
    // then build the shared object file for testing external C function calls
    // and push the relevant compiler flag.
    if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") {
        let so_file_path = build_so_for_c_ffi_tests();
        let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
        flag.push(so_file_path.into_os_string());
        config.args.push(flag);
    }

    let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();

    config.output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
        (false, false) => OutputConflictHandling::Error,
        (true, false) => OutputConflictHandling::Bless,
        (false, true) => OutputConflictHandling::Ignore,
        (true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),
    };

    // Handle command-line arguments.
    config.path_filter.extend(std::env::args().skip(1).filter(|arg| {
        match &**arg {
            "--quiet" => {
                config.quiet = true;
                false
            }
            _ => true,
        }
    }));

    let use_std = env::var_os("MIRI_NO_STD").is_none();

    if with_dependencies && use_std {
        config.dependencies_crate_manifest_path =
            Some(Path::new("test_dependencies").join("Cargo.toml"));
        config.dependency_builder.args = vec![
            "run".into(),
            "--manifest-path".into(),
            "cargo-miri/Cargo.toml".into(),
            "--".into(),
            "miri".into(),
            "run".into(), // There is no `cargo miri build` so we just use `cargo miri run`.
        ];
    }
    ui_test::run_tests(config)
}

macro_rules! regexes {
    ($name:ident: $($regex:expr => $replacement:expr,)*) => {lazy_static::lazy_static! {
        static ref $name: Vec<(Regex, &'static str)> = vec![
            $((Regex::new($regex).unwrap(), $replacement),)*
        ];
    }};
}

regexes! {
    STDOUT:
    // Windows file paths
    r"\\"                           => "/",
}

regexes! {
    STDERR:
    // erase line and column info
    r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC",
    // erase alloc ids
    "alloc[0-9]+"                    => "ALLOC",
    // erase Stacked Borrows tags
    "<[0-9]+>"                       => "<TAG>",
    // erase whitespace that differs between platforms
    r" +at (.*\.rs)"                 => " at $1",
    // erase generics in backtraces
    "([0-9]+: .*)::<.*>"             => "$1",
    // erase addresses in backtraces
    "([0-9]+: ) +0x[0-9a-f]+ - (.*)" => "$1$2",
    // erase long hexadecimals
    r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX",
    // erase specific alignments
    "alignment [0-9]+"               => "alignment ALIGN",
    // erase thread caller ids
    r"call [0-9]+"                  => "call ID",
    // erase platform module paths
    "sys::[a-z]+::"                  => "sys::PLATFORM::",
    // Windows file paths
    r"\\"                           => "/",
    // erase Rust stdlib path
    "[^ `]*/(rust[^/]*|checkout)/library/" => "RUSTLIB/",
    // erase platform file paths
    "sys/[a-z]+/"                    => "sys/PLATFORM/",
    // erase paths into the crate registry
    r"[^ ]*/\.?cargo/registry/.*/(.*\.rs)"  => "CARGO_REGISTRY/.../$1",
}

enum Dependencies {
    WithDependencies,
    WithoutDependencies,
}

use Dependencies::*;

fn ui(mode: Mode, path: &str, with_dependencies: Dependencies) -> Result<()> {
    let target = get_target();

    let msg = format!(
        "## Running ui tests in {path} against miri for {}",
        target.as_deref().unwrap_or("host")
    );
    eprintln!("{}", msg.green().bold());

    let with_dependencies = match with_dependencies {
        WithDependencies => true,
        WithoutDependencies => false,
    };
    run_tests(mode, path, target, with_dependencies)
}

fn get_target() -> Option<String> {
    env::var("MIRI_TEST_TARGET").ok()
}

fn main() -> Result<()> {
    ui_test::color_eyre::install()?;

    // Add a test env var to do environment communication tests.
    env::set_var("MIRI_ENV_VAR_TEST", "0");
    // Let the tests know where to store temp files (they might run for a different target, which can make this hard to find).
    env::set_var("MIRI_TEMP", env::temp_dir());

    ui(Mode::Pass, "tests/pass", WithoutDependencies)?;
    ui(Mode::Pass, "tests/pass-dep", WithDependencies)?;
    ui(Mode::Panic, "tests/panic", WithDependencies)?;
    ui(Mode::Fail { require_patterns: true }, "tests/fail", WithDependencies)?;
    if cfg!(target_os = "linux") {
        ui(Mode::Pass, "tests/extern-so/pass", WithoutDependencies)?;
        ui(Mode::Fail { require_patterns: true }, "tests/extern-so/fail", WithDependencies)?;
    }

    Ok(())
}
<|endoftext|>


<commit_before>extern crate compiletest_rs as compiletest;

use std::path::{PathBuf, Path};
use std::io::Write;

fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, fullmir: bool) {
    let mut config = compiletest::default_config();
    config.mode = "compile-fail".parse().expect("Invalid mode");
    config.rustc_path = "target/debug/miri".into();
    if fullmir {
        if host != target {
            // skip fullmir on nonhost
            return;
        }
        let sysroot = Path::new(&std::env::var("HOME").unwrap()).join(".xargo").join("HOST");
        config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
        config.src_base = PathBuf::from(path.to_string());
    } else {
        config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
        config.src_base = PathBuf::from(path.to_string());
    }
    config.target = target.to_owned();
    compiletest::run_tests(&config);
}

fn run_pass() {
    let mut config = compiletest::default_config();
    config.mode = "run-pass".parse().expect("Invalid mode");
    config.src_base = PathBuf::from("tests/run-pass".to_string());
    config.target_rustcflags = Some("-Dwarnings".to_string());
    config.host_rustcflags = Some("-Dwarnings".to_string());
    compiletest::run_tests(&config);
}

fn miri_pass(path: &str, target: &str, host: &str, fullmir: bool) {
    let mut config = compiletest::default_config();
    config.mode = "mir-opt".parse().expect("Invalid mode");
    config.src_base = PathBuf::from(path);
    config.target = target.to_owned();
    config.host = host.to_owned();
    config.rustc_path = PathBuf::from("target/debug/miri");
    if fullmir {
        if host != target {
            // skip fullmir on nonhost
            return;
        }
        let sysroot = Path::new(&std::env::var("HOME").unwrap()).join(".xargo").join("HOST");
        config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
    }
    // don't actually execute the final binary, it might be for other targets and we only care
    // about running miri, not the binary.
    config.runtool = Some("echo \"\" || ".to_owned());
    if target == host {
        std::env::set_var("MIRI_HOST_TARGET", "yes");
    }
    compiletest::run_tests(&config);
    std::env::set_var("MIRI_HOST_TARGET", "");
}

fn is_target_dir<P: Into<PathBuf>>(path: P) -> bool {
    let mut path = path.into();
    path.push("lib");
    path.metadata().map(|m| m.is_dir()).unwrap_or(false)
}

fn for_all_targets<F: FnMut(String)>(sysroot: &Path, mut f: F) {
    let target_dir = sysroot.join("lib").join("rustlib");
    println!("target dir: {}", target_dir.to_str().unwrap());
    for entry in std::fs::read_dir(target_dir).expect("invalid sysroot") {
        let entry = entry.unwrap();
        if !is_target_dir(entry.path()) { continue; }
        let target = entry.file_name().into_string().unwrap();
        let stderr = std::io::stderr();
        writeln!(stderr.lock(), "running tests for target {}", target).unwrap();
        f(target);
    }
}

#[test]
fn compile_test() {
    let sysroot = std::env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
        let sysroot = std::process::Command::new("rustc")
            .arg("--print")
            .arg("sysroot")
            .output()
            .expect("rustc not found")
            .stdout;
        String::from_utf8(sysroot).expect("sysroot is not utf8")
    });
    let sysroot = &Path::new(sysroot.trim());
    let host = std::process::Command::new("rustc")
        .arg("-vV")
        .output()
        .expect("rustc not found for -vV")
        .stdout;
    let host = std::str::from_utf8(&host).expect("sysroot is not utf8");
    let host = host.split("\nhost: ").nth(1).expect("no host: part in rustc -vV");
    let host = host.split('\n').next().expect("no \n after host");

    if let Ok(path) = std::env::var("MIRI_RUSTC_TEST") {
        let mut mir_not_found = Vec::new();
        let mut crate_not_found = Vec::new();
        let mut success = 0;
        let mut failed = Vec::new();
        let mut c_abi_fns = Vec::new();
        let mut abi = Vec::new();
        let mut unsupported = Vec::new();
        let mut unimplemented_intrinsic = Vec::new();
        let mut limits = Vec::new();
        let mut files: Vec<_> = std::fs::read_dir(path).unwrap().collect();
        while let Some(file) = files.pop() {
            let file = file.unwrap();
            let path = file.path();
            if file.metadata().unwrap().is_dir() {
                if !path.to_str().unwrap().ends_with("auxiliary") {
                    // add subdirs recursively
                    files.extend(std::fs::read_dir(path).unwrap());
                }
                continue;
            }
            if !file.metadata().unwrap().is_file() || !path.to_str().unwrap().ends_with(".rs") {
                continue;
            }
            let stderr = std::io::stderr();
            write!(stderr.lock(), "test [miri-pass] {} ... ", path.display()).unwrap();
            let mut cmd = std::process::Command::new("target/debug/miri");
            cmd.arg(path);
            let libs = Path::new(&sysroot).join("lib");
            let sysroot = libs.join("rustlib").join(&host).join("lib");
            let paths = std::env::join_paths(&[libs, sysroot]).unwrap();
            cmd.env(compiletest::procsrv::dylib_env_var(), paths);
            cmd.env("MIRI_SYSROOT", Path::new(&std::env::var("HOME").unwrap()).join(".xargo").join("HOST"));

            match cmd.output() {
                Ok(ref output) if output.status.success() => {
                    success += 1;
                    writeln!(stderr.lock(), "ok").unwrap()
                },
                Ok(output) => {
                    let output_err = std::str::from_utf8(&output.stderr).unwrap();
                    if let Some(text) = output_err.splitn(2, "no mir for `").nth(1) {
                        let end = text.find('`').unwrap();
                        mir_not_found.push(text[..end].to_string());
                        writeln!(stderr.lock(), "NO MIR FOR `{}`", &text[..end]).unwrap();
                    } else if let Some(text) = output_err.splitn(2, "can't find crate for `").nth(1) {
                        let end = text.find('`').unwrap();
                        crate_not_found.push(text[..end].to_string());
                        writeln!(stderr.lock(), "CAN'T FIND CRATE FOR `{}`", &text[..end]).unwrap();
                    } else {
                        for text in output_err.split("error: ").skip(1) {
                            let end = text.find('\n').unwrap_or(text.len());
                            let c_abi = "can't call C ABI function: ";
                            let unimplemented_intrinsic_s = "unimplemented intrinsic: ";
                            let unsupported_s = "miri does not support ";
                            let abi_s = "can't handle function with ";
                            let limit_s = "reached the configured maximum ";
                            if text.starts_with(c_abi) {
                                c_abi_fns.push(text[c_abi.len()..end].to_string());
                            } else if text.starts_with(unimplemented_intrinsic_s) {
                                unimplemented_intrinsic.push(text[unimplemented_intrinsic_s.len()..end].to_string());
                            } else if text.starts_with(unsupported_s) {
                                unsupported.push(text[unsupported_s.len()..end].to_string());
                            } else if text.starts_with(abi_s) {
                                abi.push(text[abi_s.len()..end].to_string());
                            } else if text.starts_with(limit_s) {
                                limits.push(text[limit_s.len()..end].to_string());
                            } else if text.find("aborting").is_none() {
                                failed.push(text[..end].to_string());
                            }
                        }
                        writeln!(stderr.lock(), "FAILED with exit code {:?}", output.status.code()).unwrap();
                        writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
                        writeln!(stderr.lock(), "stderr: \n {}", output_err).unwrap();
                    }
                }
                Err(e) => {
                    writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
                    panic!("failed to execute miri");
                },
            }
        }
        let stderr = std::io::stderr();
        let mut stderr = stderr.lock();
        writeln!(stderr, "{} success, {} no mir, {} crate not found, {} failed, \
                          {} C fn, {} ABI, {} unsupported, {} intrinsic",
                          success, mir_not_found.len(), crate_not_found.len(), failed.len(),
                          c_abi_fns.len(), abi.len(), unsupported.len(), unimplemented_intrinsic.len()).unwrap();
        writeln!(stderr, "# The \"other reasons\" errors").unwrap();
        writeln!(stderr, "(sorted, deduplicated)").unwrap();
        print_vec(&mut stderr, failed);

        writeln!(stderr, "# can't call C ABI function").unwrap();
        print_vec(&mut stderr, c_abi_fns);

        writeln!(stderr, "# unsupported ABI").unwrap();
        print_vec(&mut stderr, abi);

        writeln!(stderr, "# unsupported").unwrap();
        print_vec(&mut stderr, unsupported);

        writeln!(stderr, "# unimplemented intrinsics").unwrap();
        print_vec(&mut stderr, unimplemented_intrinsic);

        writeln!(stderr, "# mir not found").unwrap();
        print_vec(&mut stderr, mir_not_found);

        writeln!(stderr, "# crate not found").unwrap();
        print_vec(&mut stderr, crate_not_found);

        panic!("ran miri on rustc test suite. Test failing for convenience");
    } else {
        run_pass();
        for_all_targets(sysroot, |target| {
            miri_pass("tests/run-pass", &target, host, false);
            compile_fail(sysroot, "tests/compile-fail", &target, host, false);
        });
        miri_pass("tests/run-pass-fullmir", host, host, true);
        compile_fail(sysroot, "tests/compile-fail-fullmir", host, host, true);
    }
}

fn print_vec<W: std::io::Write>(stderr: &mut W, v: Vec<String>) {
    writeln!(stderr, "```").unwrap();
    for (n, s) in vec_to_hist(v).into_iter().rev() {
        writeln!(stderr, "{:4} {}", n, s).unwrap();
    }
    writeln!(stderr, "```").unwrap();
}

fn vec_to_hist<T: PartialEq + Ord>(mut v: Vec<T>) -> Vec<(usize, T)> {
    v.sort();
    let mut v = v.into_iter();
    let mut result = Vec::new();
    let mut current = v.next();
    'outer: while let Some(current_val) = current {
        let mut n = 1;
        for next in &mut v {
            if next == current_val {
                n += 1;
            } else {
                result.push((n, current_val));
                current = Some(next);
                continue 'outer;
            }
        }
        result.push((n, current_val));
        break;
    }
    result.sort();
    result
}
<commit_msg>run fullmir tests against rustc; add output explaining what is being tested<commit_after>extern crate compiletest_rs as compiletest;

use std::path::{PathBuf, Path};
use std::io::Write;

macro_rules! eprintln {
    ($($arg:tt)*) => {
        let stderr = std::io::stderr();
        writeln!(stderr.lock(), $($arg)*).unwrap();
    }
}

fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, fullmir: bool) {
    eprintln!("## Running compile-fail tests in {} against miri for target {}", path, target);
    let mut config = compiletest::default_config();
    config.mode = "compile-fail".parse().expect("Invalid mode");
    config.rustc_path = "target/debug/miri".into();
    if fullmir {
        if host != target {
            // skip fullmir on nonhost
            return;
        }
        let sysroot = Path::new(&std::env::var("HOME").unwrap()).join(".xargo").join("HOST");
        config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
        config.src_base = PathBuf::from(path.to_string());
    } else {
        config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
        config.src_base = PathBuf::from(path.to_string());
    }
    config.target = target.to_owned();
    compiletest::run_tests(&config);
}

fn run_pass(path: &str) {
    eprintln!("## Running run-pass tests in {} against rustc", path);
    let mut config = compiletest::default_config();
    config.mode = "run-pass".parse().expect("Invalid mode");
    config.src_base = PathBuf::from(path);
    config.target_rustcflags = Some("-Dwarnings".to_string());
    config.host_rustcflags = Some("-Dwarnings".to_string());
    compiletest::run_tests(&config);
}

fn miri_pass(path: &str, target: &str, host: &str, fullmir: bool) {
    eprintln!("## Running run-pass tests in {} against miri for target {}", path, target);
    let mut config = compiletest::default_config();
    config.mode = "mir-opt".parse().expect("Invalid mode");
    config.src_base = PathBuf::from(path);
    config.target = target.to_owned();
    config.host = host.to_owned();
    config.rustc_path = PathBuf::from("target/debug/miri");
    if fullmir {
        if host != target {
            // skip fullmir on nonhost
            return;
        }
        let sysroot = Path::new(&std::env::var("HOME").unwrap()).join(".xargo").join("HOST");
        config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
    }
    // don't actually execute the final binary, it might be for other targets and we only care
    // about running miri, not the binary.
    config.runtool = Some("echo \"\" || ".to_owned());
    if target == host {
        std::env::set_var("MIRI_HOST_TARGET", "yes");
    }
    compiletest::run_tests(&config);
    std::env::set_var("MIRI_HOST_TARGET", "");
}

fn is_target_dir<P: Into<PathBuf>>(path: P) -> bool {
    let mut path = path.into();
    path.push("lib");
    path.metadata().map(|m| m.is_dir()).unwrap_or(false)
}

fn for_all_targets<F: FnMut(String)>(sysroot: &Path, mut f: F) {
    let target_dir = sysroot.join("lib").join("rustlib");
    for entry in std::fs::read_dir(target_dir).expect("invalid sysroot") {
        let entry = entry.unwrap();
        if !is_target_dir(entry.path()) { continue; }
        let target = entry.file_name().into_string().unwrap();
        f(target);
    }
}

#[test]
fn compile_test() {
    let sysroot = std::env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
        let sysroot = std::process::Command::new("rustc")
            .arg("--print")
            .arg("sysroot")
            .output()
            .expect("rustc not found")
            .stdout;
        String::from_utf8(sysroot).expect("sysroot is not utf8")
    });
    let sysroot = &Path::new(sysroot.trim());
    let host = std::process::Command::new("rustc")
        .arg("-vV")
        .output()
        .expect("rustc not found for -vV")
        .stdout;
    let host = std::str::from_utf8(&host).expect("sysroot is not utf8");
    let host = host.split("\nhost: ").nth(1).expect("no host: part in rustc -vV");
    let host = host.split('\n').next().expect("no \n after host");

    if let Ok(path) = std::env::var("MIRI_RUSTC_TEST") {
        let mut mir_not_found = Vec::new();
        let mut crate_not_found = Vec::new();
        let mut success = 0;
        let mut failed = Vec::new();
        let mut c_abi_fns = Vec::new();
        let mut abi = Vec::new();
        let mut unsupported = Vec::new();
        let mut unimplemented_intrinsic = Vec::new();
        let mut limits = Vec::new();
        let mut files: Vec<_> = std::fs::read_dir(path).unwrap().collect();
        while let Some(file) = files.pop() {
            let file = file.unwrap();
            let path = file.path();
            if file.metadata().unwrap().is_dir() {
                if !path.to_str().unwrap().ends_with("auxiliary") {
                    // add subdirs recursively
                    files.extend(std::fs::read_dir(path).unwrap());
                }
                continue;
            }
            if !file.metadata().unwrap().is_file() || !path.to_str().unwrap().ends_with(".rs") {
                continue;
            }
            let stderr = std::io::stderr();
            write!(stderr.lock(), "test [miri-pass] {} ... ", path.display()).unwrap();
            let mut cmd = std::process::Command::new("target/debug/miri");
            cmd.arg(path);
            let libs = Path::new(&sysroot).join("lib");
            let sysroot = libs.join("rustlib").join(&host).join("lib");
            let paths = std::env::join_paths(&[libs, sysroot]).unwrap();
            cmd.env(compiletest::procsrv::dylib_env_var(), paths);
            cmd.env("MIRI_SYSROOT", Path::new(&std::env::var("HOME").unwrap()).join(".xargo").join("HOST"));

            match cmd.output() {
                Ok(ref output) if output.status.success() => {
                    success += 1;
                    writeln!(stderr.lock(), "ok").unwrap()
                },
                Ok(output) => {
                    let output_err = std::str::from_utf8(&output.stderr).unwrap();
                    if let Some(text) = output_err.splitn(2, "no mir for `").nth(1) {
                        let end = text.find('`').unwrap();
                        mir_not_found.push(text[..end].to_string());
                        writeln!(stderr.lock(), "NO MIR FOR `{}`", &text[..end]).unwrap();
                    } else if let Some(text) = output_err.splitn(2, "can't find crate for `").nth(1) {
                        let end = text.find('`').unwrap();
                        crate_not_found.push(text[..end].to_string());
                        writeln!(stderr.lock(), "CAN'T FIND CRATE FOR `{}`", &text[..end]).unwrap();
                    } else {
                        for text in output_err.split("error: ").skip(1) {
                            let end = text.find('\n').unwrap_or(text.len());
                            let c_abi = "can't call C ABI function: ";
                            let unimplemented_intrinsic_s = "unimplemented intrinsic: ";
                            let unsupported_s = "miri does not support ";
                            let abi_s = "can't handle function with ";
                            let limit_s = "reached the configured maximum ";
                            if text.starts_with(c_abi) {
                                c_abi_fns.push(text[c_abi.len()..end].to_string());
                            } else if text.starts_with(unimplemented_intrinsic_s) {
                                unimplemented_intrinsic.push(text[unimplemented_intrinsic_s.len()..end].to_string());
                            } else if text.starts_with(unsupported_s) {
                                unsupported.push(text[unsupported_s.len()..end].to_string());
                            } else if text.starts_with(abi_s) {
                                abi.push(text[abi_s.len()..end].to_string());
                            } else if text.starts_with(limit_s) {
                                limits.push(text[limit_s.len()..end].to_string());
                            } else if text.find("aborting").is_none() {
                                failed.push(text[..end].to_string());
                            }
                        }
                        writeln!(stderr.lock(), "FAILED with exit code {:?}", output.status.code()).unwrap();
                        writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
                        writeln!(stderr.lock(), "stderr: \n {}", output_err).unwrap();
                    }
                }
                Err(e) => {
                    writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
                    panic!("failed to execute miri");
                },
            }
        }
        let stderr = std::io::stderr();
        let mut stderr = stderr.lock();
        writeln!(stderr, "{} success, {} no mir, {} crate not found, {} failed, \
                          {} C fn, {} ABI, {} unsupported, {} intrinsic",
                          success, mir_not_found.len(), crate_not_found.len(), failed.len(),
                          c_abi_fns.len(), abi.len(), unsupported.len(), unimplemented_intrinsic.len()).unwrap();
        writeln!(stderr, "# The \"other reasons\" errors").unwrap();
        writeln!(stderr, "(sorted, deduplicated)").unwrap();
        print_vec(&mut stderr, failed);

        writeln!(stderr, "# can't call C ABI function").unwrap();
        print_vec(&mut stderr, c_abi_fns);

        writeln!(stderr, "# unsupported ABI").unwrap();
        print_vec(&mut stderr, abi);

        writeln!(stderr, "# unsupported").unwrap();
        print_vec(&mut stderr, unsupported);

        writeln!(stderr, "# unimplemented intrinsics").unwrap();
        print_vec(&mut stderr, unimplemented_intrinsic);

        writeln!(stderr, "# mir not found").unwrap();
        print_vec(&mut stderr, mir_not_found);

        writeln!(stderr, "# crate not found").unwrap();
        print_vec(&mut stderr, crate_not_found);

        panic!("ran miri on rustc test suite. Test failing for convenience");
    } else {
        run_pass("tests/run-pass");
        run_pass("tests/run-pass-fullmir");
        for_all_targets(sysroot, |target| {
            miri_pass("tests/run-pass", &target, host, false);
            compile_fail(sysroot, "tests/compile-fail", &target, host, false);
        });
        miri_pass("tests/run-pass-fullmir", host, host, true);
        compile_fail(sysroot, "tests/compile-fail-fullmir", host, host, true);
    }
}

fn print_vec<W: std::io::Write>(stderr: &mut W, v: Vec<String>) {
    writeln!(stderr, "```").unwrap();
    for (n, s) in vec_to_hist(v).into_iter().rev() {
        writeln!(stderr, "{:4} {}", n, s).unwrap();
    }
    writeln!(stderr, "```").unwrap();
}

fn vec_to_hist<T: PartialEq + Ord>(mut v: Vec<T>) -> Vec<(usize, T)> {
    v.sort();
    let mut v = v.into_iter();
    let mut result = Vec::new();
    let mut current = v.next();
    'outer: while let Some(current_val) = current {
        let mut n = 1;
        for next in &mut v {
            if next == current_val {
                n += 1;
            } else {
                result.push((n, current_val));
                current = Some(next);
                continue 'outer;
            }
        }
        result.push((n, current_val));
        break;
    }
    result.sort();
    result
}
<|endoftext|>