package com.sleekbyte.tailor.functional; import static org.junit.Assert.assertArrayEquals; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.sleekbyte.tailor.Tailor; import com.sleekbyte.tailor.common.ConfigProperties; import com.sleekbyte.tailor.common.Messages; import com.sleekbyte.tailor.common.Rules; import com.sleekbyte.tailor.common.Severity; import com.sleekbyte.tailor.format.CCFormatter; import com.sleekbyte.tailor.format.Format; import com.sleekbyte.tailor.format.Formatter; import com.sleekbyte.tailor.format.HTMLFormatter; import com.sleekbyte.tailor.output.Printer; import com.sleekbyte.tailor.output.ViolationMessage; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * Tests for {@link Tailor} output formats. */ @RunWith(MockitoJUnitRunner.class) public final class FormatTest { private static final String TEST_INPUT_DIR = "src/test/swift/com/sleekbyte/tailor/functional"; private static final String NEWLINE_REGEX = "\\r?\\n"; private static final String NEWLINE_PATTERN = "\n"; private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); protected ByteArrayOutputStream outContent; protected File inputFile; protected List<String> expectedMessages; @Rule public TemporaryFolder folder = new TemporaryFolder(); @Before public void setUp() throws IOException { inputFile = new File(TEST_INPUT_DIR + "/UpperCamelCaseTest.swift"); expectedMessages = new ArrayList<>(); outContent = new ByteArrayOutputStream(); System.setOut(new PrintStream(outContent, false, Charset.defaultCharset().name())); } @After public void tearDown() { System.setOut(null); } @Test public void testXcodeFormat() throws IOException { Format format = Format.XCODE; final String[] command = new String[] { "--format", format.getName(), "--no-color", "--only=upper-camel-case", inputFile.getPath() }; expectedMessages.addAll(getExpectedMsgs().stream().map(msg -> Printer.genOutputStringForTest( msg.getRule(), inputFile.getName(), msg.getLineNumber(), msg.getColumnNumber(), msg.getSeverity(), msg.getMessage())).collect(Collectors.toList())); Tailor.main(command); List<String> actualOutput = new ArrayList<>(); String[] msgs = outContent.toString(Charset.defaultCharset().name()).split(NEWLINE_REGEX); // Skip first four lines for file header, last two lines for summary msgs = Arrays.copyOfRange(msgs, 4, msgs.length - 2); for (String msg : msgs) { String truncatedMsg = msg.substring(msg.indexOf(inputFile.getName())); actualOutput.add(truncatedMsg); } assertArrayEquals(outContent.toString(Charset.defaultCharset().name()), this.expectedMessages.toArray(), actualOutput.toArray()); } @Test public void testJSONFormat() throws IOException { Format format = Format.JSON; final String[] command = new String[] { "--format", format.getName(), "--no-color", "--only=upper-camel-case", inputFile.getPath() }; Map<String, Object> expectedOutput = getJSONMessages(); Tailor.main(command); List<String> expected = new ArrayList<>(); List<String> actual = new ArrayList<>(); expectedMessages.addAll( Arrays.asList((GSON.toJson(expectedOutput) + System.lineSeparator()).split(NEWLINE_REGEX))); for (String msg : expectedMessages) { String strippedMsg = msg.replaceAll(inputFile.getCanonicalPath(), ""); expected.add(strippedMsg); } String[] msgs = outContent.toString(Charset.defaultCharset().name()).split(NEWLINE_REGEX); for (String msg : msgs) { String strippedMsg = msg.replaceAll(inputFile.getCanonicalPath(), ""); actual.add(strippedMsg); } assertArrayEquals(outContent.toString(Charset.defaultCharset().name()), expected.toArray(), actual.toArray()); } @Test public void testXcodeConfigOption() throws IOException { File configurationFile = xcodeFormatConfigFile(".tailor.yml"); final String[] command = new String[] { "--config", configurationFile.getAbsolutePath(), "--no-color", "--only=upper-camel-case", inputFile.getPath() }; expectedMessages.addAll(getExpectedMsgs().stream().map(msg -> Printer.genOutputStringForTest( msg.getRule(), inputFile.getName(), msg.getLineNumber(), msg.getColumnNumber(), msg.getSeverity(), msg.getMessage())).collect(Collectors.toList())); Tailor.main(command); List<String> actualOutput = new ArrayList<>(); String[] msgs = outContent.toString(Charset.defaultCharset().name()).split(NEWLINE_REGEX); // Skip first four lines for file header, last two lines for summary msgs = Arrays.copyOfRange(msgs, 4, msgs.length - 2); for (String msg : msgs) { String truncatedMsg = msg.substring(msg.indexOf(inputFile.getName())); actualOutput.add(truncatedMsg); } assertArrayEquals(outContent.toString(Charset.defaultCharset().name()), this.expectedMessages.toArray(), actualOutput.toArray()); } public void testCCFormat() throws IOException { Format format = Format.CC; final String[] command = new String[] { "--format", format.getName(), "--no-color", "--only=upper-camel-case", inputFile.getPath() }; Tailor.main(command); List<String> expected = new ArrayList<>(); List<String> actual = new ArrayList<>(); StringBuilder expectedOutput = new StringBuilder(); for (Map<String, Object> msg : getCCMessages()) { expectedOutput.append(GSON.toJson(msg)).append(CCFormatter.NULL_CHAR).append(System.lineSeparator()); } expectedMessages.addAll(Arrays.asList(expectedOutput.toString().split(NEWLINE_REGEX))); for (String msg : expectedMessages) { String strippedMsg = msg.replaceAll(inputFile.getCanonicalPath(), ""); expected.add(strippedMsg); } String[] msgs = outContent.toString(Charset.defaultCharset().name()).split(NEWLINE_REGEX); for (String msg : msgs) { String strippedMsg = msg.replaceAll(inputFile.getCanonicalPath(), ""); actual.add(strippedMsg); } assertArrayEquals(outContent.toString(Charset.defaultCharset().name()), expected.toArray(), actual.toArray()); } @Test public void testHTMLFormat() throws IOException { Format format = Format.HTML; final String[] command = new String[] { "--format", format.getName(), "--no-color", "--only=upper-camel-case", inputFile.getPath() }; Map<String, Object> expectedOutput = getHTMLMessages(); Tailor.main(command); List<String> expected = new ArrayList<>(); List<String> actual = new ArrayList<>(); Mustache mustache = new DefaultMustacheFactory().compile( new InputStreamReader(HTMLFormatter.class.getResourceAsStream("index.html"), Charset.defaultCharset()), "index.html" ); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mustache.execute(new OutputStreamWriter(baos, Charset.defaultCharset()), expectedOutput).flush(); expectedMessages.addAll(Arrays.asList(baos.toString(Charset.defaultCharset().name()).split(NEWLINE_REGEX))); for (String msg : expectedMessages) { String strippedMsg = msg.replaceAll(inputFile.getCanonicalPath(), ""); expected.add(strippedMsg); } String[] msgs = outContent.toString(Charset.defaultCharset().name()).split(NEWLINE_REGEX); for (String msg : msgs) { String strippedMsg = msg.replaceAll(inputFile.getCanonicalPath(), ""); actual.add(strippedMsg); } assertArrayEquals(outContent.toString(Charset.defaultCharset().name()), expected.toArray(), actual.toArray()); } protected List<ViolationMessage> getExpectedMsgs() { List<ViolationMessage> messages = new ArrayList<>(); messages.add(createViolationMessage(3, 7, Severity.WARNING, Messages.CLASS + Messages.NAMES)); messages.add(createViolationMessage(7, 7, Severity.WARNING, Messages.CLASS + Messages.NAMES)); messages.add(createViolationMessage(42, 6, Severity.WARNING, Messages.ENUM + Messages.NAMES)); messages.add(createViolationMessage(46, 6, Severity.WARNING, Messages.ENUM + Messages.NAMES)); messages.add(createViolationMessage(50, 6, Severity.WARNING, Messages.ENUM + Messages.NAMES)); messages.add(createViolationMessage(72, 8, Severity.WARNING, Messages.STRUCT + Messages.NAMES)); messages.add(createViolationMessage(76, 8, Severity.WARNING, Messages.STRUCT + Messages.NAMES)); messages.add(createViolationMessage(90, 10, Severity.WARNING, Messages.PROTOCOL + Messages.NAMES)); messages.add(createViolationMessage(94, 10, Severity.WARNING, Messages.PROTOCOL + Messages.NAMES)); messages.add(createViolationMessage(98, 10, Severity.WARNING, Messages.PROTOCOL + Messages.NAMES)); messages.add(createViolationMessage(119, 18, Severity.WARNING, Messages.GENERIC_PARAMETERS + Messages.NAMES)); messages.add(createViolationMessage(119, 23, Severity.WARNING, Messages.GENERIC_PARAMETERS + Messages.NAMES)); messages.add(createViolationMessage(128, 20, Severity.WARNING, Messages.GENERIC_PARAMETERS + Messages.NAMES)); messages.add(createViolationMessage(137, 14, Severity.WARNING, Messages.GENERIC_PARAMETERS + Messages.NAMES)); return messages; } private ViolationMessage createViolationMessage(int line, int column, Severity severity, String msg) { return new ViolationMessage(Rules.UPPER_CAMEL_CASE, line, column, severity, msg + Messages.UPPER_CAMEL_CASE); } private Map<String, Object> getJSONSummary(long analyzed, long skipped, long errors, long warnings) { Map<String, Object> summary = new HashMap<>(); summary.put(Messages.ANALYZED_KEY, analyzed); summary.put(Messages.SKIPPED_KEY, skipped); summary.put(Messages.VIOLATIONS_KEY, errors + warnings); summary.put(Messages.ERRORS_KEY, errors); summary.put(Messages.WARNINGS_KEY, warnings); return summary; } private Map<String, Object> getJSONMessages() { List<Map<String, Object>> violations = new ArrayList<>(); for (ViolationMessage msg : getExpectedMsgs()) { Map<String, Object> violation = new HashMap<>(); Map<String, Object> location = new HashMap<>(); location.put(Messages.LINE_KEY, msg.getLineNumber()); location.put(Messages.COLUMN_KEY, msg.getColumnNumber()); violation.put(Messages.LOCATION_KEY, location); violation.put(Messages.SEVERITY_KEY, msg.getSeverity().toString()); violation.put(Messages.RULE_KEY, Rules.UPPER_CAMEL_CASE.getName()); violation.put(Messages.MESSAGE_KEY, msg.getMessage()); violations.add(violation); } Map<String, Object> file = new HashMap<>(); file.put(Messages.PATH_KEY, ""); file.put(Messages.PARSED_KEY, true); file.put(Messages.VIOLATIONS_KEY, violations); List<Object> files = new ArrayList<>(); files.add(file); Map<String, Object> expectedOutput = new LinkedHashMap<>(); expectedOutput.put(Messages.FILES_KEY, files); expectedOutput.put(Messages.SUMMARY_KEY, getJSONSummary(1, 0, 0, violations.size())); return expectedOutput; } private File xcodeFormatConfigFile(String fileName) throws IOException { File configFile = folder.newFile(fileName); Writer streamWriter = new OutputStreamWriter(new FileOutputStream(configFile), Charset.forName("UTF-8")); PrintWriter printWriter = new PrintWriter(streamWriter); printWriter.println("format: xcode"); streamWriter.close(); printWriter.close(); return configFile; } private List<Map<String, Object>> getCCMessages() { List<Map<String, Object>> violations = new ArrayList<>(); for (ViolationMessage msg : getExpectedMsgs()) { Map<String, Object> violation = new HashMap<>(); Map<String, Object> location = new HashMap<>(); Map<String, Object> positions = new HashMap<>(); Map<String, Object> lines = new HashMap<>(); Map<String, Object> begin = new HashMap<>(); Map<String, Object> end = new HashMap<>(); if (msg.getColumnNumber() != 0) { begin.put(Messages.LINE_KEY, msg.getLineNumber()); begin.put(Messages.COLUMN_KEY, msg.getColumnNumber()); end.put(Messages.LINE_KEY, msg.getLineNumber()); end.put(Messages.COLUMN_KEY, msg.getColumnNumber()); positions.put(Messages.BEGIN_KEY, begin); positions.put(Messages.END_KEY, end); location.put(Messages.POSITIONS_KEY, positions); } else { lines.put(Messages.BEGIN_KEY, msg.getLineNumber()); lines.put(Messages.END_KEY, msg.getLineNumber()); location.put(Messages.LINES_KEY, lines); } violation.put(Messages.TYPE_KEY, Messages.ISSUE_VALUE); violation.put(Messages.CHECK_NAME_KEY, msg.getRule().getName()); violation.put(Messages.DESCRIPTION_KEY, msg.getMessage()); Map<String, Object> content = new HashMap<>(); content.put(Messages.BODY_KEY, msg.getRule().getExamples()); violation.put(Messages.CONTENT_KEY, content); List<String> categories = new ArrayList<>(); categories.add(msg.getRule().getCategory()); violation.put(Messages.CATEGORIES_KEY, categories); location.put(Messages.PATH_KEY, inputFile.getPath()); violation.put(Messages.LOCATION_KEY, location); violation.put(Messages.REMEDIATION_POINTS_KEY, msg.getRule().getRemediationPoints()); violations.add(violation); } return violations; } private Map<String, Object> getHTMLMessages() throws IOException { List<Map<String, Object>> violations = new ArrayList<>(); for (ViolationMessage msg : getExpectedMsgs()) { Map<String, Object> violation = new HashMap<>(); Map<String, Object> location = new HashMap<>(); location.put(Messages.LINE_KEY, msg.getLineNumber()); location.put(Messages.COLUMN_KEY, msg.getColumnNumber()); violation.put(Messages.LOCATION_KEY, location); switch (msg.getSeverity()) { case ERROR: violation.put(Messages.ERROR, true); break; case WARNING: violation.put(Messages.WARNING, true); break; default: break; } violation.put(Messages.RULE_KEY, Rules.UPPER_CAMEL_CASE.getName()); violation.put(Messages.MESSAGE_KEY, msg.getMessage()); violations.add(violation); } Map<String, Object> file = new HashMap<>(); file.put(Messages.PATH_KEY, ""); file.put(Messages.PARSED_KEY, true); file.put(Messages.VIOLATIONS_KEY, violations); file.put(Messages.NUM_VIOLATIONS_KEY, Formatter.pluralize(violations.size(), Messages.SINGLE_VIOLATION_KEY, Messages.MULTI_VIOLATIONS_KEY)); List<Object> files = new ArrayList<>(); files.add(file); Map<String, Object> expectedOutput = new LinkedHashMap<>(); expectedOutput.put(Messages.FILES_KEY, files); expectedOutput.put(Messages.SUMMARY_KEY, Formatter.formatSummary(1, 0, 0, violations.size()).replace(NEWLINE_PATTERN, "")); expectedOutput.put(Messages.VERSION_LONG_OPT, new ConfigProperties().getVersion()); return expectedOutput; } }