From fd343c1ef7024bcf3ab214c71d3d95bf45d30619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Nowak?= Date: Fri, 15 Apr 2016 15:50:56 +0200 Subject: [PATCH 1/2] Pylint processor added. --- README.md | 1 + build.gradle | 1 + .../sputnik/configuration/GeneralOption.java | 3 + .../processor/pylint/PylintException.java | 11 + .../processor/pylint/PylintExecutor.java | 48 +++ .../processor/pylint/PylintProcessor.java | 45 +++ .../pylint/PylintProcessorFactory.java | 17 + .../processor/pylint/PylintResultParser.java | 60 +++ .../processor/pylint/json/PylintMessage.java | 17 + .../processor/pylint/json/PylintMessages.java | 6 + .../ExternalProcessResultParser.java | 9 + .../ProcessorRunningExternalProcess.java | 34 ++ .../processor/tslint/TSLintException.java | 11 + .../processor/tslint/TSLintProcessor.java | 34 +- .../processor/tslint/TSLintResultParser.java | 7 +- .../processor/tslint/TSLintScript.java | 2 +- .../sputnik/review/filter/PythonFilter.java | 10 + .../processor/pylint/PylintProcessorTest.java | 31 ++ .../pylint/PylintResultParserTest.java | 49 +++ .../processor/tslint/TSLintScriptTest.java | 3 +- src/test/resources/pylint/pylintrc | 378 ++++++++++++++++++ .../pylint/sample-output-no-header.txt | 82 ++++ src/test/resources/pylint/sample-output.txt | 83 ++++ .../sputnik/noConfigurationFile.properties | 1 + 24 files changed, 919 insertions(+), 24 deletions(-) create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/PylintException.java create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessor.java create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessorFactory.java create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessage.java create mode 100644 src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessages.java create mode 100644 src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ExternalProcessResultParser.java create mode 100644 src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ProcessorRunningExternalProcess.java create mode 100644 src/main/java/pl/touk/sputnik/processor/tslint/TSLintException.java create mode 100644 src/main/java/pl/touk/sputnik/review/filter/PythonFilter.java create mode 100644 src/test/java/pl/touk/sputnik/processor/pylint/PylintProcessorTest.java create mode 100644 src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java create mode 100644 src/test/resources/pylint/pylintrc create mode 100644 src/test/resources/pylint/sample-output-no-header.txt create mode 100644 src/test/resources/pylint/sample-output.txt create mode 100644 src/test/resources/pylint/sputnik/noConfigurationFile.properties diff --git a/README.md b/README.md index 0ff2fc96..cd074de3 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ gradle run -Dexec.args="--conf example.properties --changeId 1234 --revisionId 4 - Henning Hoefer - Dominik Przybysz - Damian Szczepanik +- RafaƂ Nowak ## License diff --git a/build.gradle b/build.gradle index 07fc98e4..12eed7a0 100644 --- a/build.gradle +++ b/build.gradle @@ -122,6 +122,7 @@ dependencies { // Test dependencies testCompile 'junit:junit:4.11' + testCompile 'pl.pragmatists:JUnitParams:1.0.5' testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'org.assertj:assertj-core:1.5.0' testCompile 'com.googlecode.catch-exception:catch-exception:1.2.0' diff --git a/src/main/java/pl/touk/sputnik/configuration/GeneralOption.java b/src/main/java/pl/touk/sputnik/configuration/GeneralOption.java index 3e7f0818..6c2fb0c0 100644 --- a/src/main/java/pl/touk/sputnik/configuration/GeneralOption.java +++ b/src/main/java/pl/touk/sputnik/configuration/GeneralOption.java @@ -63,6 +63,9 @@ public enum GeneralOption implements ConfigurationOption { TSLINT_CONFIGURATION_FILE("tslint.configurationFile", "TSLint configuration file", "tslint.json"), TSLINT_SCRIPT("tslint.script", "TSLint script for validating files", "/usr/bin/tslint"), + PYLINT_ENABLED("pylint.enabled", "Pylint enabled", "false"), + PYLINT_RCFILE("pylint.rcfile", "Pylint rcfile", null), + SONAR_ENABLED("sonar.enabled", "Sonar enabled", "false"), SONAR_PROPERTIES("sonar.configurationFiles", "Sonar base configuration", "sonar-project.properties"), SONAR_VERBOSE("sonar.verbose", "Run sonar in verbose mode", "false"), diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintException.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintException.java new file mode 100644 index 00000000..532e0d53 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintException.java @@ -0,0 +1,11 @@ +package pl.touk.sputnik.processor.pylint; + +public class PylintException extends RuntimeException { + public PylintException(String message) { + super(message); + } + + public PylintException(String message, Throwable t) { + super(message, t); + } +} diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java new file mode 100644 index 00000000..2a536a17 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java @@ -0,0 +1,48 @@ +package pl.touk.sputnik.processor.pylint; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import pl.touk.sputnik.exec.ExternalProcess; + +import javax.annotation.Nullable; +import java.util.List; + +@Slf4j +public class PylintExecutor { + private static final String pylintExecutable = "pylint"; + private static final String pylintOutputFormat = "--output-format=json"; + private static final String pylintRcfileName = "--rcfile="; + + private String rcfileName; + + public PylintExecutor(@Nullable String rcfileName) { + this.rcfileName = rcfileName; + } + + public String runOnFile(String filePath) { + log.info("Review on file: " + filePath); + return new ExternalProcess().executeCommand(buildParams(filePath)); + } + + @NotNull + private String[] buildParams(String filePath) { + List basicPylintArgs = ImmutableList.of( + pylintExecutable, + pylintOutputFormat); + List rcfileNameArg = getRcfileNameAsList(); + List filePathArg = ImmutableList.of(filePath); + List allArgs = Lists.newArrayList(Iterables.concat(basicPylintArgs, rcfileNameArg, filePathArg)); + return allArgs.toArray(new String[allArgs.size()]); + } + + private List getRcfileNameAsList() { + if (rcfileName != null) { + return ImmutableList.of(pylintRcfileName + rcfileName); + } else { + return ImmutableList.of(); + } + } +} diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessor.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessor.java new file mode 100644 index 00000000..4d9bbc6f --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessor.java @@ -0,0 +1,45 @@ +package pl.touk.sputnik.processor.pylint; + +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import pl.touk.sputnik.configuration.Configuration; +import pl.touk.sputnik.configuration.GeneralOption; +import pl.touk.sputnik.processor.tools.externalprocess.ExternalProcessResultParser; +import pl.touk.sputnik.processor.tools.externalprocess.ProcessorRunningExternalProcess; +import pl.touk.sputnik.review.filter.FileFilter; +import pl.touk.sputnik.review.filter.PythonFilter; + +import java.io.File; + +@Slf4j +public class PylintProcessor extends ProcessorRunningExternalProcess { + + private PylintExecutor pylintExecutor; + private PylintResultParser pylintResultParser; + + public PylintProcessor(Configuration configuration) { + pylintExecutor = new PylintExecutor(configuration.getProperty(GeneralOption.PYLINT_RCFILE)); + pylintResultParser = new PylintResultParser(); + } + + @NotNull + @Override + public String getName() { + return "Pylint"; + } + + @Override + public FileFilter getReviewFileFilter() { + return new PythonFilter(); + } + + @Override + public ExternalProcessResultParser getParser() { + return pylintResultParser; + } + + @Override + public String getOutputFromExternalProcess(File fileToReview) { + return pylintExecutor.runOnFile(fileToReview.getAbsolutePath()); + } +} diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessorFactory.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessorFactory.java new file mode 100644 index 00000000..31486331 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintProcessorFactory.java @@ -0,0 +1,17 @@ +package pl.touk.sputnik.processor.pylint; + +import pl.touk.sputnik.configuration.Configuration; +import pl.touk.sputnik.configuration.GeneralOption; +import pl.touk.sputnik.processor.ReviewProcessorFactory; + +public class PylintProcessorFactory implements ReviewProcessorFactory { + @Override + public boolean isEnabled(Configuration configuration) { + return Boolean.valueOf(configuration.getProperty(GeneralOption.PYLINT_ENABLED)); + } + + @Override + public PylintProcessor create(Configuration configuration) { + return new PylintProcessor(configuration); + } +} diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java new file mode 100644 index 00000000..4437f3a4 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java @@ -0,0 +1,60 @@ +package pl.touk.sputnik.processor.pylint; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import pl.touk.sputnik.processor.tools.externalprocess.ExternalProcessResultParser; +import pl.touk.sputnik.processor.pylint.json.PylintMessage; +import pl.touk.sputnik.processor.pylint.json.PylintMessages; +import pl.touk.sputnik.review.Severity; +import pl.touk.sputnik.review.Violation; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PylintResultParser implements ExternalProcessResultParser { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public List parse(String pylintOutput) { + if (StringUtils.isEmpty(pylintOutput)) { + return Collections.emptyList(); + } + try { + List violations = new ArrayList<>(); + PylintMessages messages = objectMapper.readValue(removeHeaderFromPylintOutput(pylintOutput), PylintMessages.class); + for (PylintMessage message : messages) { + Violation violation = new Violation(message.getPath(), + message.getLine(), + formatViolationMessageFromPylint(message), + pylintMessageTypeToSeverity(message.getType())); + violations.add(violation); + } + return violations; + } catch (IOException e) { + throw new PylintException("Error when converting from json format", e); + } + } + + private String removeHeaderFromPylintOutput(String pylintOutput) { + return pylintOutput.replace("No config file found, using default configuration", ""); + } + + private String formatViolationMessageFromPylint(PylintMessage message) { + return message.getMessage() + " [" + message.getSymbol() + "]"; + } + + private Severity pylintMessageTypeToSeverity(String messageType) { + if (messageType.equals("error") || messageType.equals("fatal")) { + return Severity.ERROR; + } else if (messageType.equals("warning")) { + return Severity.WARNING; + } else if (messageType.equals("convention") || messageType.equals("refactor")) { + return Severity.INFO; + } else { + throw new PylintException("Unknown message type returned by pylint in message (type = " + messageType + ")"); + } + } +} diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessage.java b/src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessage.java new file mode 100644 index 00000000..af1245a4 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessage.java @@ -0,0 +1,17 @@ +package pl.touk.sputnik.processor.pylint.json; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class PylintMessage { + private String message; + private String obj; + private int column; + private String path; + private int line; + private String type; + private String symbol; + private String module; +} diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessages.java b/src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessages.java new file mode 100644 index 00000000..3ce01b73 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/pylint/json/PylintMessages.java @@ -0,0 +1,6 @@ +package pl.touk.sputnik.processor.pylint.json; + +import java.util.ArrayList; + +public class PylintMessages extends ArrayList { +} diff --git a/src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ExternalProcessResultParser.java b/src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ExternalProcessResultParser.java new file mode 100644 index 00000000..c1fd640c --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ExternalProcessResultParser.java @@ -0,0 +1,9 @@ +package pl.touk.sputnik.processor.tools.externalprocess; + +import pl.touk.sputnik.review.Violation; + +import java.util.List; + +public interface ExternalProcessResultParser { + List parse(String output); +} diff --git a/src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ProcessorRunningExternalProcess.java b/src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ProcessorRunningExternalProcess.java new file mode 100644 index 00000000..2c64f7a5 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/tools/externalprocess/ProcessorRunningExternalProcess.java @@ -0,0 +1,34 @@ +package pl.touk.sputnik.processor.tools.externalprocess; + +import org.jetbrains.annotations.NotNull; +import pl.touk.sputnik.review.Review; +import pl.touk.sputnik.review.ReviewProcessor; +import pl.touk.sputnik.review.ReviewResult; +import pl.touk.sputnik.review.Violation; +import pl.touk.sputnik.review.filter.FileFilter; +import pl.touk.sputnik.review.transformer.IOFileTransformer; + +import java.io.File; +import java.util.List; + +public abstract class ProcessorRunningExternalProcess implements ReviewProcessor { + + @Override + @NotNull + public ReviewResult process(@NotNull Review review) { + ReviewResult result = new ReviewResult(); + List files = review.getFiles(getReviewFileFilter(), new IOFileTransformer()); + for (File file : files) { + for (Violation violation : getParser().parse(getOutputFromExternalProcess(file))) { + result.add(violation); + } + } + return result; + } + + public abstract FileFilter getReviewFileFilter(); + + public abstract ExternalProcessResultParser getParser(); + + public abstract String getOutputFromExternalProcess(File fileToReview); +} diff --git a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintException.java b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintException.java new file mode 100644 index 00000000..51ee9c43 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintException.java @@ -0,0 +1,11 @@ +package pl.touk.sputnik.processor.tslint; + +public class TSLintException extends RuntimeException { + public TSLintException(String message) { + super(message); + } + + public TSLintException(String message, Throwable t) { + super(message, t); + } +} diff --git a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintProcessor.java b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintProcessor.java index 889ac22e..3f884c40 100644 --- a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintProcessor.java +++ b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintProcessor.java @@ -4,18 +4,15 @@ import org.jetbrains.annotations.NotNull; import pl.touk.sputnik.configuration.Configuration; import pl.touk.sputnik.configuration.GeneralOption; -import pl.touk.sputnik.review.Review; -import pl.touk.sputnik.review.ReviewProcessor; -import pl.touk.sputnik.review.ReviewResult; -import pl.touk.sputnik.review.Violation; +import pl.touk.sputnik.processor.tools.externalprocess.ExternalProcessResultParser; +import pl.touk.sputnik.processor.tools.externalprocess.ProcessorRunningExternalProcess; +import pl.touk.sputnik.review.filter.FileFilter; import pl.touk.sputnik.review.filter.TypeScriptFilter; -import pl.touk.sputnik.review.transformer.IOFileTransformer; import java.io.File; -import java.util.List; @Slf4j -public class TSLintProcessor implements ReviewProcessor { +public class TSLintProcessor extends ProcessorRunningExternalProcess { private static final String SOURCE_NAME = "TSLint"; @@ -31,23 +28,24 @@ public TSLintProcessor(Configuration config) { resultParser = new TSLintResultParser(); } + @NotNull @Override public String getName() { return SOURCE_NAME; } @Override - @NotNull - public ReviewResult process(Review review) { - ReviewResult result = new ReviewResult(); - - List files = review.getFiles(new TypeScriptFilter(), new IOFileTransformer()); - for (File file : files) { - for (Violation violation : resultParser.parse(tsLintScript.reviewFile(file.getAbsolutePath()))) { - result.add(violation); - } - } - return result; + public FileFilter getReviewFileFilter() { + return new TypeScriptFilter(); } + @Override + public ExternalProcessResultParser getParser() { + return resultParser; + } + + @Override + public String getOutputFromExternalProcess(File fileToReview) { + return tsLintScript.reviewFile(fileToReview.getAbsolutePath()); + } } diff --git a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintResultParser.java b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintResultParser.java index b6e9b551..cf36ddf8 100644 --- a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintResultParser.java +++ b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintResultParser.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import pl.touk.sputnik.connector.gerrit.GerritException; +import pl.touk.sputnik.processor.tools.externalprocess.ExternalProcessResultParser; import pl.touk.sputnik.processor.tslint.json.ListViolationsResponse; import pl.touk.sputnik.processor.tslint.json.TSLintFileInfo; import pl.touk.sputnik.review.Severity; @@ -15,10 +15,11 @@ import java.util.List; @Slf4j -public class TSLintResultParser { +public class TSLintResultParser implements ExternalProcessResultParser { private final ObjectMapper objectMapper = new ObjectMapper(); + @Override public List parse(String jsonViolations) { if (StringUtils.isEmpty(jsonViolations)) { return Collections.emptyList(); @@ -35,7 +36,7 @@ public List parse(String jsonViolations) { } return result; } catch (IOException e) { - throw new GerritException("Error when converting from json format", e); + throw new TSLintException("Error when converting from json format", e); } } } diff --git a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintScript.java b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintScript.java index d6eb432e..fe883801 100644 --- a/src/main/java/pl/touk/sputnik/processor/tslint/TSLintScript.java +++ b/src/main/java/pl/touk/sputnik/processor/tslint/TSLintScript.java @@ -41,7 +41,7 @@ public TSLintScript(String tsScript, String configFile) { public void validateConfiguration() throws ReviewException { // check if config file exist if (!new File(configFile).exists()) { - throw new ReviewException("Could not find tslint configuration file: " + configFile); + throw new TSLintException("Could not find tslint configuration file: " + configFile); } } diff --git a/src/main/java/pl/touk/sputnik/review/filter/PythonFilter.java b/src/main/java/pl/touk/sputnik/review/filter/PythonFilter.java new file mode 100644 index 00000000..b0dfde35 --- /dev/null +++ b/src/main/java/pl/touk/sputnik/review/filter/PythonFilter.java @@ -0,0 +1,10 @@ +package pl.touk.sputnik.review.filter; + +import java.util.Collections; + +public class PythonFilter extends FileExtensionFilter { + + public PythonFilter() { + super(Collections.singletonList("py")); + } +} diff --git a/src/test/java/pl/touk/sputnik/processor/pylint/PylintProcessorTest.java b/src/test/java/pl/touk/sputnik/processor/pylint/PylintProcessorTest.java new file mode 100644 index 00000000..d3c9c9f1 --- /dev/null +++ b/src/test/java/pl/touk/sputnik/processor/pylint/PylintProcessorTest.java @@ -0,0 +1,31 @@ +package pl.touk.sputnik.processor.pylint; + +import org.junit.Before; +import org.junit.Test; +import pl.touk.sputnik.TestEnvironment; +import pl.touk.sputnik.configuration.ConfigurationBuilder; +import pl.touk.sputnik.review.ReviewResult; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PylintProcessorTest extends TestEnvironment { + + private PylintProcessor fixture; + + @Before + public void setUp() { + config = ConfigurationBuilder.initFromResource("pylint/sputnik/noConfigurationFile.properties"); + fixture = new PylintProcessor(config); + } + + + @Test + public void shouldReturnNoViolationsWhenThereIsNoFileToReview() { + // when + ReviewResult reviewResult = fixture.process(nonexistantReview()); + + // then + assertThat(reviewResult).isNotNull(); + assertThat(reviewResult.getViolations()).isEmpty(); + } +} \ No newline at end of file diff --git a/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java b/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java new file mode 100644 index 00000000..c76cd106 --- /dev/null +++ b/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java @@ -0,0 +1,49 @@ +package pl.touk.sputnik.processor.pylint; + +import com.google.common.io.Resources; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import pl.touk.sputnik.review.Violation; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(JUnitParamsRunner.class) +public class PylintResultParserTest { + + private PylintResultParser pylintResultParser; + + @Before + public void setUp() { + pylintResultParser = new PylintResultParser(); + } + + @Test + @Parameters({ + "pylint/sample-output.txt", + "pylint/sample-output-no-header.txt" + }) + public void shouldParseSampleViolations(String filePath) throws IOException, URISyntaxException { + // given + String response = IOUtils.toString(Resources.getResource(filePath).toURI()); + + // when + List violations = pylintResultParser.parse(response); + + // then + assertThat(violations).hasSize(8); + assertThat(violations).extracting("filenameOrJavaClassName").contains("PythonTest.py"); + assertThat(violations) + .extracting("message") + .contains("Invalid argument name \"n\" [invalid-name]") + .contains("Black listed name \"bar\" [blacklisted-name]") + .contains("Black listed name \"foo\" [blacklisted-name]"); + } +} diff --git a/src/test/java/pl/touk/sputnik/processor/tslint/TSLintScriptTest.java b/src/test/java/pl/touk/sputnik/processor/tslint/TSLintScriptTest.java index e1d0478f..0df8a28e 100644 --- a/src/test/java/pl/touk/sputnik/processor/tslint/TSLintScriptTest.java +++ b/src/test/java/pl/touk/sputnik/processor/tslint/TSLintScriptTest.java @@ -4,7 +4,6 @@ import org.junit.Test; import pl.touk.sputnik.configuration.ConfigurationSetup; import pl.touk.sputnik.configuration.GeneralOption; -import pl.touk.sputnik.review.ReviewException; import static com.googlecode.catchexception.CatchException.catchException; import static com.googlecode.catchexception.CatchException.caughtException; @@ -25,7 +24,7 @@ public void shouldFailWhenConfigFileIsMissing() { catchException(tsLint).validateConfiguration(); // then - assertThat(caughtException()).isInstanceOf(ReviewException.class).hasMessageContaining( + assertThat(caughtException()).isInstanceOf(TSLintException.class).hasMessageContaining( "Could not find tslint configuration file: " + configFile); } diff --git a/src/test/resources/pylint/pylintrc b/src/test/resources/pylint/pylintrc new file mode 100644 index 00000000..69a9c652 --- /dev/null +++ b/src/test/resources/pylint/pylintrc @@ -0,0 +1,378 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=json + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This supports can work +# with qualified names. +ignored-classes= + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/src/test/resources/pylint/sample-output-no-header.txt b/src/test/resources/pylint/sample-output-no-header.txt new file mode 100644 index 00000000..b6f5df50 --- /dev/null +++ b/src/test/resources/pylint/sample-output-no-header.txt @@ -0,0 +1,82 @@ +[ + { + "message": "Missing module docstring", + "obj": "", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Black listed name \"foo\"", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "blacklisted-name", + "module": "PythonTest" + }, + { + "message": "Invalid argument name \"a\"", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "invalid-name", + "module": "PythonTest" + }, + { + "message": "Missing function docstring", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Black listed name \"bar\"", + "obj": "bar", + "column": 0, + "path": "PythonTest.py", + "line": 4, + "type": "convention", + "symbol": "blacklisted-name", + "module": "PythonTest" + }, + { + "message": "Invalid argument name \"n\"", + "obj": "bar", + "column": 0, + "path": "PythonTest.py", + "line": 4, + "type": "convention", + "symbol": "invalid-name", + "module": "PythonTest" + }, + { + "message": "Missing function docstring", + "obj": "bar", + "column": 0, + "path": "PythonTest.py", + "line": 4, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Unused argument 'n'", + "obj": "bar", + "column": 8, + "path": "PythonTest.py", + "line": 4, + "type": "warning", + "symbol": "unused-argument", + "module": "PythonTest" + } +] diff --git a/src/test/resources/pylint/sample-output.txt b/src/test/resources/pylint/sample-output.txt new file mode 100644 index 00000000..c8e3548f --- /dev/null +++ b/src/test/resources/pylint/sample-output.txt @@ -0,0 +1,83 @@ +No config file found, using default configuration +[ + { + "message": "Missing module docstring", + "obj": "", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Black listed name \"foo\"", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "blacklisted-name", + "module": "PythonTest" + }, + { + "message": "Invalid argument name \"a\"", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "invalid-name", + "module": "PythonTest" + }, + { + "message": "Missing function docstring", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Black listed name \"bar\"", + "obj": "bar", + "column": 0, + "path": "PythonTest.py", + "line": 4, + "type": "convention", + "symbol": "blacklisted-name", + "module": "PythonTest" + }, + { + "message": "Invalid argument name \"n\"", + "obj": "bar", + "column": 0, + "path": "PythonTest.py", + "line": 4, + "type": "convention", + "symbol": "invalid-name", + "module": "PythonTest" + }, + { + "message": "Missing function docstring", + "obj": "bar", + "column": 0, + "path": "PythonTest.py", + "line": 4, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Unused argument 'n'", + "obj": "bar", + "column": 8, + "path": "PythonTest.py", + "line": 4, + "type": "warning", + "symbol": "unused-argument", + "module": "PythonTest" + } +] diff --git a/src/test/resources/pylint/sputnik/noConfigurationFile.properties b/src/test/resources/pylint/sputnik/noConfigurationFile.properties new file mode 100644 index 00000000..3069679f --- /dev/null +++ b/src/test/resources/pylint/sputnik/noConfigurationFile.properties @@ -0,0 +1 @@ +pylint.enabled=true From bbf5b74a65b1bcb3aa2e66c00415f5dfa66e8be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Nowak?= Date: Thu, 21 Apr 2016 10:21:07 +0200 Subject: [PATCH 2/2] Refactoring after review, new tests for pylint output parser. --- .../processor/pylint/PylintExecutor.java | 15 +- .../processor/pylint/PylintResultParser.java | 20 +- .../pylint/PylintResultParserTest.java | 32 ++ .../resources/pylint/output-with-fatal.txt | 42 ++ .../pylint/output-with-many-message-types.txt | 42 ++ src/test/resources/pylint/pylintrc | 378 ------------------ 6 files changed, 137 insertions(+), 392 deletions(-) create mode 100644 src/test/resources/pylint/output-with-fatal.txt create mode 100644 src/test/resources/pylint/output-with-many-message-types.txt delete mode 100644 src/test/resources/pylint/pylintrc diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java index 2a536a17..46da7094 100644 --- a/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintExecutor.java @@ -12,9 +12,9 @@ @Slf4j public class PylintExecutor { - private static final String pylintExecutable = "pylint"; - private static final String pylintOutputFormat = "--output-format=json"; - private static final String pylintRcfileName = "--rcfile="; + private static final String PYLINT_EXECUTABLE = "pylint"; + private static final String PYLINT_OUTPUT_FORMAT = "--output-format=json"; + private static final String PYLINT_RCFILE_NAME = "--rcfile="; private String rcfileName; @@ -30,8 +30,8 @@ public String runOnFile(String filePath) { @NotNull private String[] buildParams(String filePath) { List basicPylintArgs = ImmutableList.of( - pylintExecutable, - pylintOutputFormat); + PYLINT_EXECUTABLE, + PYLINT_OUTPUT_FORMAT); List rcfileNameArg = getRcfileNameAsList(); List filePathArg = ImmutableList.of(filePath); List allArgs = Lists.newArrayList(Iterables.concat(basicPylintArgs, rcfileNameArg, filePathArg)); @@ -39,10 +39,9 @@ private String[] buildParams(String filePath) { } private List getRcfileNameAsList() { - if (rcfileName != null) { - return ImmutableList.of(pylintRcfileName + rcfileName); - } else { + if (rcfileName == null) { return ImmutableList.of(); } + return ImmutableList.of(PYLINT_RCFILE_NAME + rcfileName); } } diff --git a/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java b/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java index 4437f3a4..5c98a958 100644 --- a/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java +++ b/src/main/java/pl/touk/sputnik/processor/pylint/PylintResultParser.java @@ -17,6 +17,12 @@ public class PylintResultParser implements ExternalProcessResultParser { private final ObjectMapper objectMapper = new ObjectMapper(); + private static final String PYLINT_ERROR = "error"; + private static final String PYLINT_FATAL = "fatal"; + private static final String PYLINT_WARNING = "warning"; + private static final String PYLINT_CONVENTION = "convention"; + private static final String PYLINT_REFACTOR = "refactor"; + @Override public List parse(String pylintOutput) { if (StringUtils.isEmpty(pylintOutput)) { @@ -29,7 +35,7 @@ public List parse(String pylintOutput) { Violation violation = new Violation(message.getPath(), message.getLine(), formatViolationMessageFromPylint(message), - pylintMessageTypeToSeverity(message.getType())); + pylintMessageTypeToSeverity(message.getMessage(), message.getType())); violations.add(violation); } return violations; @@ -46,15 +52,17 @@ private String formatViolationMessageFromPylint(PylintMessage message) { return message.getMessage() + " [" + message.getSymbol() + "]"; } - private Severity pylintMessageTypeToSeverity(String messageType) { - if (messageType.equals("error") || messageType.equals("fatal")) { + private Severity pylintMessageTypeToSeverity(String message, String messageType) { + if (PYLINT_ERROR.equals(messageType)) { return Severity.ERROR; - } else if (messageType.equals("warning")) { + } else if (PYLINT_WARNING.equals(messageType)) { return Severity.WARNING; - } else if (messageType.equals("convention") || messageType.equals("refactor")) { + } else if (PYLINT_CONVENTION.equals(messageType) || PYLINT_REFACTOR.equals(messageType)) { return Severity.INFO; + } else if (PYLINT_FATAL.equals(messageType)) { + throw new PylintException("Fatal error from pylint (" + message + ")"); } else { - throw new PylintException("Unknown message type returned by pylint in message (type = " + messageType + ")"); + throw new PylintException("Unknown message type returned by pylint (type = " + messageType + ")"); } } } diff --git a/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java b/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java index c76cd106..14065060 100644 --- a/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java +++ b/src/test/java/pl/touk/sputnik/processor/pylint/PylintResultParserTest.java @@ -5,8 +5,11 @@ import junitparams.Parameters; import org.apache.commons.io.IOUtils; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import pl.touk.sputnik.review.Severity; import pl.touk.sputnik.review.Violation; import java.io.IOException; @@ -14,12 +17,16 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.core.StringStartsWith.startsWith; @RunWith(JUnitParamsRunner.class) public class PylintResultParserTest { private PylintResultParser pylintResultParser; + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Before public void setUp() { pylintResultParser = new PylintResultParser(); @@ -46,4 +53,29 @@ public void shouldParseSampleViolations(String filePath) throws IOException, URI .contains("Black listed name \"bar\" [blacklisted-name]") .contains("Black listed name \"foo\" [blacklisted-name]"); } + + @Test + public void shouldParseMessageTypes() throws IOException, URISyntaxException { + // given + String response = IOUtils.toString(Resources.getResource("pylint/output-with-many-message-types.txt").toURI()); + + // when + List violations = pylintResultParser.parse(response); + + // then + assertThat(violations).hasSize(4); + assertThat(violations) + .extracting("severity") + .containsExactly(Severity.INFO, Severity.INFO, Severity.WARNING, Severity.ERROR); + } + + @Test + public void shouldThrowExceptionWhenFatalPylintErrorOccurs() throws IOException, URISyntaxException { + // given + String response = IOUtils.toString(Resources.getResource("pylint/output-with-fatal.txt").toURI()); + + thrown.expect(PylintException.class); + thrown.expectMessage(startsWith("Fatal error from pylint")); + pylintResultParser.parse(response); + } } diff --git a/src/test/resources/pylint/output-with-fatal.txt b/src/test/resources/pylint/output-with-fatal.txt new file mode 100644 index 00000000..f2ce5522 --- /dev/null +++ b/src/test/resources/pylint/output-with-fatal.txt @@ -0,0 +1,42 @@ +[ + { + "message": "Missing module docstring", + "obj": "", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Do refactor", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "refactor", + "symbol": "blacklisted-name", + "module": "PythonTest" + }, + { + "message": "Fatal error - total destruction", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "fatal", + "symbol": "invalid-name", + "module": "PythonTest" + }, + { + "message": "Something bad", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "error", + "symbol": "missing-docstring", + "module": "PythonTest" + } +] diff --git a/src/test/resources/pylint/output-with-many-message-types.txt b/src/test/resources/pylint/output-with-many-message-types.txt new file mode 100644 index 00000000..92a2795b --- /dev/null +++ b/src/test/resources/pylint/output-with-many-message-types.txt @@ -0,0 +1,42 @@ +[ + { + "message": "Missing module docstring", + "obj": "", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "convention", + "symbol": "missing-docstring", + "module": "PythonTest" + }, + { + "message": "Do refactor", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "refactor", + "symbol": "blacklisted-name", + "module": "PythonTest" + }, + { + "message": "Some warning", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "warning", + "symbol": "invalid-name", + "module": "PythonTest" + }, + { + "message": "Something bad", + "obj": "foo", + "column": 0, + "path": "PythonTest.py", + "line": 1, + "type": "error", + "symbol": "missing-docstring", + "module": "PythonTest" + } +] diff --git a/src/test/resources/pylint/pylintrc b/src/test/resources/pylint/pylintrc deleted file mode 100644 index 69a9c652..00000000 --- a/src/test/resources/pylint/pylintrc +++ /dev/null @@ -1,378 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. -optimize-ast=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=json - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). This supports can work -# with qualified names. -ignored-classes= - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception