| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // 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.googlesource.gerrit.plugins.events.fsstore; |
| |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import junit.framework.TestCase; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class FsStoreTest extends TestCase { |
| public static String dir = "events-FsStore"; |
| public static Path base; |
| public static List<OneThread> threads = new LinkedList<>(); |
| |
| public Path myBase; |
| public FsStore store; |
| public String id = UUID.randomUUID().toString(); |
| public String submitMarker = ""; |
| public long count = 1000; |
| |
| public Map<String, Long> reported = new HashMap<String, Long>(); |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| myBase = base; |
| if (myBase == null) { |
| myBase = Files.createTempDirectory(dir); |
| } |
| store = new FsStore(myBase); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| Fs.tryRecursiveDelete(myBase); |
| } |
| |
| @Test |
| public void testUUIDInit() throws IOException { |
| UUID uuid = store.getUuid(); |
| FsStore store2 = new FsStore(myBase); |
| assertEquals(uuid, store2.getUuid()); |
| } |
| |
| @Test |
| public void testHeadInit() throws IOException { |
| assertEquals(0, store.getHead()); |
| FsStore store2 = new FsStore(myBase); |
| assertEquals(0, store2.getHead()); |
| } |
| |
| @Test |
| public void testTailInit() throws IOException { |
| assertEquals(0, store.getTail()); |
| FsStore store2 = new FsStore(myBase); |
| assertEquals(0, store2.getTail()); |
| } |
| |
| @Test |
| public void testGetInit() throws IOException { |
| assertEquals(null, store.get(1)); |
| } |
| |
| @Test |
| public void testAdd() throws IOException { |
| add(); |
| } |
| |
| public void add() throws IOException { |
| long next = store.getHead() + 1; |
| assertEquals(0, get(next)); |
| |
| add(next); |
| assertEquals(next, get(next)); |
| assertEquals(next, store.getHead()); |
| assertEquals(1, store.getTail()); |
| |
| FsStore store2 = new FsStore(myBase); |
| assertEquals(next, store2.getHead()); |
| assertEquals(1, store2.getTail()); |
| } |
| |
| private void add(long l) throws IOException { |
| store.add("" + l); |
| } |
| |
| private long get(long i) throws IOException { |
| String g = store.get(i); |
| return g == null ? 0 : Long.parseLong(g); |
| } |
| |
| @Test |
| public void testAddAgain() throws IOException { |
| add(); |
| add(); |
| add(); |
| } |
| |
| @Test |
| public void testTrim() throws IOException { |
| add(); |
| add(); |
| add(); |
| |
| assertEquals(1, store.getTail()); |
| store.trim(1); |
| assertEquals(2, store.getTail()); |
| assertEquals(0, get(1)); // just deleted |
| assertEquals(2, get(2)); // beyond deleted |
| |
| store.trim(2); |
| assertEquals(3, store.getTail()); |
| assertEquals(0, get(2)); // just deleted |
| assertEquals(3, get(3)); // beyond deleted |
| |
| store.trim(3); |
| assertEquals(3, store.getTail()); |
| assertEquals(3, get(3)); // cannot delete head |
| } |
| |
| @Test |
| public void testCount() throws Exception { |
| for (long i = 0; i < count; i++) { |
| add(); |
| } |
| } |
| |
| public void count() throws Exception { |
| for (long i = 1; i <= count; i++) { |
| store.add(id + " " + i); |
| System.out.print(submitMarker); |
| } |
| } |
| |
| public boolean verify(long head) throws Exception { |
| Set<Long> found = new HashSet<Long>(); |
| long stop = store.getHead(); |
| long mine = 1; |
| |
| for (long i = head + 1; i <= stop; i++) { |
| String event = store.get(i); |
| if (event != null) { |
| String split[] = event.split(" "); |
| if (split != null && id.equals(split[0])) { |
| long n = Long.parseLong(split[1]); |
| if (!found.add(n)) { |
| report("duplicate", n, i); |
| } |
| if (mine != n) { |
| report("ouf of order", n, i); |
| } |
| mine++; |
| } |
| } else { |
| report("gap", mine, i); |
| } |
| } |
| for (long i = 1; i <= count; i++) { |
| if (!found.contains(i)) { |
| report("missing", i, -1); |
| } |
| } |
| |
| boolean error = false; |
| error |= reportTotal("duplicate"); |
| error |= reportTotal("out of order"); |
| error |= reportTotal("missing"); |
| error |= reportTotal("gap"); |
| return error; |
| } |
| |
| private void report(String type, long n, long i) { |
| Long cnt = reported.get(type); |
| if (cnt == null) { |
| System.out.println("First " + type + ": " + n + " pos(" + i + ")"); |
| cnt = (long) 0; |
| } |
| reported.put(type, ++cnt); |
| } |
| |
| private boolean reportTotal(String type) { |
| Long cnt = reported.get(type); |
| if (cnt != null) { |
| System.out.println("Total " + type + "s: " + cnt); |
| return true; |
| } |
| return false; |
| } |
| |
| public static class OneThread implements Runnable { |
| public FsStoreTest test = new FsStoreTest(); |
| public Boolean result; |
| |
| @Override |
| public void run() { |
| try { |
| test.setUp(); |
| long head = test.store.getHead(); |
| test.count(); |
| result = !test.verify(head); |
| } catch (Exception e) { |
| } |
| if (threads.size() > 1) { |
| if (result != null && result) { |
| System.out.println("\nPASS " + test.id); |
| } else { |
| result = false; |
| System.out.println("\nFAIL " + test.id); |
| } |
| } |
| } |
| } |
| |
| /** |
| * First, make the junit jar easily available |
| * |
| * <p>ln -s ~/.m2/repository/junit/junit/4.8.1/junit-4.8.1.jar target |
| * |
| * <p>To run type: |
| * |
| * <p>java -cp target/classes:target/test-classes:target/junit-4.8.1.jar \ |
| * com.googlesource.gerrit.plugins.events.fsstore.FsStoreTest [--id store-id] [dir [count]] |
| * |
| * <p>Note: if you do not specify <dir>, it will create a directory under /tmp |
| * |
| * <p>Local(spinning) 1 worker 1M 28m2s ~17ms/event find events|wc -l .97 rm -rf 30s |
| */ |
| public static void main(String[] argv) throws Exception { |
| List<String> args = new LinkedList<>(); |
| for (String arg : argv) { |
| args.add(arg); |
| } |
| |
| setThreadCount(1); |
| int thread = 0; |
| setSubmitMarker("."); |
| for (Iterator<String> it = args.iterator(); it.hasNext(); ) { |
| String arg = it.next(); |
| if (it.hasNext()) { |
| if ("--id".equals(arg)) { |
| it.remove(); |
| setId(thread++, it.next()); |
| it.remove(); |
| } else if ("-j".equals(arg) || "--threads".equals(arg)) { |
| it.remove(); |
| setThreadCount(new Integer(it.next())); |
| it.remove(); |
| } |
| } |
| } |
| |
| if (args.size() > 0) { |
| base = Paths.get(args.remove(0)); |
| } |
| |
| if (args.size() > 0) { |
| setCount(Long.parseLong(args.remove(0))); |
| } |
| |
| runThreads(); |
| |
| if (getResult()) { |
| System.out.println("\nPASS"); |
| } else { |
| System.out.println("\nFAIL"); |
| } |
| } |
| |
| private static void runThreads() |
| throws IllegalArgumentException, InterruptedException, SecurityException { |
| List<Thread> running = new LinkedList<>(); |
| |
| for (OneThread t : threads) { |
| Thread thread = new Thread(t); |
| thread.start(); |
| running.add(thread); |
| } |
| |
| for (Thread thread : running) { |
| thread.join(); |
| } |
| } |
| |
| private static boolean getResult() { |
| for (OneThread t : threads) { |
| if (t.result == null || !t.result) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static void setCount(long count) { |
| for (OneThread t : threads) { |
| t.test.count = count; |
| } |
| } |
| |
| private static void setSubmitMarker(String marker) { |
| for (OneThread t : threads) { |
| t.test.submitMarker = marker; |
| } |
| } |
| |
| private static void setId(int n, String id) { |
| OneThread t = threads.get(n); |
| t.test.id = id; |
| t.test.submitMarker += id + "."; |
| } |
| |
| private static void setThreadCount(int n) { |
| /* Use a common FsStore for all threads so that synchronized will work across them. */ |
| FsStore commonStore = threads.get(0).test.store; |
| while (threads.size() < n) { |
| OneThread t = new OneThread(); |
| t.test.store = commonStore; |
| threads.add(t); |
| } |
| } |
| } |