Переглянути джерело

fix: GenericHashMap#equals
fix: MapUtils#equals
unit-tests

Mariusz Sieroń 6 роки тому
батько
коміт
ee8baa7b42

+ 2 - 0
assira/pom.xml

@@ -41,6 +41,8 @@
                                 <include>net/ranides/assira/collection/sets/FlatSet*</include>
 
                                 <include>net/ranides/assira/collection/utils/*</include>
+                                
+                                <include>net/ranides/assira/events/*</include>
 
                                 <include>net/ranides/assira/index/*</include>
 

+ 16 - 0
assira/src/main/java/net/ranides/assira/collection/maps/GenericEntry.java

@@ -8,6 +8,8 @@ package net.ranides.assira.collection.maps;
 
 import net.ranides.assira.reflection.IClass;
 
+import java.util.Objects;
+
 /**
  *
  * @author Ranides Atterwim <ranides@gmail.com>
@@ -51,6 +53,20 @@ public interface GenericEntry<K> {
         public void setValue(Object value) {
             this.value = value;
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof GenericEntry<?>)) return false;
+            GenericEntry<?> simple = (GenericEntry<?>) o;
+            return key.equals(simple.getKey()) &&
+                    Objects.equals(value, simple.getValue());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(key, value);
+        }
     }
     
 }

+ 34 - 8
assira/src/main/java/net/ranides/assira/collection/maps/GenericHashMap.java

@@ -6,10 +6,9 @@
  */
 package net.ranides.assira.collection.maps;
 
-import java.util.Collection;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.Set;
+
 import net.ranides.assira.collection.sets.SetUtils;
 import net.ranides.assira.collection.utils.HashCollection;
 import net.ranides.assira.reflection.IClass;
@@ -40,10 +39,6 @@ public class GenericHashMap<K> implements GenericMap<K> {
         putAll(m);
     }
 
-    private GenericHashMap(HashMultiMap<GenericKey<K, ?>, Object> map) {
-        this.map = map;
-    }
-    
     @Override
     public int size() {
         return map.size();
@@ -113,6 +108,19 @@ public class GenericHashMap<K> implements GenericMap<K> {
         return map.toString();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GenericHashMap<?> that = (GenericHashMap<?>) o;
+        return map.equals(that.map);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(map);
+    }
+
     private static final class CGEntry<K> implements GenericEntry<K> {
         
         private final Entry<GenericKey<K,?>, Object> entry;
@@ -140,7 +148,25 @@ public class GenericHashMap<K> implements GenericMap<K> {
         public void setValue(Object value) {
             entry.setValue(entry.getKey().type().cast(value));
         }
-        
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof GenericEntry<?>)) return false;
+            GenericEntry<?> cgEntry = (GenericEntry<?>) o;
+            return entry.getKey().equals(cgEntry.getKey()) &&
+                    Objects.equals(entry.getValue(), cgEntry.getValue());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(entry);
+        }
+
+        @Override
+        public String toString() {
+            return entry.toString();
+        }
     }
     
 }

+ 14 - 5
assira/src/main/java/net/ranides/assira/collection/maps/MapUtils.java

@@ -731,15 +731,15 @@ public final class MapUtils {
 
     }
 
