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:
042: package org.netbeans.test.jsf.el;
043:
044: import java.awt.Color;
045: import java.awt.Component;
046: import java.awt.Font;
047: import java.awt.Graphics;
048: import java.beans.PropertyChangeEvent;
049: import java.beans.PropertyChangeListener;
050: import java.io.File;
051: import java.lang.reflect.Method;
052: import java.util.Arrays;
053: import java.util.Iterator;
054: import java.util.List;
055: import javax.swing.JEditorPane;
056: import javax.swing.JLabel;
057: import javax.swing.JList;
058: import javax.swing.SwingUtilities;
059: import javax.swing.text.Caret;
060: import junit.framework.Test;
061: import org.netbeans.editor.Utilities;
062: import org.netbeans.editor.ext.CompletionQuery.ResultItem;
063: import org.netbeans.jellytools.JellyTestCase;
064: import org.netbeans.jellytools.modules.editor.CompletionJListOperator;
065: import org.netbeans.jemmy.JemmyException;
066: import org.netbeans.jemmy.util.PNGEncoder;
067: import org.netbeans.junit.AssertionFailedErrorException;
068: import org.netbeans.junit.NbTestCase;
069: import org.netbeans.editor.BaseDocument;
070: import org.netbeans.spi.editor.completion.CompletionItem;
071: import org.netbeans.editor.TokenID;
072: import org.netbeans.editor.TokenItem;
073: import org.netbeans.editor.ext.ExtSyntaxSupport;
074: import org.openide.actions.UndoAction;
075: import org.openide.cookies.EditorCookie;
076: import org.openide.cookies.EditorCookie.Observable;
077: import org.openide.filesystems.FileObject;
078: import org.openide.loaders.DataObject;
079: import org.openide.util.actions.SystemAction;
080:
081: /**
082: * Test goes throught files and looking for CC Test Steps.
083: * The CC Test Steps are writen in document body as three lines:
084: * JSP comments which start with '<%--CC' prefix, where:
085: *<ul>
086: *<li> first line contains ccPrefix with optional '|' character which represents
087: * cursor position
088: *<li> second line contains ccChoice item which will be used for CC substitution
089: *<li> third line contains ccResult
090: *</ul>
091: *
092: * For example:<p>
093: * <pre><%--CC
094: * <%@ taglib |
095: * uri
096: * <%@ taglib uri=""
097: * --%>
098: * </pre><p>
099: * does:
100: * <ul>
101: * <li> inserts '<%@ taglib ' string into new line
102: * <li> invokes CC
103: * <li> dumps Completion Query Result
104: * <li> choses "uri" item from the query result and substitute it
105: * <li> checks if subtituted line is: '<%@ taglib uri=""'
106: * <li> undoes all changes
107: * </ul>
108: * @author ms113234
109: *
110: */
111: public class CompletionTest extends JellyTestCase {
112: private FileObject testFileObj;
113: protected boolean debug = false;
114: protected final static List xmlExts = Arrays.asList(new String[] {
115: "xml", "xsd", "html", "tld", "jspx" });
116: protected final static List jspExts = Arrays.asList(new String[] {
117: "jsp", "tag" });
118:
119: /** Need to be defined because of JUnit */
120: public CompletionTest(String name, FileObject testFileObj) {
121: super (name);
122: this .testFileObj = testFileObj;
123: }
124:
125: public void setUp() {
126: System.out.println("######## " + getName() + " #######");
127: }
128:
129: public static Test suite() {
130: // find folder with test projects and define file objects filter
131: File dataDir = new CompletionTest(null, null).getDataDir();
132: File projectsDir = new File(dataDir, "tests");
133: FileObjectFilter filter = new FileObjectFilter() {
134: public boolean accept(FileObject fo) {
135: String ext = fo.getExt();
136: String name = fo.getName();
137: return (name.startsWith("test") || name
138: .startsWith("Test"))
139: && (xmlExts.contains(ext)
140: || jspExts.contains(ext) || jspExts
141: .equals("java"));
142: }
143: };
144: return RecurrentSuiteFactory.createSuite(CompletionTest.class,
145: projectsDir, filter);
146: }
147:
148: public void runTest() throws Exception {
149: String ext = testFileObj.getExt();
150: if (jspExts.contains(ext)) {
151: test(testFileObj, "<%--CC", "--%>");
152: } else if (xmlExts.contains(ext)) {
153: test(testFileObj, "<!--CC", "-->");
154: } else if (ext.equals("java")) {
155: test(testFileObj, "/**CC", "*/");
156: } else {
157: throw new JemmyException("File extension of: "
158: + testFileObj.getNameExt() + " is unsupported.");
159: }
160: }
161:
162: private void test(FileObject fileObj, String stepStart,
163: String stepEnd) throws Exception {
164: boolean inStepData = false;
165: String[] stepData = new String[3];
166: int dataLineIdx = 0;
167:
168: try {
169: // get token chain
170: DataObject dataObj = DataObject.find(fileObj);
171: EditorCookie.Observable ed = (Observable) dataObj
172: .getCookie(Observable.class);
173:
174: // prepare synchronization and register listener
175: final Waiter waiter = new Waiter();
176: final PropertyChangeListener pcl = new PropertyChangeListener() {
177: public void propertyChange(PropertyChangeEvent evt) {
178: if (evt.getPropertyName().equals(
179: Observable.PROP_OPENED_PANES)) {
180: waiter.notifyFinished();
181: }
182: }
183: };
184: ed.addPropertyChangeListener(pcl);
185: // open document
186: BaseDocument doc = (BaseDocument) ed.openDocument();
187: ed.open();
188: // wait for PROP_OPENED_PANES and remove listener
189: assertTrue("The editor pane was not opened in 10 secs.",
190: waiter.waitFinished(10000));
191: ed.removePropertyChangeListener(pcl);
192: // wait 2s for editor initialization
193: Thread.sleep(3000);
194: JEditorPane editor = ed.getOpenedPanes()[0];
195: ExtSyntaxSupport ess = (ExtSyntaxSupport) doc
196: .getSyntaxSupport();
197: TokenItem token = ess.getTokenChain(0, doc.getLength());
198: List steps = new java.util.ArrayList();
199: // go through token chain an look for CC test steps
200: while (token != null) {
201: TokenID tokenID = token.getTokenID();
202: if (debug) {
203: String tImage = token.getImage();
204: int tEnd = token.getOffset() + tImage.length();
205: System.err.println("# [" + token.getOffset() + ","
206: + tEnd + "] " + tokenID.getName() + " :: "
207: + token.getImage());
208: }
209: if (tokenID.getName().indexOf("comment") == -1) {
210: token = token.getNext();
211: continue;
212: }
213: if (inStepData) {
214: // probably end of step data
215: if (token.getImage().indexOf(stepEnd) > -1) {
216: inStepData = false;
217: // check obtained CC data and create test step CCsecs
218: if (dataLineIdx == 3) {
219: int offset = token.getOffset()
220: + token.getImage().length();
221: TestStep step = new TestStep(stepData,
222: offset);
223: steps.add(step);
224: } else {
225: ref("EE: expected data lines number: 3 but was: "
226: + dataLineIdx);
227: }
228: } else {
229: // assert CC TEst Data lenght
230: if (dataLineIdx > 2) {
231: String msg = "EE: to much lines in CC Test Data";
232: ref(msg);
233: ref(dumpToken(token));
234: fail(msg);
235: }
236: String str = token.getImage();
237: // suppress new lines
238: if (str.endsWith("\n")) {
239: str = str.substring(0, str.length() - 1);
240: }
241: stepData[dataLineIdx++] = str;
242: }
243: } else {
244: String text = token.getImage();
245: if (text.startsWith(stepStart)) {
246: if (text.endsWith(stepEnd)) {
247: // all steps line in one toke as .java does
248: String[] lines = text.split("\n\r?|\r\n?");
249: if (lines.length == 5) {
250: int offset = token.getOffset()
251: + token.getImage().length();
252: for (int i = 0; i < 3; i++) {
253: stepData[i] = lines[i + 1];
254: }
255: TestStep step = new TestStep(stepData,
256: offset);
257: steps.add(step);
258: } else {
259: String msg = "EE: expected 5 lines lenght token but got: "
260: + lines.length;
261: ref(msg);
262: ref(text);
263: for (int i = 0; i < lines.length; i++) {
264: ref(i + "::" + lines[i]);
265: }
266: }
267: } else {
268: // each step line in separate line as .jsp does
269: inStepData = true;
270: dataLineIdx = 0;
271: }
272: }
273: }
274: token = token.getNext();
275: } // while (token != null)
276: Iterator it = steps.iterator();
277: while (it.hasNext()) {
278: exec(editor, (TestStep) it.next());
279: }
280: } catch (Exception ex) {
281: throw new AssertionFailedErrorException(ex);
282: }
283: compareReferenceFiles();
284: }
285:
286: private void exec(JEditorPane editor, TestStep step)
287: throws Exception {
288: boolean ccError = false;
289: try {
290: ref(step.toString());
291: System.out.println("step: " + step.toString());
292: BaseDocument doc = (BaseDocument) editor.getDocument();
293: // insert prefix and set cursor
294: doc.insertString(step.getOffset(), "\n" + step.getPrefix(),
295: null);
296: Caret caret = editor.getCaret();
297: caret.setDot(step.getCursorPos());
298: // run the test at a reasonable speed
299: Thread.sleep(500);
300: // call CC, the CC window should not appear in case of instant
301: // substitution
302: CompletionJListOperator comp = null;
303: try {
304: comp = CompletionJListOperator.showCompletion();
305: } catch (JemmyException e) {
306: ccError = true;
307: ref("EE: The CC window did not appear");
308: e.printStackTrace();
309: }
310: // run the test at a reasonable speed
311: Thread.sleep(1500);
312: if (comp != null) {
313: // dump CC result to golden file
314: Iterator items = comp.getCompletionItems().iterator();
315: CompletionItem selectedItem = null;
316: while (items.hasNext()) {
317: TextGraphics2D g = new TextGraphics2D(comp
318: .getSource());
319: Object next = items.next();
320: String dispText = null;
321: if (next instanceof ResultItem) {
322: ResultItem resItem = (ResultItem) next;
323: Component component = resItem
324: .getPaintComponent((JList) comp
325: .getSource(), false, true);
326: // get display version of the component
327: Method drawM = findMethod(component.getClass(),
328: "draw", new Class[] { Graphics.class });
329: if (drawM != null) {
330: drawM.setAccessible(true);
331: drawM.invoke(component, new Object[] { g });
332: } else if (component instanceof JLabel) {
333: // ??? use java.awt.Component.paint(Grraphics g) method instead?
334: System.out.println("graph1: "
335: + ((JLabel) component).getText()
336: .trim());
337: g.drawString(((JLabel) component).getText()
338: .trim(), 0, 0);
339: } else {
340: System.out.println("graph2: "
341: + ((JLabel) component).getText()
342: .trim());
343: g.drawString(component.toString(), 0, 0);
344: }
345: } else if (next instanceof CompletionItem) {
346: CompletionItem cItem = (CompletionItem) next;
347: cItem.render(g, Font.decode("Dialog-Plain-12"),
348: Color.BLACK, Color.WHITE, 400, 30,
349: false);
350: } else {
351: System.out.println("next:" + next.toString());
352: g.drawString(next.toString(), 0, 0);
353: }
354: dispText = g.getTextUni();
355: System.out.println("DispText: " + dispText);
356: // find choice item
357: if (dispText.equals(step.getChoice())) {
358: assertInstanceOf(CompletionItem.class, next);
359: selectedItem = (CompletionItem) next;
360: }
361: System.out.println("getTextUni: " + g.getTextUni());
362: ref(g.getTextUni());
363: }
364: class DefaultActionRunner implements Runnable {
365: CompletionItem item;
366: JEditorPane editor;
367:
368: public DefaultActionRunner(CompletionItem item,
369: JEditorPane editor) {
370: this .item = item;
371: this .editor = editor;
372: }
373:
374: public void run() {
375: item.defaultAction(editor);
376: }
377:
378: }
379: // substitute completion and check result
380: if (selectedItem != null) {
381: // move to separate class
382: Runnable run = new DefaultActionRunner(
383: selectedItem, editor);
384: // XXX wait before substitution
385: Thread.currentThread().sleep(500);
386: runInAWT(run);
387: } else {
388: ref("EE: cannot find completion item: "
389: + step.getChoice());
390: }
391: } else if (!ccError) {
392: // comp == null && ccError == false => instant substitution
393: ref("Instant substitution performed");
394: }
395: // wait till CompletionJList is hidden
396: if (comp != null) {
397: int i = 0;
398: while (comp.isShowing()) {
399: Thread.currentThread().sleep(500);
400: if (i++ >= 12) {
401: // log status
402: long time = System.currentTimeMillis();
403: String screenFile = time + "-screen.png";
404: log("["
405: + time
406: + "]The CompletionJList was not hidden in 5 secs");
407: log("step: " + step);
408: log("captureScreen:" + screenFile);
409: try {
410: PNGEncoder.captureScreen(getWorkDir()
411: .getAbsolutePath()
412: + File.separator + screenFile);
413: } catch (Exception e1) {
414: }
415: break;
416: }
417: }
418: }
419: Thread.currentThread().sleep(500); //XXX
420: int rowStart = Utilities.getRowStart(doc,
421: step.getOffset() + 1);
422: int rowEnd = Utilities.getRowEnd(doc, step.getOffset() + 1);
423: String result = doc.getText(new int[] { rowStart, rowEnd });
424: if (!result.equals(step.getResult())) {
425: ref("EE: unexpected CC result:\n< " + result + "\n> "
426: + step.getResult());
427: }
428: ref("End cursor position = " + caret.getDot());
429: } finally {
430: Thread.currentThread().sleep(500); //XXX
431: // undo all changes
432: final UndoAction ua = (UndoAction) SystemAction
433: .get(UndoAction.class);
434: assertNotNull("Cannot obtain UndoAction", ua);
435: while (ua.isEnabled()) {
436: runInAWT(new Runnable() {
437: public void run() {
438: ua.performAction();
439: }
440: });
441: Thread.currentThread().sleep(50); //XXX
442: }
443: Thread.currentThread().sleep(500);
444: }
445:
446: }
447:
448: private static void runInAWT(Runnable r) {
449: if (SwingUtilities.isEventDispatchThread()) {
450: r.run();
451: } else {
452: SwingUtilities.invokeLater(r);
453: }
454: }
455:
456: private Method findMethod(Class clazz, String name,
457: Class[] paramTypes) {
458: Method method = null;
459: for (Class cls = clazz; cls.getSuperclass() != null; cls = cls
460: .getSuperclass()) {
461: try {
462: method = cls.getDeclaredMethod(name, paramTypes);
463: } catch (NoSuchMethodException e) {
464: // ignore
465: }
466: if (method != null) {
467: return method;
468: }
469: }
470: return null;
471: }
472:
473: protected void assertInstanceOf(Class expectedType, Object actual) {
474: if (!expectedType.isAssignableFrom(actual.getClass())) {
475: fail("Expected type: " + expectedType.getName()
476: + "\nbut was: " + actual.getClass().getName());
477: }
478: }
479:
480: private static class TestStep {
481: private String prefix;
482: private String choice;
483: private String result;
484: private int offset;
485: private int cursorPos;
486:
487: public TestStep(String data[], int offset) {
488: this .prefix = data[0];
489: this .choice = data[1];
490: this .result = data[2];
491: this .offset = offset;
492:
493: cursorPos = prefix.indexOf('|');
494: if (cursorPos != -1) {
495: prefix = prefix.replaceFirst("\\|", "");
496: } else {
497: cursorPos = prefix.length();
498: }
499: cursorPos += offset + 1;
500: }
501:
502: public String toString() {
503: StringBuffer sb = new StringBuffer(prefix);
504: sb.insert(cursorPos - offset - 1, '|');
505: return "[" + sb + ", " + choice + ", " + result + ", "
506: + offset + "]";
507: }
508:
509: public String getPrefix() {
510: return prefix;
511: }
512:
513: public String getChoice() {
514: return choice;
515: }
516:
517: public String getResult() {
518: return result;
519: }
520:
521: public int getOffset() {
522: return offset;
523: }
524:
525: public int getCursorPos() {
526: return cursorPos;
527: }
528:
529: };
530:
531: /** Use for execution inside IDE */
532: public static void main(java.lang.String[] args) {
533: // run whole suite
534: junit.textui.TestRunner.run(suite());
535: }
536:
537: private String dumpToken(TokenItem tokenItem) {
538: StringBuffer sb = new StringBuffer();
539: sb.append("<token \name='");
540: sb.append(tokenItem.getTokenID().getName());
541: sb.append("'>\n");
542: sb.append(tokenItem.getTokenContextPath());
543: sb.append("</token>");
544: return sb.toString();
545: }
546: }
|