/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.lib.lexer.test; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.text.Document; import org.netbeans.api.lexer.Language; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.lib.editor.util.CharSequenceUtilities; /** * Test lexer implementation for correctness by doing random * inserts/removals of random characters. * * @author mmetelka */ public class TestRandomModify { private boolean debugOperation; private boolean debugHierarchy; private boolean debugDocumentText; private boolean skipLexerConsistencyCheck; private Random random; private Document doc; private int opId; private int startDebugOpCount; private int maxDocLength; private List<SnapshotDescription> snapshots = new ArrayList<SnapshotDescription>(); public TestRandomModify() { this(0); } public TestRandomModify(long seed) { this.doc = new javax.swing.text.PlainDocument(); this.random = new Random(); if (seed == 0) { // Use currentTimeMillis() (btw nanoTime() in 1.5 instead) seed = System.currentTimeMillis(); } System.err.println("TestRandomModify with SEED=" + seed + "L"); random.setSeed(seed); } private boolean isOpDebug() { return opId >= startDebugOpCount; } public boolean isDebugOperation() { return debugOperation && isOpDebug(); } /** * Set whether info about each operation being performed in token hierarchy * should be dumped to system err after each operation. */ public void setDebugOperation(boolean debugOperation) { this.debugOperation = debugOperation; } public boolean isDebugHierarchy() { return debugHierarchy && isOpDebug(); } /** * Set whether complete text of the modified document * should be dumped to system err after each operation. */ public void setDebugHierarchy(boolean debugHierarchy) { this.debugHierarchy = debugHierarchy; } public boolean isDebugDocumentText() { return debugDocumentText && isOpDebug(); } /** * Set whether contents of the token hierarchy being tested * should be dumped to system err after each operation. */ public void setDebugDocumentText(boolean debugDocumentText) { this.debugDocumentText = debugDocumentText; } public boolean isSkipLexerConsistencyCheck() { return skipLexerConsistencyCheck; } public void setSkipLexerConsistencyCheck(boolean skipLexerConsistencyCheck) { this.skipLexerConsistencyCheck = skipLexerConsistencyCheck; } public void test(RandomModifyDescriptor[] randomModifyDescriptors) throws Exception { for (int i = 0; i < randomModifyDescriptors.length; i++) { RandomModifyDescriptor descriptor = randomModifyDescriptors[i]; int debugOpFragment = Math.max(descriptor.opCount() / 5, 100); int nextDebugOp = debugOpFragment - 1; for (int op = 0; op < descriptor.opCount(); op++) { opId++; double r = random().nextDouble() * descriptor.ratioSum(); boolean success = false; try { action(r, descriptor); success = true; } finally { if (!success) { System.err.println("ACTION FAILED opId=" + opId); } } if (op == nextDebugOp) { nextDebugOp = Math.min(nextDebugOp + debugOpFragment, descriptor.opCount() - 1); System.err.println(String.valueOf(op+1) + " of " + descriptor.opCount() + " operations finished."); } } } System.err.println("Maximum document length: " + maxDocLength()); } protected double action(double r, RandomModifyDescriptor descriptor) throws Exception { if ((r -= descriptor.insertCharRatio()) < 0) { if (descriptor.randomTextProvider().randomCharAvailable()) { char ch = descriptor.randomTextProvider().randomChar(random()); insertText(String.valueOf(ch)); } else { // random char not available insertText(""); // possibly debug the operation } } else if ((r -= descriptor.insertTextRatio()) < 0) { String text = descriptor.randomTextProvider().randomText(random(), descriptor.insertTextMaxLength()); insertText(text); } else if ((r -= descriptor.insertFixedTextRatio()) < 0) { String fixedText = descriptor.randomTextProvider().randomFixedText(random()); insertText(fixedText); } else if ((r -= descriptor.removeCharRatio()) < 0) { removeText(1); } else if ((r -= descriptor.removeTextRatio()) < 0) { int length = random().nextInt(descriptor.removeTextMaxLength()); removeText(length); // } else if ((r -= descriptor.createSnapshotRatio()) < 0) { // createSnapshot(); // // } else if ((r -= descriptor.destroySnapshotRatio()) < 0) { // destroySnapshot(); } return r; } public void insertText(int offset, String text) throws Exception { if (text.length() > 0) { if (isDebugOperation()) { int beforeTextStartOffset = Math.max(offset - 5, 0); String beforeText = document().getText(beforeTextStartOffset, offset - beforeTextStartOffset); int afterTextEndOffset = Math.min(offset + 5, document().getLength()); String afterText = doc.getText(offset, afterTextEndOffset - offset); System.err.println(opIdString() + " INSERT(" + offset + ", " + text.length() +"): \"" + CharSequenceUtilities.debugText(text) +"\" text-around: \"" + CharSequenceUtilities.debugText(beforeText) + '|' + CharSequenceUtilities.debugText(afterText) + "\"" ); if (isDebugDocumentText()) { StringBuilder sb = new StringBuilder(); String beforeOffsetText = CharSequenceUtilities.debugText(doc.getText(0, offset)); for (int i = 0; i < beforeOffsetText.length(); i++) { sb.append('-'); } sb.append("\\ \""); CharSequenceUtilities.debugText(sb, text); sb.append("\"\n\""); sb.append(beforeOffsetText).append(CharSequenceUtilities.debugText( doc.getText(offset, doc.getLength() - offset))).append('"'); System.err.println(sb.toString()); } } document().insertString(offset, text, null); insertTextNotify(offset, text); maxDocLength = Math.max(document().getLength(), maxDocLength); checkConsistency(); } else { if (isDebugOperation()) { System.err.println(opIdString() + " INSERT cannot be done (text=\"\")"); } } } public void insertText(String text) throws Exception { int offset = random().nextInt(document().getLength() + 1); insertText(offset, text); } protected void insertTextNotify(int offset, String text) throws Exception { } public void removeText(int offset, int length) throws Exception { if (length > 0) { if (isDebugOperation()) { System.err.println(opIdString() + " REMOVE(" + offset + ", " + length + "): \"" + CharSequenceUtilities.debugText(document().getText(offset, length)) + "\"" ); } if (isDebugDocumentText()) { StringBuilder sb = new StringBuilder(); String beforeOffsetText = CharSequenceUtilities.debugText(doc.getText(0, offset)); for (int i = 0; i <= beforeOffsetText.length(); i++) { sb.append('-'); } for (int i = 0; i < length; i++) { sb.append('x'); } sb.append("\n\""); sb.append(beforeOffsetText).append(CharSequenceUtilities.debugText( doc.getText(offset, doc.getLength() - offset))).append('"'); System.err.println(sb.toString()); } document().remove(offset, length); removeTextNotify(offset, length); checkConsistency(); } else { // No operation if (isDebugOperation()) { System.err.println(opIdString() + " REMOVE cannot be done (length=0)"); } } } public void removeText(int length) throws Exception { length = Math.min(document().getLength(), length); int offset = random().nextInt(document().getLength() - length + 1); removeText(offset, length); } protected void removeTextNotify(int offset, int length) throws Exception { } // public void createSnapshot() throws Exception { // junit.framework.TestCase.fail(); // TokenHierarchy hi = TokenHierarchy.get(doc); // TokenHierarchy snapshot = hi.createSnapshot(); // Language<?> language = (Language<?>) // doc.getProperty(Language.class); // TokenHierarchy batchMirror = TokenHierarchy.create(doc.getText(0, doc.getLength()), language); // snapshots.add(new SnapshotDescription(snapshot, batchMirror)); // if (isDebugOperation()) { // System.err.println(opIdString() + " CREATED SNAPSHOT. " // + snapshots.size() + " snapshots."); // } // checkConsistency(); // } // // public void destroySnapshot() throws Exception { // junit.framework.TestCase.fail(); // if (snapshots.size() > 0) { // int index = random().nextInt(snapshots.size()); // snapshots.remove(index); // if (isDebugOperation()) { // System.err.println(opIdString() + " DESTROYED SNAPSHOT. " // + snapshots.size() + " snapshots."); // } // checkConsistency(); // // } else { // no snapshots // if (isDebugOperation()) { // System.err.println(opIdString() + " DESTROY SNAPSHOT cannot be done - no snapshots."); // } // } // } public final int opId() { return opId; } public final String opIdString() { String s = String.valueOf(opId()); while (s.length() < 3) { s = " " + s; } return "OPER[" + s + "]"; } public int startDebugOpCount() { return startDebugOpCount; } public void setStartDebugOpCount(int startDebugOpCount) { this.startDebugOpCount = startDebugOpCount; } public final Random random() { return random; } public final Document document() { return doc; } public void clearDocument() throws Exception { doc.remove(0, doc.getLength()); // Verify that there are no tokens LexerTestUtilities.incCheck(doc, true); } public final Language<?> language() { return (Language<?>)doc.getProperty(Language.class); } public final void setLanguage(Language<?> language) { doc.putProperty(Language.class, language); } public final int maxDocLength() { return maxDocLength; } protected void checkConsistency() throws Exception { if (!isSkipLexerConsistencyCheck()) { LexerTestUtilities.incCheck(doc, true); // Possibly debug the hierarchy - do it after incCheck() so that hierarchy is fully inited if (isDebugHierarchy()) { TokenHierarchy<?> hi = TokenHierarchy.get(doc); if (hi != null) { System.err.println("DEBUG hierarchy:\n" + hi + "\n"); } } for (int i = 0; i < snapshots.size(); i++) { SnapshotDescription sd = snapshots.get(i); TokenHierarchy<?> bm = sd.batchMirror(); TokenHierarchy<?> s = sd.snapshot(); if (isDebugOperation()) { System.err.println("Comparing snapshot " + i + " of " + snapshots.size()); } // Check snapshot without comparing lookaheads and states LexerTestUtilities.assertTokenSequencesEqual(null, bm.tokenSequence(), bm, s.tokenSequence(), s, false, false); } } } private static final class SnapshotDescription { private final TokenHierarchy<?> snapshot; private final TokenHierarchy<?> batchMirror; public SnapshotDescription(TokenHierarchy snapshot, TokenHierarchy batchMirror) { this.snapshot = snapshot; this.batchMirror = batchMirror; } public TokenHierarchy snapshot() { return snapshot; } public TokenHierarchy batchMirror() { return batchMirror; } } }