-    private static class SynchronizedMultiMap<K, V> implements MultiMap<K, V> {
+    private static class SynchronizedMultiMap<K, V> implements MultiMap<K, V>, Serializable {
 
-        private final Object mutex = new Object();
+        private final Object mutex = new Serializable(){};
 
         private final MultiMap<K, V> data;
 
-        private Set<K> keySet;
-        private Set<Map.Entry<K,V>> entrySet;
-        private Collection<V> values;
+        private transient Set<K> keySet;
+        private transient Set<Map.Entry<K,V>> entrySet;
+        private transient Collection<V> values;
 
         public SynchronizedMultiMap(MultiMap<K, V> data) {
             this.data = data;
@@ -964,6 +964,15 @@ public final class MapUtils {
             }
         }
 
+        @Override
+        public boolean equals(Object o) {
+            return (this == o) || data.equals(o);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data);
+        }
     }
 
     static class SynchronizedCollection<E> implements Collection<E> {

+ 125 - 0
assira/src/test/java/net/ranides/assira/collection/maps/GenericHashMapTest.java

@@ -0,0 +1,125 @@
+package net.ranides.assira.collection.maps;
+
+import net.ranides.assira.junit.NewAssert;
+import org.junit.Test;
+
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+public class GenericHashMapTest {
+
+    GenericKey<String, Integer>      I_A = new GenericKey<>(Integer.class, "a");
+    GenericKey<String, String>       S_A = new GenericKey<>(String.class, "a");
+    GenericKey<String, Integer>      I_B = new GenericKey<>(Integer.class, "b");
+    GenericKey<String, Double>       D_C = new GenericKey<>(Double.class, "c");
+    GenericKey<String, CharSequence> C_D = new GenericKey<>(CharSequence.class, "d");
+    GenericKey<String, CharSequence> C_E = new GenericKey<>(CharSequence.class, "e");
+
+    @Test
+    public void basic() {
+        GenericMap<String> map1 = new GenericHashMap<>();
+
+//        map.put(a, "Hello"); // compiler error :-)
+//        map.put(a, 45.6); // compiler error :-)
+//        map.put(c, 1); // compiler error :-)
+
+        map1.put(I_A, 45);
+        map1.put(I_B, 32);
+        map1.put(D_C, 1.5);
+        map1.put(C_D, "text");
+
+        GenericMap<String> map2 = new GenericHashMap<>();
+        map2.putAll(map1);
+
+        assertEquals(map1, map2);
+
+        assertEquals((Integer)45, map1.remove(I_A));
+        assertNull(map1.remove(I_A));
+        assertNull(map1.remove(C_E));
+
+        assertEquals(3, map1.size());
+        assertEquals(4, map2.size());
+
+        assertNotEquals(map1, map2);
+
+        assertFalse(map1.containsKey(I_A));
+        assertTrue(map1.containsKey(C_D));
+        assertTrue(map1.containsValue("text"));
+        assertFalse(map1.containsValue(45));
+    }
+
+    @Test
+    public void contains() {
+        GenericMap<String> map1 = new GenericHashMap<>();
+
+        map1.put(I_A, 45);
+        map1.put(I_B, 32);
+        map1.put(D_C, 1.5);
+        map1.put(C_D, "text");
+
+        assertTrue(map1.containsKey(I_A));
+        assertTrue(map1.containsKey(I_B));
+        assertFalse(map1.containsKey(S_A));
+    }
+
+    @Test
+    public void entries() {
+        GenericMap<String> map1 = new GenericHashMap<>();
+
+        map1.put(I_A, 45);
+        map1.put(I_B, 32);
+        map1.put(D_C, 1.5);
+        map1.put(C_D, "text");
+
+        assertEquals(4, map1.entrySet().size());
+
+        assertTrue(map1.entrySet().contains(new GenericEntry.Simple<>(I_A, 45)));
+        assertFalse(map1.entrySet().contains(new GenericEntry.Simple<>(I_A, 44)));
+
+        boolean found = map1.entrySet().stream()
+                .filter(e -> e.getKey().equals(I_B))
+                .findFirst()
+                .map(e -> e.equals(new GenericEntry.Simple<>(I_B, 32)))
+                .orElse(false);
+
+        assertTrue(found);
+
+    }
+
+    @Test
+    public void views() {
+        GenericMap<String> map1 = new GenericHashMap<>();
+
+        map1.put(I_A, 45);
+        map1.put(I_B, 32);
+        map1.put(D_C, 1.5);
+        map1.put(C_D, "text");
+
+        assertEquals("1.5,32,45,text", map1.values().stream().map(String::valueOf).sorted().collect(Collectors.joining(",")) );
+        assertEquals("#a,#b,#c,#d", map1.keySet().stream().map(String::valueOf).sorted().collect(Collectors.joining(",")) );
+    }
+
+    @Test
+    public void setEntry() {
+        GenericMap<String> map1 = new GenericHashMap<>();
+
+        map1.put(I_A, 45);
+        map1.put(I_B, 32);
+        map1.put(D_C, 1.5);
+        map1.put(C_D, "text");
+
+        GenericEntry<String> entry = map1.entrySet().stream()
+                .filter(e -> e.getKey().equals(I_B))
+                .findFirst()
+                .get();
+
+        entry.setValue(88);
+        assertEquals((Integer)88, map1.get(I_B));
+
+        NewAssert.assertThrows(ClassCastException.class, () -> {
+            entry.setValue("text");
+        });
+    }
+
+}

+ 11 - 0
assira/src/test/java/net/ranides/assira/collection/maps/MapUtilsTest.java

@@ -24,6 +24,17 @@ import org.junit.Test;
  */
 public class MapUtilsTest {
 
+    private final TMap<TPoint, Integer> $map = TMaps.MAP_PI;
+
+    @Test
+    public void testSynchronized() {
+        ContractTesters.runner()
+                .param("map!", $map)
+                .ignore("MapTester.basicEquals_HC")
+                .function(new int[0], array -> $map.list(array).into(MapUtils.synchronizedMultiMap(new HashMultiMap<>())))
+                .run();
+    }
+
     @Test
     public void view() {
         Map<Object,Number> a = new HashMap<>();

+ 36 - 54
assira/src/test/java/net/ranides/assira/events/EventLockTest.java

@@ -27,77 +27,60 @@ public class EventLockTest {
             throw ExceptionUtils.rethrow(ex);
         }
     }
-    
-    /**
-     * MUSIMY tu mieć duże opóźnienia, jeśli jednocześnie chcemy sprawdzić
-     * naprawdę EventLock oraz uniknąć race-condition. Moglibyśmy wprowadzić
-     * jakąś synchronizację... ale wtedy z definicji byśmy testowali tę 
-     * smart-synchronizację, a nie nasz EventLock.
-     */
-    @Category(JCategories.UnstableTest.class)
+
     @Test
     public void testSingleLock() throws InterruptedException {
         final EventReactor main = EventReactor.newInstance("name", 32, 1000);
 
+        EventLock<WriteEvent> we1 = EventLock.singleLock(WriteEvent.class, main);
+        EventLock<WriteEvent> we2 = EventLock.singleLock(WriteEvent.class, main);
+
+        EventLock<ReadEvent> re1 = EventLock.singleLock(ReadEvent.class, main);
+        EventLock<ReadEvent> re2 = EventLock.singleLock(ReadEvent.class, main);
+        EventLock<ReadEvent> re3 = EventLock.singleLock(ReadEvent.class, main, 1);
+
+        EventLock<IOEvent> io1 = EventLock.singleLock(IOEvent.class, main);
+        EventLock<IOEvent> io2 = EventLock.singleLock(IOEvent.class, main);
+
         new Thread(() -> {
-            // sleep unikają race condition (event wysłany przed rozpoczęciem waitForEvent)
-            sleep(100);
             main.signalEvent(new WriteEvent(11));
-            sleep(100);
             main.signalEvent(new ReadEvent(12));
-            sleep(100);
             main.signalEvent(new ReadEvent(13));
-            sleep(100);
             main.signalEvent(new ReadEvent(14));
-            sleep(100);
             main.signalEvent(new Event(){});
-            sleep(100);
             main.signalEvent(new ReadEvent(16));
-            sleep(100);
             main.signalEvent(new WriteEvent(17));
-            sleep(100);
-            
             main.signalEvent(new WriteEvent(19));
-            sleep(400);
             main.signalEvent(new WriteEvent(20));
-            sleep(100);
-            
             main.signalEvent(new ReadEvent(21));
-            sleep(400);
             main.signalEvent(new ReadEvent(22));
-            sleep(100);
-            
-            main.stop();
         }).start();
-        
-        
-        IOEvent event;
-        
-        // taka konstrukcja normalnie jest podatna na race condition
-        event = EventLock.singleLock(WriteEvent.class, main).waitForEvent();
-        assertEquals(11, event.handle());
-        
-        event = EventLock.singleLock(ReadEvent.class, main).waitForEvent(ReadEvent.class);
-        assertEquals(12, event.handle());
-        
-        event = EventLock.singleLock(WriteEvent.class, main).waitForEvent(WriteEvent.class);
-        assertEquals(17, event.handle());
-        
-        event = EventLock.singleLock(IOEvent.class, main).waitForEvent(300).get();
-        assertEquals(19, event.handle());
-        
-        event = EventLock.singleLock(IOEvent.class, main).waitForEvent(200).orElse(null);
-        assertNull(event);
-        
-        event = EventLock.singleLock(IOEvent.class, main).waitForEvent(ReadEvent.class, 600).get();
-        assertEquals(21, event.handle());
-        
-        event = EventLock.singleLock(IOEvent.class, main).waitForEvent(ReadEvent.class, 200).orElse(null);
-        assertNull(event);
+
+        sleep(100);
+
+        assertEquals(7, main.getEventListenersCount());
+
+        assertEquals(11, we1.waitForEvent().handle());
+        assertEquals(17, we2.waitForEvent(e -> e.handle()>11).handle());
+
+        assertEquals(5, main.getEventListenersCount());
+
+        assertEquals(12, re1.waitForEvent().handle());
+        assertEquals(21, re2.waitForEvent(e -> e.handle()>20).handle());
+
+        assertEquals(22, re3.waitForEvent().handle());
+        assertEquals(5, re3.discarded());
+
+        assertEquals(2, main.getEventListenersCount());
+
+        assertEquals(16, io1.waitForEvent(e -> e.handle() == 16, 100).get().handle());
+        assertFalse(io2.waitForEvent(e -> e.handle() == 116, 100).isPresent());
 
         assertEquals(0, main.getEventListenersCount());
+
+        main.stop();
     }
-    
+
     /**
      * MUSIMY tu mieć duże opóźnienia, jeśli jednocześnie chcemy sprawdzić
      * naprawdę EventLock oraz uniknąć race-condition. Moglibyśmy wprowadzić
@@ -243,7 +226,6 @@ public class EventLockTest {
         assertEquals(0, main.getEventListenersCount());
     }
 
-    @Category(JCategories.UnstableTest.class)
     @Test
     public void testEventLockDiscard() throws InterruptedException {
         final EventReactor main = EventReactor.newInstance("name", 32, 1000);
@@ -267,10 +249,10 @@ public class EventLockTest {
             main.signalEvent(new ReadEvent(108));
             main.signalEvent(new ReadEvent(109));
             main.signalEvent(new ReadEvent(110));
-             main.stop();
+            main.stop();
         }).start();
         
-        sleep(750);
+        sleep(500);
 
         assertEquals(201, lock.waitForEvent(IOEvent.class).handle());
         assertEquals(202, lock.waitForEvent(IOEvent.class).handle());

+ 31 - 15
assira/src/test/java/net/ranides/assira/generic/LazyReferenceTest.java

@@ -9,6 +9,7 @@ package net.ranides.assira.generic;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -22,7 +23,6 @@ import org.junit.experimental.categories.Category;
  *
  * @author Ranides Atterwim <ranides@gmail.com>
  */
-@Category(JCategories.UnstableTest.class)
 public class LazyReferenceTest {
 
 	@Test
@@ -35,30 +35,37 @@ public class LazyReferenceTest {
 	}
 	
 	@Test
-	public void testLocal() throws InterruptedException, Throwable {
+	public void testLocal() throws Throwable {
 		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
 		
 		AtomicInteger ai = new AtomicInteger(5);
 		Supplier<Integer> ref = LazyReference.local(() -> ai.getAndIncrement());
-		
+
+		CountDownLatch latch = new CountDownLatch(3);
+
 		thread(0, errors::add, ()->{
 			for(int i=0; i<5; i++) {
 				sleep(50);
 				assertEquals(5, ref.get().intValue());
 			}
+			latch.countDown();
 		});
 		thread(50, errors::add, ()->{
 			for(int i=0; i<5; i++) {
 				sleep(50);
 				assertEquals(6, ref.get().intValue());
 			}
+			latch.countDown();
 		});
 		thread(100, errors::add, ()->{
 			for(int i=0; i<5; i++) {
 				sleep(50);
 				assertEquals(7, ref.get().intValue());
 			}
-		}).join();
+			latch.countDown();
+		});
+
+		latch.await();
 
 		if(!errors.isEmpty()) {
 			throw errors.get(0);
@@ -66,22 +73,25 @@ public class LazyReferenceTest {
 	}
 	
 	@Test
-	public void testSharedVolatile() throws InterruptedException, Throwable {
+	public void testSharedVolatile() throws Throwable {
 		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
 		
 		AtomicInteger ai = new AtomicInteger(5);
 		Supplier<Integer> ref = LazyReference.sharedVolatile(() -> ai.getAndIncrement());
-		
-		for(int t=0; t<20; t++) {
+
+		CountDownLatch latch = new CountDownLatch(20);
+
+		for(long t=0, n=latch.getCount(); t<n; t++) {
 			thread(50, errors::add, ()->{
 				for(int i=0; i<10; i++) {
 					sleep(50);
 					Integer v = ref.get();
 					assertEquals(5, v.intValue());
 				}
+				latch.countDown();
 			});
 		}
-		sleep(1000);
+		latch.await();
 
 		if(!errors.isEmpty()) {
 			throw errors.get(0);
@@ -89,22 +99,25 @@ public class LazyReferenceTest {
 	}
 	
 	@Test
-	public void testShared() throws InterruptedException, Throwable {
+	public void testShared() throws Throwable {
 		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
 		
 		AtomicInteger ai = new AtomicInteger(5);
 		Supplier<Integer> ref = LazyReference.shared(() -> ai.getAndIncrement());
-		
-		for(int t=0; t<20; t++) {
+
+		CountDownLatch latch = new CountDownLatch(20);
+
+		for(long t=0, n=latch.getCount(); t<n; t++) {
 			thread(50, errors::add, ()->{
 				for(int i=0; i<10; i++) {
 					sleep(50);
 					Integer v = ref.get();
 					assertEquals(5, v.intValue());
 				}
+				latch.countDown();
 			});
 		}
-		sleep(1000);
+		latch.await();
 
 		if(!errors.isEmpty()) {
 			throw errors.get(0);
@@ -112,13 +125,15 @@ public class LazyReferenceTest {
 	}
 	
 	@Test
-	public void testConcurrent() throws InterruptedException, Throwable {
+	public void testConcurrent() throws Throwable {
 		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
 		
 		AtomicInteger ai = new AtomicInteger(5);
 		Supplier<Integer> ref = LazyReference.concurrent(() -> ai.getAndIncrement());
 
-		for(int t=0; t<10; t++) {
+		CountDownLatch latch = new CountDownLatch(10);
+
+		for(long t=0, n=latch.getCount(); t<n; t++) {
 			thread(50, errors::add, ()->{
 				for(int i=0; i<10; i++) {
 					sleep(10);
@@ -127,9 +142,10 @@ public class LazyReferenceTest {
 						fail("ref.value = " + v);
 					}
 				}
+				latch.countDown();
 			});
 		}
-		sleep(500);
+		latch.await();
 
 		if(!errors.isEmpty()) {
 			throw errors.get(0);