1 /* 2 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jaxp.library; 24 25 import java.io.ByteArrayInputStream; 26 import java.io.File; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.StringWriter; 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 import java.nio.charset.Charset; 33 import java.nio.charset.StandardCharsets; 34 import java.nio.charset.UnsupportedCharsetException; 35 import java.nio.file.Files; 36 import java.nio.file.Paths; 37 import java.util.HashMap; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.concurrent.ConcurrentHashMap; 41 import java.util.regex.Pattern; 42 import java.util.stream.Collectors; 43 import javax.xml.parsers.DocumentBuilder; 44 import javax.xml.parsers.DocumentBuilderFactory; 45 import javax.xml.parsers.ParserConfigurationException; 46 import javax.xml.transform.Transformer; 47 import javax.xml.transform.TransformerException; 48 import javax.xml.transform.TransformerFactory; 49 import javax.xml.transform.dom.DOMSource; 50 import javax.xml.transform.stream.StreamResult; 51 import static org.testng.Assert.fail; 52 import org.w3c.dom.Document; 53 import org.w3c.dom.Node; 54 import org.xml.sax.SAXException; 55 56 /** 57 * This is an interface provide basic support for JAXP functional test. 58 */ 59 public class JAXPTestUtilities { 60 /** 61 * Prefix for error message. 62 */ 63 public static final String ERROR_MSG_HEADER = "Unexcepted exception thrown:"; 64 65 /** 66 * Prefix for error message on clean up block. 67 */ 68 public static final String ERROR_MSG_CLEANUP = "Clean up failed on %s"; 69 70 /** 71 * Force using slash as File separator as we always use cygwin to test in 72 * Windows platform. 73 */ 74 public static final String FILE_SEP = "/"; 75 76 /** 77 * Current test directory. 78 */ 79 public static final String USER_DIR = 80 System.getProperty("user.dir", ".") + FILE_SEP;; 81 82 /** 83 * A map storing every test's current test file pointer. File number should 84 * be incremental and it's a thread-safe reading on this file number. 85 */ 86 private static final ConcurrentHashMap<Class, Integer> currentFileNumber 87 = new ConcurrentHashMap<>(); 88 89 /** 90 * BOM table for storing BOM header. 91 */ 92 private final static Map<String, byte[]> bom = new HashMap<>(); 93 94 /** 95 * Initialize all BOM headers. 96 */ 97 static { 98 bom.put("UTF-8", new byte[]{(byte)0xEF, (byte) 0xBB, (byte) 0xBF}); 99 bom.put("UTF-16BE", new byte[]{(byte)0xFE, (byte)0xFF}); 100 bom.put("UTF-16LE", new byte[]{(byte)0xFF, (byte)0xFE}); 101 bom.put("UTF-32BE", new byte[]{(byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF}); 102 bom.put("UTF-32LE", new byte[]{(byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00}); 103 } 104 105 /** 106 * Compare contents of golden file with test output file line by line. 107 * return true if they're identical. 108 * @param goldfile Golden output file name 109 * @param outputfile Test output file name 110 * @return true if two files are identical. 111 * false if two files are not identical. 112 * @throws IOException if an I/O error occurs reading from the file or a 113 * malformed or unmappable byte sequence is read. 114 */ 115 public static boolean compareWithGold(String goldfile, String outputfile) 116 throws IOException { 117 return compareWithGold(goldfile, outputfile, StandardCharsets.UTF_8); 118 } 119 120 /** 121 * Compare contents of golden file with test output file line by line. 122 * return true if they're identical. 123 * @param goldfile Golden output file name. 124 * @param outputfile Test output file name. 125 * @param cs the charset to use for decoding. 126 * @return true if two files are identical. 127 * false if two files are not identical. 128 * @throws IOException if an I/O error occurs reading from the file or a 129 * malformed or unmappable byte sequence is read. 130 */ 131 public static boolean compareWithGold(String goldfile, String outputfile, 132 Charset cs) throws IOException { 133 return Files.readAllLines(Paths.get(goldfile)). 134 equals(Files.readAllLines(Paths.get(outputfile), cs)); 135 } 136 137 /** 138 * Compare contents of golden file with test output list line by line. 139 * return true if they're identical. 140 * @param goldfile Golden output file name. 141 * @param lines test output list. 142 * @return true if file's content is identical to given list. 143 * false if file's content is not identical to given list. 144 * @throws IOException if an I/O error occurs reading from the file or a 145 * malformed or unmappable byte sequence is read 146 */ 147 public static boolean compareLinesWithGold(String goldfile, List<String> lines) 148 throws IOException { 149 return Files.readAllLines(Paths.get(goldfile)).equals(lines); 150 } 151 152 /** 153 * Compare contents of golden file with a test output string. 154 * return true if they're identical. 155 * @param goldfile Golden output file name. 156 * @param string test string. 157 * @return true if file's content is identical to given string. 158 * false if file's content is not identical to given string. 159 * @throws IOException if an I/O error occurs reading from the file or a 160 * malformed or unmappable byte sequence is read 161 */ 162 public static boolean compareStringWithGold(String goldfile, String string) 163 throws IOException { 164 return Files.readAllLines(Paths.get(goldfile)).stream().collect( 165 Collectors.joining(System.getProperty("line.separator"))) 166 .equals(string); 167 } 168 169 /** 170 * Compare contents of golden file with test output file by their document 171 * representation. 172 * Here we ignore the white space and comments. return true if they're 173 * lexical identical. 174 * @param goldfile Golden output file name. 175 * @param resultFile Test output file name. 176 * @return true if two file's document representation are identical. 177 * false if two file's document representation are not identical. 178 * @throws javax.xml.parsers.ParserConfigurationException if the 179 * implementation is not available or cannot be instantiated. 180 * @throws SAXException If any parse errors occur. 181 * @throws IOException if an I/O error occurs reading from the file or a 182 * malformed or unmappable byte sequence is read . 183 */ 184 public static boolean compareDocumentWithGold(String goldfile, String resultFile) 185 throws ParserConfigurationException, SAXException, IOException { 186 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 187 factory.setNamespaceAware(true); 188 factory.setCoalescing(true); 189 factory.setIgnoringElementContentWhitespace(true); 190 factory.setIgnoringComments(true); 191 DocumentBuilder db = factory.newDocumentBuilder(); 192 193 Document goldD = db.parse(Paths.get(goldfile).toFile()); 194 goldD.normalizeDocument(); 195 Document resultD = db.parse(Paths.get(resultFile).toFile()); 196 resultD.normalizeDocument(); 197 return goldD.isEqualNode(resultD); 198 } 199 200 /** 201 * Compare contents of golden file with the serialization represent by given 202 * DOM node. 203 * Here we ignore the white space and comments. return true if they're 204 * lexical identical. 205 * @param goldfile Golden output file name. 206 * @param node A DOM node instance. 207 * @return true if file's content is identical to given node's serialization 208 * represent. 209 * false if file's content is not identical to given node's 210 * serialization represent. 211 * @throws TransformerException If an unrecoverable error occurs during the 212 * course of the transformation.. 213 * @throws IOException if an I/O error occurs reading from the file or a 214 * malformed or unmappable byte sequence is read . 215 */ 216 public static boolean compareSerializeDOMWithGold(String goldfile, Node node) 217 throws TransformerException, IOException { 218 TransformerFactory factory = TransformerFactory.newInstance(); 219 // Use identity transformer to serialize 220 Transformer identityTransformer = factory.newTransformer(); 221 StringWriter sw = new StringWriter(); 222 StreamResult streamResult = new StreamResult(sw); 223 DOMSource nodeSource = new DOMSource(node); 224 identityTransformer.transform(nodeSource, streamResult); 225 return compareStringWithGold(goldfile, sw.toString()); 226 } 227 228 /** 229 * Convert stream to ByteArrayInputStream by given character set. 230 * @param charset target character set. 231 * @param file a file that contains no BOM head content. 232 * @return a ByteArrayInputStream contains BOM heads and bytes in original 233 * stream 234 * @throws IOException I/O operation failed or unsupported character set. 235 */ 236 public static InputStream bomStream(String charset, String file) 237 throws IOException { 238 String localCharset = charset; 239 if (charset.equals("UTF-16") || charset.equals("UTF-32")) { 240 localCharset 241 += ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "BE" : "LE"; 242 } 243 if (!bom.containsKey(localCharset)) 244 throw new UnsupportedCharsetException("Charset:" + localCharset); 245 246 byte[] content = Files.readAllLines(Paths.get(file)).stream(). 247 collect(Collectors.joining()).getBytes(localCharset); 248 byte[] head = bom.get(localCharset); 249 ByteBuffer bb = ByteBuffer.allocate(content.length + head.length); 250 bb.put(head); 251 bb.put(content); 252 return new ByteArrayInputStream(bb.array()); 253 } 254 255 /** 256 * Worker method to detect common absolute URLs. 257 * 258 * @param s String path\filename or URL (or any, really) 259 * @return true if s starts with a common URI scheme (namely 260 * the ones found in the examples of RFC2396); false otherwise 261 */ 262 protected static boolean isCommonURL(String s) { 263 if (null == s) 264 return false; 265 return Pattern.compile("^(file:|http:|ftp:|gopher:|mailto:|news:|telnet:)") 266 .matcher(s).matches(); 267 } 268 269 /** 270 * Utility method to translate a String filename to URL. 271 * 272 * If the name starts with a common URI scheme (namely the ones 273 * found in the examples of RFC2396), then simply return the 274 * name as-is (the assumption is that it's already a URL). 275 * Otherwise we attempt (cheaply) to convert to a file:/ URL. 276 * 277 * @param filename local path/filename of a file. 278 * @return a file:/ URL if filename represent a file, the same string if 279 * it appears to already be a URL. 280 */ 281 public static String filenameToURL(String filename) { 282 return Paths.get(filename).toUri().toASCIIString(); 283 } 284 285 /** 286 * Prints error message if an exception is thrown 287 * @param ex The exception is thrown by test. 288 */ 289 public static void failUnexpected(Throwable ex) { 290 fail(ERROR_MSG_HEADER, ex); 291 } 292 293 /** 294 * Prints error message if an exception is thrown when clean up a file. 295 * @param ex The exception is thrown in cleaning up a file. 296 * @param name Cleaning up file name. 297 */ 298 public static void failCleanup(IOException ex, String name) { 299 fail(String.format(ERROR_MSG_CLEANUP, name), ex); 300 } 301 302 /** 303 * Retrieve next test output file name. This method is a thread-safe method. 304 * @param clazz test class. 305 * @return next test output file name. 306 */ 307 public static String getNextFile(Class clazz) { 308 int nextNumber = currentFileNumber.contains(clazz) 309 ? currentFileNumber.get(clazz) + 1 : 1; 310 Integer i = currentFileNumber.putIfAbsent(clazz, nextNumber); 311 if (i != null && i != nextNumber) { 312 do { 313 nextNumber = currentFileNumber.get(clazz) + 1; 314 } while (currentFileNumber.replace(clazz, nextNumber -1, nextNumber)); 315 } 316 return USER_DIR + clazz.getName() + nextNumber + ".out"; 317 } 318 319 /** 320 * Acquire a full path string by given class name and relative path string. 321 * @param clazz Class name for the test. 322 * @param relativeDir relative path between java source file and expected 323 * path. 324 * @return a string represents the full path of accessing path. 325 */ 326 public static String getPathByClassName(Class clazz, String relativeDir) { 327 String packageName = FILE_SEP + 328 clazz.getPackage().getName().replaceAll("[.]", FILE_SEP); 329 String javaSourcePath = System.getProperty("test.src").replaceAll("\\" + File.separator, FILE_SEP) 330 + packageName + FILE_SEP; 331 String normalizedPath = Paths.get(javaSourcePath, relativeDir).normalize(). 332 toAbsolutePath().toString(); 333 return normalizedPath.replace("\\", FILE_SEP) + FILE_SEP; 334 } 335 }