1 /*
   2  * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.ByteArrayOutputStream;
  25 import java.io.PrintStream;
  26 import java.io.StringWriter;
  27 import java.lang.reflect.Method;
  28 import java.nio.file.Path;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.Collection;
  32 import java.util.Collections;
  33 import java.util.HashMap;
  34 import java.util.LinkedHashMap;
  35 import java.util.LinkedHashSet;
  36 import java.util.List;
  37 import java.util.Map;
  38 import java.util.Set;
  39 import java.util.TreeMap;
  40 import java.util.function.Predicate;
  41 import java.util.function.Supplier;
  42 import java.util.stream.Collectors;
  43 import java.util.stream.Stream;
  44 
  45 import javax.tools.Diagnostic;
  46 
  47 import jdk.jshell.EvalException;
  48 import jdk.jshell.JShell;
  49 import jdk.jshell.JShell.Subscription;
  50 import jdk.jshell.Snippet;
  51 import jdk.jshell.DeclarationSnippet;
  52 import jdk.jshell.ExpressionSnippet;
  53 import jdk.jshell.ImportSnippet;
  54 import jdk.jshell.Snippet.Kind;
  55 import jdk.jshell.MethodSnippet;
  56 import jdk.jshell.PersistentSnippet;
  57 import jdk.jshell.Snippet.Status;
  58 import jdk.jshell.Snippet.SubKind;
  59 import jdk.jshell.TypeDeclSnippet;
  60 import jdk.jshell.VarSnippet;
  61 import jdk.jshell.SnippetEvent;
  62 import jdk.jshell.SourceCodeAnalysis;
  63 import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
  64 import jdk.jshell.SourceCodeAnalysis.Completeness;
  65 import jdk.jshell.SourceCodeAnalysis.IndexResult;
  66 import jdk.jshell.SourceCodeAnalysis.Suggestion;
  67 import jdk.jshell.UnresolvedReferenceException;
  68 import org.testng.annotations.AfterMethod;
  69 import org.testng.annotations.BeforeMethod;
  70 
  71 import jdk.jshell.Diag;
  72 import static jdk.jshell.Snippet.Status.*;
  73 import static org.testng.Assert.*;
  74 import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
  75 
  76 public class KullaTesting {
  77 
  78     public static final String IGNORE_VALUE = "<ignore-value>";
  79     public static final Class<? extends Throwable> IGNORE_EXCEPTION = (new Throwable() {}).getClass();
  80     public static final Snippet MAIN_SNIPPET;
  81 
  82     private SourceCodeAnalysis analysis = null;
  83     private JShell state = null;
  84     private TestingInputStream inStream = null;
  85     private ByteArrayOutputStream outStream = null;
  86     private ByteArrayOutputStream errStream = null;
  87 
  88     private Map<String, Snippet> idToSnippet = new LinkedHashMap<>();
  89     private Set<Snippet> allSnippets = new LinkedHashSet<>();
  90     private List<String> classpath;
  91 
  92     static {
  93         JShell js = JShell.create();
  94         MAIN_SNIPPET = js.eval("MAIN_SNIPPET").get(0).snippet();
  95         js.close();
  96         assertTrue(MAIN_SNIPPET != null, "Bad MAIN_SNIPPET set-up -- must not be null");
  97     }
  98 
  99     public enum DiagCheck {
 100         DIAG_OK,
 101         DIAG_WARNING,
 102         DIAG_ERROR,
 103         DIAG_IGNORE
 104     }
 105 
 106     public void setInput(String s) {
 107         inStream.setInput(s);
 108     }
 109 
 110     public String getOutput() {
 111         String s = outStream.toString();
 112         outStream.reset();
 113         return s;
 114     }
 115 
 116     public String getErrorOutput() {
 117         String s = errStream.toString();
 118         errStream.reset();
 119         return s;
 120     }
 121 
 122     /**
 123      * @return the analysis
 124      */
 125     public SourceCodeAnalysis getAnalysis() {
 126         if (analysis == null) {
 127             analysis = state.sourceCodeAnalysis();
 128         }
 129         return analysis;
 130     }
 131 
 132     /**
 133      * @return the state
 134      */
 135     public JShell getState() {
 136         return state;
 137     }
 138 
 139     public List<Snippet> getActiveKeys() {
 140         return allSnippets.stream()
 141                 .filter(k -> getState().status(k).isActive)
 142                 .collect(Collectors.toList());
 143     }
 144 
 145     public void addToClasspath(String path) {
 146         classpath.add(path);
 147         getState().addToClasspath(path);
 148     }
 149 
 150     public void addToClasspath(Path path) {
 151         addToClasspath(path.toString());
 152     }
 153 
 154     @BeforeMethod
 155     public void setUp() {
 156         inStream = new TestingInputStream();
 157         outStream = new ByteArrayOutputStream();
 158         errStream = new ByteArrayOutputStream();
 159         state = JShell.builder()
 160                 .in(inStream)
 161                 .out(new PrintStream(outStream))
 162                 .err(new PrintStream(errStream))
 163                 .build();
 164         allSnippets = new LinkedHashSet<>();
 165         idToSnippet = new LinkedHashMap<>();
 166         classpath = new ArrayList<>();
 167     }
 168 
 169     @AfterMethod
 170     public void tearDown() {
 171         if (state != null) state.close();
 172         state = null;
 173         analysis = null;
 174         allSnippets = null;
 175         idToSnippet = null;
 176         classpath = null;
 177     }
 178 
 179     public List<String> assertUnresolvedDependencies(DeclarationSnippet key, int unresolvedSize) {
 180         List<String> unresolved = getState().unresolvedDependencies(key);
 181         assertEquals(unresolved.size(), unresolvedSize, "Input: " + key.source() + ", checking unresolved: ");
 182         return unresolved;
 183     }
 184 
 185     public DeclarationSnippet assertUnresolvedDependencies1(DeclarationSnippet key, Status status, String name) {
 186         List<String> unresolved = assertUnresolvedDependencies(key, 1);
 187         String input = key.source();
 188         assertEquals(unresolved.size(), 1, "Given input: " + input + ", checking unresolved");
 189         assertEquals(unresolved.get(0), name, "Given input: " + input + ", checking unresolved: ");
 190         assertEquals(getState().status(key), status, "Given input: " + input + ", checking status: ");
 191         return key;
 192     }
 193 
 194     public MethodSnippet assertEvalUnresolvedException(String input, String name, int unresolvedSize, int diagnosticsSize) {
 195         List<SnippetEvent> events = assertEval(input, null, UnresolvedReferenceException.class, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, null);
 196         SnippetEvent ste = events.get(0);
 197         MethodSnippet methodKey = ((UnresolvedReferenceException) ste.exception()).getMethodSnippet();
 198         assertEquals(methodKey.name(), name, "Given input: " + input + ", checking name");
 199         assertEquals(getState().unresolvedDependencies(methodKey).size(), unresolvedSize, "Given input: " + input + ", checking unresolved");
 200         assertEquals(getState().diagnostics(methodKey).size(), diagnosticsSize, "Given input: " + input + ", checking diagnostics");
 201         return methodKey;
 202     }
 203 
 204     public Snippet assertKeyMatch(String input, boolean isExecutable, SubKind expectedSubKind, STEInfo mainInfo, STEInfo... updates) {
 205         Snippet key = key(assertEval(input, IGNORE_VALUE, mainInfo, updates));
 206         String source = key.source();
 207         assertEquals(source, input, "Key \"" + input + "\" source mismatch, got: " + source + ", expected: " + input);
 208         SubKind subkind = key.subKind();
 209         assertEquals(subkind, expectedSubKind, "Key \"" + input + "\" subkind mismatch, got: "
 210                 + subkind + ", expected: " + expectedSubKind);
 211         assertEquals(subkind.isExecutable(), isExecutable, "Key \"" + input + "\", expected isExecutable: "
 212                 + isExecutable + ", got: " + subkind.isExecutable());
 213         Snippet.Kind expectedKind = getKind(key);
 214         assertEquals(key.kind(), expectedKind, "Checking kind: ");
 215         assertEquals(expectedSubKind.kind(), expectedKind, "Checking kind: ");
 216         return key;
 217     }
 218 
 219     private Kind getKind(Snippet key) {
 220         SubKind expectedSubKind = key.subKind();
 221         Kind expectedKind;
 222         switch (expectedSubKind) {
 223             case SINGLE_TYPE_IMPORT_SUBKIND:
 224             case SINGLE_STATIC_IMPORT_SUBKIND:
 225             case TYPE_IMPORT_ON_DEMAND_SUBKIND:
 226             case STATIC_IMPORT_ON_DEMAND_SUBKIND:
 227                 expectedKind = Kind.IMPORT;
 228                 break;
 229             case CLASS_SUBKIND:
 230             case INTERFACE_SUBKIND:
 231             case ENUM_SUBKIND:
 232             case ANNOTATION_TYPE_SUBKIND:
 233                 expectedKind = Kind.TYPE_DECL;
 234                 break;
 235             case METHOD_SUBKIND:
 236                 expectedKind = Kind.METHOD;
 237                 break;
 238             case VAR_DECLARATION_SUBKIND:
 239             case TEMP_VAR_EXPRESSION_SUBKIND:
 240             case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND:
 241                 expectedKind = Kind.VAR;
 242                 break;
 243             case VAR_VALUE_SUBKIND:
 244             case ASSIGNMENT_SUBKIND:
 245                 expectedKind = Kind.EXPRESSION;
 246                 break;
 247             case STATEMENT_SUBKIND:
 248                 expectedKind = Kind.STATEMENT;
 249                 break;
 250             case UNKNOWN_SUBKIND:
 251                 expectedKind = Kind.ERRONEOUS;
 252                 break;
 253             default:
 254                 throw new AssertionError("Unsupported key: " + key.getClass().getCanonicalName());
 255         }
 256         return expectedKind;
 257     }
 258 
 259     public ImportSnippet assertImportKeyMatch(String input, String name, SubKind subkind, STEInfo mainInfo, STEInfo... updates) {
 260         Snippet key = assertKeyMatch(input, false, subkind, mainInfo, updates);
 261 
 262         assertTrue(key instanceof ImportSnippet, "Expected an ImportKey, got: " + key.getClass().getName());
 263         ImportSnippet importKey = (ImportSnippet) key;
 264         assertEquals(importKey.name(), name, "Input \"" + input +
 265                 "\" name mismatch, got: " + importKey.name() + ", expected: " + name);
 266         assertEquals(importKey.kind(), Kind.IMPORT, "Checking kind: ");
 267         return importKey;
 268     }
 269 
 270     public DeclarationSnippet assertDeclarationKeyMatch(String input, boolean isExecutable, String name, SubKind subkind, STEInfo mainInfo, STEInfo... updates) {
 271         Snippet key = assertKeyMatch(input, isExecutable, subkind, mainInfo, updates);
 272 
 273         assertTrue(key instanceof DeclarationSnippet, "Expected a DeclarationKey, got: " + key.getClass().getName());
 274         DeclarationSnippet declKey = (DeclarationSnippet) key;
 275         assertEquals(declKey.name(), name, "Input \"" + input +
 276                 "\" name mismatch, got: " + declKey.name() + ", expected: " + name);
 277         return declKey;
 278     }
 279 
 280     public VarSnippet assertVarKeyMatch(String input, boolean isExecutable, String name, SubKind kind, String typeName, STEInfo mainInfo, STEInfo... updates) {
 281         Snippet sn = assertDeclarationKeyMatch(input, isExecutable, name, kind, mainInfo, updates);
 282         assertTrue(sn instanceof VarSnippet, "Expected a VarKey, got: " + sn.getClass().getName());
 283         VarSnippet variableKey = (VarSnippet) sn;
 284         String signature = variableKey.typeName();
 285         assertEquals(signature, typeName, "Key \"" + input +
 286                 "\" typeName mismatch, got: " + signature + ", expected: " + typeName);
 287         assertEquals(variableKey.kind(), Kind.VAR, "Checking kind: ");
 288         return variableKey;
 289     }
 290 
 291     public void assertExpressionKeyMatch(String input, String name, SubKind kind, String typeName) {
 292         Snippet key = assertKeyMatch(input, true, kind, added(VALID));
 293         assertTrue(key instanceof ExpressionSnippet, "Expected a ExpressionKey, got: " + key.getClass().getName());
 294         ExpressionSnippet exprKey = (ExpressionSnippet) key;
 295         assertEquals(exprKey.name(), name, "Input \"" + input +
 296                 "\" name mismatch, got: " + exprKey.name() + ", expected: " + name);
 297         assertEquals(exprKey.typeName(), typeName, "Key \"" + input +
 298                 "\" typeName mismatch, got: " + exprKey.typeName() + ", expected: " + typeName);
 299         assertEquals(exprKey.kind(), Kind.EXPRESSION, "Checking kind: ");
 300     }
 301 
 302     // For expressions throwing an EvalException
 303     public SnippetEvent assertEvalException(String input) {
 304         List<SnippetEvent> events = assertEval(input, null, EvalException.class,
 305                 DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, null);
 306         return events.get(0);
 307     }
 308 
 309 
 310     public List<SnippetEvent> assertEvalFail(String input) {
 311         return assertEval(input, null, null,
 312                 DiagCheck.DIAG_ERROR, DiagCheck.DIAG_IGNORE, added(REJECTED));
 313     }
 314 
 315     public List<SnippetEvent> assertEval(String input) {
 316         return assertEval(input, IGNORE_VALUE, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, added(VALID));
 317     }
 318 
 319     public List<SnippetEvent> assertEval(String input, String value) {
 320         return assertEval(input, value, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, added(VALID));
 321     }
 322 
 323     public List<SnippetEvent> assertEval(String input, STEInfo mainInfo, STEInfo... updates) {
 324         return assertEval(input, IGNORE_VALUE, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, mainInfo, updates);
 325     }
 326 
 327     public List<SnippetEvent> assertEval(String input, String value,
 328             STEInfo mainInfo, STEInfo... updates) {
 329         return assertEval(input, value, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, mainInfo, updates);
 330     }
 331 
 332     public List<SnippetEvent> assertEval(String input, DiagCheck diagMain, DiagCheck diagUpdates) {
 333         return assertEval(input, IGNORE_VALUE, null, diagMain, diagUpdates, added(VALID));
 334     }
 335 
 336     public List<SnippetEvent> assertEval(String input, DiagCheck diagMain, DiagCheck diagUpdates,
 337             STEInfo mainInfo, STEInfo... updates) {
 338         return assertEval(input, IGNORE_VALUE, null, diagMain, diagUpdates, mainInfo, updates);
 339     }
 340 
 341     public List<SnippetEvent> assertEval(String input,
 342             String value, Class<? extends Throwable> exceptionClass,
 343             DiagCheck diagMain, DiagCheck diagUpdates,
 344             STEInfo mainInfo, STEInfo... updates) {
 345         return assertEval(input, diagMain, diagUpdates, new EventChain(mainInfo, value, exceptionClass, updates));
 346     }
 347 
 348     // Use this directly or usually indirectly for all non-empty calls to eval()
 349     public List<SnippetEvent> assertEval(String input,
 350            DiagCheck diagMain, DiagCheck diagUpdates, EventChain... eventChains) {
 351         return checkEvents(() -> getState().eval(input), "eval(" + input + ")", diagMain, diagUpdates, eventChains);
 352     }
 353 
 354     private Map<Snippet, Snippet> closure(List<SnippetEvent> events) {
 355         Map<Snippet, Snippet> transitions = new HashMap<>();
 356         for (SnippetEvent event : events) {
 357             transitions.put(event.snippet(), event.causeSnippet());
 358         }
 359         Map<Snippet, Snippet> causeSnippets = new HashMap<>();
 360         for (Map.Entry<Snippet, Snippet> entry : transitions.entrySet()) {
 361             Snippet snippet = entry.getKey();
 362             Snippet cause = getInitialCause(transitions, entry.getValue());
 363             causeSnippets.put(snippet, cause);
 364         }
 365         return causeSnippets;
 366     }
 367 
 368     private Snippet getInitialCause(Map<Snippet, Snippet> transitions, Snippet snippet) {
 369         Snippet result;
 370         while ((result = transitions.get(snippet)) != null) {
 371             snippet = result;
 372         }
 373         return snippet;
 374     }
 375 
 376     private Map<Snippet, List<SnippetEvent>> groupByCauseSnippet(List<SnippetEvent> events) {
 377         Map<Snippet, List<SnippetEvent>> map = new TreeMap<>((a, b) -> a.id().compareTo(b.id()));
 378         for (SnippetEvent event : events) {
 379             if (event == null) {
 380                 throw new InternalError("null event found in " + events);
 381             }
 382             if (event.snippet() == null) {
 383                 throw new InternalError("null event Snippet found in " + events);
 384             }
 385             if (event.snippet().id() == null) {
 386                 throw new InternalError("null event Snippet id() found in " + events);
 387             }
 388         }
 389         for (SnippetEvent event : events) {
 390             if (event.causeSnippet() == null) {
 391                 map.computeIfAbsent(event.snippet(), ($) -> new ArrayList<>()).add(event);
 392             }
 393         }
 394         Map<Snippet, Snippet> causeSnippets = closure(events);
 395         for (SnippetEvent event : events) {
 396             Snippet causeSnippet = causeSnippets.get(event.snippet());
 397             if (causeSnippet != null) {
 398                 map.get(causeSnippet).add(event);
 399             }
 400         }
 401         for (Map.Entry<Snippet, List<SnippetEvent>> entry : map.entrySet()) {
 402             Collections.sort(entry.getValue(),
 403                     (a, b) -> a.causeSnippet() == null
 404                             ? -1 : b.causeSnippet() == null
 405                             ? 1 : a.snippet().id().compareTo(b.snippet().id()));
 406         }
 407         return map;
 408     }
 409 
 410     private List<STEInfo> getInfos(EventChain... eventChains) {
 411         List<STEInfo> list = new ArrayList<>();
 412         for (EventChain i : eventChains) {
 413             list.add(i.mainInfo);
 414             Collections.addAll(list, i.updates);
 415         }
 416         return list;
 417     }
 418 
 419     private List<SnippetEvent> checkEvents(Supplier<List<SnippetEvent>> toTest,
 420              String descriptor,
 421              DiagCheck diagMain, DiagCheck diagUpdates,
 422              EventChain... eventChains) {
 423         List<SnippetEvent> dispatched = new ArrayList<>();
 424         Subscription token = getState().onSnippetEvent(kse -> {
 425             if (dispatched.size() > 0 && dispatched.get(dispatched.size() - 1) == null) {
 426                 throw new RuntimeException("dispatch event after done");
 427             }
 428             dispatched.add(kse);
 429         });
 430         List<SnippetEvent> events = toTest.get();
 431         getState().unsubscribe(token);
 432         assertEquals(dispatched.size(), events.size(), "dispatched event size not the same as event size");
 433         for (int i = events.size() - 1; i >= 0; --i) {
 434             assertEquals(dispatched.get(i), events.get(i), "Event element " + i + " does not match");
 435         }
 436         dispatched.add(null); // mark end of dispatchs
 437 
 438         for (SnippetEvent evt : events) {
 439             assertTrue(evt.snippet() != null, "key must never be null, but it was for: " + descriptor);
 440             assertTrue(evt.previousStatus() != null, "previousStatus must never be null, but it was for: " + descriptor);
 441             assertTrue(evt.status() != null, "status must never be null, but it was for: " + descriptor);
 442             assertTrue(evt.status() != NONEXISTENT, "status must not be NONEXISTENT: " + descriptor);
 443             if (evt.previousStatus() != NONEXISTENT) {
 444                 Snippet old = idToSnippet.get(evt.snippet().id());
 445                 if (old != null) {
 446                     switch (evt.status()) {
 447                         case DROPPED:
 448                             assertEquals(old, evt.snippet(),
 449                                     "Drop: Old snippet must be what is dropped -- input: " + descriptor);
 450                             break;
 451                         case OVERWRITTEN:
 452                             assertEquals(old, evt.snippet(),
 453                                     "Overwrite: Old snippet (" + old
 454                                     + ") must be what is overwritten -- input: "
 455                                     + descriptor + " -- " + evt);
 456                             break;
 457                         default:
 458                             if (evt.causeSnippet() == null) {
 459                                 // New source
 460                                 assertNotEquals(old, evt.snippet(),
 461                                         "New source: Old snippet must be different from the replacing -- input: "
 462                                         + descriptor);
 463                             } else {
 464                                 // An update (key Overwrite??)
 465                                 assertEquals(old, evt.snippet(),
 466                                         "Update: Old snippet must be equal to the replacing -- input: "
 467                                         + descriptor);
 468                             }
 469                             break;
 470                     }
 471                 }
 472             }
 473         }
 474         for (SnippetEvent evt : events) {
 475             if (evt.causeSnippet() == null && evt.status() != DROPPED) {
 476                 allSnippets.add(evt.snippet());
 477                 idToSnippet.put(evt.snippet().id(), evt.snippet());
 478             }
 479         }
 480         assertTrue(events.size() >= 1, "Expected at least one event, got none.");
 481         List<STEInfo> all = getInfos(eventChains);
 482         if (events.size() != all.size()) {
 483             StringBuilder sb = new StringBuilder();
 484             sb.append("Got events --\n");
 485             for (SnippetEvent evt : events) {
 486                 sb.append("  key: ").append(evt.snippet());
 487                 sb.append(" before: ").append(evt.previousStatus());
 488                 sb.append(" status: ").append(evt.status());
 489                 sb.append(" isSignatureChange: ").append(evt.isSignatureChange());
 490                 sb.append(" cause: ");
 491                 if (evt.causeSnippet() == null) {
 492                     sb.append("direct");
 493                 } else {
 494                     sb.append(evt.causeSnippet());
 495                 }
 496                 sb.append("\n");
 497             }
 498             sb.append("Expected ").append(all.size());
 499             sb.append(" events, got: ").append(events.size());
 500             fail(sb.toString());
 501         }
 502 
 503         int impactId = 0;
 504         Map<Snippet, List<SnippetEvent>> groupedEvents = groupByCauseSnippet(events);
 505         assertEquals(groupedEvents.size(), eventChains.length, "Number of main events");
 506         for (Map.Entry<Snippet, List<SnippetEvent>> entry : groupedEvents.entrySet()) {
 507             EventChain eventChain = eventChains[impactId++];
 508             SnippetEvent main = entry.getValue().get(0);
 509             Snippet mainKey = main.snippet();
 510             if (eventChain.mainInfo != null) {
 511                 eventChain.mainInfo.assertMatch(entry.getValue().get(0), mainKey);
 512                 if (eventChain.updates.length > 0) {
 513                     if (eventChain.updates.length == 1) {
 514                         eventChain.updates[0].assertMatch(entry.getValue().get(1), mainKey);
 515                     } else {
 516                         Arrays.sort(eventChain.updates, (a, b) -> ((a.snippet() == MAIN_SNIPPET)
 517                                 ? mainKey
 518                                 : a.snippet()).id().compareTo(b.snippet().id()));
 519                         List<SnippetEvent> updateEvents = new ArrayList<>(entry.getValue().subList(1, entry.getValue().size()));
 520                         int idx = 0;
 521                         for (SnippetEvent ste : updateEvents) {
 522                             eventChain.updates[idx++].assertMatch(ste, mainKey);
 523                         }
 524                     }
 525                 }
 526             }
 527             if (((Object) eventChain.value) != IGNORE_VALUE) {
 528                 assertEquals(main.value(), eventChain.value, "Expected execution value of: " + eventChain.value +
 529                         ", but got: " + main.value());
 530             }
 531             if (eventChain.exceptionClass != IGNORE_EXCEPTION) {
 532                 if (main.exception() == null) {
 533                     assertEquals(eventChain.exceptionClass, null, "Expected an exception of class "
 534                             + eventChain.exceptionClass + " got no exception");
 535                 } else if (eventChain.exceptionClass == null) {
 536                     fail("Expected no exception but got " + main.exception().toString());
 537                 } else {
 538                     assertTrue(eventChain.exceptionClass.isInstance(main.exception()),
 539                             "Expected an exception of class " + eventChain.exceptionClass +
 540                                     " got: " + main.exception().toString());
 541                 }
 542             }
 543             List<Diag> diagnostics = getState().diagnostics(mainKey);
 544             switch (diagMain) {
 545                 case DIAG_OK:
 546                     assertEquals(diagnostics.size(), 0, "Expected no diagnostics, got: " + diagnosticsToString(diagnostics));
 547                     break;
 548                 case DIAG_WARNING:
 549                     assertFalse(hasFatalError(diagnostics), "Expected no errors, got: " + diagnosticsToString(diagnostics));
 550                     break;
 551                 case DIAG_ERROR:
 552                     assertTrue(hasFatalError(diagnostics), "Expected errors, got: " + diagnosticsToString(diagnostics));
 553                     break;
 554             }
 555             if (eventChain.mainInfo != null) {
 556                 for (STEInfo ste : eventChain.updates) {
 557                     diagnostics = getState().diagnostics(ste.snippet());
 558                     switch (diagUpdates) {
 559                         case DIAG_OK:
 560                             assertEquals(diagnostics.size(), 0, "Expected no diagnostics, got: " + diagnosticsToString(diagnostics));
 561                             break;
 562                         case DIAG_WARNING:
 563                             assertFalse(hasFatalError(diagnostics), "Expected no errors, got: " + diagnosticsToString(diagnostics));
 564                             break;
 565                     }
 566                 }
 567             }
 568         }
 569         return events;
 570     }
 571 
 572     // Use this for all EMPTY calls to eval()
 573     public void assertEvalEmpty(String input) {
 574         List<SnippetEvent> events = getState().eval(input);
 575         assertEquals(events.size(), 0, "Expected no events, got: " + events.size());
 576     }
 577 
 578     public VarSnippet varKey(List<SnippetEvent> events) {
 579         Snippet key = key(events);
 580         assertTrue(key instanceof VarSnippet, "Expected a VariableKey, got: " + key);
 581         return (VarSnippet) key;
 582     }
 583 
 584     public MethodSnippet methodKey(List<SnippetEvent> events) {
 585         Snippet key = key(events);
 586         assertTrue(key instanceof MethodSnippet, "Expected a MethodKey, got: " + key);
 587         return (MethodSnippet) key;
 588     }
 589 
 590     public TypeDeclSnippet classKey(List<SnippetEvent> events) {
 591         Snippet key = key(events);
 592         assertTrue(key instanceof TypeDeclSnippet, "Expected a ClassKey, got: " + key);
 593         return (TypeDeclSnippet) key;
 594     }
 595 
 596     public ImportSnippet importKey(List<SnippetEvent> events) {
 597         Snippet key = key(events);
 598         assertTrue(key instanceof ImportSnippet, "Expected a ImportKey, got: " + key);
 599         return (ImportSnippet) key;
 600     }
 601 
 602     private Snippet key(List<SnippetEvent> events) {
 603         assertTrue(events.size() >= 1, "Expected at least one event, got none.");
 604         return events.get(0).snippet();
 605     }
 606 
 607     public void assertVarValue(Snippet key, String expected) {
 608         String value = state.varValue((VarSnippet) key);
 609         assertEquals(value, expected, "Expected var value of: " + expected + ", but got: " + value);
 610     }
 611 
 612     public Snippet assertDeclareFail(String input, String expectedErrorCode) {
 613         return assertDeclareFail(input, expectedErrorCode, added(REJECTED));
 614     }
 615 
 616     public Snippet assertDeclareFail(String input, String expectedErrorCode,
 617             STEInfo mainInfo, STEInfo... updates) {
 618         return assertDeclareFail(input,
 619                 new ExpectedDiagnostic(expectedErrorCode, -1, -1, -1, -1, -1, Diagnostic.Kind.ERROR),
 620                 mainInfo, updates);
 621     }
 622 
 623     public Snippet assertDeclareFail(String input, ExpectedDiagnostic expectedDiagnostic) {
 624         return assertDeclareFail(input, expectedDiagnostic, added(REJECTED));
 625     }
 626 
 627     public Snippet assertDeclareFail(String input, ExpectedDiagnostic expectedDiagnostic,
 628             STEInfo mainInfo, STEInfo... updates) {
 629         List<SnippetEvent> events = assertEval(input, null, null,
 630                 DiagCheck.DIAG_ERROR, DiagCheck.DIAG_IGNORE, mainInfo, updates);
 631         SnippetEvent e = events.get(0);
 632         Snippet key = e.snippet();
 633         assertEquals(getState().status(key), REJECTED);
 634         List<Diag> diagnostics = getState().diagnostics(e.snippet());
 635         assertTrue(diagnostics.size() > 0, "Expected diagnostics, got none");
 636         assertDiagnostic(input, diagnostics.get(0), expectedDiagnostic);
 637         assertTrue(key != null, "key must never be null, but it was for: " + input);
 638         return key;
 639     }
 640 
 641     public Snippet assertDeclareWarn1(String input, String expectedErrorCode) {
 642         return assertDeclareWarn1(input, new ExpectedDiagnostic(expectedErrorCode, -1, -1, -1, -1, -1, Diagnostic.Kind.WARNING));
 643     }
 644 
 645     public Snippet assertDeclareWarn1(String input, ExpectedDiagnostic expectedDiagnostic) {
 646         return assertDeclareWarn1(input, expectedDiagnostic, added(VALID));
 647     }
 648 
 649     public Snippet assertDeclareWarn1(String input, ExpectedDiagnostic expectedDiagnostic, STEInfo mainInfo, STEInfo... updates) {
 650         List<SnippetEvent> events = assertEval(input, IGNORE_VALUE, null,
 651                 DiagCheck.DIAG_WARNING, DiagCheck.DIAG_IGNORE, mainInfo, updates);
 652         SnippetEvent e = events.get(0);
 653         List<Diag> diagnostics = getState().diagnostics(e.snippet());
 654         assertDiagnostic(input, diagnostics.get(0), expectedDiagnostic);
 655         return e.snippet();
 656     }
 657 
 658     private void assertDiagnostic(String input, Diag diagnostic, ExpectedDiagnostic expectedDiagnostic) {
 659         if (expectedDiagnostic != null) expectedDiagnostic.assertDiagnostic(diagnostic);
 660         // assertEquals(diagnostic.getSource(), input, "Diagnostic source");
 661     }
 662 
 663     public void assertTypeDeclSnippet(TypeDeclSnippet type, String expectedName,
 664             Status expectedStatus, SubKind expectedSubKind,
 665             int unressz, int othersz) {
 666         assertDeclarationSnippet(type, expectedName, expectedStatus,
 667                 expectedSubKind, unressz, othersz);
 668     }
 669 
 670     public void assertMethodDeclSnippet(MethodSnippet method,
 671             String expectedName, String expectedSignature,
 672             Status expectedStatus, int unressz, int othersz) {
 673         assertDeclarationSnippet(method, expectedName, expectedStatus,
 674                 METHOD_SUBKIND, unressz, othersz);
 675         String signature = method.signature();
 676         assertEquals(signature, expectedSignature,
 677                 "Expected " + method.source() + " to have the name: " +
 678                         expectedSignature + ", got: " + signature);
 679     }
 680 
 681     public void assertVariableDeclSnippet(VarSnippet var,
 682             String expectedName, String expectedTypeName,
 683             Status expectedStatus, SubKind expectedSubKind,
 684             int unressz, int othersz) {
 685         assertDeclarationSnippet(var, expectedName, expectedStatus,
 686                 expectedSubKind, unressz, othersz);
 687         String signature = var.typeName();
 688         assertEquals(signature, expectedTypeName,
 689                 "Expected " + var.source() + " to have the name: " +
 690                         expectedTypeName + ", got: " + signature);
 691     }
 692 
 693     public void assertDeclarationSnippet(DeclarationSnippet declarationKey,
 694             String expectedName,
 695             Status expectedStatus, SubKind expectedSubKind,
 696             int unressz, int othersz) {
 697         assertKey(declarationKey, expectedStatus, expectedSubKind);
 698         String source = declarationKey.source();
 699         assertEquals(declarationKey.name(), expectedName,
 700                 "Expected " + source + " to have the name: " + expectedName + ", got: " + declarationKey.name());
 701         List<String> unresolved = getState().unresolvedDependencies(declarationKey);
 702         assertEquals(unresolved.size(), unressz, "Expected " + source + " to have " + unressz
 703                 + " unresolved symbols, got: " + unresolved.size());
 704         List<Diag> otherCorralledErrors = getState().diagnostics(declarationKey);
 705         assertEquals(otherCorralledErrors.size(), othersz, "Expected " + source + " to have " + othersz
 706                 + " other errors, got: " + otherCorralledErrors.size());
 707     }
 708 
 709     public void assertKey(Snippet key, Status expectedStatus, SubKind expectedSubKind) {
 710         String source = key.source();
 711         SubKind actualSubKind = key.subKind();
 712         assertEquals(actualSubKind, expectedSubKind,
 713                 "Expected " + source + " to have the subkind: " + expectedSubKind + ", got: " + actualSubKind);
 714         Status status = getState().status(key);
 715         assertEquals(status, expectedStatus, "Expected " + source + " to be "
 716                 + expectedStatus + ", but it is " + status);
 717         Snippet.Kind expectedKind = getKind(key);
 718         assertEquals(key.kind(), expectedKind, "Checking kind: ");
 719         assertEquals(expectedSubKind.kind(), expectedKind, "Checking kind: ");
 720     }
 721 
 722     public void assertDrop(PersistentSnippet key, STEInfo mainInfo, STEInfo... updates) {
 723         assertDrop(key, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, mainInfo, updates);
 724     }
 725 
 726     public void assertDrop(PersistentSnippet key, DiagCheck diagMain, DiagCheck diagUpdates, STEInfo mainInfo, STEInfo... updates) {
 727         assertDrop(key, diagMain, diagUpdates, new EventChain(mainInfo, null, null, updates));
 728     }
 729 
 730     public void assertDrop(PersistentSnippet key, DiagCheck diagMain, DiagCheck diagUpdates, EventChain... eventChains) {
 731         checkEvents(() -> getState().drop(key), "drop(" + key + ")", diagMain, diagUpdates, eventChains);
 732     }
 733 
 734     public void assertAnalyze(String input, String source, String remaining, boolean isComplete) {
 735         assertAnalyze(input, null, source, remaining, isComplete);
 736     }
 737 
 738      public void assertAnalyze(String input, Completeness status, String source) {
 739         assertAnalyze(input, status, source, null, null);
 740     }
 741 
 742     public void assertAnalyze(String input, Completeness status, String source, String remaining, Boolean isComplete) {
 743         CompletionInfo ci = getAnalysis().analyzeCompletion(input);
 744         if (status != null) assertEquals(ci.completeness, status, "Input : " + input + ", status: ");
 745         if (source != null) assertEquals(ci.source, source, "Input : " + input + ", source: ");
 746         if (remaining != null) assertEquals(ci.remaining, remaining, "Input : " + input + ", remaining: ");
 747         if (isComplete != null) {
 748             boolean isExpectedComplete = isComplete;
 749             assertEquals(ci.completeness.isComplete, isExpectedComplete, "Input : " + input + ", isComplete: ");
 750         }
 751     }
 752 
 753     public void assertNumberOfActiveVariables(int cnt) {
 754         Collection<VarSnippet> variables = getState().variables();
 755         assertEquals(variables.size(), cnt, "Variables : " + variables);
 756     }
 757 
 758     public void assertNumberOfActiveMethods(int cnt) {
 759         Collection<MethodSnippet> methods = getState().methods();
 760         assertEquals(methods.size(), cnt, "Methods : " + methods);
 761     }
 762 
 763     public void assertNumberOfActiveClasses(int cnt) {
 764         Collection<TypeDeclSnippet> classes = getState().types();
 765         assertEquals(classes.size(), cnt, "Classes : " + classes);
 766     }
 767 
 768     public void assertMembers(Collection<? extends Snippet> members, Set<MemberInfo> expected) {
 769         assertEquals(members.size(), expected.size(), "Expected : " + expected + ", actual : " + members);
 770         assertEquals(members.stream()
 771                         .map(this::getMemberInfo)
 772                         .collect(Collectors.toSet()),
 773                 expected);
 774     }
 775 
 776     public void assertKeys(MemberInfo... expected) {
 777         int index = 0;
 778         List<Snippet> snippets = getState().snippets();
 779         assertEquals(allSnippets.size(), snippets.size());
 780         for (Snippet sn : snippets) {
 781             if (sn.kind().isPersistent && getState().status(sn).isActive) {
 782                 MemberInfo actual = getMemberInfo(sn);
 783                 MemberInfo exp = expected[index];
 784                 assertEquals(actual, exp, String.format("Difference in #%d. Expected: %s, actual: %s",
 785                         index, exp, actual));
 786                 ++index;
 787             }
 788         }
 789     }
 790 
 791     public void assertActiveKeys() {
 792         Collection<Snippet> expected = getActiveKeys();
 793         assertActiveKeys(expected.toArray(new Snippet[expected.size()]));
 794     }
 795 
 796     public void assertActiveKeys(Snippet... expected) {
 797         int index = 0;
 798         for (Snippet key : getState().snippets()) {
 799             if (state.status(key).isActive) {
 800                 assertEquals(expected[index], key, String.format("Difference in #%d. Expected: %s, actual: %s", index, key, expected[index]));
 801                 ++index;
 802             }
 803         }
 804     }
 805 
 806     private List<Snippet> filterDeclaredKeys(Predicate<Snippet> p) {
 807         return getActiveKeys().stream()
 808                 .filter(p)
 809                 .collect(Collectors.toList());
 810     }
 811 
 812     public void assertVariables() {
 813         assertEquals(getState().variables(), filterDeclaredKeys((key) -> key instanceof VarSnippet), "Variables");
 814     }
 815 
 816     public void assertMethods() {
 817         assertEquals(getState().methods(), filterDeclaredKeys((key) -> key instanceof MethodSnippet), "Methods");
 818     }
 819 
 820     public void assertClasses() {
 821         assertEquals(getState().types(), filterDeclaredKeys((key) -> key instanceof TypeDeclSnippet), "Classes");
 822     }
 823 
 824     public void assertVariables(MemberInfo...expected) {
 825         assertMembers(getState().variables(), Stream.of(expected).collect(Collectors.toSet()));
 826     }
 827 
 828     public void assertMethods(MemberInfo...expected) {
 829         assertMembers(getState().methods(), Stream.of(expected).collect(Collectors.toSet()));
 830         for (MethodSnippet methodKey : getState().methods()) {
 831             MemberInfo expectedInfo = null;
 832             for (MemberInfo info : expected) {
 833                 if (info.name.equals(methodKey.name()) && info.type.equals(methodKey.signature())) {
 834                     expectedInfo = getMemberInfo(methodKey);
 835                 }
 836             }
 837             assertNotNull(expectedInfo, "Not found method: " + methodKey.name());
 838             int lastIndexOf = expectedInfo.type.lastIndexOf(')');
 839             assertEquals(methodKey.parameterTypes(), expectedInfo.type.substring(1, lastIndexOf), "Parameter types");
 840         }
 841     }
 842 
 843     public void assertClasses(MemberInfo...expected) {
 844         assertMembers(getState().types(), Stream.of(expected).collect(Collectors.toSet()));
 845     }
 846 
 847     public void assertCompletion(String code, String... expected) {
 848         assertCompletion(code, null, expected);
 849     }
 850 
 851     public void assertCompletion(String code, Boolean isSmart, String... expected) {
 852         List<String> completions = computeCompletions(code, isSmart);
 853         assertEquals(completions, Arrays.asList(expected), "Input: " + code + ", " + completions.toString());
 854     }
 855 
 856     public void assertCompletionIncludesExcludes(String code, Set<String> expected, Set<String> notExpected) {
 857         assertCompletionIncludesExcludes(code, null, expected, notExpected);
 858     }
 859 
 860     public void assertCompletionIncludesExcludes(String code, Boolean isSmart, Set<String> expected, Set<String> notExpected) {
 861         List<String> completions = computeCompletions(code, isSmart);
 862         assertTrue(completions.containsAll(expected), String.valueOf(completions));
 863         assertTrue(Collections.disjoint(completions, notExpected), String.valueOf(completions));
 864     }
 865 
 866     private List<String> computeCompletions(String code, Boolean isSmart) {
 867         waitIndexingFinished();
 868 
 869         int cursor =  code.indexOf('|');
 870         code = code.replace("|", "");
 871         assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
 872         List<Suggestion> completions =
 873                 getAnalysis().completionSuggestions(code, cursor, new int[1]); //XXX: ignoring anchor for now
 874         return completions.stream()
 875                           .filter(s -> isSmart == null || isSmart == s.isSmart)
 876                           .map(s -> s.continuation)
 877                           .distinct()
 878                           .collect(Collectors.toList());
 879     }
 880 
 881     public void assertInferredType(String code, String expectedType) {
 882         String inferredType = getAnalysis().analyzeType(code, code.length());
 883 
 884         assertEquals(inferredType, expectedType, "Input: " + code + ", " + inferredType);
 885     }
 886 
 887     public void assertInferredFQNs(String code, String... fqns) {
 888         waitIndexingFinished();
 889 
 890         IndexResult candidates = getAnalysis().getDeclaredSymbols(code, code.length());
 891 
 892         assertEquals(candidates.getFqns(), Arrays.asList(fqns), "Input: " + code + ", " + candidates.getFqns());
 893     }
 894 
 895     protected void waitIndexingFinished() {
 896         try {
 897             Method waitBackgroundTaskFinished = getAnalysis().getClass().getDeclaredMethod("waitBackgroundTaskFinished");
 898 
 899             waitBackgroundTaskFinished.setAccessible(true);
 900             waitBackgroundTaskFinished.invoke(getAnalysis());
 901         } catch (Exception ex) {
 902             throw new AssertionError("Cannot wait for indexing end.", ex);
 903         }
 904     }
 905 
 906     public void assertDocumentation(String code, String... expected) {
 907         int cursor =  code.indexOf('|');
 908         code = code.replace("|", "");
 909         assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
 910         String documentation = getAnalysis().documentation(code, cursor);
 911         Set<String> docSet = Stream.of(documentation.split("\r?\n")).collect(Collectors.toSet());
 912         Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
 913         assertEquals(docSet, expectedSet, "Input: " + code);
 914     }
 915 
 916     public enum ClassType {
 917         CLASS("CLASS_SUBKIND") {
 918             @Override
 919             public String toString() {
 920                 return "class";
 921             }
 922         },
 923         ENUM("ENUM_SUBKIND") {
 924             @Override
 925             public String toString() {
 926                 return "enum";
 927             }
 928         },
 929         INTERFACE("INTERFACE_SUBKIND") {
 930             @Override
 931             public String toString() {
 932                 return "interface";
 933             }
 934         },
 935         ANNOTATION("ANNOTATION_TYPE_SUBKIND") {
 936             @Override
 937             public String toString() {
 938                 return "@interface";
 939             }
 940         };
 941 
 942         private final String classType;
 943 
 944         ClassType(String classType) {
 945             this.classType = classType;
 946         }
 947 
 948         public String getClassType() {
 949             return classType;
 950         }
 951 
 952         @Override
 953         public abstract String toString();
 954     }
 955 
 956     public static MemberInfo variable(String type, String name) {
 957         return new MemberInfo(type, name);
 958     }
 959 
 960     public static MemberInfo method(String signature, String name) {
 961         return new MemberInfo(signature, name);
 962     }
 963 
 964     public static MemberInfo clazz(ClassType classType, String className) {
 965         return new MemberInfo(classType.getClassType(), className);
 966     }
 967 
 968     public static class MemberInfo {
 969         public final String type;
 970         public final String name;
 971 
 972         public MemberInfo(String type, String name) {
 973             this.type = type;
 974             this.name = name;
 975         }
 976 
 977         @Override
 978         public int hashCode() {
 979             return type.hashCode() + 3 * name.hashCode();
 980         }
 981 
 982         @Override
 983         public boolean equals(Object o) {
 984             if (o instanceof MemberInfo) {
 985                 MemberInfo other = (MemberInfo) o;
 986                 return type.equals(other.type) && name.equals(other.name);
 987             }
 988             return false;
 989         }
 990 
 991         @Override
 992         public String toString() {
 993             return String.format("%s %s", type, name);
 994         }
 995     }
 996 
 997     public MemberInfo getMemberInfo(Snippet key) {
 998         SubKind subkind = key.subKind();
 999         switch (subkind) {
1000             case CLASS_SUBKIND:
1001             case INTERFACE_SUBKIND:
1002             case ENUM_SUBKIND:
1003             case ANNOTATION_TYPE_SUBKIND:
1004                 return new MemberInfo(subkind.name(), ((DeclarationSnippet) key).name());
1005             case METHOD_SUBKIND:
1006                 MethodSnippet method = (MethodSnippet) key;
1007                 return new MemberInfo(method.signature(), method.name());
1008             case VAR_DECLARATION_SUBKIND:
1009             case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND:
1010             case TEMP_VAR_EXPRESSION_SUBKIND:
1011                 VarSnippet var = (VarSnippet) key;
1012                 return new MemberInfo(var.typeName(), var.name());
1013             default:
1014                 throw new AssertionError("Unknown snippet : " + key.kind() + " in expression " + key.toString());
1015         }
1016     }
1017 
1018     public String diagnosticsToString(List<Diag> diagnostics) {
1019         StringWriter writer = new StringWriter();
1020         for (Diag diag : diagnostics) {
1021             writer.write("Error --\n");
1022             for (String line : diag.getMessage(null).split("\\r?\\n")) {
1023                 writer.write(String.format("%s\n", line));
1024             }
1025         }
1026         return writer.toString().replace("\n", System.lineSeparator());
1027     }
1028 
1029     public boolean hasFatalError(List<Diag> diagnostics) {
1030         for (Diag diag : diagnostics) {
1031             if (diag.isError()) {
1032                 return true;
1033             }
1034         }
1035         return false;
1036     }
1037 
1038     public static EventChain chain(STEInfo mainInfo, STEInfo... updates) {
1039         return chain(mainInfo, IGNORE_VALUE, null, updates);
1040     }
1041 
1042     public static EventChain chain(STEInfo mainInfo, String value, Class<? extends Throwable> exceptionClass, STEInfo... updates) {
1043         return new EventChain(mainInfo, value, exceptionClass, updates);
1044     }
1045 
1046     public static STEInfo ste(Snippet key, Status previousStatus, Status status,
1047                 Boolean isSignatureChange, Snippet causeKey) {
1048         return new STEInfo(key, previousStatus, status, isSignatureChange, causeKey);
1049     }
1050 
1051     public static STEInfo added(Status status) {
1052         return new STEInfo(MAIN_SNIPPET, NONEXISTENT, status, status.isDefined, null);
1053     }
1054 
1055     public static class EventChain {
1056         public final STEInfo mainInfo;
1057         public final STEInfo[] updates;
1058         public final String value;
1059         public final Class<? extends Throwable> exceptionClass;
1060 
1061         public EventChain(STEInfo mainInfo, String value, Class<? extends Throwable> exceptionClass, STEInfo... updates) {
1062             this.mainInfo = mainInfo;
1063             this.updates = updates;
1064             this.value = value;
1065             this.exceptionClass = exceptionClass;
1066         }
1067     }
1068 
1069     public static class STEInfo {
1070 
1071         STEInfo(Snippet snippet, Status previousStatus, Status status,
1072                 Boolean isSignatureChange, Snippet causeSnippet) {
1073             this.snippet = snippet;
1074             this.previousStatus = previousStatus;
1075             this.status = status;
1076             this.checkIsSignatureChange = isSignatureChange != null;
1077             this.isSignatureChange = checkIsSignatureChange ? isSignatureChange : false;
1078             this.causeSnippet = causeSnippet;
1079             assertTrue(snippet != null, "Bad test set-up. The match snippet must not be null");
1080         }
1081 
1082         final Snippet snippet;
1083         final Status previousStatus;
1084         final Status status;
1085         final boolean isSignatureChange;
1086         final Snippet causeSnippet;
1087 
1088          final boolean checkIsSignatureChange;
1089         public Snippet snippet() {
1090             return snippet;
1091         }
1092         public Status previousStatus() {
1093             return previousStatus;
1094         }
1095         public Status status() {
1096             return status;
1097         }
1098         public boolean isSignatureChange() {
1099             if (!checkIsSignatureChange) {
1100                 throw new IllegalStateException("isSignatureChange value is undefined");
1101             }
1102             return isSignatureChange;
1103         }
1104         public Snippet causeSnippet() {
1105             return causeSnippet;
1106         }
1107         public String value() {
1108             return null;
1109         }
1110         public Exception exception() {
1111             return null;
1112         }
1113 
1114         public void assertMatch(SnippetEvent ste, Snippet mainSnippet) {
1115             assertKeyMatch(ste, ste.snippet(), snippet(), mainSnippet);
1116             assertStatusMatch(ste, ste.previousStatus(), previousStatus());
1117             assertStatusMatch(ste, ste.status(), status());
1118             if (checkIsSignatureChange) {
1119                 assertEquals(ste.isSignatureChange(), isSignatureChange(),
1120                         "Expected " +
1121                                 (isSignatureChange()? "" : "no ") +
1122                                 "signature-change, got: " +
1123                                 (ste.isSignatureChange()? "" : "no ") +
1124                                 "signature-change" +
1125                         "\n   expected-event: " + this + "\n   got-event: " + toString(ste));
1126             }
1127             assertKeyMatch(ste, ste.causeSnippet(), causeSnippet(), mainSnippet);
1128         }
1129 
1130         private void assertKeyMatch(SnippetEvent ste, Snippet sn, Snippet expected, Snippet mainSnippet) {
1131             Snippet testKey = expected;
1132             if (testKey != null) {
1133                 if (expected == MAIN_SNIPPET) {
1134                     assertNotNull(mainSnippet, "MAIN_SNIPPET used, test must pass value to assertMatch");
1135                     testKey = mainSnippet;
1136                 }
1137                 if (ste.causeSnippet() == null && ste.status() != DROPPED && expected != MAIN_SNIPPET) {
1138                     // Source change, always new snippet -- only match id()
1139                     assertTrue(sn != testKey,
1140                             "Main-event: Expected new snippet to be != : " + testKey
1141                             + "\n   got-event: " + toString(ste));
1142                     assertEquals(sn.id(), testKey.id(), "Expected IDs to match: " + testKey + ", got: " + sn
1143                             + "\n   expected-event: " + this + "\n   got-event: " + toString(ste));
1144                 } else {
1145                     assertEquals(sn, testKey, "Expected key to be: " + testKey + ", got: " + sn
1146                             + "\n   expected-event: " + this + "\n   got-event: " + toString(ste));
1147                 }
1148             }
1149         }
1150 
1151         private void assertStatusMatch(SnippetEvent ste, Status status, Status expected) {
1152             if (expected != null) {
1153                 assertEquals(status, expected, "Expected status to be: " + expected + ", got: " + status +
1154                         "\n   expected-event: " + this + "\n   got-event: " + toString(ste));
1155             }
1156         }
1157 
1158         @Override
1159         public String toString() {
1160             return "STEInfo key: " +
1161                     (snippet()==MAIN_SNIPPET? "MAIN_SNIPPET" : (snippet()==null? "ignore" : snippet().id())) +
1162                     " before: " + previousStatus() +
1163                     " status: " + status() + " sig: " + isSignatureChange() +
1164                     " cause: " + (causeSnippet()==null? "null" : causeSnippet().id());
1165         }
1166 
1167         private String toString(SnippetEvent ste) {
1168             return "key: " + (ste.snippet()==MAIN_SNIPPET? "MAIN_SNIPPET" : ste.snippet().id()) + " before: " + ste.previousStatus()
1169                     + " status: " + ste.status() + " sig: " + ste.isSignatureChange()
1170                     + " cause: " + ste.causeSnippet();
1171         }
1172     }
1173 }