// Copyright (C) 2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.caja.util; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringReader; import java.net.URI; import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; import org.w3c.dom.Node; import com.drawbridge.jsengine.JsEngine; import com.drawbridge.text.DocumentError; import com.drawbridge.text.TextPanel; import com.drawbridge.utils.Utils; import com.drawbridge.vl.VLPanel; import com.google.caja.SomethingWidgyHappenedError; import com.google.caja.lexer.CharProducer; import com.google.caja.lexer.CssTokenType; import com.google.caja.lexer.FetchedData; import com.google.caja.lexer.FilePosition; import com.google.caja.lexer.GuessContentType; import com.google.caja.lexer.HtmlLexer; import com.google.caja.lexer.HtmlTokenType; import com.google.caja.lexer.InputSource; import com.google.caja.lexer.JsLexer; import com.google.caja.lexer.JsTokenQueue; import com.google.caja.lexer.JsTokenType; import com.google.caja.lexer.ParseException; import com.google.caja.lexer.Token; import com.google.caja.lexer.TokenQueue; import com.google.caja.parser.css.CssParser; import com.google.caja.parser.css.CssTree; import com.google.caja.parser.html.DomParser; import com.google.caja.parser.html.Nodes; import com.google.caja.parser.js.Block; import com.google.caja.parser.js.Expression; import com.google.caja.parser.js.Parser; import com.google.caja.reporting.Message; import com.google.caja.reporting.MessageContext; import com.google.caja.reporting.MessageLevel; import com.google.caja.reporting.MessageQueue; public abstract class CajaTestCase { protected InputSource is; protected MessageContext mc; protected MessageQueue mq; /** * For random tests we choose a seed by using a system property so that failing random tests can be repeated. */ protected static final long SEED = Long.parseLong( System.getProperty("junit.seed", "" + System.currentTimeMillis())); private static boolean dumpedSeed; protected void setUp() throws Exception { synchronized (CajaTestCase.class) { if (!dumpedSeed) { dumpedSeed = true; // Make sure it shows up in the junit test runner trace. System.err.println("junit.seed=" + SEED); } } this.is = new InputSource(URI.create("http://example.org/")); this.mc = new MessageContext(); mc.addInputSource(is); this.mq = TestUtil.createTestMessageQueue(this.mc); } protected void tearDown() throws Exception { this.is = null; this.mc = null; this.mq = null; } protected CharProducer fromString(String... content) { return fromString(Join.join("\n", content), is); } protected CharProducer fromString(String content, InputSource is) { this.mc.addInputSource(is); return CharProducer.Factory.create(new StringReader(content), is); } protected CharProducer fromString(String content, FilePosition pos) { this.mc.addInputSource(is); return CharProducer.Factory.create(new StringReader(content), pos); } protected CharProducer fromResource(String resourcePath) throws IOException { URI resource = TestUtil.getResource(getClass(), resourcePath); if (resource == null) { throw new FileNotFoundException(resourcePath); } return fromResource(resourcePath, new InputSource(resource)); } protected CharProducer fromResource(String resourcePath, InputSource is) throws IOException { return dataFromResource(resourcePath, is).getTextualContent(); } protected FetchedData dataFromResource(String resourcePath, InputSource is) throws IOException { ContentType guess = GuessContentType.guess(null, resourcePath, null); mc.addInputSource(is); return FetchedData.fromStream( TestUtil.getResourceAsStream(getClass(), resourcePath), guess != null ? guess.mimeType : "", "UTF-8", is); } protected static String plain(CharProducer cp) { return cp.toString(cp.getOffset(), cp.getLimit()); } protected Block js(CharProducer cp) throws ParseException { return js(cp, false); } protected Expression jsExpr(CharProducer cp) throws ParseException { return jsExpr(cp, false); } protected Block js(CharProducer cp, boolean quasi) throws ParseException { return js(cp, JsTokenQueue.NO_COMMENT, quasi); } protected Block js( CharProducer cp, Criterion<Token<JsTokenType>> filt, boolean quasi) throws ParseException { JsLexer lexer = new JsLexer(cp); JsTokenQueue tq = new JsTokenQueue(lexer, sourceOf(cp), filt); Parser p = new Parser(tq, mq, quasi); Block b = null; try { b = p.parse(); } catch (ParseException pe) { Utils.out.println(getClass(), "Linter parse exception:"); FilePosition fp = (FilePosition) pe.getCajaMessage().getMessageParts().get(0); String title = "ErrorX"; String message = pe.getCajaMessage().getMessageType().name() + ": " + pe.getCajaMessage().getMessageParts().get(1).toString(); int errorLineNo = fp.startLineNo() - 1 - JsEngine.getInstance().mScaffoldLines; if (TextPanel.hasInstance()) { TextPanel.getInstance().getDocument().setDocumentError(new DocumentError(errorLineNo, title, message)); TextPanel.getInstance().repaint(); } if (VLPanel.hasInstance()) { VLPanel.getInstance().mCanvas.mDocumentError = new DocumentError(errorLineNo, title, message); VLPanel.getInstance().repaint(); } Utils.out.println(getClass(), "Error at " + (fp.startLineNo() - 1) + ":" + message); } tq.expectEmpty(); return b; } protected Expression jsExpr(CharProducer cp, boolean quasi) throws ParseException { JsLexer lexer = new JsLexer(cp); JsTokenQueue tq = new JsTokenQueue( lexer, sourceOf(cp), JsTokenQueue.NO_COMMENT); Parser p = new Parser(tq, mq, quasi); Expression e = p.parseExpression(true); tq.expectEmpty(); return e; } protected Block quasi(CharProducer cp) throws ParseException { return js(cp, true); } protected Element xml(CharProducer cp) throws ParseException { return (Element) parseMarkup(cp, true, true); } protected DocumentFragment xmlFragment(CharProducer cp) throws ParseException { return (DocumentFragment) parseMarkup(cp, true, false); } protected DocumentFragment html(CharProducer cp) throws ParseException { Node root = parseMarkup(cp, false, true); DocumentFragment fragment = root.getOwnerDocument() .createDocumentFragment(); fragment.appendChild(root); Nodes.setFilePositionFor(fragment, Nodes.getFilePositionFor(root)); return fragment; } protected DocumentFragment htmlFragment(CharProducer cp) throws ParseException { return (DocumentFragment) parseMarkup(cp, false, false); } private Node parseMarkup(CharProducer cp, boolean asXml, boolean asDoc) throws ParseException { InputSource is = sourceOf(cp); HtmlLexer lexer = new HtmlLexer(cp); lexer.setTreatedAsXml(asXml); TokenQueue<HtmlTokenType> tq = new TokenQueue<HtmlTokenType>( lexer, is, DomParser.SKIP_COMMENTS); DomParser p = new DomParser(tq, asXml, mq); Node t = asDoc ? p.parseDocument() : p.parseFragment(); tq.expectEmpty(); return t; } protected Element markup(CharProducer cp) throws ParseException { return new DomParser(new HtmlLexer(cp), false, sourceOf(cp), mq) .parseDocument(); } protected DocumentFragment markupFragment(CharProducer cp) throws ParseException { return new DomParser(new HtmlLexer(cp), false, sourceOf(cp), mq) .parseFragment(); } protected CssTree.StyleSheet css(CharProducer cp) throws ParseException { return css(cp, false); } protected CssTree.StyleSheet css(CharProducer cp, boolean substs) throws ParseException { TokenQueue<CssTokenType> tq = cssTokenQueue(cp, substs); CssTree.StyleSheet ss = new CssParser(tq, mq, MessageLevel.FATAL_ERROR) .parseStyleSheet(); tq.expectEmpty(); return ss; } protected CssTree.DeclarationGroup cssDecls(CharProducer cp) throws ParseException { return cssDecls(cp, false); } protected CssTree.DeclarationGroup cssDecls(CharProducer cp, boolean substs) throws ParseException { TokenQueue<CssTokenType> tq = cssTokenQueue(cp, substs); CssTree.DeclarationGroup dg = new CssParser( tq, mq, MessageLevel.FATAL_ERROR).parseDeclarationGroup(); tq.expectEmpty(); return dg; } private TokenQueue<CssTokenType> cssTokenQueue( CharProducer cp, boolean substs) { return CssParser.makeTokenQueue(cp, mq, substs); } public static String render(MessageQueue mq) { StringBuilder sb = new StringBuilder(); for (Message m : mq.getMessages()) { try { m.format(new MessageContext(), sb); } catch (IOException e) { sb.append(e.toString()); } sb.append("\n"); } return sb.toString(); } protected static String formatShort(FilePosition p) { StringBuilder sb = new StringBuilder(); try { p.formatShort(sb); } catch (IOException ex) { throw new SomethingWidgyHappenedError( "IOException from StringBuilder", ex); } return sb.toString(); } private static InputSource sourceOf(CharProducer cp) { return cp.getSourceBreaks(0).source(); } }