1   /*
2    * Copyright 2008 - 2009 JEuclid, http://jeuclid.sf.net
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  /* $Id: TestTestSuiteRendering.java,v 08b84e847c12 2010/02/12 13:22:52 max $ */
18  
19  package net.sourceforge.jeuclid.test.testsuite;
20  
21  import java.awt.Graphics2D;
22  import java.awt.Image;
23  import java.awt.image.BufferedImage;
24  import java.io.BufferedReader;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.InputStreamReader;
31  import java.io.ObjectInputStream;
32  import java.io.ObjectOutputStream;
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.LinkedList;
36  import java.util.List;
37  import java.util.Map;
38  
39  import javax.xml.transform.stream.StreamSource;
40  
41  import net.sourceforge.jeuclid.DOMBuilder;
42  import net.sourceforge.jeuclid.LayoutContext;
43  import net.sourceforge.jeuclid.MutableLayoutContext;
44  import net.sourceforge.jeuclid.context.LayoutContextImpl;
45  import net.sourceforge.jeuclid.context.Parameter;
46  import net.sourceforge.jeuclid.elements.generic.DocumentElement;
47  import net.sourceforge.jeuclid.layout.JEuclidView;
48  import net.sourceforge.jeuclid.layout.LayoutInfo;
49  import net.sourceforge.jeuclid.layout.LayoutStage;
50  import net.sourceforge.jeuclid.layout.LayoutableNode;
51  import net.sourceforge.jeuclid.parser.Parser;
52  
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  import org.testng.Assert;
56  import org.testng.annotations.Test;
57  import org.w3c.dom.Document;
58  
59  /**
60   * Compares Rendering of the testsuite to previous renderings to catch
61   * regression bugs.
62   * 
63   * @version $Revision: 08b84e847c12 $
64   */
65  // CHECKSTYLE:OFF
66  // This is a test class.
67  public class TestTestSuiteRendering {
68      private static final float LARGE_FONT_SIZE = 48.0f;
69  
70      // CHECKSTYLE:ON
71      /**
72       * Logger for this class.
73       */
74      private static final Log LOGGER = LogFactory
75              .getLog(TestTestSuiteRendering.class);
76  
77      private static final String RENDER_NAME = "renderInfos.ser";
78  
79      private final LayoutContext layoutContext;
80  
81      private final Graphics2D g2d;
82  
83      private final Map<String, List<RenderInfo>> currentRendering = new HashMap<String, List<RenderInfo>>();
84  
85      private final Map<String, List<RenderInfo>> oldRendering;
86  
87      private final File tempDir;
88  
89      /**
90       * Default Constructor.
91       */
92      public TestTestSuiteRendering() {
93          final MutableLayoutContext mlc = new LayoutContextImpl(
94                  LayoutContextImpl.getDefaultLayoutContext());
95          mlc.setParameter(Parameter.ANTIALIAS, true);
96          mlc.setParameter(Parameter.MATHSIZE,
97                  TestTestSuiteRendering.LARGE_FONT_SIZE);
98          mlc.setParameter(Parameter.FONTS_SANSSERIF, "DejaVu Sans");
99          mlc.setParameter(Parameter.FONTS_SERIF, "DejaVu Serif");
100         this.layoutContext = mlc;
101 
102         final Image tempimage = new BufferedImage(1, 1,
103                 BufferedImage.TYPE_INT_ARGB);
104         this.g2d = (Graphics2D) tempimage.getGraphics();
105 
106         this.tempDir = new File("temp");
107         if (!this.tempDir.isDirectory()) {
108             final boolean success = this.tempDir.mkdirs();
109             assert success;
110         }
111         this.oldRendering = this.loadRenderings();
112     }
113 
114     private void createInfo(final JEuclidView view, final LayoutableNode node,
115             final List<RenderInfo> renderInfos) {
116         final LayoutInfo info = view.getInfo(node);
117         final RenderInfo renderInfo = new RenderInfo(node.getNodeName(), info
118                 .getAscentHeight(LayoutStage.STAGE2), info
119                 .getDescentHeight(LayoutStage.STAGE2), info
120                 .getWidth(LayoutStage.STAGE2),
121                 info.getPosX(LayoutStage.STAGE2), info
122                         .getPosY(LayoutStage.STAGE2));
123         renderInfos.add(renderInfo);
124         for (final LayoutableNode n : node.getChildrenToLayout()) {
125             this.createInfo(view, n, renderInfos);
126         }
127     }
128 
129     /**
130      * Renders complete testsuite and compares results.
131      * 
132      * @throws Exception
133      *             if the test fails.
134      */
135     @Test
136     public void testRenderMml2Testsuite() throws Exception {
137 
138         final InputStream i = ClassLoader
139                 .getSystemResourceAsStream("mml2-testsuite.list");
140         final BufferedReader br = new BufferedReader(new InputStreamReader(i,
141                 "UTF-8"));
142         String line;
143         final List<String> failures = new ArrayList<String>();
144         while ((line = br.readLine()) != null) {
145             this.renderAndCompare(line, failures);
146         }
147         Assert.assertTrue(failures.isEmpty(), failures.toString());
148         this.saveCurrentRenderings();
149     }
150 
151     private void saveCurrentRenderings() throws Exception {
152         final ObjectOutputStream oo = new ObjectOutputStream(
153                 new FileOutputStream(new File(this.tempDir,
154                         TestTestSuiteRendering.RENDER_NAME)));
155         oo.writeObject(this.currentRendering);
156         oo.close();
157     }
158 
159     @SuppressWarnings("unchecked")
160     private Map<String, List<RenderInfo>> loadRenderings() {
161         Map<String, List<RenderInfo>> retVal = null;
162         try {
163             final ObjectInputStream oi = new ObjectInputStream(
164                     new FileInputStream(new File(this.tempDir,
165                             TestTestSuiteRendering.RENDER_NAME)));
166             retVal = (Map<String, List<RenderInfo>>) oi.readObject();
167             oi.close();
168         } catch (final Exception e) {
169             retVal = null;
170         }
171         if (retVal == null) {
172             TestTestSuiteRendering.LOGGER
173                     .info("Could not load old rendering infos. Will create them for the first time.");
174             retVal = new HashMap<String, List<RenderInfo>>();
175         }
176         return retVal;
177     }
178 
179     private void renderAndCompare(final String name, final List<String> failures)
180             throws Exception {
181         final List<RenderInfo> currentList = this.render(name);
182         this.currentRendering.put(name, currentList);
183         final List<RenderInfo> oldList = this.oldRendering.get(name);
184         this.compareRenderings(name, currentList, oldList, failures);
185     }
186 
187     private void compareRenderings(final String name,
188             final List<RenderInfo> currentList, final List<RenderInfo> oldList,
189             final List<String> failures) {
190         boolean have = false;
191         if (oldList == null) {
192             // TODO: Maybe log?
193             return;
194         }
195         if (currentList.size() != oldList.size()) {
196             failures.add(name + " has changed in number of elements! (old: "
197                     + oldList.size() + " new: " + currentList.size() + ")");
198             have = true;
199         } else
200         for (int i = 0; i < currentList.size(); i++) {
201             final RenderInfo current = currentList.get(i);
202             final RenderInfo old = oldList.get(i);
203             final String similarities = current.checkSimilar(old);
204             if (similarities.length() > 0) {
205                 failures.add(name + " differes for element "
206                         + current.getElementName() + " in" + similarities);
207                 have = true;
208             }
209         }
210         if (have) {
211             failures.add("\n");
212         }
213     }
214 
215     private List<RenderInfo> render(final String line) throws Exception {
216         final List<RenderInfo> currentList = new LinkedList<RenderInfo>();
217         try {
218             final InputStream i = ClassLoader.getSystemResourceAsStream(line);
219             final Document d = Parser.getInstance().parseStreamSource(
220                     new StreamSource(i));
221             final DocumentElement docElement = DOMBuilder.getInstance()
222                     .createJeuclidDom(d);
223             final JEuclidView view = new JEuclidView(docElement,
224                     this.layoutContext, this.g2d);
225             // Forces Layout
226             view.getAscentHeight();
227             this.createInfo(view, docElement, currentList);
228         } catch (final IOException io) {
229             // ignore
230         }
231         return currentList;
232     }
233 }