1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2017, 2019, Chris Fraire <cfraire@me.com>. 22 */ 23 package org.opengrok.indexer.util; 24 25 import java.io.ByteArrayInputStream; 26 import java.io.InputStream; 27 import java.io.InputStreamReader; 28 import java.io.Reader; 29 import java.nio.charset.StandardCharsets; 30 import java.util.ArrayList; 31 import java.util.List; 32 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 33 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute; 34 import org.opengrok.indexer.analysis.JFlexSymbolMatcher; 35 import org.opengrok.indexer.analysis.JFlexTokenizer; 36 37 import static org.junit.jupiter.api.Assertions.assertEquals; 38 import static org.junit.jupiter.api.Assertions.assertTrue; 39 import static org.opengrok.indexer.util.StreamUtils.copyStream; 40 41 /** 42 * Represents a container for custom test assertion methods. 43 */ 44 public class CustomAssertions { 45 /** 46 * non-public so as to be just a static container class. 47 */ CustomAssertions()48 protected CustomAssertions() { 49 } 50 51 /** 52 * Asserts the specified strings have equal contents, comparing line-wise 53 * after splitting on LFs. 54 * @param messagePrefix a message prefixed to line-specific or length- 55 * specific errors 56 * @param expected the expected content 57 * @param actual the actual content 58 */ assertLinesEqual(String messagePrefix, String expected, String actual)59 public static void assertLinesEqual(String messagePrefix, String expected, String actual) { 60 String[] expecteds = expected.split("\n"); 61 String[] gotten = actual.split("\n"); 62 assertLinesEqual(messagePrefix, expecteds, gotten); 63 } 64 65 /** 66 * Asserts the specified lines arrays have equal contents. 67 * @param messagePrefix a message prefixed to line-specific or length- 68 * specific errors 69 * @param expecteds the expected content of lines 70 * @param actuals the actual content of lines 71 */ assertLinesEqual(String messagePrefix, String[] expecteds, String[] actuals)72 public static void assertLinesEqual(String messagePrefix, String[] expecteds, String[] actuals) { 73 74 List<Integer> diffLines = new ArrayList<>(); 75 76 final int SHOW_N_DIFFS = 50; 77 int ndiffs = 0; 78 int lastDiff = -2; 79 for (int i = 0; i < expecteds.length || i < actuals.length; i++) { 80 if (i >= expecteds.length || i >= actuals.length || 81 !expecteds[i].equals(actuals[i])) { 82 83 if (lastDiff + 1 != i && !diffLines.isEmpty()) { 84 printDiffs(expecteds, actuals, diffLines); 85 diffLines.clear(); 86 } 87 ++ndiffs; 88 lastDiff = i; 89 diffLines.add(i); 90 if (ndiffs >= SHOW_N_DIFFS) { 91 break; 92 } 93 } 94 } 95 if (!diffLines.isEmpty()) { 96 printDiffs(expecteds, actuals, diffLines); 97 diffLines.clear(); 98 } 99 100 assertEquals(0, ndiffs, messagePrefix + "--should have no diffs"); 101 assertEquals(expecteds.length, actuals.length, messagePrefix + "--number of lines"); 102 } 103 104 /** 105 * Asserts the specified tokenizer class produces an expected stream of 106 * symbols from the specified input. 107 * @param klass the test class 108 * @param iss the input stream 109 * @param expectedTokens the expected, ordered token list 110 * @throws java.lang.Exception if an error occurs constructing a 111 * {@code klass} instance or testing the stream 112 */ assertSymbolStream(Class<? extends JFlexSymbolMatcher> klass, InputStream iss, List<String> expectedTokens)113 public static void assertSymbolStream(Class<? extends JFlexSymbolMatcher> klass, InputStream iss, 114 List<String> expectedTokens) throws Exception { 115 116 byte[] inputCopy = copyStream(iss); 117 String input = new String(inputCopy, StandardCharsets.UTF_8); 118 JFlexTokenizer tokenizer = new JFlexTokenizer( 119 klass.getConstructor(Reader.class).newInstance( 120 new InputStreamReader(new ByteArrayInputStream(inputCopy), StandardCharsets.UTF_8))); 121 122 CharTermAttribute term = tokenizer.addAttribute( 123 CharTermAttribute.class); 124 OffsetAttribute offs = tokenizer.addAttribute(OffsetAttribute.class); 125 126 int count = 0; 127 List<String> tokens = new ArrayList<>(); 128 while (tokenizer.incrementToken()) { 129 String termValue = term.toString(); 130 tokens.add(termValue); 131 132 String cutValue = input.substring(offs.startOffset(), 133 offs.endOffset()); 134 assertEquals(cutValue, termValue, "cut term" + (1 + count)); 135 ++count; 136 } 137 138 count = 0; 139 for (String token : tokens) { 140 // 1-based offset to accord with line # 141 if (count >= expectedTokens.size()) { 142 printTokens(tokens); 143 assertTrue(count < expectedTokens.size(), "too many tokens at term" + (1 + count) + ": " + token); 144 } 145 String expected = expectedTokens.get(count); 146 if (!token.equals(expected)) { 147 printTokens(tokens); 148 assertEquals(expected, token, "term" + (1 + count)); 149 } 150 count++; 151 } 152 153 if (expectedTokens.size() != count) { 154 printTokens(tokens); 155 assertEquals(expectedTokens.size(), count, "wrong number of tokens"); 156 } 157 } 158 159 private static void printDiffs(String[] expecteds, String[] actuals, List<Integer> diffLines) { 160 if (diffLines.size() < 1) { 161 return; 162 } 163 164 int ln0 = diffLines.get(0); 165 int numln = diffLines.size(); 166 int loff = (Math.min(ln0, expecteds.length)) + 1; 167 int lnum = countWithin(expecteds.length, ln0, numln); 168 int roff = (Math.min(ln0, actuals.length)) + 1; 169 int rnum = countWithin(actuals.length, ln0, numln); 170 171 System.out.format("@@ -%d,%d +%d,%d @@", loff, lnum, roff, rnum); 172 System.out.println(); 173 174 for (int i : diffLines) { 175 if (i >= expecteds.length) { 176 break; 177 } else { 178 System.out.print("- "); 179 System.out.println(expecteds[i]); 180 } 181 } 182 for (int i : diffLines) { 183 if (i >= actuals.length) { 184 break; 185 } else { 186 System.out.print("+ "); 187 System.out.println(actuals[i]); 188 } 189 } 190 } 191 countWithin(int maxoffset, int ln0, int numln)192 private static int countWithin(int maxoffset, int ln0, int numln) { 193 while (numln > 0) { 194 if (ln0 + numln <= maxoffset) { 195 return numln; 196 } 197 --numln; 198 } 199 return 0; 200 } 201 202 /** 203 * Outputs a token list to stdout for use in a competent diffing tool 204 * to compare to e.g. samplesymbols.txt. 205 */ printTokens(List<String> tokens)206 private static void printTokens(List<String> tokens) { 207 System.out.println("BEGIN TOKENS\n====="); 208 for (String token : tokens) { 209 System.out.println(token); 210 } 211 System.out.println("=====\nEND TOKENS"); 212 } 213 } 214