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