/* * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.firebase.database.core; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.TestHelpers; import com.google.firebase.database.core.view.View; import com.google.firebase.database.core.view.ViewAccess; import java.util.HashMap; import java.util.List; import java.util.concurrent.Semaphore; /** Verifies that the zombie state matches the current registration list in a Repo */ public class ZombieVerifier { public static void verifyRepoZombies(List<DatabaseReference> references) throws InterruptedException { for (DatabaseReference ref : references) { verifyRepoZombies(ref); } } public static void verifyRepoZombies(DatabaseReference ref) throws InterruptedException { verifyRepoZombies(ref.getRepo()); } public static void verifyRepoZombies(List<DatabaseReference> references, Semaphore semaphore) throws InterruptedException { for (DatabaseReference ref : references) { verifyRepoZombies(ref, semaphore); } } public static void verifyRepoZombies(DatabaseReference ref, Semaphore semaphore) throws InterruptedException { verifyRepoZombies(ref.getRepo(), semaphore); } static void verifyRepoZombies(final Repo repo) throws InterruptedException { final Semaphore wait = new Semaphore(0); verifyRepoZombies(repo, wait); TestHelpers.waitFor(wait); } // To verify our state, we: // 1. verify each eventregistration instance that exists in the zombiemanager also exist in some // view // 2. there are no zombied registrations inside zombiemanager static void verifyRepoZombies(final Repo repo, final Semaphore semaphore) throws InterruptedException { repo.scheduleNow( new Runnable() { @Override public void run() { ZombieEventManager manager = ZombieEventManager.getInstance(); HashMap<InstanceHashKey, Long> zombieCount = new HashMap<>(); for (List<EventRegistration> list : manager.globalEventRegistrations.values()) { for (EventRegistration eventRegistration : list) { if (eventRegistration.getRepo() != repo) { continue; } assertFalse(eventRegistration.isZombied()); InstanceHashKey key = new InstanceHashKey(eventRegistration); Long current = zombieCount.get(key); zombieCount.put(key, current == null ? 1 : current + 1); } } HashMap<InstanceHashKey, Long> viewCount = new HashMap<>(); //now enumerate views in the repo. for (SyncPoint point : repo.getServerSyncTree().getSyncPointTree().values()) { scanRegistrations(repo, point, viewCount); } for (SyncPoint point : repo.getInfoSyncTree().getSyncPointTree().values()) { scanRegistrations(repo, point, viewCount); } for (InstanceHashKey key : zombieCount.keySet()) { Long zombies = zombieCount.get(key); if (!key.getInner().getQuerySpec().isDefault()) { zombies = zombies / 2; } if (!zombies.equals(viewCount.get(key))) { fail("an imbalance in the force has been detected!"); } } semaphore.release(); } }); } private static void scanRegistrations( Repo repo, SyncPoint point, HashMap<InstanceHashKey, Long> viewCount) { for (View view : point.getViews().values()) { for (EventRegistration eventRegistration : ViewAccess.getEventRegistrations(view)) { if (eventRegistration.getRepo() != repo) { continue; } InstanceHashKey key = new InstanceHashKey(eventRegistration); Long current = viewCount.get(key); viewCount.put(key, current == null ? 1 : current + 1); } } } // Special class that we use to test based on instance equality. static class InstanceHashKey { private EventRegistration inner; public InstanceHashKey(EventRegistration inner) { this.inner = inner; } @Override public boolean equals(Object obj) { return inner == ((InstanceHashKey) obj).inner; } @Override public int hashCode() { return inner.hashCode(); } public EventRegistration getInner() { return inner; } } }