|
22 | 22 | import io.vertx.ext.unit.junit.RunTestOnContext;
|
23 | 23 | import io.vertx.ext.unit.junit.VertxUnitRunner;
|
24 | 24 | import org.junit.Before;
|
| 25 | +import org.junit.Ignore; |
25 | 26 | import org.junit.Rule;
|
26 | 27 | import org.junit.Test;
|
27 | 28 | import org.junit.runner.RunWith;
|
|
33 | 34 |
|
34 | 35 | import static org.awaitility.Awaitility.await;
|
35 | 36 | import static org.hamcrest.Matchers.*;
|
| 37 | +import static org.junit.Assert.assertArrayEquals; |
36 | 38 | import static org.junit.Assert.assertThat;
|
37 | 39 |
|
38 | 40 | /**
|
@@ -531,13 +533,201 @@ public void should_Accept_objects_with_a_complex_key() {
|
531 | 533 | assertThat(future2.result(), equalTo(key1));
|
532 | 534 | }
|
533 | 535 |
|
| 536 | + @Test |
| 537 | + public void should_Clear_objects_with_complex_key() { |
| 538 | + ArrayList<Collection> loadCalls = new ArrayList<>(); |
| 539 | + DataLoaderOptions options = DataLoaderOptions.create().setCacheKeyFunction(getJsonObjectCacheMapFn()); |
| 540 | + DataLoader<JsonObject, Integer> identityLoader = idLoader(options, loadCalls); |
| 541 | + |
| 542 | + JsonObject key1 = new JsonObject().put("id", 123); |
| 543 | + JsonObject key2 = new JsonObject().put("id", 123); |
| 544 | + |
| 545 | + Future<Integer> future1 = identityLoader.load(key1); |
| 546 | + identityLoader.dispatch(); |
| 547 | + |
| 548 | + await().until(future1::isComplete); |
| 549 | + identityLoader.clear(key2); // clear equivalent object key |
| 550 | + |
| 551 | + Future<Integer> future2 = identityLoader.load(key1); |
| 552 | + identityLoader.dispatch(); |
| 553 | + |
| 554 | + await().until(future2::isComplete); |
| 555 | + assertThat(loadCalls, equalTo(Arrays.asList(Collections.singletonList(key1), Collections.singletonList(key1)))); |
| 556 | + assertThat(future1.result(), equalTo(key1)); |
| 557 | + assertThat(future2.result(), equalTo(key1)); |
| 558 | + } |
| 559 | + |
| 560 | + @Test |
| 561 | + public void should_Accept_objects_with_different_order_of_keys() { |
| 562 | + ArrayList<Collection> loadCalls = new ArrayList<>(); |
| 563 | + DataLoaderOptions options = DataLoaderOptions.create().setCacheKeyFunction(getJsonObjectCacheMapFn()); |
| 564 | + DataLoader<JsonObject, Integer> identityLoader = idLoader(options, loadCalls); |
| 565 | + |
| 566 | + JsonObject key1 = new JsonObject().put("a", 123).put("b", 321); |
| 567 | + JsonObject key2 = new JsonObject().put("b", 321).put("a", 123); |
| 568 | + |
| 569 | + // Fetches as expected |
| 570 | + |
| 571 | + Future<Integer> future1 = identityLoader.load(key1); |
| 572 | + Future<Integer> future2 = identityLoader.load(key2); |
| 573 | + identityLoader.dispatch(); |
| 574 | + |
| 575 | + await().until(() -> future1.isComplete() && future2.isComplete()); |
| 576 | + assertThat(loadCalls, equalTo(Collections.singletonList(Collections.singletonList(key1)))); |
| 577 | + assertThat(loadCalls.size(), equalTo(1)); |
| 578 | + assertThat(future1.result(), equalTo(key1)); |
| 579 | + assertThat(future2.result(), equalTo(key1)); |
| 580 | + } |
| 581 | + |
| 582 | + @Test |
| 583 | + public void should_Allow_priming_the_cache_with_an_object_key() { |
| 584 | + ArrayList<Collection> loadCalls = new ArrayList<>(); |
| 585 | + DataLoaderOptions options = DataLoaderOptions.create().setCacheKeyFunction(getJsonObjectCacheMapFn()); |
| 586 | + DataLoader<JsonObject, JsonObject> identityLoader = idLoader(options, loadCalls); |
| 587 | + |
| 588 | + JsonObject key1 = new JsonObject().put("id", 123); |
| 589 | + JsonObject key2 = new JsonObject().put("id", 123); |
| 590 | + |
| 591 | + identityLoader.prime(key1, key1); |
| 592 | + |
| 593 | + Future<JsonObject> future1 = identityLoader.load(key1); |
| 594 | + Future<JsonObject> future2 = identityLoader.load(key2); |
| 595 | + identityLoader.dispatch(); |
| 596 | + |
| 597 | + await().until(() -> future1.isComplete() && future2.isComplete()); |
| 598 | + assertThat(loadCalls, equalTo(Collections.emptyList())); |
| 599 | + assertThat(future1.result(), equalTo(key1)); |
| 600 | + assertThat(future2.result(), equalTo(key1)); |
| 601 | + } |
| 602 | + |
| 603 | + @Test |
| 604 | + public void should_Accept_a_custom_cache_map_implementation() { |
| 605 | + CustomCacheMap customMap = new CustomCacheMap(); |
| 606 | + ArrayList<Collection> loadCalls = new ArrayList<>(); |
| 607 | + DataLoaderOptions options = DataLoaderOptions.create().setCacheMap(customMap); |
| 608 | + DataLoader<String, String> identityLoader = idLoader(options, loadCalls); |
| 609 | + |
| 610 | + // Fetches as expected |
| 611 | + |
| 612 | + Future future1 = identityLoader.load("a"); |
| 613 | + Future future2 = identityLoader.load("b"); |
| 614 | + CompositeFuture composite = identityLoader.dispatch(); |
| 615 | + |
| 616 | + await().until((Callable<Boolean>) composite::isComplete); |
| 617 | + assertThat(future1.result(), equalTo("a")); |
| 618 | + assertThat(future2.result(), equalTo("b")); |
| 619 | + |
| 620 | + assertThat(loadCalls, equalTo(Collections.singletonList(Arrays.asList("a", "b")))); |
| 621 | + assertArrayEquals(customMap.stash.keySet().toArray(), Arrays.asList("a", "b").toArray()); |
| 622 | + |
| 623 | + Future future3 = identityLoader.load("c"); |
| 624 | + Future future2a = identityLoader.load("b"); |
| 625 | + composite = identityLoader.dispatch(); |
| 626 | + |
| 627 | + await().until((Callable<Boolean>) composite::isComplete); |
| 628 | + assertThat(future3.result(), equalTo("c")); |
| 629 | + assertThat(future2a.result(), equalTo("b")); |
| 630 | + |
| 631 | + assertThat(loadCalls, equalTo(Arrays.asList(Arrays.asList("a", "b"), Collections.singletonList("c")))); |
| 632 | + assertArrayEquals(customMap.stash.keySet().toArray(), Arrays.asList("a", "b", "c").toArray()); |
| 633 | + |
| 634 | + // Supports clear |
| 635 | + |
| 636 | + identityLoader.clear("b"); |
| 637 | + assertArrayEquals(customMap.stash.keySet().toArray(), Arrays.asList("a", "c").toArray()); |
| 638 | + |
| 639 | + Future future2b = identityLoader.load("b"); |
| 640 | + composite = identityLoader.dispatch(); |
| 641 | + |
| 642 | + await().until((Callable<Boolean>) composite::isComplete); |
| 643 | + assertThat(future2b.result(), equalTo("b")); |
| 644 | + assertThat(loadCalls, equalTo(Arrays.asList(Arrays.asList("a", "b"), |
| 645 | + Collections.singletonList("c"), Collections.singletonList("b")))); |
| 646 | + assertArrayEquals(customMap.stash.keySet().toArray(), Arrays.asList("a", "c", "b").toArray()); |
| 647 | + |
| 648 | + // Supports clear all |
| 649 | + |
| 650 | + identityLoader.clearAll(); |
| 651 | + assertArrayEquals(customMap.stash.keySet().toArray(), Collections.emptyList().toArray()); |
| 652 | + } |
| 653 | + |
| 654 | + // It is resilient to job queue ordering |
| 655 | + |
| 656 | + @Test |
| 657 | + public void should_Batch_loads_occurring_within_futures() { |
| 658 | + ArrayList<Collection> loadCalls = new ArrayList<>(); |
| 659 | + DataLoader<String, String> identityLoader = idLoader(DataLoaderOptions.create(), loadCalls); |
| 660 | + |
| 661 | + Future.<String>future().setHandler(rh -> { |
| 662 | + identityLoader.load("a"); |
| 663 | + Future.future().setHandler(rh2 -> { |
| 664 | + identityLoader.load("b"); |
| 665 | + Future.future().setHandler(rh3 -> { |
| 666 | + identityLoader.load("c"); |
| 667 | + Future.future().setHandler(rh4 -> |
| 668 | + identityLoader.load("d")).complete(); |
| 669 | + }).complete(); |
| 670 | + }).complete(); |
| 671 | + }).complete(); |
| 672 | + CompositeFuture composite = identityLoader.dispatch(); |
| 673 | + |
| 674 | + await().until((Callable<Boolean>) composite::isComplete); |
| 675 | + assertThat(loadCalls, equalTo( |
| 676 | + Collections.singletonList(Arrays.asList("a", "b", "c", "d")))); |
| 677 | + } |
| 678 | + |
| 679 | + @Test |
| 680 | + @Ignore |
| 681 | + public void should_Call_a_loader_from_a_loader() { |
| 682 | + // TODO Provide implementation with Futures |
| 683 | + } |
| 684 | + |
| 685 | + // Helper methods |
| 686 | + |
534 | 687 | private static CacheKey<JsonObject> getJsonObjectCacheMapFn() {
|
535 | 688 | return key -> key.stream()
|
536 |
| - .sorted() |
537 | 689 | .map(entry -> entry.getKey() + ":" + entry.getValue())
|
| 690 | + .sorted() |
538 | 691 | .collect(Collectors.joining());
|
539 | 692 | }
|
540 | 693 |
|
| 694 | + public class CustomCacheMap implements CacheMap<String, Object> { |
| 695 | + |
| 696 | + public Map<String, Object> stash; |
| 697 | + |
| 698 | + public CustomCacheMap() { |
| 699 | + stash = new LinkedHashMap<>(); |
| 700 | + } |
| 701 | + |
| 702 | + @Override |
| 703 | + public boolean containsKey(String key) { |
| 704 | + return stash.containsKey(key); |
| 705 | + } |
| 706 | + |
| 707 | + @Override |
| 708 | + public Object get(String key) { |
| 709 | + return stash.get(key); |
| 710 | + } |
| 711 | + |
| 712 | + @Override |
| 713 | + public CacheMap<String, Object> set(String key, Object value) { |
| 714 | + stash.put(key, value); |
| 715 | + return this; |
| 716 | + } |
| 717 | + |
| 718 | + @Override |
| 719 | + public CacheMap<String, Object> delete(String key) { |
| 720 | + stash.remove(key); |
| 721 | + return this; |
| 722 | + } |
| 723 | + |
| 724 | + @Override |
| 725 | + public CacheMap<String, Object> clear() { |
| 726 | + stash.clear(); |
| 727 | + return this; |
| 728 | + } |
| 729 | + } |
| 730 | + |
541 | 731 | @SuppressWarnings("unchecked")
|
542 | 732 | private static <K, V> DataLoader<K, V> idLoader(DataLoaderOptions options, List<Collection> loadCalls) {
|
543 | 733 | return new DataLoader<>(keys -> {
|
|
0 commit comments