1 /// Tests that errors are raised & correct.
2 mod common;
3 
4 use common::OsoTest;
5 use oso::errors::polar::{
6     ErrorKind as PolarErrorKind, PolarError, RuntimeError as PolarRuntimeError,
7 };
8 use oso::{Oso, OsoError, PolarClass, PolarValue};
9 
10 // TODO in all tests, check type of error & message
11 
12 /// Test that external unification on raises error on:
13 /// - Same type that doesn't support unification
14 /// - Type that does unified with a different type
15 #[test]
test_unify_external_not_supported() -> oso::Result<()>16 fn test_unify_external_not_supported() -> oso::Result<()> {
17     common::setup();
18 
19     #[derive(PolarClass)]
20     struct Foo(pub i64);
21 
22     let mut oso = OsoTest::new();
23 
24     oso.oso.register_class(Foo::get_polar_class())?;
25 
26     oso.load_str("unify(x, y) if x = y;");
27 
28     // Type that doesn't support unification.
29     let mut query = oso.oso.query_rule("unify", (Foo(1), Foo(1)))?;
30     let error = query.next().unwrap().unwrap_err();
31     assert!(
32         matches!(
33             &error,
34             OsoError::UnsupportedOperation {
35                 operation,
36                 type_name
37             } if operation == "equals" && type_name == "Foo"),
38         "{} doesn't match expected error",
39         error
40     );
41 
42     // TODO Any meaningful support of PartialEq implementations with a different
43     // Rhs would require the ability to configure multiple equality_checks for
44     // a given class, which is not currently supported.
45     // See https://github.com/osohq/oso/pull/796 for more context.
46 
47     // Type that does support unification with a type that doesn't.
48     // #[derive(PolarClass, PartialEq)]
49     // struct EqFoo(i64);
50 
51     // impl PartialEq<Foo> for EqFoo {
52     //     fn eq(&self, other: &Foo) -> bool {
53     //         self.0 == other.0
54     //     }
55     // }
56 
57     // let eq_foo_class = EqFoo::get_polar_class_builder()
58     //     .with_equality_check()
59     //     .build();
60 
61     // oso.oso.register_class(eq_foo_class)?;
62 
63     // let mut query = oso.oso.query_rule("unify", (EqFoo(1), Foo(1)))?;
64     // let error = query.next().unwrap().unwrap_err();
65 
66     // TODO definitely need stack traces, these would be hard to diagnose
67     // otherwise.
68     // assert!(
69     //     matches!(
70     //         &error,
71     //         OsoError::TypeError(oso::errors::TypeError {
72     //             expected,
73     //             got
74     //         }) if expected == "EqFoo" && got.as_deref() == Some("Foo")),
75     //     "{} doesn't match expected error",
76     //     error
77     // );
78 
79     // TODO (dhatch): Right now, this doesn't work because unify only occurs on
80     // built-in types.  See https://www.notion.so/osohq/Unify-of-external-instance-with-internal-type-should-use-ExternalUnify-175bac1414324b25b902c1b1f51fafe9
81     //let mut query = oso.oso.query_rule("unify", (EqFoo(1), 1))?;
82     //let error = query.next().unwrap().unwrap_err();
83     //assert!(
84     //matches!(
85     //&error,
86     //OsoError::TypeError(oso::errors::TypeError {
87     //expected,
88     //got
89     //}) if expected == "EqFoo" && got.as_deref() == Some("Integer")),
90     //"{} doesn't match expected error",
91     //error
92     //);
93 
94     Ok(())
95 }
96 
97 /// Test that failing inline query sends a sane error message
98 #[test]
test_failing_inline_query()99 fn test_failing_inline_query() {
100     common::setup();
101 
102     let oso = Oso::new();
103 
104     let result = oso.load_str("?= 1 == 1;\n?= 1 == 0;");
105     match result {
106         Ok(_) => panic!("failed to detect failure of inline query"),
107         Err(e @ OsoError::InlineQueryFailedError { .. }) => {
108             assert_eq!(
109                 format!("{}", e),
110                 "Inline query failed 1 == 0 at line 2, column 3"
111             )
112         }
113         Err(_) => panic!("returned unexpected error"),
114     }
115 }
116 
117 /// Test that lookup of attribute that doesn't exist raises error.
118 #[test]
test_attribute_does_not_exist() -> oso::Result<()>119 fn test_attribute_does_not_exist() -> oso::Result<()> {
120     common::setup();
121 
122     let mut oso = OsoTest::new();
123 
124     #[derive(PolarClass)]
125     struct Foo;
126 
127     oso.oso.register_class(Foo::get_polar_class())?;
128     oso.load_str("getattr(x, y, val) if val = x.(y);");
129 
130     // TODO dhatch: Query API for variables needs improvement.
131     let mut query = oso.oso.query_rule(
132         "getattr",
133         (Foo, "bar", PolarValue::Variable("a".to_owned())),
134     )?;
135     let error = query.next().unwrap().unwrap_err();
136 
137     if let OsoError::Polar(PolarError {
138         kind: PolarErrorKind::Runtime(PolarRuntimeError::Application { msg, .. }),
139         ..
140     }) = &error
141     {
142         assert_eq!(msg, "Attribute bar not found on type Foo.");
143     } else {
144         panic!("Error {} doesn't match expected type", error);
145     }
146 
147     Ok(())
148 }
149 
150 /// Test that lookup of method that doesn't exist raises error.
151 #[test]
test_method_does_not_exist() -> oso::Result<()>152 fn test_method_does_not_exist() -> oso::Result<()> {
153     common::setup();
154 
155     let mut oso = OsoTest::new();
156 
157     #[derive(PolarClass)]
158     struct Foo;
159 
160     impl Foo {
161         fn a(&self) -> i64 {
162             1
163         }
164     }
165 
166     let foo_class = Foo::get_polar_class_builder()
167         .add_method("a", Foo::a)
168         .build();
169 
170     oso.oso.register_class(foo_class)?;
171     oso.load_str("getmethod_b(x, val) if val = x.b();");
172 
173     let mut query = oso.oso.query_rule("getmethod_b", (Foo, 1))?;
174     let error = query.next().unwrap().unwrap_err();
175 
176     if let OsoError::Polar(PolarError {
177         kind: PolarErrorKind::Runtime(PolarRuntimeError::Application { msg, .. }),
178         ..
179     }) = &error
180     {
181         assert_eq!(msg, "Method b not found on type Foo.");
182     } else {
183         panic!("Error {} was not the expected type", error);
184     }
185 
186     Ok(())
187 }
188 
189 /// Test that lookup of class method that doesn't exist raises error.
190 #[test]
test_class_method_does_not_exist() -> oso::Result<()>191 fn test_class_method_does_not_exist() -> oso::Result<()> {
192     common::setup();
193 
194     let mut oso = OsoTest::new();
195 
196     #[derive(PolarClass)]
197     struct Foo;
198 
199     impl Foo {
200         fn a() -> i64 {
201             1
202         }
203     }
204 
205     let foo_class = Foo::get_polar_class_builder()
206         .add_class_method("a", Foo::a)
207         .build();
208 
209     oso.oso.register_class(foo_class)?;
210     oso.load_str("getmethod_b(val) if val = Foo.b();");
211 
212     let mut query = oso.oso.query_rule("getmethod_b", (1,))?;
213     let error = query.next().unwrap().unwrap_err();
214 
215     if let OsoError::Polar(PolarError {
216         kind: PolarErrorKind::Runtime(PolarRuntimeError::Application { msg, .. }),
217         ..
218     }) = &error
219     {
220         assert_eq!(msg, "Class method b not found on type Foo.");
221     } else {
222         panic!("Error {} was not the expected type", error);
223     }
224 
225     Ok(())
226 }
227 
228 /// Test that method call with incorrect type raises error:
229 /// - Wrong type of arguments
230 /// - Arguments that are not registered
231 #[test]
test_wrong_argument_types()232 fn test_wrong_argument_types() {
233     common::setup();
234 
235     let mut oso = OsoTest::new();
236 
237     #[derive(PolarClass)]
238     struct Foo;
239 
240     impl Foo {
241         fn a(&self) -> i64 {
242             1
243         }
244 
245         fn bar(&self, _bar: Bar) -> i64 {
246             2
247         }
248 
249         fn bar_x(&self, _x: i64, _bar: Bar) -> i64 {
250             3
251         }
252 
253         fn int(&self, _x: u8) -> i64 {
254             4
255         }
256     }
257 
258     // TODO (dhatch): Note for memory mgmt PR. Clone is required to use a type
259     // as a method argument! But not otherwise (see Foo doesn't need Clone).
260     #[derive(PolarClass, Clone)]
261     struct Bar;
262 
263     #[derive(PolarClass)]
264     struct Unregistered;
265 
266     let foo_class = Foo::get_polar_class_builder()
267         .add_method("a", Foo::a)
268         .add_method("bar", Foo::bar)
269         .add_method("bar_x", Foo::bar_x)
270         .add_method("int", Foo::int)
271         .build();
272 
273     oso.oso.register_class(foo_class).unwrap();
274     oso.oso.register_class(Bar::get_polar_class()).unwrap();
275 
276     oso.load_str("a(f, v) if v = f.a();");
277     oso.load_str("bar(f, arg) if _v = f.bar(arg);");
278     oso.load_str("bar_x(f, arg, arg1) if _v = f.bar_x(arg, arg1);");
279     oso.load_str("int(f, arg) if _v = f.int(arg);");
280 
281     let mut query = oso.oso.query_rule("a", (Foo, 1)).unwrap();
282     assert_eq!(query.next().unwrap().unwrap().keys().count(), 0);
283 
284     let mut query = oso.oso.query_rule("bar", (Foo, Bar)).unwrap();
285     assert_eq!(query.next().unwrap().unwrap().keys().count(), 0);
286 
287     // Wrong type of argument.
288     let mut query = oso.oso.query_rule("bar", (Foo, 1)).unwrap();
289     assert!(query.next().unwrap().is_err());
290 
291     // TODO test type error like this
292     //if let OsoError::TypeError(TypeError {
293     //got: Some(got),
294     //expected
295     //}) = &error {
296     //assert_eq!(got, "Integer");
297     //assert_eq!(expected, "Bar");
298     //} else {
299     //panic!("Error type {} doesn't match expected", error);
300     //}
301 
302     // Wrong type of argument.
303     let mut query = oso.oso.query_rule("bar", (Foo, Foo)).unwrap();
304     assert!(query.next().unwrap().is_err());
305 
306     // Unregistered argument.
307     let mut query = oso.oso.query_rule("bar", (Foo, Unregistered)).unwrap();
308     assert!(query.next().unwrap().is_err());
309 
310     let mut query = oso.oso.query_rule("bar_x", (Foo, 1, Bar)).unwrap();
311     assert_eq!(query.next().unwrap().unwrap().keys().count(), 0);
312 
313     // Wrong type of argument.
314     let mut query = oso.oso.query_rule("bar_x", (Foo, Foo, 1)).unwrap();
315     assert!(query.next().unwrap().is_err());
316 
317     // Wrong type of argument.
318     let mut query = oso.oso.query_rule("bar_x", (Foo, Bar, 1)).unwrap();
319     assert!(query.next().unwrap().is_err());
320 
321     // Wrong type of argument.
322     let mut query = oso.oso.query_rule("bar_x", (Foo, Bar, Bar)).unwrap();
323     assert!(query.next().unwrap().is_err());
324 
325     // Unregistered argument.
326     let mut query = oso
327         .oso
328         .query_rule("bar_x", (Foo, Bar, Unregistered))
329         .unwrap();
330     assert!(query.next().unwrap().is_err());
331 
332     // Wrong type of argument.
333     let mut query = oso.oso.query_rule("int", (Foo, 0)).unwrap();
334     assert_eq!(query.next().unwrap().unwrap().keys().count(), 0);
335 
336     // Wrong type of argument.
337     let mut query = oso.oso.query_rule("int", (Foo, Bar)).unwrap();
338     assert!(query.next().unwrap().is_err());
339 
340     // Wrong type of argument.
341     let mut query = oso.oso.query_rule("int", (Foo, 0.)).unwrap();
342     assert!(query.next().unwrap().is_err());
343 
344     // Out of bound argument.
345     let mut query = oso.oso.query_rule("int", (Foo, i64::MAX)).unwrap();
346     assert!(query.next().unwrap().is_err());
347 
348     // Out of bound argument.
349     let mut query = oso.oso.query_rule("int", (Foo, -1_i8)).unwrap();
350     assert!(query.next().unwrap().is_err());
351 }
352 
353 /// Test that constructor call with incorrect type raises error:
354 /// - Wrong type of arguments
355 /// - Arguments that are not registered
356 #[test]
test_wrong_argument_types_constructor()357 fn test_wrong_argument_types_constructor() {
358     common::setup();
359 
360     let mut oso = OsoTest::new();
361 
362     #[derive(PolarClass)]
363     struct Foo;
364 
365     // TODO (dhatch): This must be clone do to constraints on ToPolar.
366     #[derive(PolarClass, Clone)]
367     struct Bar;
368 
369     impl Foo {
370         fn new(_bar: Bar) -> Self {
371             Foo
372         }
373     }
374 
375     let foo_class = Foo::get_polar_class_builder()
376         .set_constructor(Foo::new)
377         .build();
378 
379     oso.oso.register_class(foo_class).unwrap();
380     oso.oso.register_class(Bar::get_polar_class()).unwrap();
381 
382     oso.load_str("new_foo(val) if _v = new Foo(val);");
383 
384     let mut query = oso.oso.query_rule("new_foo", (Bar,)).unwrap();
385     assert_eq!(query.next().unwrap().unwrap().keys().count(), 0);
386 
387     let mut query = oso.oso.query_rule("new_foo", (1,)).unwrap();
388     assert!(query.next().unwrap().is_err());
389 
390     let mut query = oso.oso.query_rule("new_foo", (Foo,)).unwrap();
391     assert!(query.next().unwrap().is_err());
392 }
393 
394 /// Test match with non-existent attributes does not raise error
395 #[test]
test_match_attribute_does_not_exist()396 fn test_match_attribute_does_not_exist() {
397     common::setup();
398 
399     let mut oso = OsoTest::new();
400 
401     #[derive(PolarClass)]
402     struct Foo {
403         #[polar(attribute)]
404         x: i64,
405     }
406 
407     impl Foo {
408         fn new() -> Self {
409             Foo { x: 1 }
410         }
411     }
412 
413     let foo_class = Foo::get_polar_class_builder()
414         .set_constructor(Foo::new)
415         .build();
416 
417     oso.oso.register_class(foo_class).unwrap();
418 
419     oso.load_str("foo(d) if d matches Foo{x: 1};");
420     oso.load_str("no_match_foo(d) if not d matches Foo{not_an_attr: 1};");
421     oso.qeval("foo(new Foo())");
422     oso.qeval("no_match_foo(new Foo())");
423 }
424 
425 /// Test that match with class that doesn't exist raises error
426 #[test]
test_match_non_existent_class()427 fn test_match_non_existent_class() {
428     common::setup();
429 
430     let mut oso = OsoTest::new();
431 
432     #[derive(PolarClass)]
433     struct Foo {
434         #[polar(attribute)]
435         x: i64,
436     }
437 
438     impl Foo {
439         fn new() -> Self {
440             Foo { x: 1 }
441         }
442     }
443 
444     let foo_class = Foo::get_polar_class_builder()
445         .set_constructor(Foo::new)
446         .build();
447 
448     oso.oso.register_class(foo_class).unwrap();
449 
450     oso.load_str("foo(d) if d matches Bar{x: 1};");
451     oso.query_err("foo(new Foo())");
452 }
453 
454 /// Test that incorrect number of arguments raises error:
455 /// - Incorrect number of arguments on method
456 /// - Incorrect number of arguments for constructor
457 #[test]
test_wrong_argument_arity() -> oso::Result<()>458 fn test_wrong_argument_arity() -> oso::Result<()> {
459     common::setup();
460 
461     let mut oso = OsoTest::new();
462 
463     #[derive(PolarClass)]
464     struct Foo;
465 
466     impl Foo {
467         fn a(&self, x: i64) -> i64 {
468             x
469         }
470     }
471 
472     let foo_class = Foo::get_polar_class_builder()
473         .add_method("a", Foo::a)
474         .build();
475 
476     oso.oso.register_class(foo_class)?;
477 
478     oso.load_str("getmethod_a1(x, val) if val = x.a(val);");
479     oso.load_str("getmethod_a2(x, val, val2) if val = x.a(val, val2);");
480     oso.load_str("getmethod_a0(x) if val = x.a();");
481 
482     // Correct number of arguments
483     let mut query = oso.oso.query_rule("getmethod_a1", (Foo, 1))?;
484     assert_eq!(query.next().unwrap().unwrap().keys().count(), 0);
485 
486     // Too many arguments
487     let mut query = oso.oso.query_rule("getmethod_a2", (Foo, 1, 2))?;
488     assert!(query.next().unwrap().is_err());
489 
490     // Too few arguments
491     let mut query = oso.oso.query_rule("getmethod_a0", (Foo,))?;
492     assert!(query.next().unwrap().is_err());
493 
494     Ok(())
495 }
496 
497 /// Test that constructing a class that is not registered raises error
498 #[test]
test_class_does_not_exist()499 fn test_class_does_not_exist() {
500     common::setup();
501 
502     let mut oso = OsoTest::new();
503 
504     #[derive(PolarClass)]
505     struct Foo {
506         #[polar(attribute)]
507         x: i64,
508     }
509 
510     impl Foo {
511         fn new() -> Self {
512             Foo { x: 1 }
513         }
514     }
515 
516     let foo_class = Foo::get_polar_class_builder()
517         .set_constructor(Foo::new)
518         .build();
519 
520     oso.oso.register_class(foo_class).unwrap();
521 
522     oso.load_str("bar(b) if b = new Bar();");
523     let mut query = oso.oso.query("bar(b)").unwrap();
524     assert!(query.next().unwrap().is_err());
525 }
526 
527 /// Test that using keyword arguments for constructor raises error:
528 /// - Keyword args only
529 /// - Mixed parameters
530 #[test]
test_constructor_keyword_arguments_error()531 fn test_constructor_keyword_arguments_error() {
532     common::setup();
533 
534     let mut oso = OsoTest::new();
535 
536     #[derive(PolarClass)]
537     struct Foo {
538         #[polar(attribute)]
539         x: i64,
540     }
541 
542     impl Foo {
543         fn new(x: i64) -> Self {
544             Foo { x }
545         }
546     }
547 
548     let foo_class = Foo::get_polar_class_builder()
549         .set_constructor(Foo::new)
550         .build();
551 
552     oso.oso.register_class(foo_class).unwrap();
553 
554     let mut query = oso.oso.query("x = new Foo(1)").unwrap();
555     assert_eq!(query.next().unwrap().unwrap().keys().count(), 1);
556 
557     let mut query = oso.oso.query("x = new Foo(x: 1)").unwrap();
558     assert!(query.next().unwrap().is_err());
559 
560     let mut query = oso.oso.query("x = new Foo(1, x: 1)").unwrap();
561     assert!(query.next().unwrap().is_err());
562 }
563 
564 /// Test that using keyword arguments for method raises error:
565 /// - Keyword args only
566 /// - Mixed parameters
567 #[test]
test_method_keyword_arguments_error() -> oso::Result<()>568 fn test_method_keyword_arguments_error() -> oso::Result<()> {
569     common::setup();
570 
571     let mut oso = OsoTest::new();
572 
573     #[derive(PolarClass)]
574     struct Foo {
575         #[polar(attribute)]
576         x: i64,
577     }
578 
579     impl Foo {
580         fn new() -> Self {
581             Foo { x: 1 }
582         }
583 
584         fn a(&self, x: i64) -> i64 {
585             x
586         }
587     }
588 
589     let foo_class = Foo::get_polar_class_builder()
590         .set_constructor(Foo::new)
591         .add_method("a", Foo::a)
592         .build();
593 
594     oso.oso.register_class(foo_class)?;
595 
596     let mut query = oso.oso.query("x = new Foo().a(1)").unwrap();
597     assert_eq!(query.next().unwrap().unwrap().get_typed::<i64>("x")?, 1);
598 
599     let mut query = oso.oso.query("x = new Foo().a(x: 1)").unwrap();
600     assert!(query.next().unwrap().is_err());
601 
602     let mut query = oso.oso.query("x = new Foo().a(1, x: 1)").unwrap();
603     assert!(query.next().unwrap().is_err());
604 
605     Ok(())
606 }
607 
608 /// Test operator raises not implemented error.
609 #[test]
test_operator_unimplemented() -> oso::Result<()>610 fn test_operator_unimplemented() -> oso::Result<()> {
611     common::setup();
612 
613     let mut oso = OsoTest::new();
614 
615     #[derive(PolarClass, PartialOrd, PartialEq)]
616     struct Foo(i64);
617 
618     let foo_class = Foo::get_polar_class_builder().with_equality_check().build();
619 
620     oso.oso.register_class(foo_class)?;
621 
622     oso.load_str("lt(a, b) if a < b;");
623     oso.qeval("lt(1, 2)");
624 
625     assert!(Foo(0) < Foo(1));
626     let mut query = oso.oso.query_rule("lt", (Foo(0), Foo(1))).unwrap();
627     assert!(query.next().unwrap().is_err());
628 
629     Ok(())
630 }
631 
632 // TODO (dhatch): Test errors for application method failures.
633 
634 // TODO (dhatch): What would happen for something like
635 // val matches Foo { x: 1 } where val.x is not an integer.
636 //
637 // This would raise a type error (if we did one-sided external unification,
638 // but we want the matches to just fail.  This wouldn't be caught by the
639 // current application error implementation.
640