Package ca.uwaterloo.fydp.xcde

Source Code of ca.uwaterloo.fydp.xcde.XCDEStressTest

package ca.uwaterloo.fydp.xcde;

import ca.uwaterloo.fydp.ossp.*;
import ca.uwaterloo.fydp.ossp.std.*;
import ca.uwaterloo.fydp.ossp.impl.OSSPImpl;
import java.util.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import ca.uwaterloo.fydp.ossp.impl.OSSPSimpleSynchronizer;

public class XCDEStressTest {
 
  /**
   * Connects the specified number of clients to the specified server
   * and has the clients make random concurrent changes
   * to the server as fast as possible for the indicated amount of time.
   * At the end, it has each client re-download the state and check it
   * versus the local copy. The process is repeated the specified number
   * of times. The results are printed to System.out.
   *
   * @param addr
   * @param port
   * @param numberOfClients
   * @param timeInMillis
   * @param repeats
   */
  public static void stressTest(InetAddress addr, int port, final int numberOfClients, long timeInMillis, int repeats) {
    final Object ob = new Object();
    for (int trial = 0; trial < repeats; trial++) {
      final boolean[] running = new boolean[1];
      final boolean[] passed = new boolean[1];
      final boolean[] disconnected = new boolean[1];
      passed[0] = true;
      running[0] = true;
      disconnected[0] = false;
      final int[] runningClients = new int[1];
      runningClients[0] = 0;
      final Object runningClientsSync = new Object();

      // We make all clients share the same synchronizer, simply so that
      // the text on standard out won't be garbled.
      final OSSPSynchronizer sync = new OSSPSimpleSynchronizer();
      for (int i = 0; i < numberOfClients; i++) {
        final OSSPLink client = OSSPImpl.getInstance().makeTCPLink(addr, port, sync);
        client.setReceiver(new OSSPHalfLink() {
          private java.util.Random rnd = new java.util.Random();
          private XCDERootDirectory localState = null;
         
          public void openResourceSet(OSSPMask mask) {
            throw new RuntimeException("Broken remote server.");
          }
          public void closeResourceSet(OSSPMask close, OSSPMask post, OSSPState current) {
            throw new RuntimeException("Broken remote server.");
          }
          public void answerOpenResourceSet(OSSPState state, OSSPMask mask) {
            if (state == null) {
              // Ignore.
              return;
            }
            if (localState == null) {
              // First download. Starts everything.
              localState = (XCDERootDirectory)state;
              new Thread(new Runnable() {
                private static final int maxDepth = 10;
               
                private OSSPDirectoryElement randomNewDirectoryElement(OSSPDirectory dir, int depth) {
                  // Make a new directory element unlikely
                  final double createDirProb = 0.5;
                  String name = randomUnusedElementName(dir);
                  double val = rnd.nextDouble();
                  if (val < createDirProb && depth < maxDepth) {
                    // Create a dir.
                    return new OSSPDirectoryElement(name, randomDirectory(depth));
                  } else {
                    // Create a file.
                    return new OSSPDirectoryElement(name, randomDocument());
                  }
                }
               
                private XCDEDocument randomDocument() {
                  return new XCDEDocument(randomString());
                }
               
                private OSSPStimulus randomEdit(OSSPDirectory dir, int depth) {
                  // First randomly decide to create, delete, or edit.
                  final double createProb = 0.33, deleteProb = 0.33;
                 
                  double val = rnd.nextDouble();
                  if ((val < createProb) || dir.elements.isEmpty()) {
                    return new OSSPDirectoryStimulusInitiateCreate(randomNewDirectoryElement(dir, depth+1));
                  } else if (val < (createProb + deleteProb)) {
                    // Delete a directory.
                    return new OSSPDirectoryStimulusDelete(randomElement(dir).name);
                  } else {
                    // Edit an element.
                    OSSPDirectoryElement el = randomElement(dir);
                    if (el.state instanceof OSSPDirectory) {
                      return new OSSPDirectoryStimulusChange(el.name, randomEdit((OSSPDirectory)el.state, depth+1));
                    } else {
                      return new OSSPDirectoryStimulusChange(el.name, randomEdit((XCDEDocument)el.state));
                    }
                  }
                }
               
                private String randomUnusedElementName(OSSPDirectory dir) {
                  outer: for (;;) {
                    String s = randomString();
                    for (ListIterator i = dir.ls.listIterator(); i.hasNext(); ) {
                      String name = ((OSSPDirectoryListingElement)i.next()).name;
                      if (s.equals(name)) {
                        continue outer;
                      }
                    }
                    return s;
                  }
                }
               
                private OSSPDirectoryElement randomElement(OSSPDirectory dir) {
                  return (OSSPDirectoryElement)dir.elements.get(randomInt(dir.elements.size()-1));
                }
               
                private OSSPStimulus randomEdit(XCDEDocument file) {
                  final double insertProb = 0.5;
                  double val = rnd.nextDouble();
                  int pos = randomInt(file.content.length());
                  int sel = randomInt(file.content.length() - pos);
                  if (val < insertProb) {
                    // Insert
                    return new XCDEDocChangeInsertion(randomEditingUser(file), pos, randomString());
                  } else { // i.e. delete text
                    // Delete
                    return new XCDEDocChangeDeletion(randomEditingUser(file), pos, sel);
                  }
                }
               
                private String randomEditingUser(XCDEDocument doc) {
                  /*
                  int i = randomInt(doc.users.size());
                  if (i >= doc.users.size()) {
                    return "anon";
                  }
                  return ((XCDERootDirectoryUserState)doc.users.get(i)).userName;
                  */
                  return "anon";
                }
               
                private int randomInt(int max) {
                  return (int)((max + 1) * rnd.nextDouble());
                }
               
                private String randomString() {
                  final int longestString = 32;
                  /**
                   * Add 1 to the random number b/c element names of length 0 are
                   * illegal and inserts of length 0 are pointless.
                   */
                  int length = 1 + randomInt(longestString);
                  StringBuffer buf = new StringBuffer();
                  for (int i = 0; i < length; i++) {
                    // Choose a random ASCII letter or digit.
                    char c;
                    for (;;) {
                      c = (char)(byte)(rnd.nextDouble()*256);
                      if ((('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z')) || (('0' <= c) && (c <= '9'))) {
                        break;
                      }
                    }
                    buf.append(c);
                  }
                  return buf.toString();
                }
               
                private OSSPDirectory randomDirectory(int depth) {
                  final int maxInitialElements = 5;
                  int numElements = randomInt(maxInitialElements);
                  OSSPDirectory dir = new OSSPDirectory();
                  for (int i = 0; i < numElements; i++) {
                    OSSPDirectoryElement el = randomNewDirectoryElement(dir, depth+1);
                    dir.ls.add(new OSSPDirectoryListingElement(el.name, el.state.getClass().getName()));
                    dir.elements.add(el);
                  }
                  return dir;
                }
               
                private String randomUsedUserName(XCDERootDirectory doc) {
                  return ((XCDERootDirectoryUserState)doc.users.get(randomInt(doc.users.size()-1))).userName;
                }
               
                private String randomUnusedUserName(XCDERootDirectory doc) {
                  outer: for (;;) {
                    String s = randomString();
                    for (ListIterator i = doc.users.listIterator(); i.hasNext(); ) {
                      String name = ((XCDERootDirectoryUserState)i.next()).userName;
                      if (s.equals(name)) {
                        continue outer;
                      }
                    }
                    return s;
                  }
                }
               
                private XCDEDocumentSelection randomSelection(XCDEDocument file) {
                  int pos = randomInt(file.content.length());
                  int sel = randomInt(file.content.length() - pos);
                  return new XCDEDocumentSelection(pos, sel);
                }
               
                private XCDERootDirectoryUserState randomViewingUserState(String name, Vector path, OSSPDirectory dir) {
                  if (dir.elements.isEmpty()) {
                    // We'll abort doing a viewing user and instead do a non-viewing user.
                    return null;
                  }
                  OSSPDirectoryElement el = (OSSPDirectoryElement)dir.elements.get( randomInt(dir.elements.size()-1) );
                  path.add(el.name);
                  if (el.state instanceof OSSPDirectory) {
                    return randomViewingUserState(name, path, (OSSPDirectory)el.state);
                  } else {
                    // Found a file. Decide if we want to have a cursor in it or not.
                    final double selecting = 0.75;
                    double val = rnd.nextDouble();
                    if (val < selecting) {
                      return new XCDERootDirectoryUserState(name, (String[])path.toArray(new String[path.size()]), randomSelection((XCDEDocument)el.state), rnd.nextBoolean(), rnd.nextBoolean());
                    } else {
                      return new XCDERootDirectoryUserState(name, (String[])path.toArray(new String[path.size()]), null, rnd.nextBoolean(), rnd.nextBoolean());
                    }
                  }
                }
               
                private XCDERootDirectoryUserState randomUserState(String name, XCDERootDirectory root) {
                  final double viewingProb = 0.5;
                  double val = rnd.nextDouble();
                  if (val < viewingProb) {
                    XCDERootDirectoryUserState tmp = randomViewingUserState(name, new Vector(), root);
                    if (tmp != null) {
                      return tmp;
                    }
                    // Else we ended up at an empty directory instead of a file. There might be files somewhere
                    // in the repository, but let's just not view anything.
                  }
                  return new XCDERootDirectoryUserState(name, null, null, rnd.nextBoolean(), rnd.nextBoolean());
                }
               
                OSSPStimulus randomRootEdit(XCDERootDirectory root) {
                  // Randomly choose to do a user edit or "normal" edit.
                  double userProb = 0.25;
                  double val = rnd.nextDouble();
                  if (val < userProb) {
                    final double addUserProb = 0.33, delUserProb = 0.66;
                    val = rnd.nextDouble();
                    if (val < addUserProb || root.users.isEmpty()) {
                      return new XCDERootDirectoryUserStimulusAdd(randomUserState(randomUnusedUserName(root), root));
                    } else if (val < delUserProb) {
                      return new XCDERootDirectoryUserStimulusRemove(randomUsedUserName(root));
                    } else {
                      final double sameName = 0.75;
                      val = rnd.nextDouble();
                      if (val < sameName) {
                        String name = randomUsedUserName(root);
                        return new XCDERootDirectoryUserStimulusChange(name, randomUserState(name, root));
                      } else {
                        return new XCDERootDirectoryUserStimulusChange(randomUsedUserName(root), randomUserState(randomUnusedUserName(root), root));
                      }
                    }
                  } else {
                    // Normal edit.
                    return randomEdit(root, 0);
                  }
                }
               
                public void run() {
                  synchronized (runningClientsSync) {
                    runningClients[0]++;
                    if (runningClients[0] == numberOfClients) {
                      runningClientsSync.notify();
                    }
                  }
                  while (running[0]) {
                    // Randomly edit the current state as fast as possible.
                    client.pace();
                    synchronized (sync) {
                      try {
                        if (disconnected[0]) {
                          return;
                        }
                        OSSPStimulus ch = randomRootEdit(localState);
                        client.sendStimulus(ch);
                        localState = (XCDERootDirectory)ch.apply(localState);
                      } catch (Exception e) {
                        e.printStackTrace();
                      }
                    }
                  }
                  // We now stop editing. We check our correctness
                  // by re-downloading the state and comparing. After
                  // the download, the connection is severed.
                  client.pace();
                  synchronized (sync) {
                    client.openResourceSet(new OSSPFullMask());
                  }
                }
              }).start();
            } else {
              // Second download. Compare with local state and then
              // disconnect.
              boolean usersCheckPass = true;
              outer2: for (ListIterator k = ((XCDERootDirectory)state).users.listIterator(); k.hasNext(); ) {
                XCDERootDirectoryUserState user = (XCDERootDirectoryUserState)k.next();
                for (ListIterator k2 = localState.users.listIterator(); k2.hasNext(); ) {
                  XCDERootDirectoryUserState user2 = (XCDERootDirectoryUserState)k2.next();
                  if (user2.equals(user)) {
                    k2.remove();
                    continue outer2;
                  }
                }
                System.out.println("Missing user on root: " + user);
                System.out.println("(Users on the other copy are: " + localState.users + ")");
                usersCheckPass = false;
                break outer2;
              }
              // Equal as long as there's no unremoved user states in localState.
              if (usersCheckPass && !localState.users.isEmpty()) {
                System.out.println("Missing users on root: " + localState.users);
                usersCheckPass = false;
              }
              if (!usersCheckPass || !destructiveEquals(localState, (OSSPDirectory)state)) {
                System.out.println("ERROR!! Final state for this client does not match server.");
                passed[0] = false;
              } else {
                System.out.println("SUCCESS!! Final state for this client matches server.");
              }
              client.disconnect();
              synchronized (runningClientsSync) {
                runningClients[0]--;
                if (runningClients[0] == 0) {
                  runningClientsSync.notify();
                }
              }
            }
          }
          public void sendStimulus(OSSPStimulus stim) {
            localState = (XCDERootDirectory)stim.apply(localState);
          }
          public void disconnect() {
            disconnected[0] = true;
            throw new RuntimeException("Unexpected remote server disconnect during stress test.");
          }
        });
        client.pace();
        synchronized (sync) {
          client.openResourceSet(new OSSPFullMask());
        }
      }
     
      // Wait for all threads to start.
      synchronized (runningClientsSync) {
        try {
          runningClientsSync.wait();
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
     
      // Run for the indicated amount of time.
      synchronized (ob) {
        try {
          ob.wait(timeInMillis);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
     
      // Start stopping the threads.
      running[0] = false;
     
      // Wait for all threads to stop.
      synchronized (runningClientsSync) {
        try {
          runningClientsSync.wait();
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
     
      if (passed[0]) {
        System.out.println("SUCCESS!! All clients match the server.");
      } else {
        System.out.println("ERROR!! Some clients didn't match the server. Halting trials.");
        return;
      }
     
    }
    System.out.println("SUCCESS!! All trials completed without incident.");
  }
 
  /**
   * Determines whether dir1 and dir2 are equivalent, except that in doing so
   * it destroys dir1.
   *
   * @param dir1
   * @param dir2
   * @return
   */
  private static boolean destructiveEquals(OSSPDirectory dir1, OSSPDirectory dir2) {
    // Note: we ignore the .ls field, because we assume they'll be correct
    // for the .elements field.
    outer: for (ListIterator i = dir2.elements.listIterator(); i.hasNext(); ) {
      OSSPDirectoryElement el = (OSSPDirectoryElement)i.next();
      for (ListIterator j = dir1.elements.listIterator(); j.hasNext(); ) {
        OSSPDirectoryElement tmp = (OSSPDirectoryElement)j.next();
        if (el.name.equals(tmp.name)) {
          // Found the entry in dir1.
          if (el.state instanceof OSSPDirectory) {
            if (!(tmp.state instanceof OSSPDirectory)) {
              System.out.println("Element class mismatch.");
              return false;
            }
            if (!destructiveEquals((OSSPDirectory)tmp.state, (OSSPDirectory)el.state)) {
              return false;
            }
          } else {
            // Must be XCDEDocument.
            if (!(tmp.state instanceof XCDEDocument)) {
              System.out.println("Element class mismatch.");
              return false;
            }
            if (!((XCDEDocument)el.state).content.equals(((XCDEDocument)tmp.state).content)) {
              System.out.println("Content discrepancy for file named " + tmp.name);
              System.out.println("<<<FILE #1's CONTENT>>>");
              System.out.println();
              System.out.println();
              System.out.println();
              System.out.println();
              System.out.println();
              System.out.println(((XCDEDocument)tmp.state).content);
             
              System.out.println("<<<FILE #2's CONTENT>>>");
              System.out.println();
              System.out.println();
              System.out.println();
              System.out.println();
              System.out.println();
              System.out.println(((XCDEDocument)el.state).content);
              return false;
            }
            /*
            outer2: for (ListIterator k = ((XCDEDocument)el.state).users.listIterator(); k.hasNext(); ) {
              XCDERootDirectoryUserState user = (XCDERootDirectoryUserState)k.next();
              for (ListIterator k2 = ((XCDEDocument)tmp.state).users.listIterator(); k2.hasNext(); ) {
                XCDERootDirectoryUserState user2 = (XCDERootDirectoryUserState)k2.next();
                if (user2.equals(user)) {
                  k2.remove();
                  continue outer2;
                }
              }
              // Couldn't find it.
              System.out.println("Missing user in file " + tmp.name + ": " + user);
              return false;
            }
            // Equal as long as there's no unremoved user states in tmp.
            if (!((XCDEDocument)tmp.state).users.isEmpty()) {
              System.out.println("Missing users in file " + tmp.name + ": " + ((XCDEDocument)tmp.state).users);
              return false;
            }
            */
          }
          j.remove();
          continue outer;
        }
      }
      // No entry for this in the other directory.
      System.out.println("Missing element: " + el.name);
      return false;
    }
  // Equal as long as there's no unremoved elements in dir1.
  if (!dir1.elements.isEmpty()) {
    System.out.println("Missing elements: " + dir1.elements);
    return false;
  }
  return true;
  }
 
  /**
   * args[0] = server machine name as string
   * args[1] = server port number as string
   * args[2] = number of clients to connect with
   * args[3] = milliseconds to run each trial for
   * args[4] = max. number of trials to do
   *
   * @param args
   */
  public static void main(String[] args) throws UnknownHostException {
    stressTest(InetAddress.getByName(args[0]), Integer.parseInt(args[1]),
        Integer.parseInt(args[2]), Integer.parseInt(args[3]), Integer.parseInt(args[4]));
  }
}
TOP

Related Classes of ca.uwaterloo.fydp.xcde.XCDEStressTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.