| // Copyright (C) 2006 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 prettify; | |
| import prettify.parser.Util; | |
| import prettify.parser.Job; | |
| import prettify.parser.Prettify; | |
| import prettify.lang.Lang; | |
| import prettify.lang.LangSql; | |
| import java.util.Arrays; | |
| import java.util.ListIterator; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import java.util.ArrayList; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.util.List; | |
| import org.junit.After; | |
| import org.junit.AfterClass; | |
| import org.junit.Before; | |
| import org.junit.BeforeClass; | |
| import org.junit.Test; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.junit.Assert.*; | |
| /** | |
| * Tests are adapted from JavaScript Prettify. | |
| * @author Chan Wai Shing <cws1989@gmail.com> | |
| */ | |
| public class PrettifyTest { | |
| static { | |
| // set debug mode | |
| System.setProperty("PrettifyDebugMode", "true"); | |
| } | |
| protected final String packagePath = "test/" + this.getClass().getCanonicalName().replace('.', '/') + "/"; | |
| protected Prettify prettify; | |
| public PrettifyTest() { | |
| } | |
| protected static String getClassName() { | |
| return new Object() { | |
| }.getClass().getEnclosingClass().getName(); | |
| } | |
| @BeforeClass | |
| public static void setUpClass() throws Exception { | |
| System.out.println("***** " + getClassName() + " *****"); | |
| } | |
| @AfterClass | |
| public static void tearDownClass() throws Exception { | |
| System.out.println("******************************\r\n"); | |
| } | |
| @Before | |
| public void setUp() { | |
| prettify = new Prettify(); | |
| } | |
| @After | |
| public void tearDown() { | |
| } | |
| /** | |
| * Do test. The Java Prettify line numbering is different from JavaScript | |
| * Prettify. If the result is adapted from JavaScript Prettify tests and | |
| * that {@code resultText} have line numbering, set {@code removeNewLines} | |
| * to true. | |
| * | |
| * @param extension the file name of the source file | |
| * @param code the source code | |
| * @param removeJSLineNumbering true to remove new line and combine | |
| * identical adjacent tag | |
| * | |
| * @throws IOException error occurred when reading source or result file | |
| */ | |
| public void test(String extension, String code, boolean removeJSLineNumbering) throws IOException { | |
| System.out.println("+ performing '" + code + "' test..."); | |
| String sourceString = unescapeHtmlSpecialChars(new String(readResource(packagePath + "source/" + code + ".txt"), UTF_8)); | |
| List<Object> decorations = null; | |
| // parse/decorate the sourceString | |
| Job prettifyJob = new Job(0, sourceString); | |
| prettify.langHandlerForExtension(extension, sourceString).decorate(prettifyJob); | |
| decorations = removeJSLineNumbering ? removeNewLines(prettifyJob.getDecorations(), sourceString) : prettifyJob.getDecorations(); | |
| String plainResult = null; | |
| String resultString = null; | |
| // read expected result | |
| final StringBuilder plainResultSb = new StringBuilder(); | |
| resultString = new String(readResource(packagePath + "result/" + code + ".txt"), UTF_8); | |
| List<Object> compare = readResult(unescapeHtmlSpecialChars(resultString), removeJSLineNumbering, plainResultSb); | |
| plainResult = plainResultSb.toString(); | |
| boolean testPassed = true; | |
| if (!removeJSLineNumbering && !sourceString.equals(plainResult)) { | |
| // check to ensure source and result, without decorations, are the same | |
| testPassed = false; | |
| } | |
| if (!Arrays.equals(compare.toArray(), decorations.toArray())) { | |
| testPassed = false; | |
| } | |
| if (!testPassed) { | |
| String parsedSourceString = prettify(sourceString, compare); | |
| String parsedResultString = prettify(sourceString, decorations); | |
| final StringBuilder errorMessage = new StringBuilder(); | |
| errorMessage.append("++++++++++++++++++++++++++++++ Expected Result ++++++++++++++++++++++++++++++"); | |
| errorMessage.append("\n"); | |
| errorMessage.append(parsedSourceString); | |
| errorMessage.append("\n\n"); | |
| errorMessage.append("++++++++++++++++++++++++++++++ Actual Result ++++++++++++++++++++++++++++++"); | |
| errorMessage.append("\n"); | |
| errorMessage.append(parsedResultString); | |
| fail(errorMessage.toString()); | |
| } | |
| } | |
| @Test | |
| public void testBatch() throws IOException { | |
| System.out.println("+++++ testBatch +++++"); | |
| test(null, "bash", false); | |
| test("sh", "bash_lang", true); | |
| test(null, "java", false); | |
| test("java", "java_lang", true); | |
| test(null, "C", false); | |
| test("c", "C_lang", false); | |
| test(null, "Cpp", false); | |
| test("cpp", "Cpp_lang", false); | |
| test(null, "dart", false); | |
| test("dart", "dart_lang", false); | |
| test(null, "javascript", false); | |
| test(null, "perl", false); | |
| test(null, "python", false); | |
| test("py", "python_lang", false); | |
| test(null, "PHP", false); | |
| test("coffee", "coffee", false); | |
| test("css", "css", false); | |
| test("go", "go", false); | |
| test(null, "html", false); | |
| test(null, "htmlXmp", false); | |
| test("html", "html_lang", false); | |
| test(null, "issue12", false); | |
| test("js", "issue12_lang", false); | |
| test(null, "issue14a", false); | |
| test(null, "issue14b", false); | |
| test("sh", "issue165", false); | |
| test(null, "issue20", false); | |
| test("c", "issue201", false); | |
| test(null, "issue21", false); | |
| test("lua", "issue24", false); | |
| test("vb", "issue27", false); | |
| test("hs", "issue30", false); | |
| test("ml", "issue33", false); | |
| test(null, "issue4", false); | |
| test("el", "issue42", false); | |
| test(null, "issue45", false); | |
| test(null, "issue79", false); | |
| test(null, "issue8", false); | |
| test("java", "issue84", false); | |
| test(null, "issue92", false); | |
| test("cs", "issue93", false); | |
| test(null, "misc1", false); | |
| test("proto", "proto", false); | |
| test("scala", "scala", false); | |
| test("vhdl", "vhdl", false); | |
| test("wiki", "wiki", false); | |
| test(null, "xhtml", false); | |
| test(null, "xml", false); | |
| test(null, "xsl", false); | |
| test("yaml", "yaml1", false); | |
| test("yaml", "yaml2", false); | |
| test("tcl", "tcl_lang", false); | |
| test("r", "r_lang", false); | |
| test("erlang", "erlang", false); | |
| test("mumps", "mumps", false); | |
| test("llvm", "llvm", false); | |
| test("pascal", "pascal_lang", false); | |
| test("basic", "basic_lang", false); | |
| test("js", "issue217", false); | |
| test("matlab", "matlab", false); | |
| } | |
| @Test | |
| public void getFileExtensionsFromClassInvokesStaticNoArgMethod() throws Exception { | |
| assertEquals(Arrays.asList("sql"), | |
| new TestPrettify().getFileExtensionsFrom(LangSql.class)); | |
| } | |
| private static class TestPrettify extends Prettify { | |
| List<String> getFileExtensionsFrom(Class<? extends Lang> clazz) throws Exception { | |
| return getFileExtensionsFromClass(clazz); | |
| } | |
| } | |
| /** | |
| * Read a test resource from the classpath and return its content as a byte | |
| * array. Test data is packaged as Java resources (see the java_test | |
| * "resources" attribute), so it is looked up on the classpath rather than | |
| * through the working directory. | |
| * @param resourcePath the classpath-relative resource path | |
| * @return the content of the resource in byte array | |
| * @throws IOException if the resource is missing or cannot be read | |
| */ | |
| public static byte[] readResource(String resourcePath) throws IOException { | |
| if (resourcePath == null) { | |
| throw new NullPointerException("argument 'resourcePath' cannot be null"); | |
| } | |
| try (InputStream in = | |
| PrettifyTest.class.getClassLoader().getResourceAsStream(resourcePath)) { | |
| if (in == null) { | |
| throw new IOException("Test resource not found on classpath: " + resourcePath); | |
| } | |
| return in.readAllBytes(); | |
| } | |
| } | |
| /** | |
| * Determine the positions of new line characters in {@code source} and | |
| * adjust the position value in {@code decorations}. Finally, it will | |
| * combine adjacent identical tags. | |
| * | |
| * @param decorations see {@link prettify.Job#decorations} | |
| * @param source the source code in string | |
| * | |
| * @return the adjusted list of {@code decorations} | |
| */ | |
| public static List<Object> removeNewLines(List<Object> decorations, String source) { | |
| if (decorations == null) { | |
| throw new NullPointerException("argument 'decorations' cannot be null"); | |
| } | |
| if (source == null) { | |
| throw new NullPointerException("argument 'source' cannot be null"); | |
| } | |
| List<Object> _decorations = new ArrayList<Object>(decorations); | |
| StringBuffer sb = new StringBuffer(); | |
| Pattern pattern = Pattern.compile("[\r\n]"); | |
| Matcher matcher = pattern.matcher(source); | |
| while (matcher.find()) { | |
| matcher.appendReplacement(sb, ""); | |
| int posStart = sb.length(); | |
| ListIterator<Object> iterator = _decorations.listIterator(); | |
| while (iterator.hasNext()) { | |
| Integer pos = (Integer) iterator.next(); | |
| if (pos > posStart) { | |
| iterator.set(pos - 1); | |
| } | |
| iterator.next(); | |
| } | |
| } | |
| // matcher.appendTail(sb); | |
| return Util.removeDuplicates(_decorations, source); | |
| } | |
| /** | |
| * Parse the {@code resultText}. The Java Prettify line numbering is | |
| * different from JavaScript Prettify. If the result is adapted from | |
| * JavaScript Prettify tests and that {@code resultText} have line | |
| * numbering, set {@code removeNewLines} to true. Also, the | |
| * {@code resultText} should not escape html special characters | |
| * | |
| * @param resultText the result in string | |
| * @param removeJSLineNumbering true to remove new line and combine identical | |
| * adjacent tag | |
| * | |
| * @return the parsed result with format similar to | |
| * {@link prettify.Job#decorations} | |
| */ | |
| private static List<Object> readResult(String resultText, boolean removeJSLineNumbering, StringBuilder plainResult) { | |
| if (resultText == null) { | |
| throw new NullPointerException("argument 'resultText' cannot be null"); | |
| } | |
| String _resultText = resultText; | |
| List<Object> returnList = new ArrayList<Object>(); | |
| if (removeJSLineNumbering) { | |
| _resultText = _resultText.replaceAll("[\r\n]", ""); | |
| // combine 'adjacent with identical decoration keyword' segments | |
| StringBuffer sb = new StringBuffer(); | |
| Pattern pattern = Pattern.compile("(`[A-Z]{3})([^`]+?)`END\\1([^`]+?)`END", Pattern.MULTILINE | Pattern.DOTALL); | |
| Matcher matcher = pattern.matcher(_resultText); | |
| while (matcher.find()) { | |
| matcher.appendReplacement(sb, ""); | |
| sb.append(matcher.group(1)).append(matcher.group(2)).append(matcher.group(3)).append("`END"); | |
| } | |
| matcher.appendTail(sb); | |
| _resultText = sb.toString(); | |
| } | |
| // digest result(string) into segments by identifing standard 'decoration keyword' start & end | |
| int count = 0; | |
| Pattern decorationPattern = Pattern.compile("`([A-Z]{3})(.+?)`END|<span class=\"([a-z]+)\">(.+?)`END", Pattern.MULTILINE | Pattern.DOTALL); | |
| Matcher matcher = decorationPattern.matcher(_resultText); | |
| while (matcher.find()) { | |
| returnList.add(count); | |
| String decorationKeyword = null; | |
| String decoratedContent = null; | |
| if (matcher.group(1) != null) { | |
| decorationKeyword = matcher.group(1); | |
| decoratedContent = matcher.group(2); | |
| } else { | |
| decorationKeyword = matcher.group(3); | |
| decoratedContent = matcher.group(4); | |
| } | |
| returnList.add(decorationKeyword.toLowerCase()); | |
| count += decoratedContent.length(); | |
| plainResult.append(decoratedContent); | |
| } | |
| returnList = Util.removeDuplicates(returnList, _resultText); | |
| // for debug | |
| // StringBuffer sb = new StringBuffer(); | |
| // matcher = decorationPattern.matcher(resultText); | |
| // while (matcher.find()) { | |
| // if (matcher.group(1) != null) { | |
| // matcher.appendReplacement(sb, ""); | |
| // sb.append(matcher.group(2)); | |
| // } else { | |
| // matcher.appendReplacement(sb, ""); | |
| // sb.append(matcher.group(4)); | |
| // } | |
| // } | |
| // matcher.appendTail(sb); | |
| // String plainCode = sb.toString(); | |
| // | |
| // System.out.println(plainCode); | |
| // | |
| // for (int i = 0, iEnd = returnList.size(); i < iEnd; i += 2) { | |
| // int end = i + 2 < iEnd ? (Integer) returnList.get(i + 2) : plainCode.length(); | |
| // System.out.println(returnList.get(i) + ": " + returnList.get(i + 1) + ": " + plainCode.substring((Integer) returnList.get(i), end)); | |
| // } | |
| return returnList; | |
| } | |
| protected static String unescapeHtmlSpecialChars(String inputString) { | |
| return inputString.replace("<", "<").replace(">", ">").replace(" ", " ").replace("&", "&"); | |
| } | |
| private static String prettify(String source, List<Object> decorations) { | |
| if (decorations.size() % 2 != 0) { | |
| throw new IllegalArgumentException(); | |
| } | |
| final StringBuilder result = new StringBuilder(); | |
| for (int index = 0; index < decorations.size(); index += 2) { | |
| final int start = ((Integer) decorations.get(index)).intValue(); | |
| final int end = index < decorations.size() - 2 ? ((Integer) decorations.get(index + 2)).intValue() : source.length(); | |
| final String type = (String) decorations.get(index + 1); | |
| result.append("`"); | |
| result.append(type.toUpperCase()); | |
| if (end > source.length()) { | |
| result.append("..."); | |
| break; | |
| } | |
| result.append(source.substring(start, end)); | |
| result.append("`"); | |
| result.append("END"); | |
| } | |
| return result.toString(); | |
| } | |
| } |