001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.test.syntax;
042:
043: import java.awt.Color;
044: import java.awt.Component;
045: import java.awt.EventQueue;
046: import java.awt.Font;
047: import java.awt.FontMetrics;
048: import java.awt.Graphics;
049: import java.io.File;
050: import java.lang.reflect.Method;
051: import java.util.ArrayList;
052: import java.util.Arrays;
053: import java.util.Iterator;
054: import java.util.LinkedList;
055: import java.util.List;
056: import javax.swing.JEditorPane;
057: import javax.swing.JLabel;
058: import javax.swing.JList;
059: import javax.swing.SwingUtilities;
060: import javax.swing.event.DocumentEvent;
061: import javax.swing.event.DocumentListener;
062: import javax.swing.text.Caret;
063: import junit.framework.Test;
064: import org.netbeans.editor.Utilities;
065: import org.netbeans.editor.ext.CompletionQuery.ResultItem;
066: import org.netbeans.jellytools.JellyTestCase;
067: import org.netbeans.jellytools.modules.editor.CompletionJListOperator;
068: import org.netbeans.jemmy.JemmyException;
069: import org.netbeans.jemmy.util.PNGEncoder;
070: import org.netbeans.junit.AssertionFailedErrorException;
071: import org.netbeans.editor.BaseDocument;
072: import org.netbeans.editor.TokenID;
073: import org.netbeans.editor.TokenItem;
074: import org.netbeans.editor.ext.ExtSyntaxSupport;
075: import org.netbeans.jellytools.EditorOperator;
076: import org.netbeans.spi.editor.completion.CompletionItem;
077: import org.netbeans.test.web.FileObjectFilter;
078: import org.netbeans.test.web.RecurrentSuiteFactory;
079: import org.netbeans.test.web.TextGraphics2D;
080: import org.openide.actions.UndoAction;
081: import org.openide.cookies.EditorCookie;
082: import org.openide.cookies.EditorCookie.Observable;
083: import org.openide.filesystems.FileObject;
084: import org.openide.loaders.DataObject;
085: import org.openide.util.RequestProcessor;
086: import org.openide.util.actions.SystemAction;
087:
088: /**
089: * Test goes throught files and looking for CC Test Steps.
090: * The CC Test Steps are writen in document body as three lines:
091: * JSP comments which start with '<%--CC' prefix, where:
092: *<ul>
093: *<li> first line contains ccPrefix with optional '|' character which represents
094: * cursor position
095: *<li> second line contains ccChoice item which will be used for CC substitution
096: *<li> third line contains ccResult
097: *</ul>
098: *
099: * For example:<p>
100: * <pre><%--CC
101: * <%@ taglib |
102: * uri
103: * <%@ taglib uri=""
104: * --%>
105: * </pre><p>
106: * does:
107: * <ul>
108: * <li> inserts '<%@ taglib ' string into new line
109: * <li> invokes CC
110: * <li> dumps Completion Query Result
111: * <li> choses "uri" item from the query result and substitute it
112: * <li> checks if subtituted line is: '<%@ taglib uri=""'
113: * <li> undoes all changes
114: * </ul>
115: * @author ms113234
116: *
117: */
118: public class CompletionTest extends JellyTestCase {
119:
120: private static boolean GENERATE_GOLDEN_FILES = false;//generate golden files, or test
121: protected FileObject testFileObj;
122: protected boolean debug = false;
123: protected final static List XML_EXTS = Arrays.asList(new String[] {
124: "html", "tld", "jspx", "tagx", "xhtml" });
125: protected final static List JSP_EXTS = Arrays.asList(new String[] {
126: "jsp", "tag", "jspf", "tagf" });
127: protected final static List JS_EXTS = Arrays
128: .asList(new String[] { "js"/*,"java"*/});
129:
130: /** Need to be defined because of JUnit */
131: public CompletionTest(String name, FileObject testFileObj) {
132: super (name);
133: this .testFileObj = testFileObj;
134: }
135:
136: public void setUp() {
137: System.out.println("######## " + getName() + " #######");
138: }
139:
140: public static Test suite() {
141: // find folder with test projects and define file objects filter
142: File datadir = new CompletionTest(null, null).getDataDir();
143: File projectsDir = new File(datadir, "CompletionTestProjects");
144: FileObjectFilter filter = new FileObjectFilter() {
145:
146: public boolean accept(FileObject fo) {
147: String ext = fo.getExt();
148: String name = fo.getName();
149: return (name.startsWith("test") || name
150: .startsWith("Test"))
151: && (XML_EXTS.contains(ext)
152: || JSP_EXTS.contains(ext) || JS_EXTS
153: .contains(ext));
154: }
155: };
156: return RecurrentSuiteFactory.createSuite(CompletionTest.class,
157: projectsDir, filter);
158: }
159:
160: public void runTest() throws Exception {
161: String ext = testFileObj.getExt();
162: if (JSP_EXTS.contains(ext)) {
163: test(testFileObj, "<%--CC", "--%>");
164: } else if (XML_EXTS.contains(ext)) {
165: test(testFileObj, "<!--CC", "-->", false);
166: } else if (JS_EXTS.contains(ext)) {
167: test(testFileObj, "/**CC", "*/");
168: } else {
169: throw new JemmyException("File extension of: "
170: + testFileObj.getNameExt() + " is unsupported.");
171: }
172: }
173:
174: private boolean isJavaScript() {
175: return JS_EXTS.contains(testFileObj.getExt());
176: }
177:
178: private void test(FileObject fileObj, String stepStart,
179: String stepEnd) throws Exception {
180: test(fileObj, stepStart, stepEnd, true);
181: }
182:
183: public static BaseDocument openFile(FileObject fileObject)
184: throws Exception {
185: DataObject dataObj = DataObject.find(fileObject);
186: final EditorCookie.Observable ed = dataObj
187: .getCookie(Observable.class);
188: BaseDocument doc = (BaseDocument) ed.openDocument();
189: ed.open();
190: new EditorOperator(fileObject.getName());
191: return doc;
192: }
193:
194: protected void waitTypingFinished(BaseDocument doc) {
195: final int delay = 500;
196: final int repeat = 20;
197: final Object lock = new Object();
198: Runnable posted = new Runnable() {
199:
200: public void run() {
201: synchronized (lock) {
202: lock.notifyAll();
203: }
204: }
205: };
206: RequestProcessor RP = new RequestProcessor(
207: "TEST REQUEST PROCESSOR");
208: final RequestProcessor.Task task = RP.post(posted, delay);
209: DocumentListener listener = new DocumentListener() {
210:
211: public void insertUpdate(DocumentEvent e) {
212: task.schedule(delay);
213: }
214:
215: public void removeUpdate(DocumentEvent e) {
216: }
217:
218: public void changedUpdate(DocumentEvent e) {
219: }
220: };
221: doc.addDocumentListener(listener);
222: try {
223: synchronized (lock) {
224: lock.wait(repeat * delay);
225: }
226: } catch (InterruptedException intExc) {
227: throw new JemmyException("TYPING DID NOT FINISHED IN "
228: + repeat * delay + " SECONDS", intExc);
229: } finally {
230: doc.removeDocumentListener(listener);
231: }
232: }
233:
234: protected void test(FileObject fileObj, String stepStart,
235: String stepEnd, boolean ignoreComment) throws Exception {
236: boolean inStepData = false;
237: String[] stepData = new String[3];
238: int dataLineIdx = 0;
239:
240: try {
241: // get token chain
242: DataObject dataObj = DataObject.find(fileObj);
243: final EditorCookie.Observable ed = dataObj
244: .getCookie(Observable.class);
245: BaseDocument doc = openFile(fileObj);
246: final List<JEditorPane> editorPane = new LinkedList<JEditorPane>();
247: Runnable runnable = new Runnable() {
248:
249: public void run() {
250: editorPane.add(ed.getOpenedPanes()[0]);
251: }
252: };
253: EventQueue.invokeAndWait(runnable);
254: JEditorPane editor = editorPane.get(0);
255: ExtSyntaxSupport ess = (ExtSyntaxSupport) doc
256: .getSyntaxSupport();
257: TokenItem token = ess.getTokenChain(0, doc.getLength());
258: List<TestStep> steps = new java.util.ArrayList<TestStep>();
259: // go through token chain an look for CC test steps
260: while (token != null) {
261: TokenID tokenID = token.getTokenID();
262: if (tokenID.getName().indexOf("EOL") != -1) {
263: token = token.getNext();
264: continue;
265: }
266: if (debug) {
267: String tImage = token.getImage();
268: int tEnd = token.getOffset() + tImage.length();
269: System.err.println("# [" + token.getOffset() + ","
270: + tEnd + "] " + tokenID.getName() + " :: "
271: + token.getImage());
272: }
273: String commentBlock;
274: if (isJavaScript()) {
275: commentBlock = "text";
276: } else {
277: commentBlock = "comment";
278: }
279: if ((ignoreComment)
280: && (tokenID.getName().indexOf(commentBlock) == -1)) {
281: token = token.getNext();
282: continue;
283: }
284: if (inStepData) {
285: // probably end of step data
286: if (token.getImage().indexOf(stepEnd) > -1) {
287: inStepData = false;
288: // check obtained CC data and create test step CCsecs
289: if (dataLineIdx == 3) {
290: int offset = token.getOffset()
291: + token.getImage().length();
292: TestStep step = new TestStep(stepData,
293: offset);
294: steps.add(step);
295: } else {
296: ref("EE: expected data lines number: 3 but was: "
297: + dataLineIdx);
298: }
299: } else {
300: // assert CC TEst Data lenght
301: if (dataLineIdx > 2) {
302: String msg = "EE: to much lines in CC Test Data";
303: ref(msg);
304: ref(dumpToken(token));
305: fail(msg);
306: }
307: String str = token.getImage();
308: // suppress new lines
309: if (str.endsWith("\n")) {
310: str = str.substring(0, str.length() - 1);
311: }
312: stepData[dataLineIdx++] = str;
313: }
314: } else {
315: String text = token.getImage();
316: if (text.startsWith(stepStart)) {
317: if (text.endsWith(stepEnd)) {
318: // all steps line in one toke as .java does
319: String[] lines = text.split("\n\r?|\r\n?");
320: if (lines.length == 5) {
321: int offset = token.getOffset()
322: + token.getImage().length();
323: for (int i = 0; i < 3; i++) {
324: stepData[i] = lines[i + 1];
325: }
326: TestStep step = new TestStep(stepData,
327: offset);
328: steps.add(step);
329: } else {
330: String msg = "EE: expected 5 lines lenght token but got: "
331: + lines.length;
332: ref(msg);
333: ref(text);
334: for (int i = 0; i < lines.length; i++) {
335: ref(i + "::" + lines[i]);
336: }
337: }
338: } else {
339: // each step line in separate line as .jsp does
340: inStepData = true;
341: dataLineIdx = 0;
342: }
343: }
344: }
345: token = token.getNext();
346: } // while (token != null)
347: if (debug) {
348: System.err.println("Steps count:"
349: + Integer.toString(steps.size()));
350: }
351: run(editor, steps);
352: } catch (Exception ex) {
353: throw new AssertionFailedErrorException(ex);
354: }
355: ending();
356: }
357:
358: protected void run(JEditorPane editor, List<TestStep> steps)
359: throws Exception {
360: Iterator<TestStep> it = steps.iterator();
361: while (it.hasNext()) {
362: exec(editor, it.next());
363: }
364: }
365:
366: protected void exec(JEditorPane editor, TestStep step)
367: throws Exception {
368: try {
369: BaseDocument doc = (BaseDocument) editor.getDocument();
370: ref(step.toString());
371: Caret caret = editor.getCaret();
372: caret.setDot(step.getOffset() + 1);
373: EditorOperator eo = new EditorOperator(testFileObj
374: .getNameExt());
375: eo.insert(step.getPrefix());
376: waitTypingFinished(doc);
377: if (!isJavaScript()) {
378: caret.setDot(step.getCursorPos());
379: }
380:
381: CompletionJListOperator comp = null;
382: boolean print = false;
383: int counter = 0;
384: while (!print) {
385: ++counter;
386: if (counter > 5) {
387: print = true;
388: }
389: try {
390: comp = CompletionJListOperator.showCompletion();
391: } catch (JemmyException e) {
392: log("EE: The CC window did not appear");
393: e.printStackTrace(getLog());
394: }
395: if (comp != null) {
396: print = dumpCompletion(comp, step, editor, print)
397: || print;
398: CompletionJListOperator.hideAll();
399: if (!print) {// wait for refresh
400: Thread.sleep(1000);
401: if (!isJavaScript()) {
402: caret.setDot(step.getCursorPos());
403: }
404: }
405: } else {
406: long time = System.currentTimeMillis();
407: String screenFile = time + "-screen.png";
408: log("CompletionJList is null");
409: log("step: " + step);
410: log("captureScreen:" + screenFile);
411: try {
412: PNGEncoder.captureScreen(getWorkDir()
413: .getAbsolutePath()
414: + File.separator + screenFile);
415: } catch (Exception e1) {
416: e1.printStackTrace(getLog());
417: }
418: }
419: }
420: waitTypingFinished(doc);
421: doc.atomicLock();
422: int rowStart = Utilities.getRowStart(doc,
423: step.getOffset() + 1);
424: int rowEnd = Utilities.getRowEnd(doc, step.getOffset() + 1);
425: String result = doc.getText(new int[] { rowStart, rowEnd });
426: doc.atomicUnlock();
427: if (!result.equals(step.getResult())) {
428: ref("EE: unexpected CC result:\n< " + result + "\n> "
429: + step.getResult());
430: }
431: ref("End cursor position = " + caret.getDot());
432: } finally {
433: // undo all changes
434: final UndoAction ua = SystemAction.get(UndoAction.class);
435: assertNotNull("Cannot obtain UndoAction", ua);
436: while (ua.isEnabled()) {
437: runInAWT(new Runnable() {
438:
439: public void run() {
440: ua.performAction();
441: }
442: });
443: }
444: }
445: }
446:
447: /**
448: *
449: * @param printDirectly if to print directly
450: * @return whether the selected code completion item was found
451: * @throws java.lang.Exception
452: */
453: protected boolean dumpCompletion(CompletionJListOperator comp,
454: TestStep step, final JEditorPane editor,
455: boolean printDirectly) throws Exception {
456: List<String> finalItems = new ArrayList<String>();
457: if (comp != null) {
458: // dump CC result to golden file
459: Iterator items = comp.getCompletionItems().iterator();
460: CompletionItem selectedItem = null;
461: while (items.hasNext()) {
462: TextGraphics2D g = new TextGraphics2D(comp.getSource());
463: Object next = items.next();
464: String dispText = null;
465: if (next instanceof ResultItem) {
466: ResultItem resItem = (ResultItem) next;
467: Component component = resItem.getPaintComponent(
468: (JList) comp.getSource(), false, true);
469: // get display version of the component
470: Method drawM = findMethod(component.getClass(),
471: "draw", new Class[] { Graphics.class });
472: if (drawM != null) {
473: drawM.setAccessible(true);
474: drawM.invoke(component, new Object[] { g });
475: } else if (component instanceof JLabel) {
476: // ??? use java.awt.Component.paint(Grraphics g) method instead?
477: g.drawString(((JLabel) component).getText()
478: .trim(), 0, 0);
479: } else {
480: g.drawString(component.toString(), 0, 0);
481: }
482: } else if (next instanceof CompletionItem) {
483: CompletionItem cItem = (CompletionItem) next;
484: Font default_font = new JLabel(cItem.getSortText()
485: .toString()).getFont();
486: FontMetrics fm = g.getFontMetrics(default_font);
487: assertNotNull(fm);
488: cItem.render(g, default_font, Color.BLACK,
489: Color.WHITE, 400, 30, false);
490: } else {
491: g.drawString(next.toString(), 0, 0);
492: }
493: dispText = g.getTextUni();
494: // find choice item
495: if (dispText.equals(step.getChoice())) {
496: assertInstanceOf(CompletionItem.class, next);
497: selectedItem = (CompletionItem) next;
498: }
499: if (printDirectly) {
500: ref(g.getTextUni());
501: } else {
502: finalItems.add(g.getTextUni());
503: }
504: }
505: class DefaultActionRunner implements Runnable {
506:
507: CompletionItem item;
508: JEditorPane editor;
509:
510: public DefaultActionRunner(CompletionItem item,
511: JEditorPane editor) {
512: this .item = item;
513: this .editor = editor;
514: }
515:
516: public void run() {
517: item.defaultAction(editor);
518: }
519: }
520: // substitute completion and check result
521: if (selectedItem != null) {
522: // move to separate class
523: if (!printDirectly) {
524: for (String str : finalItems) {
525: ref(str);
526: }
527: }
528: Runnable run = new DefaultActionRunner(selectedItem,
529: editor);
530: runInAWT(run);
531: return true;
532: } else {
533: if (printDirectly) {
534: ref("EE: cannot find completion item: "
535: + step.getChoice());
536: }
537: }
538: } else {
539: // comp == null && ccError == false => instant substitution
540: ref("Instant substitution performed");
541: }
542: return false;
543: }
544:
545: protected static void runInAWT(Runnable r) {
546: if (SwingUtilities.isEventDispatchThread()) {
547: r.run();
548: } else {
549: try {
550: SwingUtilities.invokeAndWait(r);
551: } catch (Exception exc) {
552: throw new JemmyException("INVOKATION FAILED", exc);
553: }
554: }
555: }
556:
557: protected Method findMethod(Class clazz, String name,
558: Class[] paramTypes) {
559: Method method = null;
560: for (Class cls = clazz; cls.getSuperclass() != null; cls = cls
561: .getSuperclass()) {
562: try {
563: method = cls.getDeclaredMethod(name, paramTypes);
564: } catch (NoSuchMethodException e) {
565: // ignore
566: }
567: }
568: return method;
569: }
570:
571: protected void assertInstanceOf(Class expectedType, Object actual) {
572: if (!expectedType.isAssignableFrom(actual.getClass())) {
573: fail("Expected type: " + expectedType.getName()
574: + "\nbut was: " + actual.getClass().getName());
575: }
576: }
577:
578: protected static class TestStep {
579:
580: private String prefix;
581: private String choice;
582: private String result;
583: private int offset;
584: private int cursorPos;
585:
586: public TestStep(String data[], int offset) {
587: this .prefix = data[0];
588: this .choice = data[1];
589: this .result = data[2];
590: this .offset = offset;
591:
592: cursorPos = prefix.indexOf('|');
593: if (cursorPos != -1) {
594: prefix = prefix.replaceFirst("\\|", "");
595: } else {
596: cursorPos = prefix.length();
597: }
598: cursorPos += offset + 1;
599: }
600:
601: public String toString() {
602: StringBuffer sb = new StringBuffer(prefix);
603: sb.insert(cursorPos - offset - 1, '|');
604: return "[" + sb + ", " + choice + ", " + result + ", "
605: + offset + "]";
606: }
607:
608: public String getPrefix() {
609: return prefix;
610: }
611:
612: public String getChoice() {
613: return choice;
614: }
615:
616: public String getResult() {
617: return result;
618: }
619:
620: public int getOffset() {
621: return offset;
622: }
623:
624: public int getCursorPos() {
625: return cursorPos;
626: }
627: }
628:
629: protected String dumpToken(TokenItem tokenItem) {
630: StringBuffer sb = new StringBuffer();
631: sb.append("<token \name='");
632: sb.append(tokenItem.getTokenID().getName());
633: sb.append("'>\n");
634: sb.append(tokenItem.getTokenContextPath());
635: sb.append("</token>");
636: return sb.toString();
637: }
638:
639: protected void ending() throws Exception {
640: if (!GENERATE_GOLDEN_FILES) {
641: compareReferenceFiles();
642: } else {
643: getRef().flush();
644: File ref = new File(getWorkDir(), this .getName() + ".ref");
645: File f = getDataDir();
646: ArrayList<String> names = new ArrayList<String>();
647: names.add("goldenfiles");
648: names.add("data");
649: names.add("qa-functional");
650: while (!f.getName().equals("test")) {
651: f = f.getParentFile();
652: }
653: // f= new File("/home/jindra/TRUNK/web/jspsyntax/test/"); //internal execution
654: for (int i = names.size() - 1; i > -1; i--) {
655: f = new File(f, names.get(i));
656: }
657: f = new File(f, getClass().getName().replace('.',
658: File.separatorChar));
659: f = new File(f, this .getName() + ".pass");
660: if (!f.getParentFile().exists()) {
661: f.getParentFile().mkdirs();
662: }
663: ref.renameTo(f);
664: assertTrue("Generating golden files to "
665: + f.getAbsolutePath(), false);
666: }
667:
668: }
669:
670: /** Use for execution inside IDE */
671: public static void main(java.lang.String[] args) {
672: /* File datadir = new CompletionTest(null, null).getDataDir();
673: File projectsDir = new File(datadir, "CompletionTestProjects");
674: suite.addTest(new CompletionTest("testXMLWellFormed", ));
675: suite.addTest(new CompletionTest("testXMLDTDFormed"));
676: suite.addTest(new CompletionTest("testXMLXSDFormed"));
677: suite.addTest(new CompletionTest("testGenerateDTD"));
678: suite.addTest(new CompletionTest("testXSLT"));
679: */junit.textui.TestRunner.run(suite());
680: }
681: }
|