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