// 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();
}
}