Merge pull request #36 from yzhs/add-tests

Tests
This commit is contained in:
Tiffany Bennett 2018-11-15 20:30:49 -08:00 committed by GitHub
commit 8f7642efe9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 816 additions and 59 deletions

2
.codecov.yml Normal file
View file

@ -0,0 +1,2 @@
ignore:
- "src/bin"

36
.travis.yml Normal file
View file

@ -0,0 +1,36 @@
language: rust
sudo: required
rust:
- nightly
cache: cargo
script:
- rm -f target/debug/rink-* target/debug/query-* target/debug/canonicalize-*
- cargo build --verbose
- cargo test --verbose
addons:
apt:
packages:
- libcurl4-openssl-dev
- libelf-dev
- libdw-dev
- cmake
- gcc
- binutils-dev
- libiberty-dev
after_success: |
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
tar xzf master.tar.gz &&
cd kcov-master &&
mkdir build &&
cd build &&
cmake .. &&
make &&
make install DESTDIR=../../kcov-build &&
cd ../.. &&
rm -rf kcov-master &&
for file in target/debug/*-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
bash <(curl -s https://codecov.io/bash) &&
echo "Uploaded code coverage"

View file

@ -81,6 +81,7 @@ pub enum Query {
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub enum DatePattern {
Literal(String),
Match(String),
@ -312,3 +313,26 @@ impl fmt::Display for DateToken {
}
}
}
#[cfg(test)]
mod test {
use super::Expr::{self, *};
fn check<T: ::std::fmt::Display>(e: T, expected: &str) {
assert_eq!(format!("{}", e), expected);
}
impl From<i64> for Expr {
fn from(x: i64) -> Self {
Const(x.into())
}
}
#[test]
fn test_display_call() {
check(Call("f".into(), vec![]), "f()");
check(Call("f".into(), vec![1.into()]), "f(1)");
check(Call("f".into(), vec![1.into(), 2.into()]), "f(1, 2)");
check(Call("f".into(), vec![1.into(), 2.into(), 3.into()]), "f(1, 2, 3)");
}
}

View file

@ -119,8 +119,8 @@ pub fn parse_date<I>(
let value = u32::from_str_radix(&**s, 10).unwrap();
out.hour_mod_12 = Some(value % 12);
Ok(())
},
x => Err(format!("Expected 2-digit hour24, got {}", ts(x)))
}
x => Err(format!("Expected 2-digit hour12, got {}", ts(x))),
},
"hour24" => match tok {
Some(DateToken::Number(ref s, None)) if s.len() == 2 => {
@ -274,15 +274,11 @@ impl GenericDateTime {
}
}
pub fn try_decode(date: &[DateToken], context: &Context) -> Result<GenericDateTime, String> {
let mut best = None;
for pat in &context.datepatterns {
//println!("Tring {:?} against {}", date, show_datepattern(pat));
let attempt = || -> Result<GenericDateTime, (String, usize)> {
fn attempt(date: &[DateToken], pat: &[DatePattern]) -> Result<GenericDateTime, (String, usize)> {
let mut parsed = Parsed::new();
let mut tz = None;
let mut iter = date.iter().cloned().peekable();
let res = parse_date(&mut parsed, &mut tz, &mut iter, &pat[..]);
let res = parse_date(&mut parsed, &mut tz, &mut iter, pat);
let count = iter.count();
let res = if count > 0 && res.is_ok() {
Err(format!("Expected eof, got {}",
@ -328,8 +324,13 @@ pub fn try_decode(date: &[DateToken], context: &Context) -> Result<GenericDateTi
_ => Err((format!("Failed to construct a useful datetime"), count))
}
}
};
match attempt() {
}
pub fn try_decode(date: &[DateToken], context: &Context) -> Result<GenericDateTime, String> {
let mut best = None;
for pat in &context.datepatterns {
//println!("Tring {:?} against {}", date, show_datepattern(pat));
match attempt(date, pat) {
Ok(datetime) => return Ok(datetime),
Err((e, c)) => {
//println!("{}", e);
@ -474,3 +475,264 @@ impl Context {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pattern(s: &str) -> Vec<DatePattern> {
parse_datepattern(&mut s.chars().peekable()).unwrap()
}
fn parse_with_tz(date: Vec<DateToken>, pat: &str) -> (Result<(), String>, Parsed, Option<Tz>) {
let mut parsed = Parsed::new();
let mut tz = None;
let pat = pattern(pat);
let res = parse_date(&mut parsed, &mut tz, &mut date.into_iter().peekable(), &pat);
(res, parsed, tz)
}
fn parse(date: Vec<DateToken>, pat: &str) -> (Result<(), String>, Parsed) {
let (res, parsed, _) = parse_with_tz(date, pat);
(res, parsed)
}
#[test]
fn test_literal() {
let date = vec![DateToken::Literal("abc".into())];
let (res, parsed) = parse(date.clone(), "'abc'");
assert_eq!(parsed, Parsed::new());
assert!(res.is_ok());
let (res, parsed) = parse(date, "'def'");
assert_eq!(parsed, Parsed::new());
assert_eq!(res, Err("Expected `def`, got `abc`".into()));
}
#[test]
fn test_year_plus() {
let mut expected = Parsed::new();
expected.set_year(123).unwrap();
let date = vec![
DateToken::Plus,
DateToken::Number(format!("{}", expected.year.unwrap()), None),
];
let (res, parsed) = parse(date.clone(), "year");
assert!(res.is_ok());
assert_eq!(parsed, expected);
let date = vec![DateToken::Number(
format!("{}", expected.year.unwrap()),
None,
)];
let (res, parsed2) = parse(date.clone(), "year");
assert!(res.is_ok());
assert_eq!(parsed2, parsed);
}
#[test]
fn test_complicated_date_input() {
let mut expected = Parsed::new();
expected.set_year(123).unwrap();
expected.set_month(5).unwrap();
expected.set_day(2).unwrap();
expected.set_ampm(true).unwrap();
expected.set_hour(13).unwrap();
expected.set_minute(57).unwrap();
let date = vec![
DateToken::Number(format!("{}", expected.day.unwrap()), None),
DateToken::Space,
DateToken::Literal("Pm".into()),
DateToken::Dash,
DateToken::Number(format!("{:02}", expected.month.unwrap()), None),
DateToken::Colon,
DateToken::Number(format!("{}", expected.year.unwrap()), None),
DateToken::Space,
DateToken::Number(format!("{:02}", expected.hour_mod_12.unwrap()), None),
DateToken::Dash,
DateToken::Number(format!("{:02}", expected.minute.unwrap()), None),
DateToken::Space,
DateToken::Literal("May".into()),
];
let (res, parsed) = parse(date, "day meridiem-monthnum:year hour12-min monthname");
assert!(res.is_ok());
assert_eq!(parsed, expected);
}
#[test]
fn ad_bc() {
let year = -100;
let mut expected = Parsed::new();
expected.set_year(year + 1).unwrap();
expected.set_hour(7).unwrap();
let date = vec![
DateToken::Number(format!("{}", year.abs()), None),
DateToken::Space,
DateToken::Literal("bce".into()),
DateToken::Space,
DateToken::Number(format!("{:02}", expected.hour_mod_12.unwrap()), None),
DateToken::Space,
DateToken::Literal("am".into()),
];
let (res, parsed) = parse(date, "year adbc hour12 meridiem");
assert!(res.is_ok(), res.unwrap_err());
assert_eq!(parsed, expected);
}
#[test]
fn ad_bc_wrong() {
for date in vec![
vec![DateToken::Literal("foo".into())],
vec![DateToken::Plus],
] {
let (res, _) = parse(date, "adbc");
assert!(res.is_err());
}
}
#[test]
fn wrong_length_24h() {
let date = vec![DateToken::Number("7".into(), None)];
let (res, _) = parse(date, "hour24");
assert_eq!(res, Err(format!("Expected 2-digit hour24, got `{}`", 7)));
}
#[test]
fn test_24h() {
let (res, parsed) = parse(vec![DateToken::Number("23".into(), None)], "hour24");
assert!(res.is_ok());
assert_eq!(parsed.hour_div_12, Some(1));
assert_eq!(parsed.hour_mod_12, Some(11));
}
#[test]
fn seconds() {
let mut expected = Parsed::new();
expected.set_second(27).unwrap();
expected.set_nanosecond(12345).unwrap();
let date = vec![DateToken::Number("27".into(), Some("000012345".into()))];
let (res, parsed) = parse(date, "sec");
assert!(res.is_ok());
assert_eq!(parsed, expected);
let date = vec![DateToken::Number("27".into(), None)];
let (res, parsed) = parse(date, "sec");
assert!(res.is_ok());
expected.nanosecond = None;
assert_eq!(parsed, expected);
}
#[test]
fn test_offset() {
let date = vec![DateToken::Plus, DateToken::Number("0200".into(), None)];
let (res, parsed) = parse(date, "offset");
assert!(res.is_ok());
assert_eq!(parsed.offset, Some(2 * 3600));
let date = vec![
DateToken::Dash,
DateToken::Number("01".into(), None),
DateToken::Colon,
DateToken::Number("23".into(), None),
];
let (res, parsed) = parse(date, "offset");
assert!(res.is_ok());
assert_eq!(parsed.offset, Some(-(1 * 60 + 23) * 60));
let date = vec![DateToken::Literal("Europe/London".into())];
let (res, parsed, tz) = parse_with_tz(date, "offset");
assert!(res.is_ok(), res.unwrap_err());
assert_eq!(tz.unwrap(), Tz::Europe__London);
assert_eq!(parsed.offset, None);
}
#[test]
fn test_weekday() {
let date = vec![DateToken::Literal("saturday".into())];
let (res, parsed) = parse(date, "weekday");
assert!(res.is_ok());
assert_eq!(parsed.weekday, Some(Weekday::Sat));
let date = vec![DateToken::Literal("sun".into())];
assert!(parse(date, "weekday").0.is_ok());
let date = vec![DateToken::Literal("snu".into())];
assert_eq!(parse(date, "weekday").0, Err("Unknown weekday: snu".into()));
}
#[test]
fn test_monthname() {
for (i, &s) in [
"jan", "feb", "mar", "apr", "may", "june", "jul", "AUGUST", "SEp", "Oct", "novemBer",
"dec",
]
.into_iter()
.enumerate()
{
let date = vec![DateToken::Literal(s.into())];
let (res, parsed) = parse(date, "monthname");
assert!(res.is_ok());
assert_eq!(parsed.month, Some(i as u32 + 1));
}
let date = vec![DateToken::Literal("foobar".into())];
let (res, parsed) = parse(date, "monthname");
assert_eq!(res, Err("Unknown month name: foobar".into()));
assert_eq!(parsed.month, None);
}
#[test]
fn test_parse_datepattern() {
use self::DatePattern::*;
fn parse(s: &str) -> Result<Vec<DatePattern>, String> {
parse_datepattern(&mut s.chars().peekable())
}
assert_eq!(
parse("-:['abc']"),
Ok(vec![Dash, Colon, Optional(vec![Literal("abc".into())])])
);
assert!(parse("-:['abc'").is_err());
assert!(parse("*").is_err());
}
#[test]
fn test_attempt() {
use self::DateToken::*;
fn n(x: &str) -> DateToken {
Number(x.into(), None)
}
macro_rules! check_attempt {
($date:expr, $pat:expr) => {{
let pat = parse_datepattern(&mut $pat.chars().peekable()).unwrap();
attempt($date, pat.as_ref())
}};
}
let tz = Literal("Europe/London".into());
let date = &[n("23"), Space, n("05"), Space, tz.clone()];
let res = check_attempt!(date, "hour24 min offset");
assert!(res.is_ok(), "{:?}", res);
let date = &[n("23"), Space, tz.clone()];
let res = check_attempt!(date, "hour24 offset");
assert_eq!(
res,
Err(("Failed to construct a useful datetime".into(), 0))
);
let date = &[n("2018"), Space, n("01"), Space, n("01"), Space, tz.clone()];
let res = check_attempt!(date, "year monthnum day offset");
assert!(res.is_ok(), "{:?}", res);
}
}

View file

@ -56,6 +56,9 @@ impl<'a> Iterator for TokenIterator<'a> {
}
}
/**
* Compute the molar mass of a compound given its chemical formula.
*/
pub fn substance_from_formula(formula: &str,
symbols: &BTreeMap<String, String>,
substances: &BTreeMap<String, Substance>) -> Option<Substance> {

View file

@ -545,3 +545,83 @@ pub fn tokens(iter: &mut Iter) -> Vec<Token> {
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use ast::Expr;
fn do_parse(s: &str) -> Expr {
let mut iter = TokenIterator::new(s).peekable();
parse_term(&mut iter)
}
macro_rules! expect {
($expr:expr, $pattern:path, $expected:expr) => {
match do_parse($expr) {
$pattern(s) => assert_eq!(s, $expected),
x => panic!("{}", x),
}
};
}
#[test]
fn test_parse_term_plus() {
let expr = do_parse("+1");
if let Expr::Plus(x) = expr {
if let Expr::Const(x) = *x {
if x != 1.into() {
panic!("number != 1");
}
} else {
panic!("argument of x is not Expr::Const");
}
} else {
panic!("missing plus");
}
}
#[test]
fn test_missing_bracket() {
match do_parse("(") {
Expr::Error(ref s) => assert_eq!(s, "Expected ), got Eof"),
x => panic!("Wrong result: {}", x),
}
}
#[test]
fn test_escapes() {
expect!(
"\\\r",
Expr::Error,
"Expected term, got Error(\"Expected LF or CRLF line endings\")"
);
expect!("\\\r\n1", Expr::Const, 1.into());
expect!(
"\\a",
Expr::Error,
"Expected term, got Error(\"Invalid escape: \\\\a\")"
);
expect!(
"\\",
Expr::Error,
"Expected term, got Error(\"Unexpected EOF\")"
);
}
#[test]
fn test_float_leading_dot() {
use gmp::mpq::Mpq;
use gmp::mpz::Mpz;
let num = Mpz::from(123);
let den = Mpz::from(1000);
expect!(".123", Expr::Const, Num::Mpq(Mpq::ratio(&num, &den)));
}
#[test]
fn test_escaped_quotes() {
expect!("\"ab\\\"\"", Expr::Unit, "ab\"")
}
}

View file

@ -138,6 +138,7 @@ impl<'a> Iterator for TokenIterator<'a> {
}
if let Some('*') = self.0.next() {
if let Some(&'/') = self.0.peek() {
self.0.next();
return Some(Token::Comment(lines))
}
}
@ -357,7 +358,7 @@ impl<'a> Iterator for TokenIterator<'a> {
let mut frac = String::new();
self.0.next();
while let Some(c) = self.0.peek().cloned() {
if x.is_digit(10) {
if c.is_digit(10) {
self.0.next();
frac.push(c);
} else {
@ -563,7 +564,8 @@ fn parse_term(iter: &mut Iter) -> Expr {
},
Token::Percent => Expr::Unit("percent".to_owned()),
Token::Date(toks) => Expr::Date(toks),
x => Expr::Error(format!("Expected term, got {}", describe(&x)))
Token::Comment(_) => parse_term(iter),
x => Expr::Error(format!("Expected term, got {}", describe(&x))),
}
}

View file

@ -27,6 +27,22 @@ fn test(input: &str, output: &str) {
});
}
fn test_starts_with(input: &str, output: &str) {
let mut iter = text_query::TokenIterator::new(input.trim()).peekable();
let expr = text_query::parse_query(&mut iter);
CONTEXT.with(|ctx| {
let res = ctx.eval_outer(&expr);
let res = match res {
Ok(v) => v.to_string(),
Err(v) => v.to_string(),
};
assert!(
res.starts_with(output),
format!("\n'{}' !=\n'{}'", res, output)
);
});
}
#[test]
fn test_definition() {
test("watt", "Definition: watt = J / s = 1 watt (power; kg m^2 / s^3)");
@ -108,9 +124,31 @@ fn test_factorize() {
#[test]
fn test_conformance() {
test("W -> J",
test(
"W -> J",
"Conformance error: 1 watt (power) != 1 joule (energy)\n\
Suggestions: multiply left side by time, multiply right side by frequency");
Suggestions: multiply left side by time, multiply right side by frequency",
);
test(
"W/s -> J^2",
"Conformance error: 1 newton^2 / kilogram != 1 joule^2\n\
Suggestions: multiply left side by moment_of_inertia, divide right side by moment_of_inertia",
);
test(
"m^2 -> kg^2",
"Conformance error: 1 meter^2 (area) != 1 kilogram^2 (kg^2)\n\
Suggestions: multiply left side by linear_density^2, multiply right side by area / mass^2",
);
test(
"c -> kg",
"Conformance error: 299792458 meter / second (velocity) != 1 kilogram (mass)\n\
Suggestions: multiply left side by mass time / length, multiply right side by length / mass time"
);
test(
"1/m -> 'abc'",
"Conformance error: 1 / meter (m^-1) != 1 abc (abc)\n\
Suggestions: multiply left side by 'abc' length, divide right side by 'abc' length",
);
}
#[test]
@ -153,6 +191,13 @@ fn test_bases() {
test("pi m -> bin m", "approx. 11.00100 meter (length)");
test("100K -> hex °C", "Conversion to °C is not defined in base 16");
test("now -> hex +00:00", "Conversion to 00:00 is not defined in base 16");
test("256 -> base 16", "100 (dimensionless)");
test(
"123 -> base 37",
"Unsupported base 37, must be from 2 to 36",
);
test("123 -> base 0xf", "Expected decimal base, got hex");
}
#[test]
@ -221,3 +266,306 @@ fn percent_operator() {
test("120% 2", "2.4 (dimensionless)");
test("% 1", "0.01 (dimensionless)");
}
#[test]
fn test_kilosecond() {
test("1ks", "16 minute, 40 second (time)");
test("1kss", "16 minute, 40 second (time)");
}
#[test]
#[should_panic]
fn test_second_double_prefix() {
let mut iter = text_query::TokenIterator::new("mks").peekable();
let expr = text_query::parse_query(&mut iter);
CONTEXT.with(|ctx| {
ctx.eval_outer(&expr).unwrap();
});
}
#[test]
fn test_missing_substance() {
test(
"density of flubber",
"No such unit flubber, did you mean flour?",
);
}
#[test]
fn test_missing_property() {
test("mass of flour", "No such property mass of flour");
}
#[test]
fn test_unary_operators() {
test("+--+42", "42 (dimensionless)");
test("++-+42", "-42 (dimensionless)");
}
#[test]
fn test_equals() {
test("a = kg N / W^2", "1 second^2 / gray meter");
test(
"1 = kg",
"= is currently only used for inline unit definitions: expected unit, got 1",
);
}
#[test]
fn mismatched_units() {
test(
"W - kg",
"Subtraction of units with mismatched units is not meaningful: \
<1 watt (power)> - <1 kilogram (mass)>",
);
}
#[test]
fn temperature_with_dimension() {
test("kg °C", "Expected dimensionless, got: <1 kilogram (mass)>");
}
#[test]
fn test_functions() {
test("exp(ln(10))", "approx. 10.00000 (dimensionless)");
test("log2(65536)", "approx. 16 (dimensionless)");
test("10^log10(123)", "approx. 123.0000 (dimensionless)");
test("log(27, 3)", "approx. 3 (dimensionless)");
test("sin(pi/2)", "approx. 1 (dimensionless)");
test("cos(asin(0.5) - pi/2)", "approx. 0.5000000 (dimensionless)");
test("atan(tan(0.42))", "approx. 0.4199999 (dimensionless)");
test("acos(1)", "approx. 0 (dimensionless)");
test("acosh(cosh(1))", "approx. 1 (dimensionless)");
test("asinh(sinh(0.123))", "approx. 0.1230000 (dimensionless)");
test("atanh(tanh(1.23))", "approx. 1.230000 (dimensionless)");
test("hypot(3 m, 4 m)", "approx. 5 meter (length)");
test("atan2(7, 6)", "approx. 0.8621700 (dimensionless)");
}
#[test]
fn test_equal_rhs() {
test("1 -> a=3", "1/3, approx. 0.3333333 a (dimensionless)");
}
#[test]
fn test_pow_with_dimension() {
test(
"2^m",
"Exponent must be dimensionless: <2 (dimensionless)> ^ <1 meter (length)>",
);
}
#[test]
fn test_reciprocal_conversion() {
test(
"miles / gallon -> l / 100km",
"Conformance error: approx. 425143.7 / meter^2 (fuel_efficiency) != 10000 micrometer^2 (area)\n\
Suggestions: Reciprocal conversion, invert one side",
);
}
#[test]
fn test_non_conversion_input() {
test("g", "Definition: gram = (1 / 1000) kg = 1 gram (mass; kg)");
}
#[test]
fn test_of_non_substance() {
test("mass of 1kg", "Not defined: mass of <1 kilogram (mass)>");
}
#[test]
fn test_mul_not_defined() {
test(
"#2018-10-03# * kg",
"Operation is not defined: <1 (dimensionless)> * <2018-10-03 00:00:00 +00:00>",
);
}
#[test]
fn test_log_base_with_dimension() {
test(
"log(10, 5m)",
"Base must be dimensionless: log(10 (dimensionless), 5 meter (length))",
);
}
#[test]
fn test_hypot_dimension_mismatch() {
test(
"hypot(3s, 4m)",
"Arguments to hypot must have matching dimensionality: \
hypot(3 second (time), 4 meter (length))",
);
}
#[test]
fn test_radix() {
test("0xff", "255 (dimensionless)");
test(
"0off",
"Expected term, got <Malformed octal literal: No digits after 0o>",
);
test("0b101010", "42 (dimensionless)");
test("0o10lux", "8 lux (illuminance)");
}
#[test]
fn test_comments() {
test("1 // *3", "1 (dimensionless)");
test("1 + /*2*/ 3", "4 (dimensionless)");
test("1 + /*2", "Expected term, got <Expected `*/`, got EOF>");
}
#[test]
fn test_leading_dot() {
test(".12345Ee3", "123.45 (dimensionless)");
}
#[test]
fn test_underscores_in_number() {
test("123_456\u{2009}789", "123456789 (dimensionless)");
}
#[test]
fn test_date_input() {
test_starts_with(
"#2018-10-04T09:13:25.123 +2:00#",
"2018-10-04 11:13:25.123 +02:00",
);
}
#[test]
fn test_unicode_arrow() {
test("pound → kg", "approx. 0.4535923 kilogram (mass)");
}
#[test]
fn test_attributes() {
test(
"roman mile",
"Definition: romanmile = 8 stadia = 1.48 kilometer (length; m)",
);
test(
"romanmile",
"Definition: romanmile = 8 stadia = 1.48 kilometer (length; m)",
);
test(
"international",
"Attribute must be followed by ident, got eof",
);
}
#[test]
fn test_search() {
test(
"search cm",
"Search results: CM¥ (money), cmil (area), cminv (energy), \
cmcapacitance (capacitance), sccm (power)",
);
}
#[test]
fn test_digits() {
test(
"ln(1234) -> digits 100",
"approx. 7.11801620446533345187845043255947530269622802734375 (dimensionless)",
);
}
#[test]
fn test_escapes() {
test("'ab\\'cd\\n\\t'", "1 ab'cd\n\t (ab'cd\n\t)");
test("'x\\a'", "Expected term, got <Invalid escape sequence \\a>");
}
#[test]
fn test_missing_bracket() {
test("(1+2", "Expected `)`, got eof");
}
#[test]
fn test_to_timezone() {
test_starts_with(
"#2000-01-01 12:46 Asia/Tokyo# -> GMT",
"2000-01-01 03:46:00 GMT",
);
}
#[test]
fn test_missing_base() {
test("3 -> base", "Expected decimal base, got eof");
}
#[test]
fn test_date_difference() {
test_starts_with("now - (now - 3days)", "2 day, 23 hour, 59 minute, 59.99");
}
#[test]
fn test_date_time_formats() {
test_starts_with("#1970-01-01 10:30 GMT#", "1970-01-01 10:30:00 GMT");
test_starts_with("(now-#10:30#) - (now-#11:30#)", "59 minute, 59.99");
}
#[test]
fn test_no_calls_on_rhs() {
test(
"1 -> sin(2)",
"Calls are not allowed in the right hand side of conversions",
);
}
#[test]
fn test_conversion_to_list() {
test(
"ly -> teram,Gm,Mm,km,m",
"9.46 kiloteram, 730 gigameter, 472 megameter, 580 kilometer, 800 meter (length)",
);
test(
"1 -> m, hour",
"Units in unit list must conform: <1 meter (length)> ; <3.6 kilosecond (time)>",
);
test(
"1g -> m, cm",
"Conformance error: 1 gram (mass) != 1 meter (length)\n\
Suggestions: divide left side by linear_density, multiply right side by linear_density",
);
}
#[test]
fn test_definition_with_doc() {
test(
"mass",
"Definition: kilogram = base unit of mass. Equal to the mass of the \
international prototype of the kilogram. 3rd CGPM (1901, CR, 70).",
);
}
#[test]
fn test_try_decode_fail() {
test(
"#abc#",
"Most likely pattern `--monthnum-day[ hour24:min[:sec][ offset]]` failed: \
Expected `-`, got `abc`",
)
}
#[test]
fn test_formula() {
test(
"methane=CH4",
"CH4: molar_mass = 0.01604276 kilogram / mole",
);
test(
"NaCl",
"NaCl: molar_mass = approx. 0.05844246 kilogram / mole",
);
test(
"C8H10N4O2",
"C8H10N4O2: molar_mass = approx. 0.1941931 kilogram / mole",
);
test("C60", "C60: molar_mass = 0.72066 kilogram / mole");
}