/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.scanner.mediumtest.log; import com.google.common.collect.ImmutableMap; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.sonar.batch.bootstrapper.LogOutput; import org.sonar.batch.bootstrapper.LogOutput.Level; import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.xoo.XooPlugin; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; public class LogListenerTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @Rule public ExpectedException thrown = ExpectedException.none(); private Pattern simpleTimePattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); private List<LogEvent> logOutput; private StringBuilder logOutputStr; private ByteArrayOutputStream stdOutTarget; private ByteArrayOutputStream stdErrTarget; private static PrintStream savedStdOut; private static PrintStream savedStdErr; public ScannerMediumTester tester = ScannerMediumTester.builder() .registerPlugin("xoo", new XooPlugin()) .addDefaultQProfile("xoo", "Sonar Way") .setLogOutput(new SimpleLogListener()) .build(); private File baseDir; private ImmutableMap.Builder<String, String> builder; @BeforeClass public static void backupStdStreams() { savedStdOut = System.out; savedStdErr = System.err; } @AfterClass public static void resumeStdStreams() { if (savedStdOut != null) { System.setOut(savedStdOut); } if (savedStdErr != null) { System.setErr(savedStdErr); } } @Before public void prepare() throws IOException { stdOutTarget = new ByteArrayOutputStream(); stdErrTarget = new ByteArrayOutputStream(); System.setOut(new PrintStream(stdOutTarget)); System.setErr(new PrintStream(stdErrTarget)); // logger from the batch might write to it asynchronously logOutput = Collections.synchronizedList(new LinkedList<LogEvent>()); logOutputStr = new StringBuilder(); tester.start(); baseDir = temp.getRoot(); builder = ImmutableMap.<String, String>builder() .put("sonar.task", "scan") .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.projectName", "Foo Project") .put("sonar.projectVersion", "1.0-SNAPSHOT") .put("sonar.projectDescription", "Description of Foo Project"); } private void assertNoStdOutput() { assertThat(new String(stdOutTarget.toByteArray())).isEmpty(); assertThat(new String(stdErrTarget.toByteArray())).isEmpty(); } /** * Check that log message is not formatted, i.e. has no log level and timestamp. */ private void assertMsgClean(String msg) { // FP: [JOURNAL_FLUSHER] WARNING Journal flush operation took 2,093ms last 8 cycles average is 262ms if (msg.contains("[JOURNAL_FLUSHER]")) { return; } for (LogOutput.Level l : LogOutput.Level.values()) { assertThat(msg).doesNotContain(l.toString()); } Matcher matcher = simpleTimePattern.matcher(msg); assertThat(matcher.find()).isFalse(); } @Test public void testChangeLogForAnalysis() throws IOException, InterruptedException { File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); FileUtils.write(xooFile, "Sample xoo\ncontent"); tester.newTask() .properties(builder .put("sonar.sources", "src") .put("sonar.verbose", "true") .build()) .start(); tester.stop(); for (LogEvent e : logOutput) { savedStdOut.println("[captured]" + e.level + " " + e.msg); } // only done in DEBUG during analysis assertThat(logOutputStr.toString()).contains("Post-jobs : "); } @Test public void testNoStdLog() throws IOException { File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); FileUtils.write(xooFile, "Sample xoo\ncontent"); tester.newTask() .properties(builder .put("sonar.sources", "src") .build()) .start(); tester.stop(); assertNoStdOutput(); assertThat(logOutput).isNotEmpty(); synchronized (logOutput) { for (LogEvent e : logOutput) { savedStdOut.println("[captured]" + e.level + " " + e.msg); } } } @Test public void testNoFormattedMsgs() throws IOException { File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); FileUtils.write(xooFile, "Sample xoo\ncontent"); tester.newTask() .properties(builder .put("sonar.sources", "src") .build()) .start(); tester.stop(); assertNoStdOutput(); synchronized (logOutput) { for (LogEvent e : logOutput) { assertMsgClean(e.msg); savedStdOut.println("[captured]" + e.level + " " + e.msg); } } } // SONAR-7540 @Test public void testStackTrace() throws IOException { File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); FileUtils.write(xooFile, "Sample xoo\ncontent"); File xooFileMeasure = new File(srcDir, "sample.xoo.measures"); FileUtils.write(xooFileMeasure, "foo:bar"); try { tester.newTask() .properties(builder .put("sonar.sources", "src") .build()) .start(); fail("Expected exception"); } catch (Exception e) { assertThat(e.getMessage()).contains("Error processing line 1"); } tester.stop(); assertNoStdOutput(); synchronized (logOutput) { for (LogEvent e : logOutput) { if (e.level == Level.ERROR) { assertThat(e.msg).contains("Error processing line 1 of file", "src" + File.separator + "sample.xoo.measures", "java.lang.IllegalStateException: Unknow metric with key: foo", "at org.sonar.xoo.lang.MeasureSensor.saveMeasure"); } } } } private class SimpleLogListener implements LogOutput { @Override public void log(String msg, Level level) { logOutput.add(new LogEvent(msg, level)); logOutputStr.append(msg).append("\n"); } } private static class LogEvent { String msg; LogOutput.Level level; LogEvent(String msg, LogOutput.Level level) { this.msg = msg; this.level = level; } } }