From 08d786ee5a547238a541e565b6ef3ee288995cbd Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 24 Sep 2018 18:49:19 +0200 Subject: [PATCH 01/84] Add another test for base conversions --- tests/query.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/query.rs b/tests/query.rs index 44dd8df..204dea2 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -153,6 +153,7 @@ 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] From 9718c1675dfa7e6770c2db1d87c94a80cfb8916e Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 27 Sep 2018 19:10:28 +0200 Subject: [PATCH 02/84] Add Travis and Codecov configuration --- .codecov.yml | 2 ++ .travis.yml | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .codecov.yml create mode 100644 .travis.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..7cc32ca --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "src/bin" diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2663894 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,38 @@ +language: rust +sudo: required +rust: + - nightly +before_script: + - rustup component add clippy-preview +cache: cargo + +script: + - cargo build --verbose + - cargo test --verbose + - cargo clippy --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/rink-*[^\.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" From 0ddb576443d9686437392c62b42cbc72eac34b82 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 27 Sep 2018 20:43:34 +0200 Subject: [PATCH 03/84] Do not use clippy in Travis build for now --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2663894..f99fd61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,11 @@ language: rust sudo: required rust: - nightly -before_script: - - rustup component add clippy-preview cache: cargo script: - cargo build --verbose - cargo test --verbose - - cargo clippy --verbose addons: apt: From eb48aecf841c3e48da8a16b2d12f871278ad5b87 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 27 Sep 2018 21:27:12 +0200 Subject: [PATCH 04/84] Run all tests when collecting coverage information --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f99fd61..8791212 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,6 @@ after_success: | make install DESTDIR=../../kcov-build && cd ../.. && rm -rf kcov-master && - for file in target/debug/rink-*[^\.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 && + 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" From 592c76cc29b76c61600c9934dcc8ec2e2a205db1 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 28 Sep 2018 00:16:04 +0200 Subject: [PATCH 05/84] Test Expr::fmt --- src/ast.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ast.rs b/src/ast.rs index b23b0d2..ec2a837 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -312,3 +312,26 @@ impl fmt::Display for DateToken { } } } + +#[cfg(test)] +mod test { + use super::Expr::{self, *}; + + fn check(e: T, expected: &str) { + assert_eq!(format!("{}", e), expected); + } + + impl From 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)"); + } +} From 579aeb4ea0c68cb4ccd7d7eb7eafb85daef81dc7 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 28 Sep 2018 09:36:37 +0200 Subject: [PATCH 06/84] Test more parts of Context::lookup --- tests/query.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 204dea2..5fc4bb2 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -222,3 +222,19 @@ 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(); + }); +} From 260de942d41550b77c709eb44c886d98ddf2fa73 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Sat, 29 Sep 2018 08:51:59 +0200 Subject: [PATCH 07/84] Test conformance errors more thoroughly --- tests/query.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/query.rs b/tests/query.rs index 5fc4bb2..6628c82 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -108,9 +108,17 @@ fn test_factorize() { #[test] fn test_conformance() { - test("W -> J", - "Conformance error: 1 watt (power) != 1 joule (energy)\n\ - Suggestions: multiply left side by time, multiply right side by frequency"); + test( + "W -> J", + "Conformance error: 1 watt (power) != 1 joule (energy)\n\ + 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] From 3e2003e31c8a8271bbffa305b80e2f5e37413e0a Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Sun, 30 Sep 2018 09:05:06 +0200 Subject: [PATCH 08/84] Cover more cases in Context::describe_unit --- tests/query.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/query.rs b/tests/query.rs index 6628c82..705ecd4 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -113,12 +113,26 @@ fn test_conformance() { "Conformance error: 1 watt (power) != 1 joule (energy)\n\ 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] From bb1b0774f10273de76a56391c075fdd7e0f1ca5d Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 07:55:41 +0200 Subject: [PATCH 09/84] Add unit tests to gnu_units --- src/gnu_units.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index 37ba02e..1b6ec38 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -545,3 +545,37 @@ pub fn tokens(iter: &mut Iter) -> Vec { } out } + +#[cfg(test)] +mod tests { + use super::*; + use ast::Expr; + + #[test] + fn test_parse_term_plus() { + let mut iter = TokenIterator::new("+1").peekable(); + let expr = parse_term(&mut iter); + + 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() { + let mut iter = TokenIterator::new("(").peekable(); + let expr = parse_term(&mut iter); + match expr { + Expr::Error(ref s) if s == "Expected ), got eof" => (), + x => panic!("Wrong result: {}", x), + } + } +} From 9f742ffc3d3f0f31125c18e1c79736e3b7b84203 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 08:21:33 +0200 Subject: [PATCH 10/84] Fix missing brackets test --- src/gnu_units.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index 1b6ec38..6dff300 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -574,7 +574,7 @@ mod tests { let mut iter = TokenIterator::new("(").peekable(); let expr = parse_term(&mut iter); match expr { - Expr::Error(ref s) if s == "Expected ), got eof" => (), + Expr::Error(ref s) => assert_eq!(s, "Expected ), got Eof"), x => panic!("Wrong result: {}", x), } } From 9e0bd52e2e96e8f81ed1c77ddc4b25619bf2d60e Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 09:45:40 +0200 Subject: [PATCH 11/84] Move duplicate code out of tests --- src/gnu_units.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index 6dff300..2fe8027 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -551,10 +551,14 @@ mod tests { use super::*; use ast::Expr; + fn do_parse(s: &str) -> Expr { + let mut iter = TokenIterator::new(s).peekable(); + parse_term(&mut iter) + } + #[test] fn test_parse_term_plus() { - let mut iter = TokenIterator::new("+1").peekable(); - let expr = parse_term(&mut iter); + let expr = do_parse("+1"); if let Expr::Plus(x) = expr { if let Expr::Const(x) = *x { @@ -571,9 +575,7 @@ mod tests { #[test] fn test_missing_bracket() { - let mut iter = TokenIterator::new("(").peekable(); - let expr = parse_term(&mut iter); - match expr { + match do_parse("(") { Expr::Error(ref s) => assert_eq!(s, "Expected ), got Eof"), x => panic!("Wrong result: {}", x), } From 51df05702fb43ce69bd1a592050d0ab91b971184 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 10:15:59 +0200 Subject: [PATCH 12/84] Add more unit tests for gnu_units --- src/gnu_units.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index 2fe8027..cf3717e 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -556,6 +556,15 @@ mod tests { 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"); @@ -580,4 +589,15 @@ mod tests { 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()) + } } From 999f59bc78a89e2d6d97a77dc59b0875264749ca Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 11:24:33 +0200 Subject: [PATCH 13/84] Remove test executables after measuring coverage --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8791212..c77e05a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,4 +32,6 @@ after_success: | 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" + echo "Uploaded code coverage" && + cd target/debug && + rm rink-* query-* canonicalize-* From 30fcab3528f6ab0e1c0db4105433ed6ba72885b3 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 11:31:06 +0200 Subject: [PATCH 14/84] Test escape handling in gnu_units --- src/gnu_units.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index cf3717e..b541d1c 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -597,7 +597,17 @@ mod tests { Expr::Error, "Expected term, got Error(\"Expected LF or CRLF line endings\")" ); + expect!("\\\r\n1", Expr::Const, 1.into()); - 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\")" + ); } } From a5ccdaf1b94e2ba021086bba369d2289c836e85f Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 12:03:48 +0200 Subject: [PATCH 15/84] Test floats with leading dots in gnu_units --- src/gnu_units.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index b541d1c..21ab48b 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -610,4 +610,13 @@ mod tests { "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))); + } } From e92d0387502d66e2753e333cc35fc7eda8e59d43 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 12:11:06 +0200 Subject: [PATCH 16/84] Test escaped quotes in gnu_units --- src/gnu_units.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gnu_units.rs b/src/gnu_units.rs index 21ab48b..926c33c 100644 --- a/src/gnu_units.rs +++ b/src/gnu_units.rs @@ -619,4 +619,9 @@ mod tests { 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\"") + } } From f678261856ce25f41bbe91b83b63a58ecadbb7df Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 12:56:41 +0200 Subject: [PATCH 17/84] Add first test for date parser --- src/date.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/date.rs b/src/date.rs index 4fb2411..68b7dad 100644 --- a/src/date.rs +++ b/src/date.rs @@ -474,3 +474,29 @@ impl Context { None } } + +#[cfg(test)] +mod tests { + use super::*; + + fn match_literal(s: &str, expected: &str) -> (Result<(), String>, Parsed) { + let mut parsed = Parsed::new(); + let mut tz = None; + let mut date = vec![DateToken::Literal(s.into())].into_iter().peekable(); + let pat = &[DatePattern::Literal(expected.into())]; + let res = parse_date(&mut parsed, &mut tz, &mut date, pat); + + (res, parsed) + } + + #[test] + fn test_literal() { + let (res, parsed) = match_literal("abc", "abc"); + assert_eq!(parsed, Parsed::new()); + assert!(res.is_ok()); + + let (res, parsed) = match_literal("abc", "def"); + assert_eq!(parsed, Parsed::new()); + assert_eq!(res, Err("Expected `def`, got `abc`".into())); + } +} From 443750094176391606571ab327372a89c8ce9ec8 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 13:13:19 +0200 Subject: [PATCH 18/84] Run date parser on "+123" --- src/date.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/date.rs b/src/date.rs index 68b7dad..d5231c2 100644 --- a/src/date.rs +++ b/src/date.rs @@ -479,24 +479,41 @@ impl Context { mod tests { use super::*; - fn match_literal(s: &str, expected: &str) -> (Result<(), String>, Parsed) { + fn parse(date: Vec, pat: &[DatePattern]) -> (Result<(), String>, Parsed) { let mut parsed = Parsed::new(); let mut tz = None; - let mut date = vec![DateToken::Literal(s.into())].into_iter().peekable(); - let pat = &[DatePattern::Literal(expected.into())]; - let res = parse_date(&mut parsed, &mut tz, &mut date, pat); + let res = parse_date(&mut parsed, &mut tz, &mut date.into_iter().peekable(), pat); (res, parsed) } #[test] fn test_literal() { - let (res, parsed) = match_literal("abc", "abc"); + let date = vec![DateToken::Literal("abc".into())]; + let pat = &[DatePattern::Literal("abc".into())]; + let (res, parsed) = parse(date.clone(), pat); assert_eq!(parsed, Parsed::new()); assert!(res.is_ok()); - let (res, parsed) = match_literal("abc", "def"); + let pat = &[DatePattern::Literal("def".into())]; + let (res, parsed) = parse(date, pat); 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 pat = &[DatePattern::Match("year".into())]; + let (res, parsed) = parse(date.clone(), pat); + + assert_eq!(parsed, expected); + assert!(res.is_ok()); + } } From 00dcdd4f527e53d84b3733e91d0f0ebc23003107 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 13:28:06 +0200 Subject: [PATCH 19/84] Simplify tests in date.rs --- src/date.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/date.rs b/src/date.rs index d5231c2..954a906 100644 --- a/src/date.rs +++ b/src/date.rs @@ -479,10 +479,15 @@ impl Context { mod tests { use super::*; - fn parse(date: Vec, pat: &[DatePattern]) -> (Result<(), String>, Parsed) { + fn pattern(s: &str) -> Vec { + parse_datepattern(&mut s.chars().peekable()).unwrap() + } + + fn parse(date: Vec, pat: &str) -> (Result<(), String>, Parsed) { let mut parsed = Parsed::new(); let mut tz = None; - let res = parse_date(&mut parsed, &mut tz, &mut date.into_iter().peekable(), pat); + let pat = pattern(pat); + let res = parse_date(&mut parsed, &mut tz, &mut date.into_iter().peekable(), &pat); (res, parsed) } @@ -490,13 +495,11 @@ mod tests { #[test] fn test_literal() { let date = vec![DateToken::Literal("abc".into())]; - let pat = &[DatePattern::Literal("abc".into())]; - let (res, parsed) = parse(date.clone(), pat); + let (res, parsed) = parse(date.clone(), "'abc'"); assert_eq!(parsed, Parsed::new()); assert!(res.is_ok()); - let pat = &[DatePattern::Literal("def".into())]; - let (res, parsed) = parse(date, pat); + let (res, parsed) = parse(date, "'def'"); assert_eq!(parsed, Parsed::new()); assert_eq!(res, Err("Expected `def`, got `abc`".into())); } @@ -510,9 +513,7 @@ mod tests { DateToken::Plus, DateToken::Number(format!("{}", expected.year.unwrap()), None), ]; - let pat = &[DatePattern::Match("year".into())]; - let (res, parsed) = parse(date.clone(), pat); - + let (res, parsed) = parse(date.clone(), "year"); assert_eq!(parsed, expected); assert!(res.is_ok()); } From 4474c376b0c8302cc0409da79d7908c35bea144a Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 13:28:21 +0200 Subject: [PATCH 20/84] Add another test case in date.rs --- src/date.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/date.rs b/src/date.rs index 954a906..ef64981 100644 --- a/src/date.rs +++ b/src/date.rs @@ -514,7 +514,15 @@ mod tests { DateToken::Number(format!("{}", expected.year.unwrap()), None), ]; let (res, parsed) = parse(date.clone(), "year"); - assert_eq!(parsed, expected); 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); } } From b3670b85598ce33580de380b5a74dc73cf0f53c5 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 13:45:10 +0200 Subject: [PATCH 21/84] Add test for a bunch of cases in parse_date --- src/date.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/date.rs b/src/date.rs index ef64981..d67ab69 100644 --- a/src/date.rs +++ b/src/date.rs @@ -525,4 +525,35 @@ mod tests { 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"); + res.unwrap(); + assert_eq!(parsed, expected); + } } From 0667110c2ec735107e116faa5dec5c0c1b6066f8 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 13:58:46 +0200 Subject: [PATCH 22/84] Try deleting test executables before building --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c77e05a..4ff14a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ rust: cache: cargo script: + - rm -f target/debug/rink-* target/debug/query-* target/debug/canonicalize-* - cargo build --verbose - cargo test --verbose @@ -32,6 +33,4 @@ after_success: | 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" && - cd target/debug && - rm rink-* query-* canonicalize-* + echo "Uploaded code coverage" From a6d044511dec97ef5c78f3be158b684dd80c294c Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 14:28:08 +0200 Subject: [PATCH 23/84] More parse_date tests --- src/date.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/date.rs b/src/date.rs index d67ab69..c87cc93 100644 --- a/src/date.rs +++ b/src/date.rs @@ -553,7 +553,36 @@ mod tests { ]; let (res, parsed) = parse(date, "day meridiem-monthnum:year hour12-min monthname"); - res.unwrap(); + 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 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))); + } } From c203481a08b81ea841a44d63bfc7b9a1c9b1f7f6 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 14:32:51 +0200 Subject: [PATCH 24/84] More parse_date tests --- src/date.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/date.rs b/src/date.rs index c87cc93..b2e01d9 100644 --- a/src/date.rs +++ b/src/date.rs @@ -579,10 +579,33 @@ mod tests { 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 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); + } } From 64f72fe7434c3d3c10e32c9c6c362e74282f9a94 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 14:38:01 +0200 Subject: [PATCH 25/84] Test parsing offsets --- src/date.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/date.rs b/src/date.rs index b2e01d9..42e061a 100644 --- a/src/date.rs +++ b/src/date.rs @@ -608,4 +608,22 @@ mod tests { assert!(res.is_ok()); 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)); + } } From 1f9fda92fb98bf4f8a418ca5c52af13f7d43cc67 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 14:48:16 +0200 Subject: [PATCH 26/84] Test weekday parser --- src/date.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/date.rs b/src/date.rs index 42e061a..514306c 100644 --- a/src/date.rs +++ b/src/date.rs @@ -626,4 +626,12 @@ mod tests { assert!(res.is_ok()); assert_eq!(parsed.offset, Some(-(1 * 60 + 23) * 60)); } + + #[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)); + } } From cbc5c896708fc93623dc686b7b6da382870e65c6 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 14:48:37 +0200 Subject: [PATCH 27/84] Test parsing timezone names --- src/date.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/date.rs b/src/date.rs index 514306c..bfca1c7 100644 --- a/src/date.rs +++ b/src/date.rs @@ -483,12 +483,17 @@ mod tests { parse_datepattern(&mut s.chars().peekable()).unwrap() } - fn parse(date: Vec, pat: &str) -> (Result<(), String>, Parsed) { + fn parse_with_tz(date: Vec, pat: &str) -> (Result<(), String>, Parsed, Option) { 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, pat: &str) -> (Result<(), String>, Parsed) { + let (res, parsed, _) = parse_with_tz(date, pat); (res, parsed) } @@ -625,12 +630,18 @@ mod tests { 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"); + let (res, parsed) = parse(date, "weekday"); assert!(res.is_ok()); assert_eq!(parsed.weekday, Some(Weekday::Sat)); } From 7787156d883ee1609533320a05c486a618bdc0b0 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 15:00:35 +0200 Subject: [PATCH 28/84] Test parse_date on 24h and plain seconds --- src/date.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/date.rs b/src/date.rs index bfca1c7..e1dcbf4 100644 --- a/src/date.rs +++ b/src/date.rs @@ -602,6 +602,14 @@ mod tests { 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(); @@ -612,6 +620,12 @@ mod tests { 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] From 592b7e9d41545b25a3842bd2391a258be91fb78a Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 08:57:40 +0200 Subject: [PATCH 29/84] Test more cases in eval() --- tests/query.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 705ecd4..b8df6df 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -260,3 +260,40 @@ fn test_second_double_prefix() { 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)>", + ); +} From 79f257524513a370d9e091815a34caa01ed83385 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 09:20:14 +0200 Subject: [PATCH 30/84] Test some functions --- tests/query.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index b8df6df..f466ad9 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -297,3 +297,27 @@ fn mismatched_units() { <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)"); +} From 68f2db70552e02b3f1d92f90bbfd794cfa994351 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 09:56:00 +0200 Subject: [PATCH 31/84] More tests for eval's helper functions --- tests/query.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index f466ad9..443c69e 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -321,3 +321,25 @@ fn test_functions() { 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", + ); +} From fdcffecddc0f6644deed1fda1fb65ebe9f2ca09d Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 12:54:34 +0200 Subject: [PATCH 32/84] Add test for eval_outer_expr_unit_cond --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 443c69e..08bb31e 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -343,3 +343,8 @@ fn test_reciprocal_conversion() { Suggestions: Reciprocal conversion, invert one side", ); } + +#[test] +fn test_non_conversion_input() { + test("g", "Definition: gram = (1 / 1000) kg = 1 gram (mass; kg)"); +} From ff8f243177509937ef388c71c33e7bb9a839bec1 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 19:12:11 +0200 Subject: [PATCH 33/84] Test another case in eval() --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 08bb31e..64db9e6 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -348,3 +348,8 @@ fn test_reciprocal_conversion() { 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)>"); +} From f2f57612dcb44bb98b45ac3393aa7f43f0655f52 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 19:16:46 +0200 Subject: [PATCH 34/84] Test error case in eval() --- tests/query.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 64db9e6..de44013 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -353,3 +353,11 @@ fn test_non_conversion_input() { 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>", + ); +} From b25de640bdecc4162401fdd5c2e21a8f1f59799a Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 20:01:43 +0200 Subject: [PATCH 35/84] Add tests for binary function error cases --- tests/query.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index de44013..ea081a5 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -361,3 +361,20 @@ fn test_mul_not_defined() { "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))", + ); +} From 4f8d044099d5177e2ba839579287c8f9ca8253f5 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 21:43:41 +0200 Subject: [PATCH 36/84] Start testing input in different bases --- tests/query.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index ea081a5..a1ad242 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -378,3 +378,13 @@ fn test_hypot_dimension_mismatch() { hypot(3 second (time), 4 meter (length))", ); } + +#[test] +fn test_radix() { + test("0xff", "255 (dimensionless)"); + test( + "0off", + "Expected term, got ", + ); + test("0b101010", "42 (dimensionless)"); +} From 9ae738ad159e8f6f0e53dcdf38a91ec8e5a2d961 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 22:03:36 +0200 Subject: [PATCH 37/84] Properly handle comments: skip closing slash When we reach the end of an inline comment, we need to call self.0.next(). Otherwise, the closing slash is treated as a separate token. Ignore `Token::Comment` in `parse_term`, do not return an error. Add regression test. --- src/text_query.rs | 4 +++- tests/query.rs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/text_query.rs b/src/text_query.rs index 7b9f316..ffbf812 100644 --- a/src/text_query.rs +++ b/src/text_query.rs @@ -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)) } } @@ -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))), } } diff --git a/tests/query.rs b/tests/query.rs index a1ad242..7858f2c 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -388,3 +388,9 @@ fn test_radix() { ); test("0b101010", "42 (dimensionless)"); } + +#[test] +fn test_comments() { + test("1 // *3", "1 (dimensionless)"); + test("1 + /*2*/ 3", "4 (dimensionless)"); +} From 2a19addc0be4c2f0663feae8cd81dd6c984f117f Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 22:25:37 +0200 Subject: [PATCH 38/84] Add a few more tests for text_query.rs --- tests/query.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 7858f2c..88a2fdd 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -393,4 +393,15 @@ fn test_radix() { fn test_comments() { test("1 // *3", "1 (dimensionless)"); test("1 + /*2*/ 3", "4 (dimensionless)"); + test("1 + /*2", "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)"); } From f07b33c56b7b2c76c0e068f6b89d6b24180e233e Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 10:20:21 +0200 Subject: [PATCH 39/84] Another test for the date and comment parsers --- tests/query.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/query.rs b/tests/query.rs index 88a2fdd..c78d217 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -393,7 +393,7 @@ fn test_radix() { fn test_comments() { test("1 // *3", "1 (dimensionless)"); test("1 + /*2*/ 3", "4 (dimensionless)"); - test("1 + /*2", "Expected `*/`, got EOF"); + test("1 + /*2", "Expected term, got "); } #[test] @@ -405,3 +405,23 @@ fn test_leading_dot() { fn test_underscores_in_number() { test("123_456\u{2009}789", "123456789 (dimensionless)"); } + +#[test] +fn test_date_input() { + let input = "#2018-10-04T09:13:25 +2:00#"; + let expected = "2018-10-04 11:13:25 +02:00"; + + 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(expected), + format!("\n'{}' !=\n'{}'", res, expected) + ); + }); +} From 7f1d07e85e2781044d9384c94be6336c0efb20fd Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:04:32 +0200 Subject: [PATCH 40/84] Test fractional seconds --- tests/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/query.rs b/tests/query.rs index c78d217..9fa3054 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -408,8 +408,8 @@ fn test_underscores_in_number() { #[test] fn test_date_input() { - let input = "#2018-10-04T09:13:25 +2:00#"; - let expected = "2018-10-04 11:13:25 +02:00"; + let input = "#2018-10-04T09:13:25.123 +2:00#"; + let expected = "2018-10-04 11:13:25.123 +02:00"; let mut iter = text_query::TokenIterator::new(input.trim()).peekable(); let expr = text_query::parse_query(&mut iter); From 63a1d9cb85efa07358577223805097d0e00fcc75 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:21:08 +0200 Subject: [PATCH 41/84] Add more tests for text_query --- tests/query.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 9fa3054..f3f20dc 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -387,6 +387,7 @@ fn test_radix() { "Expected term, got ", ); test("0b101010", "42 (dimensionless)"); + test("0o10lux", "8 lux (illuminance)"); } #[test] @@ -425,3 +426,41 @@ fn test_date_input() { ); }); } + +#[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)", + ); +} From 679c41caf9b1825c2049398c2da88744ea2dbd07 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:25:39 +0200 Subject: [PATCH 42/84] Test unsupported base --- tests/query.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index f3f20dc..5f28fb2 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -176,6 +176,12 @@ fn test_bases() { 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] From dc82d2db5c737435b0b1ebc134e66e65bc6a1411 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:34:27 +0200 Subject: [PATCH 43/84] Test escape sequences between single quotes --- tests/query.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 5f28fb2..2c12e17 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -470,3 +470,9 @@ fn test_digits() { "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 "); +} From 94e21932872e9350bd6d68f1d92e0423f1bd735c Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 12:14:40 +0200 Subject: [PATCH 44/84] Add tests for Unicode escape sequences --- tests/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 2c12e17..0ac4ace 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -476,3 +476,12 @@ fn test_escapes() { test("'ab\\'cd\\n\\t'", "1 ab'cd\n\t (ab'cd\n\t)"); test("'x\\a'", "Expected term, got "); } + +#[test] +fn test_unicode_escape() { + test( + "\\u9999999", + "Expected term, got ", + ); + test("0\\u2103", "273.15 kelvin (temperature)"); +} From 2468aa2e39d345f301e7f1cf869c3c5ea39a1d42 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 12:54:16 +0200 Subject: [PATCH 45/84] Test timezone conversion --- tests/query.rs | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/tests/query.rs b/tests/query.rs index 0ac4ace..23a2441 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -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)"); @@ -415,22 +431,10 @@ fn test_underscores_in_number() { #[test] fn test_date_input() { - let input = "#2018-10-04T09:13:25.123 +2:00#"; - let expected = "2018-10-04 11:13:25.123 +02:00"; - - 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(expected), - format!("\n'{}' !=\n'{}'", res, expected) - ); - }); + test_starts_with( + "#2018-10-04T09:13:25.123 +2:00#", + "2018-10-04 11:13:25.123 +02:00", + ); } #[test] @@ -485,3 +489,16 @@ fn test_unicode_escape() { ); test("0\\u2103", "273.15 kelvin (temperature)"); } + +#[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", + ); +} From 9e10c6dc09128380edfa841c58ee3099d28d01bc Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 13:04:48 +0200 Subject: [PATCH 46/84] Test another error case --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 23a2441..0212f15 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -502,3 +502,8 @@ fn test_to_timezone() { "2000-01-01 03:46:00 GMT", ); } + +#[test] +fn test_missing_base() { + test("3 -> base", "Expected decimal base, got eof"); +} From e86ae5ab52d6ceaec2c89a8c2190e62e43c362f8 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 09:05:28 +0200 Subject: [PATCH 47/84] Test 'now' and date arithmetic --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 0212f15..0511e40 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -507,3 +507,8 @@ fn test_to_timezone() { 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"); +} From 2bd02fd7a703303ad8f82e862515b5f9efa6a4e6 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 11:10:23 +0200 Subject: [PATCH 48/84] Test try_decode --- tests/query.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 0511e40..39505b5 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -512,3 +512,9 @@ fn test_missing_base() { 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"); +} From 9248d49fdbc7de0d1f22a90fdb7fd907c1c95846 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 13:31:22 +0200 Subject: [PATCH 49/84] Make sure there is no call on the right hand side --- tests/query.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 39505b5..b3c8811 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -518,3 +518,11 @@ 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", + ); +} From b70fdab6b6b9654088372391f043b31bdfab33a9 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 13:31:30 +0200 Subject: [PATCH 50/84] Test conversion to list --- tests/query.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index b3c8811..728852a 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -526,3 +526,20 @@ fn test_no_calls_on_rhs() { "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", + ); +} From 0f37dbf29dcb87c9d59b80a534f4413bd171d8aa Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 15:24:44 +0200 Subject: [PATCH 51/84] Test formula.rs --- tests/query.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 728852a..ef0415f 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -543,3 +543,15 @@ fn test_conversion_to_list() { Suggestions: divide left side by linear_density, multiply right side by linear_density", ); } + +#[test] +fn test_formula() { + test( + "methane=CH4", + "CH4: molar_mass = 0.01604276 kilogram / mole", + ); + test( + "NaCl", + "NaCl: molar_mass = approx. 0.05844246 kilogram / mole", + ); +} From 43a12f96e2a97057de340010c84887738387c9e5 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 21:00:01 +0200 Subject: [PATCH 52/84] Test chemical formulas containing larger numbers --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index ef0415f..231f1e0 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -554,4 +554,9 @@ fn test_formula() { "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"); } From 8e610760025931a988306dc0e59e4f7fbe3a523b Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Sat, 6 Oct 2018 07:56:49 +0200 Subject: [PATCH 53/84] Test definition with doc --- tests/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 231f1e0..695052e 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -560,3 +560,12 @@ fn test_formula() { ); test("C60", "C60: molar_mass = 0.72066 kilogram / mole"); } + +#[test] +fn test_definition() { + test( + "mass", + "Definition: kilogram = base unit of mass. Equal to the mass of the \ + international prototype of the kilogram. 3rd CGPM (1901, CR, 70).", + ); +} From 8f6302adf0a99cfaded69117fb3c96c60820fa0e Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Sat, 6 Oct 2018 08:08:54 +0200 Subject: [PATCH 54/84] Fix duplicate test name --- tests/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/query.rs b/tests/query.rs index 695052e..75df497 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -562,7 +562,7 @@ fn test_formula() { } #[test] -fn test_definition() { +fn test_definition_with_doc() { test( "mass", "Definition: kilogram = base unit of mass. Equal to the mass of the \ From 6a1d250694ec7da45c15dcc6fcbf72652b43fe78 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 10 Oct 2018 15:21:29 +0200 Subject: [PATCH 55/84] Test `try_decode` failure case --- tests/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 75df497..09fe6ae 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -569,3 +569,12 @@ fn test_definition_with_doc() { 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`", + ); +} From 36db08836d9ebef7a0acb55b0236633066f801dd Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 08:57:40 +0200 Subject: [PATCH 56/84] Test more cases in eval() --- tests/query.rs | 259 ------------------------------------------------- 1 file changed, 259 deletions(-) diff --git a/tests/query.rs b/tests/query.rs index 09fe6ae..42e7d3d 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -319,262 +319,3 @@ fn mismatched_units() { <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 ", - ); - 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 "); -} - -#[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 "); -} - -#[test] -fn test_unicode_escape() { - test( - "\\u9999999", - "Expected term, got ", - ); - test("0\\u2103", "273.15 kelvin (temperature)"); -} - -#[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_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"); -} - -#[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`", - ); -} From 1ce0f1c392342828f9d57f60f47fcff825904d75 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 09:20:14 +0200 Subject: [PATCH 57/84] Test some functions --- tests/query.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 42e7d3d..820a0bd 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -319,3 +319,27 @@ fn mismatched_units() { <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)"); +} From a99399260d13aa555dc2b5858418a1585deb284d Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 09:56:00 +0200 Subject: [PATCH 58/84] More tests for eval's helper functions --- tests/query.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 820a0bd..9169c9d 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -343,3 +343,25 @@ fn test_functions() { 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", + ); +} From f2c22983b9985e81669fe914fbe6c7258ac0d0ae Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Tue, 2 Oct 2018 12:54:34 +0200 Subject: [PATCH 59/84] Add test for eval_outer_expr_unit_cond --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 9169c9d..7590f87 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -365,3 +365,8 @@ fn test_reciprocal_conversion() { Suggestions: Reciprocal conversion, invert one side", ); } + +#[test] +fn test_non_conversion_input() { + test("g", "Definition: gram = (1 / 1000) kg = 1 gram (mass; kg)"); +} From 0273571fb33ca9473342d536e79a01e39689daa3 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 19:12:11 +0200 Subject: [PATCH 60/84] Test another case in eval() --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 7590f87..9e12428 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -370,3 +370,8 @@ fn test_reciprocal_conversion() { 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)>"); +} From cb0b3a1c466cb314d46d34d6e024cea8e9fb90ca Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 19:16:46 +0200 Subject: [PATCH 61/84] Test error case in eval() --- tests/query.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 9e12428..5a40612 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -375,3 +375,11 @@ fn test_non_conversion_input() { 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>", + ); +} From f396f8176ee0bcf84ca118a28325eec15118f74d Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 20:01:43 +0200 Subject: [PATCH 62/84] Add tests for binary function error cases --- tests/query.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 5a40612..27cbd8c 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -383,3 +383,20 @@ fn test_mul_not_defined() { "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))", + ); +} From 317f07c3b9880e39b3f24c124d3c57f40c097132 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 21:43:41 +0200 Subject: [PATCH 63/84] Start testing input in different bases --- tests/query.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 27cbd8c..6ca317b 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -400,3 +400,13 @@ fn test_hypot_dimension_mismatch() { hypot(3 second (time), 4 meter (length))", ); } + +#[test] +fn test_radix() { + test("0xff", "255 (dimensionless)"); + test( + "0off", + "Expected term, got ", + ); + test("0b101010", "42 (dimensionless)"); +} From 16ecadb90cf3942d3154b228188943f72a5675c5 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 3 Oct 2018 22:25:37 +0200 Subject: [PATCH 64/84] Add a few more tests for text_query.rs --- tests/query.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 6ca317b..03bea6e 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -410,3 +410,20 @@ fn test_radix() { ); test("0b101010", "42 (dimensionless)"); } + +#[test] +fn test_comments() { + test("1 // *3", "1 (dimensionless)"); + test("1 + /*2*/ 3", "4 (dimensionless)"); + test("1 + /*2", "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)"); +} From 54ffee994569b8216d385229c4e519c0c3bdd9e2 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 10:20:21 +0200 Subject: [PATCH 65/84] Another test for the date parser --- src/ast.rs | 1 + src/date.rs | 105 +++++++++++++++++++++++++------------------------ tests/query.rs | 22 ++++++++++- 3 files changed, 75 insertions(+), 53 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index ec2a837..be3893b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -81,6 +81,7 @@ pub enum Query { } #[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] pub enum DatePattern { Literal(String), Match(String), diff --git a/src/date.rs b/src/date.rs index e1dcbf4..5a0c30c 100644 --- a/src/date.rs +++ b/src/date.rs @@ -274,62 +274,63 @@ impl GenericDateTime { } } +fn attempt(date: &[DateToken], pat: &[DatePattern]) -> Result { + 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 count = iter.count(); + let res = if count > 0 && res.is_ok() { + Err(format!("Expected eof, got {}", + date[date.len()-count..].iter() + .map(ToString::to_string) + .collect::>().join(""))) + } else { + res + }; + try!(res.map_err(|e| (e, count))); + let time = parsed.to_naive_time(); + let date = parsed.to_naive_date(); + if let Some(tz) = tz { + match (time, date) { + (Ok(time), Ok(date)) => + tz.from_local_datetime(&date.and_time(time)).earliest().ok_or_else(|| (format!( + "Datetime does not represent a valid moment in time" + ), count)).map(GenericDateTime::Timezone), + (Ok(time), Err(_)) => + Ok(UTC::now().with_timezone(&tz).date().and_time(time).unwrap()).map( + GenericDateTime::Timezone), + (Err(_), Ok(date)) => + tz.from_local_date(&date).earliest().map(|x| x.and_hms(0, 0, 0)).ok_or_else(|| (format!( + "Datetime does not represent a valid moment in time" + ), count)).map(GenericDateTime::Timezone), + _ => Err((format!("Failed to construct a useful datetime"), count)) + } + } else { + let offset = parsed.to_fixed_offset().unwrap_or(FixedOffset::east(0)); + match (time, date) { + (Ok(time), Ok(date)) => + Ok(GenericDateTime::Fixed(DateTime::::from_utc( + date.and_time(time), offset + ))), + (Ok(time), Err(_)) => + Ok(GenericDateTime::Fixed(UTC::now().with_timezone( + &offset + ).date().and_time(time).unwrap())), + (Err(_), Ok(date)) => + Ok(GenericDateTime::Fixed(Date::::from_utc( + date, offset + ).and_hms(0, 0, 0))), + _ => Err((format!("Failed to construct a useful datetime"), count)) + } + } +} + pub fn try_decode(date: &[DateToken], context: &Context) -> Result { let mut best = None; for pat in &context.datepatterns { //println!("Tring {:?} against {}", date, show_datepattern(pat)); - let attempt = || -> Result { - 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 count = iter.count(); - let res = if count > 0 && res.is_ok() { - Err(format!("Expected eof, got {}", - date[date.len()-count..].iter() - .map(ToString::to_string) - .collect::>().join(""))) - } else { - res - }; - try!(res.map_err(|e| (e, count))); - let time = parsed.to_naive_time(); - let date = parsed.to_naive_date(); - if let Some(tz) = tz { - match (time, date) { - (Ok(time), Ok(date)) => - tz.from_local_datetime(&date.and_time(time)).earliest().ok_or_else(|| (format!( - "Datetime does not represent a valid moment in time" - ), count)).map(GenericDateTime::Timezone), - (Ok(time), Err(_)) => - Ok(UTC::now().with_timezone(&tz).date().and_time(time).unwrap()).map( - GenericDateTime::Timezone), - (Err(_), Ok(date)) => - tz.from_local_date(&date).earliest().map(|x| x.and_hms(0, 0, 0)).ok_or_else(|| (format!( - "Datetime does not represent a valid moment in time" - ), count)).map(GenericDateTime::Timezone), - _ => Err((format!("Failed to construct a useful datetime"), count)) - } - } else { - let offset = parsed.to_fixed_offset().unwrap_or(FixedOffset::east(0)); - match (time, date) { - (Ok(time), Ok(date)) => - Ok(GenericDateTime::Fixed(DateTime::::from_utc( - date.and_time(time), offset - ))), - (Ok(time), Err(_)) => - Ok(GenericDateTime::Fixed(UTC::now().with_timezone( - &offset - ).date().and_time(time).unwrap())), - (Err(_), Ok(date)) => - Ok(GenericDateTime::Fixed(Date::::from_utc( - date, offset - ).and_hms(0, 0, 0))), - _ => Err((format!("Failed to construct a useful datetime"), count)) - } - } - }; - match attempt() { + match attempt(date, pat) { Ok(datetime) => return Ok(datetime), Err((e, c)) => { //println!("{}", e); diff --git a/tests/query.rs b/tests/query.rs index 03bea6e..c43963f 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -415,7 +415,7 @@ fn test_radix() { fn test_comments() { test("1 // *3", "1 (dimensionless)"); test("1 + /*2*/ 3", "4 (dimensionless)"); - test("1 + /*2", "Expected `*/`, got EOF"); + test("1 + /*2", "Expected term, got "); } #[test] @@ -427,3 +427,23 @@ fn test_leading_dot() { fn test_underscores_in_number() { test("123_456\u{2009}789", "123456789 (dimensionless)"); } + +#[test] +fn test_date_input() { + let input = "#2018-10-04T09:13:25 +2:00#"; + let expected = "2018-10-04 11:13:25 +02:00"; + + 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(expected), + format!("\n'{}' !=\n'{}'", res, expected) + ); + }); +} From 77cfd4945487c8de5413cf95f3e205d3dde3d5e1 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 15 Nov 2018 22:28:35 +0100 Subject: [PATCH 66/84] Fix bug found by `test_date_input` --- src/text_query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_query.rs b/src/text_query.rs index ffbf812..00d1d2d 100644 --- a/src/text_query.rs +++ b/src/text_query.rs @@ -358,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 { From ec3bc1273e586e1b4b66560d5530b81432fedce2 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:04:32 +0200 Subject: [PATCH 67/84] Test fractional seconds --- tests/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/query.rs b/tests/query.rs index c43963f..f21918b 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -430,8 +430,8 @@ fn test_underscores_in_number() { #[test] fn test_date_input() { - let input = "#2018-10-04T09:13:25 +2:00#"; - let expected = "2018-10-04 11:13:25 +02:00"; + let input = "#2018-10-04T09:13:25.123 +2:00#"; + let expected = "2018-10-04 11:13:25.123 +02:00"; let mut iter = text_query::TokenIterator::new(input.trim()).peekable(); let expr = text_query::parse_query(&mut iter); From 72a4e8e01fba0b6acffb22f67ece52e4911e2b13 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:21:08 +0200 Subject: [PATCH 68/84] Add more tests for text_query --- tests/query.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index f21918b..08dad0d 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -409,6 +409,7 @@ fn test_radix() { "Expected term, got ", ); test("0b101010", "42 (dimensionless)"); + test("0o10lux", "8 lux (illuminance)"); } #[test] @@ -447,3 +448,41 @@ fn test_date_input() { ); }); } + +#[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)", + ); +} From 94de4c033a24e6ccc7ff54a380fe573f4247548a Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 11:34:27 +0200 Subject: [PATCH 69/84] Test escape sequences between single quotes --- tests/query.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 08dad0d..07a9ad3 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -486,3 +486,9 @@ fn test_digits() { "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 "); +} From cffc40b1b309e9bdca5935621bd45097e89648e3 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 12:54:16 +0200 Subject: [PATCH 70/84] Test timezone conversion --- tests/query.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/query.rs b/tests/query.rs index 07a9ad3..d7d3392 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -431,22 +431,10 @@ fn test_underscores_in_number() { #[test] fn test_date_input() { - let input = "#2018-10-04T09:13:25.123 +2:00#"; - let expected = "2018-10-04 11:13:25.123 +02:00"; - - 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(expected), - format!("\n'{}' !=\n'{}'", res, expected) - ); - }); + test_starts_with( + "#2018-10-04T09:13:25.123 +2:00#", + "2018-10-04 11:13:25.123 +02:00", + ); } #[test] @@ -492,3 +480,16 @@ fn test_escapes() { test("'ab\\'cd\\n\\t'", "1 ab'cd\n\t (ab'cd\n\t)"); test("'x\\a'", "Expected term, got "); } + +#[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", + ); +} \ No newline at end of file From caa504da6ae1ebb7b245136071cee080ebf4dcd3 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Thu, 4 Oct 2018 13:04:48 +0200 Subject: [PATCH 71/84] Test unspecified base --- tests/query.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/query.rs b/tests/query.rs index d7d3392..1a2fba0 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -492,4 +492,9 @@ fn test_to_timezone() { "#2000-01-01 12:46 Asia/Tokyo# -> GMT", "2000-01-01 03:46:00 GMT", ); -} \ No newline at end of file +} + +#[test] +fn test_missing_base() { + test("3 -> base", "Expected decimal base, got eof"); +} From 7c35cbc9f074b69a031fec801d83eb3cfe539ff5 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 09:05:28 +0200 Subject: [PATCH 72/84] Test 'now' and date arithmetic --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 1a2fba0..0230c57 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -498,3 +498,8 @@ fn test_to_timezone() { 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"); +} From ebb6326503e420284eba04e8c88dddc6a53ddc6f Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 11:10:23 +0200 Subject: [PATCH 73/84] Test try_decode --- tests/query.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 0230c57..a0b0d44 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -503,3 +503,9 @@ fn test_missing_base() { 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"); +} From 62459d05b57993e45c132ebc163800954172d2da Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 13:31:22 +0200 Subject: [PATCH 74/84] Make sure there is no call on the right hand side --- tests/query.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index a0b0d44..92bc5b6 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -509,3 +509,11 @@ 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", + ); +} From 67e98857de1af89234092e16b6c126641f5c0050 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 13:31:30 +0200 Subject: [PATCH 75/84] Test conversion to list --- tests/query.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 92bc5b6..fd2a39c 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -517,3 +517,20 @@ fn test_no_calls_on_rhs() { "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", + ); +} From a3e061f0cabceecfaac0e03c1a3c2d0f5ca4247c Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Sat, 6 Oct 2018 07:56:49 +0200 Subject: [PATCH 76/84] Test definition with doc --- tests/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index fd2a39c..5e95713 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -534,3 +534,12 @@ fn test_conversion_to_list() { 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).", + ); +} From 9cf86a58390a5a7815de424e1dc6e187d721b21c Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 10 Oct 2018 15:14:15 +0200 Subject: [PATCH 77/84] Test more cases in `parse_{weekday, monthname}` --- src/date.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/date.rs b/src/date.rs index 5a0c30c..abb101f 100644 --- a/src/date.rs +++ b/src/date.rs @@ -659,5 +659,32 @@ mod tests { 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); } } From 963e2494ec1305df4d2433067b1aafba6f08a0b2 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 10 Oct 2018 15:21:29 +0200 Subject: [PATCH 78/84] Test `try_decode` failure case --- tests/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 5e95713..fcf4259 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -543,3 +543,12 @@ fn test_definition_with_doc() { 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`", + ); +} From d60961145479e20aab68dc0b5d1ef9da40ecffc8 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 10 Oct 2018 16:39:14 +0200 Subject: [PATCH 79/84] Test `parse_datepattern` --- src/date.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/date.rs b/src/date.rs index abb101f..38b440e 100644 --- a/src/date.rs +++ b/src/date.rs @@ -687,4 +687,20 @@ mod tests { 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, 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()); + } } From f97a076f5251e56e910786f531ae0b8129951624 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Wed, 10 Oct 2018 21:15:31 +0200 Subject: [PATCH 80/84] Test additional cases in `date::attempt` --- src/date.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/date.rs b/src/date.rs index 38b440e..43e52a9 100644 --- a/src/date.rs +++ b/src/date.rs @@ -703,4 +703,36 @@ mod tests { 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); + } } From d54d410d15501917dca92174e89b037527775a05 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 13:46:17 +0200 Subject: [PATCH 81/84] Document what `substance_from_formula` does --- src/formula.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/formula.rs b/src/formula.rs index 5b9233e..bbd9b8f 100644 --- a/src/formula.rs +++ b/src/formula.rs @@ -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, substances: &BTreeMap) -> Option { From f7bf0438361bea530a620dd1cee57df4f2113b05 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 15:24:44 +0200 Subject: [PATCH 82/84] Test formula.rs --- tests/query.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index fcf4259..8752545 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -550,5 +550,17 @@ fn test_try_decode_fail() { "#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", ); } From 5f37f1506c37b253499349ef37e8f02e878b7e13 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Fri, 5 Oct 2018 21:00:01 +0200 Subject: [PATCH 83/84] Test chemical formulas containing larger numbers --- tests/query.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/query.rs b/tests/query.rs index 8752545..1974947 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -563,4 +563,9 @@ fn test_formula() { "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"); } From 4a41da18c8a1d8afcabceeeb44fa5360dddf06f5 Mon Sep 17 00:00:00 2001 From: Colin Benner Date: Mon, 1 Oct 2018 21:32:18 +0200 Subject: [PATCH 84/84] Fix typo --- src/date.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/date.rs b/src/date.rs index 43e52a9..12ff770 100644 --- a/src/date.rs +++ b/src/date.rs @@ -119,8 +119,8 @@ pub fn parse_date( 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 => {