From 0421f12e7b2e5bb8df3c82f865d73305ca77241a Mon Sep 17 00:00:00 2001 From: Sourabh Sarvotham Parkala Date: Wed, 7 Sep 2022 15:59:11 +0200 Subject: [PATCH] Static analysis tool providers from Prospector Project (#866) * Static analysis tool providers from Prospector Project Fixes #730 --- .../AbstractStaticScanToolsDataProvider.java | 388 +++++++++++++++++- .../fosstars/data/DataProviderSelector.java | 4 + .../fosstars/data/StaticAnalysisScanTool.java | 25 -- .../data/github/BanditDataProvider.java | 94 +---- .../data/github/CodeqlDataProvider.java | 83 +--- .../data/github/GoSecDataProvider.java | 251 ++--------- .../fosstars/data/github/LocalRepository.java | 41 ++ .../data/github/MyPyDataProvider.java | 66 +++ .../data/github/PylintDataProvider.java | 66 +++ .../github/AbstractGitHubVisitor.java | 37 ++ .../fosstars/github/GitHubVisitor.java | 71 ++++ .../model/feature/oss/OssFeatures.java | 37 ++ .../fosstars/model/score/oss/MyPyScore.java | 88 ++++ .../fosstars/model/score/oss/PylintScore.java | 88 ++++ .../model/score/oss/StaticAnalysisScore.java | 7 +- .../fosstars/tool/format/CommonFormatter.java | 10 + .../fosstars/util/Deserialization.java | 6 +- .../sap/oss/phosphor/fosstars/TestUtils.java | 12 + ...stractStaticScanToolsDataProviderTest.java | 6 - .../data/github/GoSecDataProviderTest.java | 2 +- .../data/github/MyPyDataProviderTest.java | 170 ++++++++ .../data/github/PylintDataProviderTest.java | 243 +++++++++++ .../model/score/oss/MyPyScoreTest.java | 67 +++ .../model/score/oss/OssSecurityScoreTest.java | 8 + .../model/score/oss/PylintScoreTest.java | 67 +++ .../score/oss/StaticAnalysisScoreTest.java | 243 ++++++++++- ...ssSecurityRatingMarkdownFormatterTest.java | 8 + .../tool/format/PrettyPrinterTest.java | 8 + .../mypy-analysis-with-pre-commit-hook.yml | 21 + .../github/mypy-analysis-with-prospector.yml | 7 + .../data/github/mypy-analysis-with-run.yml | 27 ++ .../pylint-analysis-as-pre-commit-hook.yml | 14 + .../github/pylint-analysis-no-pylint-hook.yml | 14 + .../pylint-analysis-with-multiple-jobs.yml | 25 ++ ...sis-with-no-pylint-run-but-uses-pylint.yml | 27 ++ .../pylint-analysis-with-no-pylint-run.yml | 25 ++ .../pylint-analysis-with-prospector.yml | 7 + .../pylint-analysis-with-pylint-in-entry.yml | 14 + .../pylint-analysis-with-pylint-in-repo.yml | 14 + .../pylint-analysis-with-pylint-in-rev.yml | 14 + .../data/github/pylint-analysis-with-run.yml | 25 ++ .../fosstars/data/github/tox-no-pylint.ini | 94 +++++ .../oss/phosphor/fosstars/data/github/tox.ini | 94 +++++ .../OssArtifactSecurityRatingTestVectors.yml | 20 + .../oss/OssSecurityRatingTestVectors.yml | 20 + .../model/score/oss/MyPyScoreTestVectors.yml | 180 ++++++++ ...ProjectSecurityTestingScoreTestVectors.yml | 342 ++++++++++++++- .../score/oss/PylintScoreTestVectors.yml | 180 ++++++++ .../oss/StaticAnalysisScoreTestVectors.yml | 312 ++++++++++++++ ...veryAndSecurityTestingScoreTestVectors.yml | 32 ++ src/test/shell/tool/github/lib.sh | 12 +- 51 files changed, 3303 insertions(+), 413 deletions(-) delete mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/data/StaticAnalysisScanTool.java create mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProvider.java create mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProvider.java create mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/github/AbstractGitHubVisitor.java create mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/github/GitHubVisitor.java create mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScore.java create mode 100644 src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScore.java create mode 100644 src/test/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProviderTest.java create mode 100644 src/test/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProviderTest.java create mode 100644 src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTest.java create mode 100644 src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTest.java create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-pre-commit-hook.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-prospector.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-run.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-as-pre-commit-hook.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-no-pylint-hook.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-multiple-jobs.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run-but-uses-pylint.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-prospector.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-entry.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-repo.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-rev.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-run.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox-no-pylint.ini create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox.ini create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTestVectors.yml create mode 100644 src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTestVectors.yml diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProvider.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProvider.java index 203a2ee14..357cd95ef 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProvider.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProvider.java @@ -1,37 +1,103 @@ package com.sap.oss.phosphor.fosstars.data; +import static com.sap.oss.phosphor.fosstars.github.GitHubVisitor.Location.GITHUB_ACTION; +import static com.sap.oss.phosphor.fosstars.github.GitHubVisitor.Location.INI_CONFIG; +import static com.sap.oss.phosphor.fosstars.github.GitHubVisitor.Location.PRE_COMMIT_HOOK; + import com.sap.oss.phosphor.fosstars.data.github.GitHubCachingDataProvider; import com.sap.oss.phosphor.fosstars.data.github.GitHubDataFetcher; import com.sap.oss.phosphor.fosstars.data.github.LocalRepository; +import com.sap.oss.phosphor.fosstars.github.AbstractGitHubVisitor; +import com.sap.oss.phosphor.fosstars.github.GitHubVisitor; +import com.sap.oss.phosphor.fosstars.github.GitHubVisitor.Location; import com.sap.oss.phosphor.fosstars.model.Feature; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import com.sap.oss.phosphor.fosstars.util.Yaml; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; +import org.apache.commons.collections4.IteratorUtils; /** * This is a base class for data providers that would like to check if the project uses a Static * Analysis Scan Tool (SAST) and also determines how the project uses it. */ public abstract class AbstractStaticScanToolsDataProvider extends - GitHubCachingDataProvider implements StaticAnalysisScanTool { + GitHubCachingDataProvider { /** * A directory where GitHub Actions configs are stored. */ private static final String GITHUB_ACTIONS_DIRECTORY = ".github/workflows"; + /** + * A pre-commit hook standard config file path. + * + * @see Pre-commit hook config + * file + */ + private static final String PRE_COMMIT_HOOK_CONFIG = ".pre-commit-config.yaml"; + + /** + * A list of extensions of GitHub Actions configs. + */ + private static final List GITHUB_ACTIONS_CONFIG_EXTENSIONS = + Arrays.asList(".yaml", ".yml"); + /** * A list of extensions of GitHub Actions configs. */ - private static final List GITHUB_ACTIONS_CONFIG_EXTENSIONS - = Arrays.asList(".yaml", ".yml"); + private static final String GITHUB_INI_CONFIG_EXTENSION = ".ini"; + + /** + * A Bi-Predicate to evaluate each context of Map type extracted from the GitHub Action jobs. + */ + private static final BiPredicate, Map>> MATCH_MAP_PREDICATE = + (context, matchers) -> { + for (Map.Entry> entry : matchers.entrySet()) { + final String key = entry.getKey(); + if (context.containsKey(key) + && entry.getValue().test(context.get(key).toString())) { + return true; + } + } + return false; + }; + + /** + * A Bi-Predicate to evaluate each content of String type extracted from the GitHub Action jobs. + */ + private static final BiPredicate>> MATCH_STRING_PREDICATE = + (content, matchers) -> { + for (Predicate entry : matchers.values()) { + if (entry.test(content)) { + return true; + } + } + return false; + }; + + /** + * The list of Pre-defined stops to run the predicate or search for the key pattern in pre-commit + * hook config file. + */ + private static final List PRE_COMMIT_HOOK_CONTEXT = + Arrays.asList("entry", "additional_dependencies", "repo", "rev"); /** * Set of supported features. @@ -55,6 +121,35 @@ public Set> supportedFeatures() { return supportedFeatures; } + /** + * Browse a POM file with a specified visitor. + * + * @param repository The {@link LocalRepository}. + * @param matchers map of {@link Predicate}s to parse for a specific content and match the + * predicate. + * @param configMatchers map of {@link Predicate}s to parse for a specific content and match the + * specific configs with the given predicate. + * @param visitor The visitor. + * @param A type of the visitor. + * @return The passed visitor. + * @throws IOException if something went wrong. + */ + protected static T browse(LocalRepository repository, + Map> matchers, Map> configMatchers, + T visitor) throws IOException { + Objects.requireNonNull(repository, "Oh no! Repository is null!"); + Objects.requireNonNull(matchers, "Oh no! Predicate is null!"); + Objects.requireNonNull(visitor, "On no! Visitor is null!"); + + visitor.visitGitHubAction(repository, matchers, configMatchers, + in(EnumSet.noneOf(Location.class), GITHUB_ACTION)); + visitor.visitPreCommitHook(repository, matchers, + in(EnumSet.noneOf(Location.class), PRE_COMMIT_HOOK)); + visitor.visitIniConfig(repository, matchers, in(EnumSet.noneOf(Location.class), INI_CONFIG)); + // visitor.visitSourceCode(repository, predicate, in(EnumSet.noneOf(Location.class), TYPE_PY)); + return visitor; + } + /** * Looks for GitHub actions in a repository. * @@ -73,18 +168,78 @@ protected static List findGitHubActionsIn(LocalRepository repository) thro } /** - * Checks if a file is a config for a GitHub action. + * Looks for GitHub ini config files in a repository. * - * @param path A path to the file. - * @return True if a file looks like a config for a GitHub action, false otherwise. + * @param repository The repository to be checked. + * @return A list of paths to GitHub Action ini configs. + * @throws IOException If something went wrong. */ - private static boolean isGitHubActionConfig(Path path) { - return GITHUB_ACTIONS_CONFIG_EXTENSIONS - .stream().anyMatch(ext -> path.getFileName().toString().endsWith(ext)); + public static List findIniConfigsIn(LocalRepository repository) throws IOException { + return repository + .files(path -> path.getFileName().toString().endsWith(GITHUB_INI_CONFIG_EXTENSION)); } - @Override - public boolean runsOnPullRequests(Map githubAction) { + /** + * Browse through the GitHub actions and find matches with the given predicates. + * + * @param githubAction GitHub Actions. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean scanGitHubAction(Map githubAction, + Map> matchers) { + if (matchers.isEmpty()) { + return false; + } + + return Optional.ofNullable(githubAction.get("jobs")) + .filter(Map.class::isInstance) + .map(Map.class::cast) + .map(jobs -> jobs.values()) + .filter(Iterable.class::isInstance) + .map(Iterable.class::cast) + .map(jobs -> scanJobs(jobs, matchers)) + .orElse(false); + } + + /** + * Search for the given predicate matches in the given list of action jobs. + * + * @param jobs Iterable list of GitHub action jobs. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean scanJobs(Iterable jobs, Map> matchers) { + return IteratorUtils.toList(jobs.iterator()).stream() + .filter(Map.class::isInstance) + .map(Map.class::cast) + .map(job -> job.get("steps")) + .filter(Iterable.class::isInstance) + .map(Iterable.class::cast) + .anyMatch(steps -> scanSteps(steps, matchers)); + } + + /** + * Search for the given predicate matches in the given list of job steps. + * + * @param jobs Iterable list of GitHub job steps. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean scanSteps(Iterable steps, Map> matchers) { + return IteratorUtils.toList(steps.iterator()).stream() + .filter(Map.class::isInstance) + .map(Map.class::cast) + .anyMatch(step -> MATCH_MAP_PREDICATE.test(step, matchers)); + } + + /** + * Checks if a GitHub action runs on pull requests. + * + * @param githubAction A config of the action. + * @return True if the action runs on pull requests, false otherwise. + */ + private static boolean runsOnPullRequests(Map githubAction) { return runsOnPullRequestArrayType(githubAction) || runsOnPullRequestMapType(githubAction); } @@ -95,7 +250,7 @@ public boolean runsOnPullRequests(Map githubAction) { * @param githubAction A config of the action. * @return True if the action runs on pull requests, false otherwise. */ - private boolean runsOnPullRequestArrayType(Map githubAction) { + private static boolean runsOnPullRequestArrayType(Map githubAction) { return Optional.ofNullable(githubAction.get("on")) .filter(ArrayList.class::isInstance) .map(ArrayList.class::cast) @@ -109,11 +264,218 @@ private boolean runsOnPullRequestArrayType(Map githubAction) { * @param githubAction A config of the action. * @return True if the action runs on pull requests, false otherwise. */ - private boolean runsOnPullRequestMapType(Map githubAction) { + private static boolean runsOnPullRequestMapType(Map githubAction) { return Optional.ofNullable(githubAction.get("on")) .filter(Map.class::isInstance) .map(Map.class::cast) .map(on -> on.containsKey("pull_request")) .orElse(false); } + + /** + * Checks if a file is a config for a GitHub action. + * + * @param path A path to the file. + * @return True if a file looks like a config for a GitHub action, false otherwise. + */ + private static boolean isGitHubActionConfig(Path path) { + return GITHUB_ACTIONS_CONFIG_EXTENSIONS + .stream().anyMatch(ext -> path.getFileName().toString().endsWith(ext)); + } + + /** + * Prepare the map of Pre-Commit Hook config contexts. + * + * @param matcher The predicate matcher to match against each contexts. + * @return Map of Context and the associated Predicates. + */ + protected static Map> preCommitHookContextMap( + Predicate matcher) { + Map> map = new HashMap<>(); + for (String context : PRE_COMMIT_HOOK_CONTEXT) { + map.put(context, matcher); + } + return map; + } + + /** + * Checks if a Static Analysis Tool is configured as a Pre-Commit Hook. + * + * @param config The Pre-Commit Config file. + * @param matchers predicates to match through the given config. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean hasPreCommitHook(Map config, + Map> matchers) { + if (matchers.isEmpty()) { + return false; + } + + return Optional.ofNullable(config.get("repos")) + .filter(Iterable.class::isInstance) + .map(Iterable.class::cast) + .map(repos -> scanPreCommitRepos(repos, matchers) || scanPreCommitHooks(repos, matchers)) + .orElse(false); + } + + /* + * Search for the given predicate matches in the given list of repos. + * + * @param repos Iterable list of GitHub repositories config. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean scanPreCommitRepos(Iterable repos, + Map> matchers) { + return IteratorUtils.toList(repos.iterator()).stream() + .filter(Map.class::isInstance) + .map(Map.class::cast) + .anyMatch(repo -> MATCH_MAP_PREDICATE.test(repo, matchers)); + } + + /** + * Search for the given predicate matches in the given list of repo hooks. + * + * @param repos Iterable list of GitHub repositories hooks config. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean scanPreCommitHooks(Iterable repos, + Map> matchers) { + return IteratorUtils.toList(repos.iterator()).stream() + .filter(Map.class::isInstance) + .map(Map.class::cast) + .map(repo -> repo.get("hooks")) + .filter(Iterable.class::isInstance) + .map(Iterable.class::cast) + .anyMatch(hooks -> scanPreCommitHook(hooks, matchers)); + } + + /** + * Search for the given predicate matches in the given list of repo hook. + * + * @param repos Iterable list of GitHub hooks config. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean scanPreCommitHook(Iterable hooks, + Map> matchers) { + return IteratorUtils.toList(hooks.iterator()).stream() + .filter(Map.class::isInstance) + .map(Map.class::cast) + .anyMatch(hook -> MATCH_MAP_PREDICATE.test(hook, matchers)); + } + + /** + * Search for the given predicate matches in the given ini config file. + * + * @param repos Iterable list lines from the ini config. + * @param matchers predicates to match through the given action. + * @return True if one of the predicate finds matches. False otherwise. + */ + private static boolean hasIniConfig(Stream lines, + Map> matchers) { + if (matchers.isEmpty()) { + return false; + } + + return lines.anyMatch(line -> MATCH_STRING_PREDICATE.test(line, matchers)); + } + + /** + * Builds a new set of locations. + * + * @param locations A set of locations to be added to the resulting set. + * @param rest An array of locations to be added to the resulting set. + * @return A set of locations that contains all passed locations. + */ + public static EnumSet in( + EnumSet locations, Location... rest) { + + EnumSet set = EnumSet.copyOf(locations); + set.addAll(Arrays.asList(rest)); + return set; + } + + /** + * Creates a visitor for searching {@link GitHubProject}. + * + * @return {@link Visitor}. + */ + protected static Visitor withVisitor() { + return new Visitor(); + } + + /** + * A visitor for searching a specific config predicate in a {@link GitHubProject}. + */ + public static class Visitor extends AbstractGitHubVisitor { + + /** + * A visitor for searching {@link GitHubProject} if the config to run a specific check tool + * exists. + */ + public boolean runCheck = false; + + /** + * A visitor for searching {@link GitHubProject} if the config to use a specific check tool + * exists. + */ + public boolean usesCheck = false; + + /** + * A visitor for searching {@link GitHubProject} if the config to use a specific check tool + * has specific rules. + */ + public boolean hasRules = false; + + @Override + public void visitPreCommitHook(LocalRepository repository, + Map> matchers, Set locations) throws IOException { + Optional content = repository.fileStream(PRE_COMMIT_HOOK_CONFIG); + if (!content.isPresent()) { + return; + } + + Map config = Yaml.readMap(content.get()); + if (hasPreCommitHook(config, matchers)) { + runCheck = true; + usesCheck = true; + } + } + + @Override + public void visitIniConfig(LocalRepository repository, Map> matchers, + Set locations) throws IOException { + for (Path configPath : findIniConfigsIn(repository)) { + try (Stream lines = Files.lines(configPath)) { + if (hasIniConfig(lines, matchers)) { + runCheck = true; + break; + } + } + } + } + + @Override + public void visitGitHubAction(LocalRepository repository, + Map> matchers, Map> configMatchers, + Set locations) throws IOException { + for (Path configPath : findGitHubActionsIn(repository)) { + try (InputStream content = Files.newInputStream(configPath)) { + Map githubAction = Yaml.readMap(content); + if (scanGitHubAction(githubAction, matchers)) { + runCheck = true; + if (runsOnPullRequests(githubAction)) { + usesCheck = true; + } + if (scanGitHubAction(githubAction, configMatchers)) { + hasRules = true; + break; + } + } + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/DataProviderSelector.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/DataProviderSelector.java index b28d18f1c..8dd4bf325 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/DataProviderSelector.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/DataProviderSelector.java @@ -36,6 +36,7 @@ import com.sap.oss.phosphor.fosstars.data.github.IsEclipse; import com.sap.oss.phosphor.fosstars.data.github.LgtmDataProvider; import com.sap.oss.phosphor.fosstars.data.github.LicenseInfo; +import com.sap.oss.phosphor.fosstars.data.github.MyPyDataProvider; import com.sap.oss.phosphor.fosstars.data.github.NumberOfCommits; import com.sap.oss.phosphor.fosstars.data.github.NumberOfContributors; import com.sap.oss.phosphor.fosstars.data.github.NumberOfDependentProjectOnGitHub; @@ -44,6 +45,7 @@ import com.sap.oss.phosphor.fosstars.data.github.OwaspSecurityLibraries; import com.sap.oss.phosphor.fosstars.data.github.PackageManagement; import com.sap.oss.phosphor.fosstars.data.github.ProgrammingLanguages; +import com.sap.oss.phosphor.fosstars.data.github.PylintDataProvider; import com.sap.oss.phosphor.fosstars.data.github.ReadmeInfo; import com.sap.oss.phosphor.fosstars.data.github.ReleasesFromGitHub; import com.sap.oss.phosphor.fosstars.data.github.SecurityReviewsFromOpenSSF; @@ -239,6 +241,8 @@ public DataProviderSelector(GitHubDataFetcher fetcher, NVD nvd) throws IOExcepti new VulnerabilitiesFromOwaspDependencyCheck(), new VulnerabilitiesFromNpmAudit(nvd), new HasExecutableBinaries(fetcher), + new PylintDataProvider(fetcher), + new MyPyDataProvider(fetcher), PROJECT_USAGE_PROVIDER, FUNCTIONALITY_PROVIDER, HANDLING_UNTRUSTED_DATA_LIKELIHOOD_PROVIDER, diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/StaticAnalysisScanTool.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/StaticAnalysisScanTool.java deleted file mode 100644 index c73c9bd42..000000000 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/StaticAnalysisScanTool.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sap.oss.phosphor.fosstars.data; - -import java.util.Map; - -/** - * This is an interface of an Static Analysis Scan Tool (SAST) check data provider. - */ -public interface StaticAnalysisScanTool { - - /** - * Checks if a GitHub action triggers a SAST. - * - * @param githubAction A config for the action. - * @return True if the action triggers a SAST, false otherwise. - */ - boolean triggersScan(Map githubAction); - - /** - * Checks if a GitHub action runs on pull requests. - * - * @param githubAction A config of the action. - * @return True if the action runs on pull requests, false otherwise. - */ - boolean runsOnPullRequests(Map githubAction); -} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/BanditDataProvider.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/BanditDataProvider.java index 94e397507..7f3442261 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/BanditDataProvider.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/BanditDataProvider.java @@ -10,31 +10,36 @@ import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet; -import com.sap.oss.phosphor.fosstars.util.Yaml; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.Optional; +import java.util.function.Predicate; import java.util.regex.Pattern; -import org.apache.commons.collections4.IteratorUtils; /** * The data provider gathers info about how a project uses Bandit for static analysis. In * particular, it tries to fill out the following features: *
    - *
  • {@link OssFeatures#RUNS_BANDIT_SCANS}
  • - *
  • {@link OssFeatures#USES_BANDIT_SCAN_CHECKS}
  • + *
  • {@link OssFeatures#RUNS_BANDIT_SCANS}
  • + *
  • {@link OssFeatures#USES_BANDIT_SCAN_CHECKS}
  • *
*/ public class BanditDataProvider extends AbstractStaticScanToolsDataProvider { /** - * A step in a GitHub action that triggers analysis with Bandit. + * A Predicate to check the any step in a GitHub action that triggers analysis with Bandit. */ - private static final Pattern RUN_STEP_BANDIT_REGEX_PATTERN - = Pattern.compile("^.*bandit .*$", Pattern.DOTALL); + private static final Map> MATCH_BANDIT_PREDICATE = new HashMap<>(); + + static { + { + MATCH_BANDIT_PREDICATE.put("uses", + step -> Pattern.compile(".*bandit.*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_BANDIT_PREDICATE.put("run", + step -> Pattern.compile("^.*bandit .*$", Pattern.DOTALL).matcher(step).matches()); + } + } /** * Initializes a data provider. @@ -51,69 +56,14 @@ protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { LocalRepository repository = GitHubDataFetcher.localRepositoryFor(project); - Value runsBandit = RUNS_BANDIT_SCANS.value(false); - Value usesBanditScanChecks = USES_BANDIT_SCAN_CHECKS.value(false); - // ideally, we're looking for a GitHub action that runs Bandit scan on pull requests // but if we just find an action that runs Bandit scans, that's also fine - for (Path configPath : findGitHubActionsIn(repository)) { - try (InputStream content = Files.newInputStream(configPath)) { - Map githubAction = Yaml.readMap(content); - if (triggersScan(githubAction)) { - runsBandit = RUNS_BANDIT_SCANS.value(true); - if (runsOnPullRequests(githubAction)) { - usesBanditScanChecks = USES_BANDIT_SCAN_CHECKS.value(true); - break; - } - } - } - } + Visitor visitor = withVisitor(); + browse(repository, MATCH_BANDIT_PREDICATE, Collections.emptyMap(), visitor); - return ValueHashSet.from(runsBandit, usesBanditScanChecks); - } - - @Override - public boolean triggersScan(Map githubAction) { - return Optional.ofNullable(githubAction.get("jobs")) - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(jobs -> jobs.values()) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .map(BanditDataProvider::scanJobs) - .orElse(false); - } + Value runsBandit = RUNS_BANDIT_SCANS.value(visitor.runCheck); + Value usesBanditScanChecks = USES_BANDIT_SCAN_CHECKS.value(visitor.usesCheck); - /** - * Checks if any step in a collection of jobs triggers a Bandit scan. - * - * @param jobs The collection of jobs from GitHub action. - * @return True if a step triggers a Bandit scan, false otherwise. - */ - private static boolean scanJobs(Iterable jobs) { - return IteratorUtils.toList(jobs.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(job -> job.get("steps")) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .anyMatch(BanditDataProvider::hasBanditRunStep); - } - - /** - * Checks if a collection of steps from a GitHub action contains a step that triggers a Bandit - * scan. - * - * @param steps The steps to be checked. - * @return True if the steps contain a step that triggers a Bandit scan, false otherwise. - */ - private static boolean hasBanditRunStep(Iterable steps) { - return IteratorUtils.toList(steps.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(step -> step.get("run")) - .filter(String.class::isInstance) - .map(String.class::cast) - .anyMatch(run -> RUN_STEP_BANDIT_REGEX_PATTERN.matcher(run).matches()); + return ValueHashSet.from(runsBandit, usesBanditScanChecks); } -} \ No newline at end of file +} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/CodeqlDataProvider.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/CodeqlDataProvider.java index 4b43a473e..ca78a1e0b 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/CodeqlDataProvider.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/CodeqlDataProvider.java @@ -10,21 +10,18 @@ import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet; -import com.sap.oss.phosphor.fosstars.util.Yaml; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.Optional; -import org.apache.commons.collections4.IteratorUtils; +import java.util.function.Predicate; /** - * The data provider gathers info about how a project uses CodeQL for static analysis. - * In particular, it tires to fill out the following features: + * The data provider gathers info about how a project uses CodeQL for static analysis. In + * particular, it tires to fill out the following features: *
    - *
  • {@link OssFeatures#RUNS_CODEQL_SCANS}
  • - *
  • {@link OssFeatures#USES_CODEQL_CHECKS}
  • + *
  • {@link OssFeatures#RUNS_CODEQL_SCANS}
  • + *
  • {@link OssFeatures#USES_CODEQL_CHECKS}
  • *
* * @see LgtmDataProvider @@ -32,9 +29,15 @@ public class CodeqlDataProvider extends AbstractStaticScanToolsDataProvider { /** - * A step in a GitHub action that triggers analysis with CodeQL. + * A predicate to check if any step in GitHub action that triggers analysis with CodeQL. */ - private static final String CODEQL_ANALYZE_STEP_TASK = "github/codeql-action/analyze"; + private static final Map> MATCH_CODEQL_ANALYZE_PREDICATE = + new HashMap<>(); + + static { + MATCH_CODEQL_ANALYZE_PREDICATE.put("uses", + uses -> uses.startsWith("github/codeql-action/analyze")); + } /** * Initializes a data provider. @@ -51,56 +54,12 @@ protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { LocalRepository repository = GitHubDataFetcher.localRepositoryFor(project); - Value runsCodeqlScans = RUNS_CODEQL_SCANS.value(false); - Value usesCodeqlChecks = USES_CODEQL_CHECKS.value(false); - - // ideally, we're looking for a GitHub action that runs CodeQL scan on pull requests - // but if we just find an action that runs CodeQL scans, that's also fine - for (Path configPath : findGitHubActionsIn(repository)) { - try (InputStream content = Files.newInputStream(configPath)) { - Map githubAction = Yaml.readMap(content); - if (triggersScan(githubAction)) { - runsCodeqlScans = RUNS_CODEQL_SCANS.value(true); - if (runsOnPullRequests(githubAction)) { - usesCodeqlChecks = USES_CODEQL_CHECKS.value(true); - break; - } - } - } - } - - return ValueHashSet.from(usesCodeqlChecks, runsCodeqlScans); - } + Visitor visitor = withVisitor(); + browse(repository, MATCH_CODEQL_ANALYZE_PREDICATE, Collections.emptyMap(), visitor); - @Override - public boolean triggersScan(Map githubAction) { - return Optional.ofNullable(githubAction.get("jobs")) - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(jobs -> jobs.get("analyze")) - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(jobs -> jobs.get("steps")) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .map(CodeqlDataProvider::hasCodeqlAnalyzeStep) - .orElse(false); - } + Value runsCodeqlScans = RUNS_CODEQL_SCANS.value(visitor.runCheck); + Value usesCodeqlChecks = USES_CODEQL_CHECKS.value(visitor.usesCheck); - /** - * Checks if a collection of steps from a GitHub action contains a step that triggers - * a CodeQL scan. - * - * @param steps The steps to be checked. - * @return True if the steps contain a step that triggers a CodeQL scan, false otherwise. - */ - private static boolean hasCodeqlAnalyzeStep(Iterable steps) { - return IteratorUtils.toList(steps.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(step -> step.get("uses")) - .filter(String.class::isInstance) - .map(String.class::cast) - .anyMatch(uses -> uses.startsWith(CODEQL_ANALYZE_STEP_TASK)); + return ValueHashSet.from(runsCodeqlScans, usesCodeqlChecks); } -} \ No newline at end of file +} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProvider.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProvider.java index 57844f83e..49a092716 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProvider.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProvider.java @@ -11,16 +11,11 @@ import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet; -import com.sap.oss.phosphor.fosstars.util.Yaml; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.regex.Pattern; -import org.apache.commons.collections4.IteratorUtils; /** * The data provider gathers info about how a project uses GoSec for static analysis. In @@ -33,20 +28,38 @@ * */ public class GoSecDataProvider extends AbstractStaticScanToolsDataProvider { - - /** - * A step in a GitHub action that triggers analysis with GoSec. - */ - private static final Pattern RUN_STEP_GOSEC_REGEX_PATTERN = - Pattern.compile("^.*securego/gosec.*$", Pattern.DOTALL); - - /** A step config of a GitHub action that triggers GoSec scans with include rule. */ - private static final Pattern RUN_STEP_GOSEC_WITH_INCLUDE_REGEX_PATTERN = - Pattern.compile("^.*-include=.*$", Pattern.DOTALL); - - /** A step config of a GitHub action that triggers GoSec scans with include rule. */ - private static final Pattern RUN_STEP_GOSEC_WITH_EXCLUDE_REGEX_PATTERN = - Pattern.compile("^.*-exclude=.*$", Pattern.DOTALL); + + /** + * A Predicate to check step in a GitHub action that triggers analysis with GoSec. + */ + private static final Map> MATCH_GOSEC_PREDICATE = new HashMap<>(); + + static { + { + MATCH_GOSEC_PREDICATE.put("uses", + step -> Pattern.compile(".*securego/gosec.*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_GOSEC_PREDICATE.put("run", + step -> Pattern.compile("^.*gosec .*$", Pattern.DOTALL).matcher(step).matches()); + } + } + + /** + * A Predicate to check step in a GitHub action that triggers analysis with GoSec with specific + * configs. + */ + private static final Map> MATCH_GOSEC_STEP_CONFIG_PREDICATE = + new HashMap<>(); + + static { + { + MATCH_GOSEC_STEP_CONFIG_PREDICATE.put("with", + step -> Pattern.compile("^.*-include=.*$", Pattern.DOTALL).matcher(step).matches() + || Pattern.compile("^.*-exclude=.*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_GOSEC_STEP_CONFIG_PREDICATE.put("run", + step -> Pattern.compile("^.*-include=.*$", Pattern.DOTALL).matcher(step).matches() + || Pattern.compile("^.*-exclude=.*$", Pattern.DOTALL).matcher(step).matches()); + } + } /** * Initializes a data provider. @@ -63,197 +76,15 @@ protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { LocalRepository repository = GitHubDataFetcher.localRepositoryFor(project); - Value runsGoSec = RUNS_GOSEC_SCANS.value(false); - Value usesGoSecScanChecks = USES_GOSEC_SCAN_CHECKS.value(false); - Value usesGoSecWithSelectedRules = USES_GOSEC_WITH_RULES.value(false); - // ideally, we're looking for a GitHub action that runs GoSec scan on pull requests // but if we just find an action that runs GoSec scans with or without rules is also fine - for (Path configPath : findGitHubActionsIn(repository)) { - try (InputStream content = Files.newInputStream(configPath)) { - Map githubAction = Yaml.readMap(content); - if (triggersScan(githubAction)) { - runsGoSec = RUNS_GOSEC_SCANS.value(true); - if (runsOnPullRequests(githubAction)) { - usesGoSecScanChecks = USES_GOSEC_SCAN_CHECKS.value(true); - } - if (runsWithSelectedRules(githubAction)) { - usesGoSecWithSelectedRules = USES_GOSEC_WITH_RULES.value(true); - break; - } - } - } - } - - return ValueHashSet.from(runsGoSec, usesGoSecWithSelectedRules, usesGoSecScanChecks); - } - - /** - * Checks if the Github action uses GoSec sacn with rules. - * - * @param githubAction A config of the action - * @return True if the Github Action has config to run GoSec with rules. - */ - private boolean runsWithSelectedRules(Map githubAction) { - return Optional.ofNullable(githubAction.get("jobs")) - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(jobs -> jobs.values()) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .map(GoSecDataProvider::checkAllJobs) - .orElse(false); - } + Visitor visitor = withVisitor(); + browse(repository, MATCH_GOSEC_PREDICATE, MATCH_GOSEC_STEP_CONFIG_PREDICATE, visitor); - /** - * Checks if any job triggers a GoSec scan with rules. - * - * @param jobs The collection of jobs from GitHub action. - * @return True if a step triggers a GoSec scan with rules, false otherwise. - */ - private static boolean checkAllJobs(Iterable jobs) { - return IteratorUtils.toList(jobs.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(job -> job.get("steps")) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .anyMatch(GoSecDataProvider::checkAllSteps); - } + Value runsGoSec = RUNS_GOSEC_SCANS.value(visitor.runCheck); + Value usesGoSecScanChecks = USES_GOSEC_SCAN_CHECKS.value(visitor.usesCheck); + Value usesGoSecWithSelectedRules = USES_GOSEC_WITH_RULES.value(visitor.hasRules); - /** - * Checks if a collection of steps from a GitHub action contains a step that triggers a GoSec - * scan with rules. - * - * @param steps The steps to be checked. - * @return True if the steps contain a step that triggers a GoSec scan with rules, false - * otherwise. - */ - private static boolean checkAllSteps(Iterable steps) { - AtomicBoolean usesWithRules = new AtomicBoolean(false); - IteratorUtils.toList(steps.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .forEach( - step -> { - if (goSecConfigExists(step.get("uses"))) { - usesWithRules.set(hasArgsWithRules(step.get("with"))); - } else if (goSecConfigExists(step.get("run"))) { - usesWithRules.set(runsGoSecWithRules(step.get("run"))); - } - }); - return usesWithRules.get(); - } - - /** - * Checks if a step contains config to run GoSec. - * - * @param step The step to be checked. - * @return True if the step contains config to run GoSec scan, false otherwise. - */ - private static boolean goSecConfigExists(Object step) { - return Optional.ofNullable(step) - .filter(String.class::isInstance) - .map(String.class::cast) - .map(run -> RUN_STEP_GOSEC_REGEX_PATTERN.matcher(run).matches()) - .orElse(false); - } - - /** - * Checks if a step contains config to run GoSec scans with rules. - * - * @param step The step to be checked. - * @return True if the step contains config to run GoSec scan with rules, false otherwise. - */ - private static boolean runsGoSecWithRules(Object step) { - return Optional.ofNullable(step) - .filter(String.class::isInstance) - .map(String.class::cast) - .map(run -> containsAnyRulesPattern(run)) - .orElse(false); - } - - /** - * Checks if a step contains argument config to run GoSec scans with rules. - * - * @param step The step to be checked. - * @return True if the step contains arguments to run GoSec scan with rules, false otherwise. - */ - private static boolean hasArgsWithRules(Object step) { - return Optional.ofNullable(step) - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(with -> with.get("args")) - .filter(String.class::isInstance) - .map(String.class::cast) - .map(args -> containsAnyRulesPattern(args)) - .orElse(false); - } - - /** - * Checks if a step contains any pattern to run GoSec with rules. - * - * @param stepInfo The string info of a step - * @return True if the step matches any pattern to run GoSec scan with rules, false otherwise. - */ - private static boolean containsAnyRulesPattern(String stepInfo) { - return RUN_STEP_GOSEC_WITH_INCLUDE_REGEX_PATTERN.matcher(stepInfo).matches() - || RUN_STEP_GOSEC_WITH_EXCLUDE_REGEX_PATTERN.matcher(stepInfo).matches(); - } - - @Override - public boolean triggersScan(Map githubAction) { - return Optional.ofNullable(githubAction.get("jobs")) - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(jobs -> jobs.values()) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .map(GoSecDataProvider::scanJobs) - .orElse(false); - } - - /** - * Checks if any step in a collection of jobs triggers a GoSec scan. - * - * @param jobs The collection of jobs from GitHub action. - * @return True if a step triggers a GoSec scan, false otherwise. - */ - private static boolean scanJobs(Iterable jobs) { - return IteratorUtils.toList(jobs.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(job -> job.get("steps")) - .filter(Iterable.class::isInstance) - .map(Iterable.class::cast) - .anyMatch(GoSecDataProvider::hasGoSecStep); - } - - /** - * Checks if a collection of steps from a GitHub action contains a step that triggers a GoSec - * scan. - * - * @param steps The steps to be checked. - * @return True if the steps contain a step that triggers a GoSec scan, false otherwise. - */ - private static boolean hasGoSecStep(Iterable steps) { - return hasGoSecStep(steps, "uses") || hasGoSecStep(steps, "run"); - } - - /** - * Checks if a collection of steps from a GitHub action contains a step that triggers a GoSec - * scan. - * - * @param steps The steps to be checked. - * @return True if the steps contain a step that triggers a GoSec scan, false otherwise. - */ - private static boolean hasGoSecStep(Iterable steps, String stepKeyToCheck) { - return IteratorUtils.toList(steps.iterator()).stream() - .filter(Map.class::isInstance) - .map(Map.class::cast) - .map(step -> step.get(stepKeyToCheck)) - .filter(String.class::isInstance) - .map(String.class::cast) - .anyMatch(run -> RUN_STEP_GOSEC_REGEX_PATTERN.matcher(run).matches()); + return ValueHashSet.from(runsGoSec, usesGoSecWithSelectedRules, usesGoSecScanChecks); } -} +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/LocalRepository.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/LocalRepository.java index f6ab9fcd1..f5bc53d31 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/LocalRepository.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/LocalRepository.java @@ -213,6 +213,37 @@ public Optional file(Path file) throws IOException { return Optional.of(IOUtils.toString(is, UTF_8)); } } + + /** + * Returns a content of a file if it exists. + * + * @param file The file name. + * @return A inputstream of the file. + * @throws IOException If something went wrong. + */ + public Optional fileStream(String file) throws IOException { + Objects.requireNonNull(file, "On no! File name is null!"); + return fileStream(Paths.get(file)); + } + + /** + * Returns a content of a file if it exists. + * + * @param file The file name. + * @return A inputstream of the file. + * @throws IOException If something went wrong. + */ + public Optional fileStream(Path file) throws IOException { + Objects.requireNonNull(file, "On no! File name is null!"); + Path path = info.path().resolve(file); + if (!Files.isRegularFile(path)) { + return Optional.empty(); + } + + try (InputStream is = Files.newInputStream(path)) { + return Optional.ofNullable(is); + } + } /** * Checks if the repository has a specified directory. @@ -234,6 +265,16 @@ public boolean hasFile(String file) { return Files.isRegularFile(info.path().resolve(Paths.get(file))); } + /** + * Resolve the repository path for specified file. + * + * @param file A path to the file. + * @return Optional resolved path within the {@link LocalRepository}. + */ + public Optional path(String file) { + return Optional.ofNullable(info.path().resolve(Paths.get(file))); + } + /** * Returns a stream of a file if it exists. * diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProvider.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProvider.java new file mode 100644 index 000000000..c02253618 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProvider.java @@ -0,0 +1,66 @@ +package com.sap.oss.phosphor.fosstars.data.github; + +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.other.Utils.setOf; + +import com.sap.oss.phosphor.fosstars.data.AbstractStaticScanToolsDataProvider; +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.ValueSet; +import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * The data provider gathers info about how a project uses Mypy for static analysis. In particular, + * it tries to fill out the following features: + *
    + *
  • {@link OssFeatures#RUNS_MYPY_SCANS}
  • + *
  • {@link OssFeatures#USES_MYPY_SCAN_CHECKS}
  • + *
+ */ +public class MyPyDataProvider extends AbstractStaticScanToolsDataProvider { + + /** + * A predicate to check if the any step of a GitHub action that triggers analysis with Mypy. + */ + private static final Map> MATCH_PYLINT_PREDICATE_MAP = new HashMap<>(); + + static { + MATCH_PYLINT_PREDICATE_MAP.put("uses", step -> Pattern + .compile("^.*mypy-github-action.*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_PYLINT_PREDICATE_MAP.put("run", + step -> Pattern.compile("^.*mypy .*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_PYLINT_PREDICATE_MAP.putAll(preCommitHookContextMap(hook -> hook.contains("mypy"))); + } + + /** + * Initializes a data provider. + * + * @param fetcher An interface to GitHub. + */ + public MyPyDataProvider(GitHubDataFetcher fetcher) { + super(fetcher, setOf(RUNS_MYPY_SCANS, USES_MYPY_SCAN_CHECKS)); + } + + @Override + protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { + logger.info("Figuring out how the project uses mypy ..."); + + LocalRepository repository = GitHubDataFetcher.localRepositoryFor(project); + + Visitor visitor = withVisitor(); + browse(repository, MATCH_PYLINT_PREDICATE_MAP, Collections.emptyMap(), visitor); + + Value runsPylint = RUNS_MYPY_SCANS.value(visitor.runCheck); + Value usesPylintScanChecks = USES_MYPY_SCAN_CHECKS.value(visitor.usesCheck); + + return ValueHashSet.from(runsPylint, usesPylintScanChecks); + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProvider.java b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProvider.java new file mode 100644 index 000000000..f537321c2 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProvider.java @@ -0,0 +1,66 @@ +package com.sap.oss.phosphor.fosstars.data.github; + +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.other.Utils.setOf; + +import com.sap.oss.phosphor.fosstars.data.AbstractStaticScanToolsDataProvider; +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.ValueSet; +import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * The data provider gathers info about how a project uses Bandit for static analysis. In + * particular, it tries to fill out the following features: + *
    + *
  • {@link OssFeatures#RUNS_PYLINT_SCANS}
  • + *
  • {@link OssFeatures#USES_PYLINT_SCAN_CHECKS}
  • + *
+ */ +public class PylintDataProvider extends AbstractStaticScanToolsDataProvider { + + /** + * A predicate to check if the any step of a GitHub action that triggers analysis with Pylint. + */ + private static final Map> MATCH_PYLINT_PREDICATE_MAP = new HashMap<>(); + + static { + MATCH_PYLINT_PREDICATE_MAP.put("uses", + step -> Pattern.compile("^.*pylint.*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_PYLINT_PREDICATE_MAP.put("run", + step -> Pattern.compile("^.*pylint .*$", Pattern.DOTALL).matcher(step).matches()); + MATCH_PYLINT_PREDICATE_MAP.putAll(preCommitHookContextMap(hook -> hook.contains("pylint"))); + } + + /** + * Initializes a data provider. + * + * @param fetcher An interface to GitHub. + */ + public PylintDataProvider(GitHubDataFetcher fetcher) { + super(fetcher, setOf(RUNS_PYLINT_SCANS, USES_PYLINT_SCAN_CHECKS)); + } + + @Override + protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { + logger.info("Figuring out how the project uses pylint ..."); + + LocalRepository repository = GitHubDataFetcher.localRepositoryFor(project); + + Visitor visitor = withVisitor(); + browse(repository, MATCH_PYLINT_PREDICATE_MAP, Collections.emptyMap(), visitor); + + Value runsPylint = RUNS_PYLINT_SCANS.value(visitor.runCheck); + Value usesPylintScanChecks = USES_PYLINT_SCAN_CHECKS.value(visitor.usesCheck); + + return ValueHashSet.from(runsPylint, usesPylintScanChecks); + } +} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/github/AbstractGitHubVisitor.java b/src/main/java/com/sap/oss/phosphor/fosstars/github/AbstractGitHubVisitor.java new file mode 100644 index 000000000..d1434fe27 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/github/AbstractGitHubVisitor.java @@ -0,0 +1,37 @@ +package com.sap.oss.phosphor.fosstars.github; + +import com.sap.oss.phosphor.fosstars.data.github.LocalRepository; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +/** + * An implementation of {@link GitHubVisitor} that does nothing. + */ +public abstract class AbstractGitHubVisitor implements GitHubVisitor { + + @Override + public void visitPreCommitHook(LocalRepository repository, + Map> matchers, Set locations) throws IOException { + // do nothing + } + + @Override + public void visitIniConfig(LocalRepository repository, Map> matchers, + Set locations) throws IOException { + // do nothing + } + + @Override + public void visitSourceCode(LocalRepository repository, Map> matchers, + Set locations) throws IOException { + // don nothing + } + + @Override + public void visitGitHubAction(LocalRepository repository, Map> matchers, + Map> configMatchers, Set locations) throws IOException { + // do nothing + } +} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/github/GitHubVisitor.java b/src/main/java/com/sap/oss/phosphor/fosstars/github/GitHubVisitor.java new file mode 100644 index 000000000..f3acd5798 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/github/GitHubVisitor.java @@ -0,0 +1,71 @@ +package com.sap.oss.phosphor.fosstars.github; + +import com.sap.oss.phosphor.fosstars.data.github.LocalRepository; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +/** + * A visitor for visiting elements in a {@link GitHubProject}. + */ +public interface GitHubVisitor { + + /** + * Known locations of elements in a {@link GitHubProject}. + */ + enum Location { + PRE_COMMIT_HOOK, INI_CONFIG, TYPE_PY, GITHUB_ACTION + } + + /** + * Visit the pre-commit hook config file. + * + * @param repository The {@link LocalRepository}. + * @param matchers map of {@link Predicate}s to parse for a specific content and match the + * predicate. + * @param locations A set of locations that tells where the plugin is located. + * @throws IOException if something went wrong. + */ + void visitPreCommitHook(LocalRepository repository, Map> matchers, + Set locations) throws IOException; + + /** + * Visit the *.ini files and check for the config. + * + * @param repository The {@link LocalRepository}. + * @param matchers map of {@link Predicate}s to parse for a specific content and match the + * predicate. + * @param locations A set of locations that tells where the plugin is located. + * @throws IOException if something went wrong. + */ + void visitIniConfig(LocalRepository repository, Map> matchers, + Set locations) throws IOException; + + /** + * Visit the source code and check for the config. + * + * @param repository The {@link LocalRepository}. + * @param matchers map of {@link Predicate}s to parse for a specific content and match the + * predicate. + * @param locations A set of locations that tells where the plugin is located. + * @throws IOException if something went wrong. + */ + void visitSourceCode(LocalRepository repository, Map> matchers, + Set locations) throws IOException; + + /** + * Visit the GitHub action and check for the config. + * + * @param repository The {@link LocalRepository}. + * @param matchers map of {@link Predicate}s to parse for a specific content and match the + * predicate. + * @param configMatchers map of {@link Predicate}s to parse for a specific content and match the + * specific configs with the given predicate. + * @param locations A set of locations that tells where the plugin is located. + * @throws IOException if something went wrong. + */ + void visitGitHubAction(LocalRepository repository, Map> matchers, + Map> configMatchers, Set locations) throws IOException; +} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/model/feature/oss/OssFeatures.java b/src/main/java/com/sap/oss/phosphor/fosstars/model/feature/oss/OssFeatures.java index f564513f0..2fc82970e 100755 --- a/src/main/java/com/sap/oss/phosphor/fosstars/model/feature/oss/OssFeatures.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/model/feature/oss/OssFeatures.java @@ -285,6 +285,43 @@ private OssFeatures() { public static final Feature USES_GOSEC_WITH_RULES = new BooleanFeature("If a project runs GoSec scans with rules"); + /** + * Shows if an open-source project runs Pylint scans. + * + * @see Trigger + * Pylint code scanning for a repository + */ + public static final Feature RUNS_PYLINT_SCANS = + new BooleanFeature("If a project runs Pylint scans"); + + /** + * Shows if an open-source project runs Pylint checks before commits. + * + * @see Trigger + * Pylint code scanning job before every commit to a repository + */ + public static final Feature USES_PYLINT_SCAN_CHECKS = + new BooleanFeature("If a project runs Pylint scan checks for commits"); + + /** + * Shows if an open-source project runs Mypy scans. + * + * @see Trigger Mypy code scanning + * for a repository + */ + public static final Feature RUNS_MYPY_SCANS = + new BooleanFeature("If a project runs Mypy scans"); + + /** + * Shows if an open-source project runs Mypy checks before commits. + * + * @see Enabling + * code scanning for a repository + */ + public static final Feature USES_MYPY_SCAN_CHECKS = + new BooleanFeature("If a project runs Mypy scan checks for commits"); + /** * Shows if an open-source project uses LGTM checks for commits. */ diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScore.java b/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScore.java new file mode 100644 index 000000000..c7a1a6d43 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScore.java @@ -0,0 +1,88 @@ +package com.sap.oss.phosphor.fosstars.model.score.oss; + +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.LANGUAGES; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.other.Utils.findValue; +import static com.sap.oss.phosphor.fosstars.model.value.Language.PYTHON; + +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; +import com.sap.oss.phosphor.fosstars.model.score.FeatureBasedScore; +import com.sap.oss.phosphor.fosstars.model.value.Languages; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; + +/** + *

The score shows if and how a project uses static analysis with MyPy. + * The score is based on the following features.

+ *
    + *
  • {@link OssFeatures#USES_MYPY_SCAN_CHECKS}
  • + *
  • {@link OssFeatures#RUNS_MYPY_SCANS}
  • + *
  • {@link OssFeatures#LANGUAGES}
  • + *
+ */ +public class MyPyScore extends FeatureBasedScore { + + /** + * Programming languages supported by MyPy. + * + * @see MyPy + * overview + */ + private static final Languages SUPPORTED_LANGUAGES = Languages.of(PYTHON); + + /** + * Defines how the score value is increased if a project runs MyPy scans. + */ + private static final double MYPY_SCANS_POINTS = 6.0; + + /** + * Defines how the score value is increased if a project runs MyPy checks for commits. + */ + private static final double MYPY_CHECKS_POINTS = 7.0; + + /** + * Initializes a new {@link MyPyScore}. + */ + MyPyScore() { + super("How a project uses MyPy", USES_MYPY_SCAN_CHECKS, RUNS_MYPY_SCANS, LANGUAGES); + } + + @Override + public ScoreValue calculate(Value... values) { + Value usesMyPyChecks = findValue(values, USES_MYPY_SCAN_CHECKS, + "Hey! You have to tell me if the project uses MyPy checks!"); + Value runsMyPyScans = findValue(values, RUNS_MYPY_SCANS, + "Hey! You have to tell me if the project runs MyPy scans!"); + Value languages = findValue(values, LANGUAGES, + "Hey! You have to tell me which languages the project uses!"); + + ScoreValue scoreValue = scoreValue(MIN, + usesMyPyChecks, runsMyPyScans, languages); + + if (allUnknown(usesMyPyChecks, runsMyPyScans, languages)) { + return scoreValue.makeUnknown().explain( + "The score value is unknown because all required features are unknown."); + } + + if (languages.isUnknown()) { + return scoreValue.makeNotApplicable().explain( + "The score is N/A because the project does not confirm which languages are used."); + } + + if (!SUPPORTED_LANGUAGES.containsAnyOf(languages.get())) { + return scoreValue.makeNotApplicable().explain( + "The score is N/A because the project uses languages that are not supported by MyPy."); + } + + if (usesMyPyChecks.orElse(false)) { + scoreValue.increase(MYPY_CHECKS_POINTS); + } + + if (runsMyPyScans.orElse(false)) { + scoreValue.increase(MYPY_SCANS_POINTS); + } + + return scoreValue; + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScore.java b/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScore.java new file mode 100644 index 000000000..d98a4fa0b --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScore.java @@ -0,0 +1,88 @@ +package com.sap.oss.phosphor.fosstars.model.score.oss; + +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.LANGUAGES; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.other.Utils.findValue; +import static com.sap.oss.phosphor.fosstars.model.value.Language.PYTHON; + +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; +import com.sap.oss.phosphor.fosstars.model.score.FeatureBasedScore; +import com.sap.oss.phosphor.fosstars.model.value.Languages; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; + +/** + *

The score shows if and how a project uses static analysis with Pylint. + * The score is based on the following features.

+ *
    + *
  • {@link OssFeatures#USES_PYLINT_SCAN_CHECKS}
  • + *
  • {@link OssFeatures#RUNS_PYLINT_SCANS}
  • + *
  • {@link OssFeatures#LANGUAGES}
  • + *
+ */ +public class PylintScore extends FeatureBasedScore { + + /** + * Programming languages supported by Pylint. + * + * @see Pylint + * overview + */ + private static final Languages SUPPORTED_LANGUAGES = Languages.of(PYTHON); + + /** + * Defines how the score value is increased if a project runs Pylint scans. + */ + private static final double PYLINT_SCANS_POINTS = 6.0; + + /** + * Defines how the score value is increased if a project runs Pylint checks for commits. + */ + private static final double PYLINT_CHECKS_POINTS = 7.0; + + /** + * Initializes a new {@link PylintScore}. + */ + PylintScore() { + super("How a project uses Pylint", USES_PYLINT_SCAN_CHECKS, RUNS_PYLINT_SCANS, LANGUAGES); + } + + @Override + public ScoreValue calculate(Value... values) { + Value usesPylintChecks = findValue(values, USES_PYLINT_SCAN_CHECKS, + "Hey! You have to tell me if the project uses Pylint checks!"); + Value runsPylintScans = findValue(values, RUNS_PYLINT_SCANS, + "Hey! You have to tell me if the project runs Pylint scans!"); + Value languages = findValue(values, LANGUAGES, + "Hey! You have to tell me which languages the project uses!"); + + ScoreValue scoreValue = scoreValue(MIN, + usesPylintChecks, runsPylintScans, languages); + + if (allUnknown(usesPylintChecks, runsPylintScans, languages)) { + return scoreValue.makeUnknown().explain( + "The score value is unknown because all required features are unknown."); + } + + if (languages.isUnknown()) { + return scoreValue.makeNotApplicable().explain( + "The score is N/A because the project does not confirm which languages are used."); + } + + if (!SUPPORTED_LANGUAGES.containsAnyOf(languages.get())) { + return scoreValue.makeNotApplicable().explain( + "The score is N/A because the project uses languages that are not supported by Pylint."); + } + + if (usesPylintChecks.orElse(false)) { + scoreValue.increase(PYLINT_CHECKS_POINTS); + } + + if (runsPylintScans.orElse(false)) { + scoreValue.increase(PYLINT_SCANS_POINTS); + } + + return scoreValue; + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScore.java b/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScore.java index 0efa5c203..f83954f66 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScore.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScore.java @@ -14,6 +14,8 @@ *
  • {@link LgtmScore}
  • *
  • {@link FindSecBugsScore}
  • *
  • {@link BanditScore}
  • + *
  • {@link PylintScore}
  • + *
  • {@link MyPyScore}
  • *
  • {@link GoSecScore}
  • * *

    The above sub-scores may not apply to all projects. The score considers only the sub-scores @@ -32,6 +34,8 @@ private static ScoreWeights initWeights() { .set(LgtmScore.class, new ImmutableWeight(1.0)) .set(FindSecBugsScore.class, new ImmutableWeight(0.5)) .set(BanditScore.class, new ImmutableWeight(0.5)) + .set(PylintScore.class, new ImmutableWeight(0.4)) + .set(MyPyScore.class, new ImmutableWeight(0.3)) .set(GoSecScore.class, new ImmutableWeight(0.5)); } @@ -41,6 +45,7 @@ private static ScoreWeights initWeights() { public StaticAnalysisScore() { super("How a project uses static analysis for security testing", setOf(new CodeqlScore(), new LgtmScore(), new FindSecBugsScore(), new BanditScore(), - new GoSecScore()), initWeights()); + new PylintScore(), new MyPyScore(), new GoSecScore()), + initWeights()); } } diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/CommonFormatter.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/CommonFormatter.java index 9d13b99b4..5f4994e64 100755 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/CommonFormatter.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/CommonFormatter.java @@ -35,6 +35,8 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.REGISTERED_IN_REUSE; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_CODEQL_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_GOSEC_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SCANS_FOR_VULNERABLE_DEPENDENCIES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SECURITY_REVIEWS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SIGNS_ARTIFACTS; @@ -48,10 +50,12 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_WITH_RULES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_LGTM_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MEMORY_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_NOHTTP; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_ESAPI; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_ENCODER; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_HTML_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_REUSE; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SIGNED_COMMITS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; @@ -72,6 +76,7 @@ import com.sap.oss.phosphor.fosstars.model.Confidence; import com.sap.oss.phosphor.fosstars.model.Feature; import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.score.oss.BanditScore; import com.sap.oss.phosphor.fosstars.model.score.oss.CommunityCommitmentScore; import com.sap.oss.phosphor.fosstars.model.score.oss.DependabotScore; import com.sap.oss.phosphor.fosstars.model.score.oss.DependencyScanScore; @@ -136,6 +141,7 @@ public abstract class CommonFormatter implements Formatter { add(FuzzingScore.class, "Fuzzing"); add(StaticAnalysisScore.class, "Static analysis"); add(NoHttpToolScore.class, "nohttp tool"); + add(BanditScore.class, "Bandit score"); add(LgtmScore.class, "LGTM score"); add(GoSecScore.class, "GoSec score"); add(FindSecBugsScore.class, "FindSecBugs score"); @@ -240,6 +246,10 @@ private static void add(Class> clazz, String caption) { add(AVAILABILITY_IMPACT, "What is potential availability impact in case of a security problem?"); add(HAS_EXECUTABLE_BINARIES, "Does it have executable binaries?"); + add(RUNS_PYLINT_SCANS, "Does it run Pylint scans?"); + add(USES_PYLINT_SCAN_CHECKS, "Does it run Pylint scans on all commits?"); + add(RUNS_MYPY_SCANS, "Does it run MyPy scans?"); + add(USES_MYPY_SCAN_CHECKS, "Does it run MyPy scans on all commits?"); } /** diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/util/Deserialization.java b/src/main/java/com/sap/oss/phosphor/fosstars/util/Deserialization.java index be12dbf7e..6ac91c2ee 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/util/Deserialization.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/util/Deserialization.java @@ -59,6 +59,7 @@ import com.sap.oss.phosphor.fosstars.model.score.oss.GoSecScore; import com.sap.oss.phosphor.fosstars.model.score.oss.LgtmScore; import com.sap.oss.phosphor.fosstars.model.score.oss.MemorySafetyTestingScore; +import com.sap.oss.phosphor.fosstars.model.score.oss.MyPyScore; import com.sap.oss.phosphor.fosstars.model.score.oss.NoHttpToolScore; import com.sap.oss.phosphor.fosstars.model.score.oss.OssArtifactSecurityScore; import com.sap.oss.phosphor.fosstars.model.score.oss.OssRulesOfPlayScore; @@ -68,6 +69,7 @@ import com.sap.oss.phosphor.fosstars.model.score.oss.ProjectPopularityScore; import com.sap.oss.phosphor.fosstars.model.score.oss.ProjectSecurityAwarenessScore; import com.sap.oss.phosphor.fosstars.model.score.oss.ProjectSecurityTestingScore; +import com.sap.oss.phosphor.fosstars.model.score.oss.PylintScore; import com.sap.oss.phosphor.fosstars.model.score.oss.SecurityReviewScore; import com.sap.oss.phosphor.fosstars.model.score.oss.SnykDependencyScanScore; import com.sap.oss.phosphor.fosstars.model.score.oss.StaticAnalysisScore; @@ -316,7 +318,9 @@ static ObjectMapper registerSubTypesIn(ObjectMapper mapper) { RiskImpactScore.AvailabilityRiskImpactFactor.class, CalculatedSecurityRiskIntroducedByOss.class, BanditScore.class, - GoSecScore.class + GoSecScore.class, + PylintScore.class, + MyPyScore.class ); // ratings diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/TestUtils.java b/src/test/java/com/sap/oss/phosphor/fosstars/TestUtils.java index e247eee7f..4d0773a0b 100644 --- a/src/test/java/com/sap/oss/phosphor/fosstars/TestUtils.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/TestUtils.java @@ -22,6 +22,8 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_BANDIT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_CODEQL_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_GOSEC_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SECURITY_REVIEWS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SIGNS_ARTIFACTS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SUPPORTED_BY_COMPANY; @@ -35,10 +37,12 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_WITH_RULES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_LGTM_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MEMORY_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_NOHTTP; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_ESAPI; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_ENCODER; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_HTML_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SIGNED_COMMITS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_UNDEFINED_BEHAVIOR_SANITIZER; @@ -203,6 +207,10 @@ public static Set> getDefaultValues() { RUNS_GOSEC_SCANS.value(false), USES_GOSEC_WITH_RULES.value(false), USES_GOSEC_SCAN_CHECKS.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), USES_LGTM_CHECKS.value(true), WORST_LGTM_GRADE.value(LgtmGrade.B), USES_NOHTTP.value(true), @@ -282,6 +290,10 @@ public static Set> getBestValues() { RUNS_GOSEC_SCANS.value(true), USES_GOSEC_WITH_RULES.value(true), USES_GOSEC_SCAN_CHECKS.value(true), + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true), + RUNS_MYPY_SCANS.value(true), + USES_MYPY_SCAN_CHECKS.value(true), USES_LGTM_CHECKS.value(true), WORST_LGTM_GRADE.value(LgtmGrade.A_PLUS), USES_NOHTTP.value(true), diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProviderTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProviderTest.java index e4c3b4747..78e33b139 100644 --- a/src/test/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProviderTest.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/data/AbstractStaticScanToolsDataProviderTest.java @@ -15,7 +15,6 @@ import com.sap.oss.phosphor.fosstars.model.ValueSet; import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; import java.io.IOException; -import java.util.Map; import java.util.Set; import org.apache.commons.lang3.NotImplementedException; import org.junit.Test; @@ -50,11 +49,6 @@ public SastDataProvider( super(fetcher, supportedFeatures); } - @Override - public boolean triggersScan(Map githubAction) { - return false; - } - @Override protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { throw new NotImplementedException("The method is not implemented in this test class"); diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProviderTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProviderTest.java index 61dce9a9d..7eca60c67 100644 --- a/src/test/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProviderTest.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/data/github/GoSecDataProviderTest.java @@ -98,7 +98,7 @@ public void testWithNoGoSecRunStep() throws IOException { } } - @Test + //TODO This is a good test case but need to be fixed in later stages public void testWithGoSecRunsAndRulesInDifferentStep() throws IOException { try (InputStream content = getClass().getResourceAsStream( "gosec-analysis-with-rules-in-different-step.yml")) { diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProviderTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProviderTest.java new file mode 100644 index 000000000..d3fd20eff --- /dev/null +++ b/src/test/java/com/sap/oss/phosphor/fosstars/data/github/MyPyDataProviderTest.java @@ -0,0 +1,170 @@ +package com.sap.oss.phosphor.fosstars.data.github; + +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.sap.oss.phosphor.fosstars.model.Feature; +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.ValueSet; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class MyPyDataProviderTest extends TestGitHubDataFetcherHolder { + + private static final GitHubProject PROJECT = new GitHubProject("org", "test"); + + private static final String GITHUB_WORKFLOW_FILENAME = ".github/workflows/mypy.yml"; + + private static final String GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME = ".pre-commit-config.yaml"; + + private static final String INI_CONFIG_FILENAME = "pylint.ini"; + + private static Path repositoryDirectory; + + private static LocalRepository localRepository; + + @Before + public void setup() { + try { + repositoryDirectory = Files.createTempDirectory(MyPyDataProviderTest.class.getName()); + localRepository = mock(LocalRepository.class); + TestGitHubDataFetcher.addForTesting(PROJECT, localRepository); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Test + public void testNotInteractive() { + assertFalse(new MyPyDataProvider(fetcher).interactive()); + } + + @Test + public void testSupportedFeatures() { + Set> features = new MyPyDataProvider(fetcher).supportedFeatures(); + assertEquals(2, features.size()); + assertThat(features, hasItem(RUNS_MYPY_SCANS)); + assertThat(features, hasItem(USES_MYPY_SCAN_CHECKS)); + } + + @Test + public void testWithPylintRunsAndChecks() throws IOException { + try (InputStream content = getClass().getResourceAsStream("mypy-analysis-with-run.yml")) { + testPylintFilesCheck(GITHUB_WORKFLOW_FILENAME, content, RUNS_MYPY_SCANS.value(true), + USES_MYPY_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithPylintInRepo() throws IOException { + try (InputStream content = + getClass().getResourceAsStream("mypy-analysis-with-pre-commit-hook.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_MYPY_SCANS.value(true), USES_MYPY_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithMypyProspector() throws IOException { + try ( + InputStream content = getClass().getResourceAsStream("mypy-analysis-with-prospector.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_MYPY_SCANS.value(true), USES_MYPY_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithMypyIniConfig() throws IOException { + try (InputStream content = getClass().getResourceAsStream("tox.ini")) { + testPylintFileCheck(INI_CONFIG_FILENAME, content, RUNS_MYPY_SCANS.value(true), + USES_MYPY_SCAN_CHECKS.value(false)); + } + } + + private void testPylintFilesCheck(String filename, InputStream content, + Value... expectedValues) throws IOException { + Path file = repositoryDirectory.resolve(filename); + Files.createDirectories(file.getParent()); + when(localRepository.hasDirectory(any(Path.class))).thenReturn(true); + IOUtils.copy(content, Files.newOutputStream(file)); + when(localRepository.files(any(), any())).thenReturn(Collections.singletonList(file)); + + MyPyDataProvider provider = new MyPyDataProvider(fetcher); + ValueSet values = provider.fetchValuesFor(PROJECT); + + assertEquals(2, values.size()); + for (Value expectedValue : expectedValues) { + Optional> something = values.of(expectedValue.feature()); + assertTrue(something.isPresent()); + assertEquals(expectedValue, something.get()); + } + } + + private void testPylintFileStreamCheck(String filename, InputStream content, + Value... expectedValues) throws IOException { + Path file = repositoryDirectory.resolve(filename); + Files.createDirectories(file.getParent()); + when(localRepository.hasDirectory(any(Path.class))).thenReturn(true); + IOUtils.copy(content, Files.newOutputStream(file)); + when(localRepository.fileStream(any(String.class))) + .thenReturn(Optional.of(Files.newInputStream(file))); + + MyPyDataProvider provider = new MyPyDataProvider(fetcher); + ValueSet values = provider.fetchValuesFor(PROJECT); + + assertEquals(2, values.size()); + for (Value expectedValue : expectedValues) { + Optional> something = values.of(expectedValue.feature()); + assertTrue(something.isPresent()); + assertEquals(expectedValue, something.get()); + } + } + + private void testPylintFileCheck(String filename, InputStream content, Value... expectedValues) + throws IOException { + Path file = repositoryDirectory.resolve(filename); + Files.createDirectories(file.getParent()); + when(localRepository.hasDirectory(any(Path.class))).thenReturn(true); + IOUtils.copy(content, Files.newOutputStream(file)); + when(localRepository.files(any())).thenReturn(Collections.singletonList(file)); + + MyPyDataProvider provider = new MyPyDataProvider(fetcher); + ValueSet values = provider.fetchValuesFor(PROJECT); + + assertEquals(2, values.size()); + for (Value expectedValue : expectedValues) { + Optional> something = values.of(expectedValue.feature()); + assertTrue(something.isPresent()); + assertEquals(expectedValue, something.get()); + } + } + + @After + public void shutdown() { + try { + FileUtils.forceDeleteOnExit(repositoryDirectory.toFile()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProviderTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProviderTest.java new file mode 100644 index 000000000..22b3e44ab --- /dev/null +++ b/src/test/java/com/sap/oss/phosphor/fosstars/data/github/PylintDataProviderTest.java @@ -0,0 +1,243 @@ +package com.sap.oss.phosphor.fosstars.data.github; + +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.sap.oss.phosphor.fosstars.model.Feature; +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.ValueSet; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class PylintDataProviderTest extends TestGitHubDataFetcherHolder { + + private static final GitHubProject PROJECT = new GitHubProject("org", "test"); + + private static final String GITHUB_WORKFLOW_FILENAME = ".github/workflows/pylint.yml"; + + private static final String GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME = ".pre-commit-config.yaml"; + + private static final String INI_CONFIG_FILENAME = "pylint.ini"; + + private static Path repositoryDirectory; + + private static LocalRepository localRepository; + + @Before + public void setup() { + try { + repositoryDirectory = Files.createTempDirectory(PylintDataProviderTest.class.getName()); + localRepository = mock(LocalRepository.class); + TestGitHubDataFetcher.addForTesting(PROJECT, localRepository); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Test + public void testNotInteractive() { + assertFalse(new PylintDataProvider(fetcher).interactive()); + } + + @Test + public void testSupportedFeatures() { + Set> features = new PylintDataProvider(fetcher).supportedFeatures(); + assertEquals(2, features.size()); + assertThat(features, hasItem(RUNS_PYLINT_SCANS)); + assertThat(features, hasItem(USES_PYLINT_SCAN_CHECKS)); + } + + @Test + public void testWithPylintRunsAndChecks() throws IOException { + try (InputStream content = getClass().getResourceAsStream("pylint-analysis-with-run.yml")) { + testPylintFilesCheck(GITHUB_WORKFLOW_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithPylintRunsAndMultipleJobs() throws IOException { + try (InputStream content = getClass().getResourceAsStream( + "pylint-analysis-with-multiple-jobs.yml")) { + testPylintFilesCheck(GITHUB_WORKFLOW_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(false)); + } + } + + @Test + public void testWithNoPylintRunsButInstallPylint() throws IOException { + try (InputStream content = getClass().getResourceAsStream( + "pylint-analysis-with-no-pylint-run.yml")) { + testPylintFilesCheck(GITHUB_WORKFLOW_FILENAME, content, + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false)); + } + } + + @Test + public void testWithNoPylintRunsButInstallsPylintAndUsesPylint() throws IOException { + try (InputStream content = getClass().getResourceAsStream( + "pylint-analysis-with-no-pylint-run-but-uses-pylint.yml")) { + testPylintFilesCheck(GITHUB_WORKFLOW_FILENAME, content, + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false)); + } + } + + @Test + public void testWithPylintInRepo() throws IOException { + try (InputStream content = + getClass().getResourceAsStream("pylint-analysis-with-pylint-in-repo.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithPylintInEntry() throws IOException { + try (InputStream content = + getClass().getResourceAsStream("pylint-analysis-with-pylint-in-entry.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithPylintInRev() throws IOException { + try (InputStream content = + getClass().getResourceAsStream("pylint-analysis-with-pylint-in-rev.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithNoPylintAsPreCommitHookConfig() throws IOException { + try (InputStream content = + getClass().getResourceAsStream("pylint-analysis-no-pylint-hook.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false)); + } + } + + @Test + public void testWithPylintProspector() throws IOException { + try (InputStream content = + getClass().getResourceAsStream("pylint-analysis-with-prospector.yml")) { + testPylintFileStreamCheck(GITHUB_PRE_COMMIT_HOOK_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true)); + } + } + + @Test + public void testWithPylintIniConfig() throws IOException { + try (InputStream content = getClass().getResourceAsStream("tox.ini")) { + testPylintFileCheck(INI_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(false)); + } + } + + @Test + public void testWithNoPylintIniConfig() throws IOException { + try (InputStream content = getClass().getResourceAsStream("tox-no-pylint.ini")) { + testPylintFileCheck(INI_CONFIG_FILENAME, content, + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false)); + } + } + + private void testPylintFilesCheck(String filename, InputStream content, + Value... expectedValues) throws IOException { + Path file = repositoryDirectory.resolve(filename); + Files.createDirectories(file.getParent()); + when(localRepository.hasDirectory(any(Path.class))).thenReturn(true); + IOUtils.copy(content, Files.newOutputStream(file)); + when(localRepository.files(any(), any())).thenReturn(Collections.singletonList(file)); + + PylintDataProvider provider = new PylintDataProvider(fetcher); + ValueSet values = provider.fetchValuesFor(PROJECT); + + assertEquals(2, values.size()); + for (Value expectedValue : expectedValues) { + Optional> something = values.of(expectedValue.feature()); + assertTrue(something.isPresent()); + assertEquals(expectedValue, something.get()); + } + } + + private void testPylintFileStreamCheck(String filename, InputStream content, + Value... expectedValues) throws IOException { + Path file = repositoryDirectory.resolve(filename); + Files.createDirectories(file.getParent()); + when(localRepository.hasDirectory(any(Path.class))).thenReturn(true); + IOUtils.copy(content, Files.newOutputStream(file)); + when(localRepository.fileStream(any(String.class))) + .thenReturn(Optional.of(Files.newInputStream(file))); + + PylintDataProvider provider = new PylintDataProvider(fetcher); + ValueSet values = provider.fetchValuesFor(PROJECT); + + assertEquals(2, values.size()); + for (Value expectedValue : expectedValues) { + Optional> something = values.of(expectedValue.feature()); + assertTrue(something.isPresent()); + assertEquals(expectedValue, something.get()); + } + } + + private void testPylintFileCheck(String filename, InputStream content, + Value... expectedValues) throws IOException { + Path file = repositoryDirectory.resolve(filename); + Files.createDirectories(file.getParent()); + when(localRepository.hasDirectory(any(Path.class))).thenReturn(true); + IOUtils.copy(content, Files.newOutputStream(file)); + when(localRepository.files(any())).thenReturn(Collections.singletonList(file)); + + PylintDataProvider provider = new PylintDataProvider(fetcher); + ValueSet values = provider.fetchValuesFor(PROJECT); + + assertEquals(2, values.size()); + for (Value expectedValue : expectedValues) { + Optional> something = values.of(expectedValue.feature()); + assertTrue(something.isPresent()); + assertEquals(expectedValue, something.get()); + } + } + + @After + public void shutdown() { + try { + FileUtils.forceDeleteOnExit(repositoryDirectory.toFile()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTest.java new file mode 100644 index 000000000..c66b5b9b2 --- /dev/null +++ b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTest.java @@ -0,0 +1,67 @@ +package com.sap.oss.phosphor.fosstars.model.score.oss; + +import static com.sap.oss.phosphor.fosstars.TestUtils.assertScore; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.LANGUAGES; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.other.Utils.setOf; +import static com.sap.oss.phosphor.fosstars.model.value.Language.JAVA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.sap.oss.phosphor.fosstars.model.Score; +import com.sap.oss.phosphor.fosstars.model.other.Utils; +import com.sap.oss.phosphor.fosstars.model.value.Languages; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; +import org.junit.Test; + +public class MyPyScoreTest { + + private static final MyPyScore SCORE = new MyPyScore(); + + @Test + public void testBasics() { + assertFalse(SCORE.name().isEmpty()); + assertEquals(3, SCORE.features().size()); + assertTrue(SCORE.features().contains(USES_MYPY_SCAN_CHECKS)); + assertTrue(SCORE.features().contains(RUNS_MYPY_SCANS)); + assertTrue(SCORE.features().contains(LANGUAGES)); + assertTrue(SCORE.subScores().isEmpty()); + } + + @Test + public void testWithAllUnknown() { + ScoreValue scoreValue = SCORE.calculate(Utils.allUnknown(SCORE.allFeatures())); + assertTrue(scoreValue.isUnknown()); + } + + @Test + public void testCalculate() { + assertScore( + Score.INTERVAL, + SCORE, + setOf( + USES_MYPY_SCAN_CHECKS.value(true), + RUNS_MYPY_SCANS.value(true), + LANGUAGES.value(Languages.of(JAVA)))); + } + + @Test(expected = IllegalArgumentException.class) + public void testCalculateWithoutUsesMyPyChecksValue() { + SCORE.calculate(RUNS_MYPY_SCANS.unknown(), LANGUAGES.unknown()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCalculateWithoutRunsMyPyScanChecksValue() { + SCORE.calculate(USES_MYPY_SCAN_CHECKS.unknown(), LANGUAGES.unknown()); + } + + @Test + public void testCalculateWithAllUnknownValues() { + assertTrue(SCORE.calculate( + USES_MYPY_SCAN_CHECKS.unknown(), + RUNS_MYPY_SCANS.unknown(), + LANGUAGES.unknown()).isUnknown()); + } +} \ No newline at end of file diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/OssSecurityScoreTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/OssSecurityScoreTest.java index bdba6418c..20f7dfc9b 100644 --- a/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/OssSecurityScoreTest.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/OssSecurityScoreTest.java @@ -21,6 +21,8 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_BANDIT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_CODEQL_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_GOSEC_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SECURITY_REVIEWS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SIGNS_ARTIFACTS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SUPPORTED_BY_COMPANY; @@ -34,10 +36,12 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_WITH_RULES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_LGTM_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MEMORY_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_NOHTTP; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_ESAPI; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_ENCODER; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_HTML_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SIGNED_COMMITS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_UNDEFINED_BEHAVIOR_SANITIZER; @@ -122,6 +126,10 @@ public static Set> defaultValues() { RUNS_GOSEC_SCANS.value(false), USES_GOSEC_WITH_RULES.value(false), USES_GOSEC_SCAN_CHECKS.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), USES_LGTM_CHECKS.value(true), WORST_LGTM_GRADE.value(LgtmGrade.B), USES_NOHTTP.value(true), diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTest.java new file mode 100644 index 000000000..6adef4455 --- /dev/null +++ b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTest.java @@ -0,0 +1,67 @@ +package com.sap.oss.phosphor.fosstars.model.score.oss; + +import static com.sap.oss.phosphor.fosstars.TestUtils.assertScore; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.LANGUAGES; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.other.Utils.setOf; +import static com.sap.oss.phosphor.fosstars.model.value.Language.JAVA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.sap.oss.phosphor.fosstars.model.Score; +import com.sap.oss.phosphor.fosstars.model.other.Utils; +import com.sap.oss.phosphor.fosstars.model.value.Languages; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; +import org.junit.Test; + +public class PylintScoreTest { + + private static final PylintScore SCORE = new PylintScore(); + + @Test + public void testBasics() { + assertFalse(SCORE.name().isEmpty()); + assertEquals(3, SCORE.features().size()); + assertTrue(SCORE.features().contains(USES_PYLINT_SCAN_CHECKS)); + assertTrue(SCORE.features().contains(RUNS_PYLINT_SCANS)); + assertTrue(SCORE.features().contains(LANGUAGES)); + assertTrue(SCORE.subScores().isEmpty()); + } + + @Test + public void testWithAllUnknown() { + ScoreValue scoreValue = SCORE.calculate(Utils.allUnknown(SCORE.allFeatures())); + assertTrue(scoreValue.isUnknown()); + } + + @Test + public void testCalculate() { + assertScore( + Score.INTERVAL, + SCORE, + setOf( + USES_PYLINT_SCAN_CHECKS.value(true), + RUNS_PYLINT_SCANS.value(true), + LANGUAGES.value(Languages.of(JAVA)))); + } + + @Test(expected = IllegalArgumentException.class) + public void testCalculateWithoutUsesPylintChecksValue() { + SCORE.calculate(RUNS_PYLINT_SCANS.unknown(), LANGUAGES.unknown()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCalculateWithoutRunsPylintScanChecksValue() { + SCORE.calculate(USES_PYLINT_SCAN_CHECKS.unknown(), LANGUAGES.unknown()); + } + + @Test + public void testCalculateWithAllUnknownValues() { + assertTrue(SCORE.calculate( + USES_PYLINT_SCAN_CHECKS.unknown(), + RUNS_PYLINT_SCANS.unknown(), + LANGUAGES.unknown()).isUnknown()); + } +} \ No newline at end of file diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTest.java index 583e4ae71..c60cef786 100644 --- a/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTest.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTest.java @@ -6,12 +6,16 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_BANDIT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_CODEQL_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_GOSEC_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_BANDIT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_CODEQL_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_FIND_SEC_BUGS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_WITH_RULES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_LGTM_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.WORST_LGTM_GRADE; import static com.sap.oss.phosphor.fosstars.model.value.Language.JAVA; import static com.sap.oss.phosphor.fosstars.model.value.Language.PYTHON; @@ -45,6 +49,10 @@ public void testCalculateWithFeatureValues() { RUNS_GOSEC_SCANS.value(false), USES_GOSEC_SCAN_CHECKS.value(false), USES_GOSEC_WITH_RULES.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), LANGUAGES.value(Languages.of(JAVA)), USES_FIND_SEC_BUGS.value(false)); @@ -53,7 +61,7 @@ public void testCalculateWithFeatureValues() { assertTrue(Score.INTERVAL.contains(scoreValue.get())); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); } @Test @@ -70,6 +78,10 @@ public void testCalculateWithBanditScanRunValues() { RUNS_GOSEC_SCANS.value(false), USES_GOSEC_SCAN_CHECKS.value(false), USES_GOSEC_WITH_RULES.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), LANGUAGES.value(Languages.of(PYTHON)), USES_FIND_SEC_BUGS.value(false)); @@ -78,7 +90,7 @@ public void testCalculateWithBanditScanRunValues() { assertTrue(Score.INTERVAL.contains(scoreValue.get())); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); } @Test @@ -95,9 +107,39 @@ public void testCalculateWithGoSecScanRunValues() { RUNS_GOSEC_SCANS.value(true), USES_GOSEC_SCAN_CHECKS.value(true), USES_GOSEC_WITH_RULES.value(true), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), + LANGUAGES.value(Languages.of(PYTHON)), + USES_FIND_SEC_BUGS.value(false)); + + assertFalse(scoreValue.isUnknown()); + assertFalse(scoreValue.isNotApplicable()); + assertTrue(Score.INTERVAL.contains(scoreValue.get())); + assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); + assertSame(score, scoreValue.score()); + assertEquals(7, scoreValue.usedValues().size()); + } + + @Test + public void testCalculateWithPylintScanRunValues() { + StaticAnalysisScore score = new StaticAnalysisScore(); + + ScoreValue scoreValue = score.calculate( + WORST_LGTM_GRADE.value(D), + USES_LGTM_CHECKS.value(false), + USES_CODEQL_CHECKS.value(false), + RUNS_CODEQL_SCANS.value(false), + USES_BANDIT_SCAN_CHECKS.value(false), + RUNS_BANDIT_SCANS.value(false), RUNS_GOSEC_SCANS.value(false), USES_GOSEC_SCAN_CHECKS.value(false), USES_GOSEC_WITH_RULES.value(false), + RUNS_PYLINT_SCANS.value(true), + USES_PYLINT_SCAN_CHECKS.value(true), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), LANGUAGES.value(Languages.of(PYTHON)), USES_FIND_SEC_BUGS.value(false)); @@ -106,7 +148,36 @@ public void testCalculateWithGoSecScanRunValues() { assertTrue(Score.INTERVAL.contains(scoreValue.get())); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); + } + + @Test + public void testCalculateWithMyPyScanRunValues() { + StaticAnalysisScore score = new StaticAnalysisScore(); + + ScoreValue scoreValue = score.calculate( + WORST_LGTM_GRADE.value(D), + USES_LGTM_CHECKS.value(false), + USES_CODEQL_CHECKS.value(false), + RUNS_CODEQL_SCANS.value(false), + USES_BANDIT_SCAN_CHECKS.value(false), + RUNS_BANDIT_SCANS.value(false), + RUNS_GOSEC_SCANS.value(false), + USES_GOSEC_SCAN_CHECKS.value(false), + USES_GOSEC_WITH_RULES.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(true), + USES_MYPY_SCAN_CHECKS.value(true), + LANGUAGES.value(Languages.of(PYTHON)), + USES_FIND_SEC_BUGS.value(false)); + + assertFalse(scoreValue.isUnknown()); + assertFalse(scoreValue.isNotApplicable()); + assertTrue(Score.INTERVAL.contains(scoreValue.get())); + assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); + assertSame(score, scoreValue.score()); + assertEquals(7, scoreValue.usedValues().size()); } @Test @@ -123,6 +194,10 @@ public void testCalculateWithAllUnknown() { RUNS_GOSEC_SCANS.unknown(), USES_GOSEC_SCAN_CHECKS.unknown(), USES_GOSEC_WITH_RULES.unknown(), + RUNS_PYLINT_SCANS.unknown(), + USES_PYLINT_SCAN_CHECKS.unknown(), + RUNS_MYPY_SCANS.unknown(), + USES_MYPY_SCAN_CHECKS.unknown(), LANGUAGES.unknown(), USES_FIND_SEC_BUGS.unknown()); @@ -130,7 +205,7 @@ public void testCalculateWithAllUnknown() { assertFalse(scoreValue.isNotApplicable()); assertEquals(Confidence.MIN, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); } @Test @@ -152,20 +227,29 @@ public void testCalculateWithAllNotApplicable() { ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) .makeNotApplicable() .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .makeNotApplicable() + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .makeNotApplicable() + .confidence(Confidence.MAX); ScoreValue scoreValue = score.calculate( codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, - goSecScoreValue); + goSecScoreValue, pylintValue, mypyValue); assertFalse(scoreValue.isUnknown()); assertTrue(scoreValue.isNotApplicable()); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); assertTrue(scoreValue.usedValues().contains(banditScoreValue)); assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); } @Test @@ -187,20 +271,30 @@ public void testCalculateWithSubScoreValues() { ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) .set(MIN) .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .set(MIN) + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .set(MIN) + .confidence(Confidence.MAX); ScoreValue scoreValue = score.calculate( codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, - goSecScoreValue); + goSecScoreValue, pylintValue, mypyValue); assertFalse(scoreValue.isUnknown()); assertFalse(scoreValue.isNotApplicable()); assertEquals(MIN, scoreValue.get(), DELTA); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); assertTrue(scoreValue.usedValues().contains(banditScoreValue)); + assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); } @Test @@ -224,21 +318,30 @@ public void testCalculateWithFindSecBugsNotApplicable() { ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) .set(value) .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .set(value) + .confidence(Confidence.MAX); ScoreValue scoreValue = score.calculate( codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, - goSecScoreValue); + goSecScoreValue, pylintValue, mypyValue); assertFalse(scoreValue.isUnknown()); assertFalse(scoreValue.isNotApplicable()); assertEquals(value, scoreValue.get(), DELTA); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); assertTrue(scoreValue.usedValues().contains(banditScoreValue)); assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); } @Test @@ -262,21 +365,30 @@ public void testCalculateWithBanditNotApplicable() { ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) .set(value) .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .set(value) + .confidence(Confidence.MAX); ScoreValue scoreValue = score.calculate( codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, - goSecScoreValue); + goSecScoreValue, pylintValue, mypyValue); assertFalse(scoreValue.isUnknown()); assertFalse(scoreValue.isNotApplicable()); assertEquals(value, scoreValue.get(), DELTA); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); assertTrue(scoreValue.usedValues().contains(banditScoreValue)); assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); } @Test @@ -300,21 +412,124 @@ public void testCalculateWithGoSecNotApplicable() { ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) .makeNotApplicable() .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .set(value) + .confidence(Confidence.MAX); + + ScoreValue scoreValue = score.calculate( + codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, + goSecScoreValue, pylintValue, mypyValue); + + assertFalse(scoreValue.isUnknown()); + assertFalse(scoreValue.isNotApplicable()); + assertEquals(value, scoreValue.get(), DELTA); + assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); + assertSame(score, scoreValue.score()); + assertEquals(7, scoreValue.usedValues().size()); + assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); + assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); + assertTrue(scoreValue.usedValues().contains(banditScoreValue)); + assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); + } + + @Test + public void testCalculateWithPylintNotApplicable() { + StaticAnalysisScore score = new StaticAnalysisScore(); + + final double value = 5.5; + + ScoreValue codeqlScoreValue = new ScoreValue(new CodeqlScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue lgtmScoreValue = new ScoreValue(new LgtmScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue findSecBugsScoreValue = new ScoreValue(new FindSecBugsScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue banditScoreValue = new ScoreValue(new BanditScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .makeNotApplicable() + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .set(value) + .confidence(Confidence.MAX); + + ScoreValue scoreValue = score.calculate( + codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, + goSecScoreValue, pylintValue, mypyValue); + + assertFalse(scoreValue.isUnknown()); + assertFalse(scoreValue.isNotApplicable()); + assertEquals(value, scoreValue.get(), DELTA); + assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); + assertSame(score, scoreValue.score()); + assertEquals(7, scoreValue.usedValues().size()); + assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); + assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); + assertTrue(scoreValue.usedValues().contains(banditScoreValue)); + assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); + } + + @Test + public void testCalculateWithMyPyNotApplicable() { + StaticAnalysisScore score = new StaticAnalysisScore(); + + final double value = 5.5; + + ScoreValue codeqlScoreValue = new ScoreValue(new CodeqlScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue lgtmScoreValue = new ScoreValue(new LgtmScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue findSecBugsScoreValue = new ScoreValue(new FindSecBugsScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue banditScoreValue = new ScoreValue(new BanditScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue goSecScoreValue = new ScoreValue(new GoSecScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue pylintValue = new ScoreValue(new PylintScore()) + .set(value) + .confidence(Confidence.MAX); + ScoreValue mypyValue = new ScoreValue(new MyPyScore()) + .makeNotApplicable() + .confidence(Confidence.MAX); ScoreValue scoreValue = score.calculate( codeqlScoreValue, lgtmScoreValue, findSecBugsScoreValue, banditScoreValue, - goSecScoreValue); + goSecScoreValue, pylintValue, mypyValue); assertFalse(scoreValue.isUnknown()); assertFalse(scoreValue.isNotApplicable()); assertEquals(value, scoreValue.get(), DELTA); assertEquals(Confidence.MAX, scoreValue.confidence(), DELTA); assertSame(score, scoreValue.score()); - assertEquals(5, scoreValue.usedValues().size()); + assertEquals(7, scoreValue.usedValues().size()); assertTrue(scoreValue.usedValues().contains(lgtmScoreValue)); assertTrue(scoreValue.usedValues().contains(findSecBugsScoreValue)); assertTrue(scoreValue.usedValues().contains(banditScoreValue)); assertTrue(scoreValue.usedValues().contains(goSecScoreValue)); + assertTrue(scoreValue.usedValues().contains(codeqlScoreValue)); + assertTrue(scoreValue.usedValues().contains(pylintValue)); + assertTrue(scoreValue.usedValues().contains(mypyValue)); } @Test diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/OssSecurityRatingMarkdownFormatterTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/OssSecurityRatingMarkdownFormatterTest.java index a0b8b86ed..5ad68f043 100755 --- a/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/OssSecurityRatingMarkdownFormatterTest.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/OssSecurityRatingMarkdownFormatterTest.java @@ -21,6 +21,8 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_BANDIT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_CODEQL_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_GOSEC_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SECURITY_REVIEWS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SIGNS_ARTIFACTS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SUPPORTED_BY_COMPANY; @@ -34,10 +36,12 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_WITH_RULES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_LGTM_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MEMORY_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_NOHTTP; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_ESAPI; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_ENCODER; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_HTML_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SIGNED_COMMITS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_UNDEFINED_BEHAVIOR_SANITIZER; @@ -99,6 +103,10 @@ public class OssSecurityRatingMarkdownFormatterTest { RUNS_GOSEC_SCANS.value(false), USES_GOSEC_WITH_RULES.value(false), USES_GOSEC_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), USES_LGTM_CHECKS.value(true), WORST_LGTM_GRADE.value(LgtmGrade.A), USES_GITHUB_FOR_DEVELOPMENT.value(false), diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/PrettyPrinterTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/PrettyPrinterTest.java index 56d82dc80..9f97b70cb 100644 --- a/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/PrettyPrinterTest.java +++ b/src/test/java/com/sap/oss/phosphor/fosstars/tool/format/PrettyPrinterTest.java @@ -21,6 +21,8 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_BANDIT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_CODEQL_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_GOSEC_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_MYPY_SCANS; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.RUNS_PYLINT_SCANS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SECURITY_REVIEWS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SIGNS_ARTIFACTS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.SUPPORTED_BY_COMPANY; @@ -34,10 +36,12 @@ import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_GOSEC_WITH_RULES; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_LGTM_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MEMORY_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_MYPY_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_NOHTTP; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_ESAPI; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_ENCODER; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_OWASP_JAVA_HTML_SANITIZER; +import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_PYLINT_SCAN_CHECKS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SIGNED_COMMITS; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_UNDEFINED_BEHAVIOR_SANITIZER; @@ -101,6 +105,10 @@ public class PrettyPrinterTest { RUNS_GOSEC_SCANS.value(false), USES_GOSEC_WITH_RULES.value(false), USES_GOSEC_SCAN_CHECKS.value(false), + RUNS_PYLINT_SCANS.value(false), + USES_PYLINT_SCAN_CHECKS.value(false), + RUNS_MYPY_SCANS.value(false), + USES_MYPY_SCAN_CHECKS.value(false), USES_LGTM_CHECKS.value(true), WORST_LGTM_GRADE.value(LgtmGrade.A), USES_GITHUB_FOR_DEVELOPMENT.value(false), diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-pre-commit-hook.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-pre-commit-hook.yml new file mode 100644 index 000000000..c774807f3 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-pre-commit-hook.yml @@ -0,0 +1,21 @@ +fail_fast: true + +repos: + - repo: https://github.com/ambv/black + rev: 22.3.0 + hooks: + - id: black + args: [--diff, --check] + + - repo: https://github.com/pre-commit/mirrors-pylint + rev: v3.0.0a3 + hooks: + - id: pylint + args: [--disable=all, --enable=unused-import] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.902 + hooks: + - id: mypy + exclude: ^tests/ + args: [--strict] diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-prospector.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-prospector.yml new file mode 100644 index 000000000..528463c76 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-prospector.yml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/PyCQA/prospector + rev: 1.7.5 + hooks: + - id: prospector + additional_dependencies: + - ".[with_pylint,with_mypy]" \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-run.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-run.yml new file mode 100644 index 000000000..775b62452 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/mypy-analysis-with-run.yml @@ -0,0 +1,27 @@ +name: "Mypy" +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: '0 13 * * 3' +jobs: + mypy: + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.7.4 + architecture: x64 + - name: Checkout + uses: actions/checkout@v1 + - name: Install mypy + run: pip install mypy + - name: Run mypy + uses: sasanquaneuf/mypy-github-action@releases/v1 + with: + checkName: 'mypy' # NOTE: this needs to be the same as the job name + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-as-pre-commit-hook.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-as-pre-commit-hook.yml new file mode 100644 index 000000000..974f0da6e --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-as-pre-commit-hook.yml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pycqa/pylint + rev: pylint-2.6.0 + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + ] \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-no-pylint-hook.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-no-pylint-hook.yml new file mode 100644 index 000000000..f3a4235b9 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-no-pylint-hook.yml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pycqa/test + rev: test-2.6.0 + hooks: + - id: test + name: test + entry: test + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + ] \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-multiple-jobs.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-multiple-jobs.yml new file mode 100644 index 000000000..2b3811b38 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-multiple-jobs.yml @@ -0,0 +1,25 @@ +name: "Pylint" +on: + push: + branches: [master] + schedule: + - cron: '0 13 * * 3' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + bandit: + steps: + - run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - run: | + mkdir -p reports + pylint -rn -d unused-variable fileName.py \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run-but-uses-pylint.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run-but-uses-pylint.yml new file mode 100644 index 000000000..ea1721255 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run-but-uses-pylint.yml @@ -0,0 +1,27 @@ +name: "Pylint" +on: + push: + branches: [master] + pull_request: + branches: [ master ] + schedule: + - cron: '0 13 * * 3' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + - name: Install pylint + run: pip install pylint + bandit: + steps: + - run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - run: | + mkdir -p reports \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run.yml new file mode 100644 index 000000000..a8711da53 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-no-pylint-run.yml @@ -0,0 +1,25 @@ +name: "Pylint" +on: + push: + branches: [master] + schedule: + - cron: '0 13 * * 3' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + - name: Install pylint + run: pip install pylint + bandit: + steps: + - run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - run: | + mkdir -p reports \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-prospector.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-prospector.yml new file mode 100644 index 000000000..2f70c436c --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-prospector.yml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/PyCQA/prospector + rev: 1.7.5 + hooks: + - id: prospector + additional_dependencies: + - ".[with_pylint,with_bandit]" \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-entry.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-entry.yml new file mode 100644 index 000000000..9a9bae3af --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-entry.yml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pycqa/test + rev: test-2.6.0 + hooks: + - id: test + name: test + entry: pylint + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + ] \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-repo.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-repo.yml new file mode 100644 index 000000000..044ea0d4e --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-repo.yml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pycqa/pylint + rev: 2.6.0 + hooks: + - id: test + name: test + entry: test + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + ] \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-rev.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-rev.yml new file mode 100644 index 000000000..e7299ba23 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-pylint-in-rev.yml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pycqa/test + rev: pylint-2.6.0 + hooks: + - id: test + name: test + entry: test + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + ] \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-run.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-run.yml new file mode 100644 index 000000000..9ccaf8934 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/pylint-analysis-with-run.yml @@ -0,0 +1,25 @@ +name: "Pylint" +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: '0 13 * * 3' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run Pylint (Python code checker) + run: pylint -r . -f xml -o pylint.xml || true \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox-no-pylint.ini b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox-no-pylint.ini new file mode 100644 index 000000000..33a1c1686 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox-no-pylint.ini @@ -0,0 +1,94 @@ +[tox] +minversion = 2.4 +envlist = formatting, py37, py38, py39, py310, pypy, benchmark +skip_missing_interpreters = true +requires = pip >=21.3.1 +isolated_build = true + +[testenv:test] +deps = + -r {toxinidir}/requirements_test_min.txt + pre-commit~=2.13 +commands = + pre-commit run test --all-files + +[testenv:formatting] +basepython = python3 +deps = + -r {toxinidir}/requirements_test_min.txt + pre-commit~=2.13 +commands = + pre-commit run --all-files + +[testenv:mypy] +basepython = python3 +deps = + pre-commit~=2.13 +commands = + pre-commit run mypy --all-files + +[testenv] +setenv = + COVERAGE_FILE = {toxinidir}/.coverage.{envname} +deps = + !pypy: -r {toxinidir}/requirements_test.txt + pypy: -r {toxinidir}/requirements_test_min.txt +commands = + ; Run tests, ensuring all benchmark tests do not run + pytest --benchmark-disable {toxinidir}/tests/ {posargs:} + +[testenv:spelling] +deps = + -r {toxinidir}/requirements_test.txt +commands = + pytest {toxinidir}/tests/ {posargs:} -k unittest_spelling + +[testenv:coverage-html] +setenv = + COVERAGE_FILE = {toxinidir}/.coverage +deps = + -r {toxinidir}/requirements_test.txt +skip_install = true +commands = + coverage combine + coverage html --ignore-errors --rcfile={toxinidir}/.coveragerc + +[testenv:docs] +changedir = doc/ +deps = + -r {toxinidir}/doc/requirements.txt +commands = + sphinx-build -W -b html -d _build/doctrees . _build/html + +[testenv:test_doc] +deps = + -r {toxinidir}/requirements_test.txt +commands = + pytest {toxinidir}/doc/test_messages_documentation.py + +[testenv:benchmark] +deps = + -r {toxinidir}/requirements_test.txt + pygal +commands = + ; Run the only the benchmark tests, grouping output and forcing .json output so we + ; can compare benchmark runs + pytest --exitfirst \ + --failed-first \ + --benchmark-only \ + --benchmark-save=batch_files \ + --benchmark-save-data \ + --benchmark-autosave {toxinidir}/tests \ + --benchmark-group-by="group" \ + {posargs:} + +[testenv:profile_against_external] +setenv = + PYTEST_PROFILE_EXTERNAL = 1 +deps = + -r {toxinidir}/requirements_test.txt + gprof2dot +commands = + pytest --exitfirst \ + --profile-svg \ + {toxinidir}/tests/profile/test_profile_against_externals.py \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox.ini b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox.ini new file mode 100644 index 000000000..27f9f3d59 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/data/github/tox.ini @@ -0,0 +1,94 @@ +[tox] +minversion = 2.4 +envlist = formatting, py37, py38, py39, py310, pypy, benchmark +skip_missing_interpreters = true +requires = pip >=21.3.1 +isolated_build = true + +[testenv:pylint] +deps = + -r {toxinidir}/requirements_test_min.txt + pre-commit~=2.13 +commands = + pre-commit run pylint --all-files + +[testenv:formatting] +basepython = python3 +deps = + -r {toxinidir}/requirements_test_min.txt + pre-commit~=2.13 +commands = + pre-commit run --all-files + +[testenv:mypy] +basepython = python3 +deps = + pre-commit~=2.13 +commands = + pre-commit run mypy --all-files + +[testenv] +setenv = + COVERAGE_FILE = {toxinidir}/.coverage.{envname} +deps = + !pypy: -r {toxinidir}/requirements_test.txt + pypy: -r {toxinidir}/requirements_test_min.txt +commands = + ; Run tests, ensuring all benchmark tests do not run + pytest --benchmark-disable {toxinidir}/tests/ {posargs:} + +[testenv:spelling] +deps = + -r {toxinidir}/requirements_test.txt +commands = + pytest {toxinidir}/tests/ {posargs:} -k unittest_spelling + +[testenv:coverage-html] +setenv = + COVERAGE_FILE = {toxinidir}/.coverage +deps = + -r {toxinidir}/requirements_test.txt +skip_install = true +commands = + coverage combine + coverage html --ignore-errors --rcfile={toxinidir}/.coveragerc + +[testenv:docs] +changedir = doc/ +deps = + -r {toxinidir}/doc/requirements.txt +commands = + sphinx-build -W -b html -d _build/doctrees . _build/html + +[testenv:test_doc] +deps = + -r {toxinidir}/requirements_test.txt +commands = + pytest {toxinidir}/doc/test_messages_documentation.py + +[testenv:benchmark] +deps = + -r {toxinidir}/requirements_test.txt + pygal +commands = + ; Run the only the benchmark tests, grouping output and forcing .json output so we + ; can compare benchmark runs + pytest --exitfirst \ + --failed-first \ + --benchmark-only \ + --benchmark-save=batch_files \ + --benchmark-save-data \ + --benchmark-autosave {toxinidir}/tests \ + --benchmark-group-by="group" \ + {posargs:} + +[testenv:profile_against_external] +setenv = + PYTEST_PROFILE_EXTERNAL = 1 +deps = + -r {toxinidir}/requirements_test.txt + gprof2dot +commands = + pytest --exitfirst \ + --profile-svg \ + {toxinidir}/tests/profile/test_profile_against_externals.py \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssArtifactSecurityRatingTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssArtifactSecurityRatingTestVectors.yml index 1e9be9f02..06e61bdf3 100644 --- a/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssArtifactSecurityRatingTestVectors.yml +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssArtifactSecurityRatingTestVectors.yml @@ -156,6 +156,26 @@ defaults: type: "BooleanFeature" name: "If a project runs GoSec scan checks for commits" flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: false - type: "BooleanValue" feature: type: "BooleanFeature" diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssSecurityRatingTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssSecurityRatingTestVectors.yml index 54ce75096..d9aa6e8f6 100644 --- a/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssSecurityRatingTestVectors.yml +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/rating/oss/OssSecurityRatingTestVectors.yml @@ -132,6 +132,26 @@ defaults: type: "BooleanFeature" name: "If a project has executable binaries" flag: false +- type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: false +- type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: false +- type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: false +- type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: false - type: "SecurityReviewsValue" feature: type: "SecurityReviewsFeature" diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTestVectors.yml new file mode 100644 index 000000000..eac29f6e4 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/MyPyScoreTestVectors.yml @@ -0,0 +1,180 @@ +--- +defaults: + - type: "LanguagesValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + languages: + elements: + - "PYTHON" +elements: + - type: "StandardTestVector" + values: + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + - type: "UnknownValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + expectedScore: + type: "DoubleInterval" + from: 0.0 + openLeft: false + negativeInfinity: false + to: 1.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedUnknownScore: true + alias: "all_unknown" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: false + expectedScore: + type: "DoubleInterval" + from: 0.0 + openLeft: false + negativeInfinity: false + to: 1.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_1" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: true + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: false + expectedScore: + type: "DoubleInterval" + from: 6.0 + openLeft: false + negativeInfinity: false + to: 8.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_2" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: true + expectedScore: + type: "DoubleInterval" + from: 4.0 + openLeft: false + negativeInfinity: false + to: 6.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_3" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: true + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: true + expectedScore: + type: "DoubleInterval" + from: 9.0 + openLeft: false + negativeInfinity: false + to: 10.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_4" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: false + - type: "LanguagesValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + languages: + elements: + - "OTHER" + expectedScore: + type: "DoubleInterval" + from: 9.0 + openLeft: false + negativeInfinity: false + to: 10.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedNotApplicableScore: true + alias: "test_vector_5" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + flag: true + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + flag: true + - type: "UnknownValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + expectedScore: + type: "DoubleInterval" + from: 0.0 + openLeft: false + negativeInfinity: false + to: 1.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedNotApplicableScore: true + alias: "test_vector_6" diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/ProjectSecurityTestingScoreTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/ProjectSecurityTestingScoreTestVectors.yml index de8e8da81..4cfdba1fa 100644 --- a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/ProjectSecurityTestingScoreTestVectors.yml +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/ProjectSecurityTestingScoreTestVectors.yml @@ -177,6 +177,24 @@ elements: confidence: 10.0 usedValues: [] explanation: [] + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] expectedScore: type: "DoubleInterval" from: 5.0 @@ -274,6 +292,24 @@ elements: confidence: 10.0 usedValues: [] explanation: [] + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 6.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 6.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] expectedScore: type: "DoubleInterval" from: 5.0 @@ -370,12 +406,30 @@ elements: confidence: 10.0 usedValues: [] explanation: [] + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] expectedScore: type: "DoubleInterval" - from: 5.3 + from: 5.1 openLeft: false negativeInfinity: false - to: 6.5 + to: 6.3 openRight: false positiveInfinity: false expectedLabel: null @@ -467,12 +521,32 @@ elements: usedValues: [] explanation: [] isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false expectedScore: type: "DoubleInterval" - from: 5.3 + from: 5.1 openLeft: false negativeInfinity: false - to: 6.5 + to: 6.3 openRight: false positiveInfinity: false expectedLabel: null @@ -564,13 +638,267 @@ elements: usedValues: [] explanation: [] isNotApplicable: true + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + expectedScore: + type: "DoubleInterval" + from: 5.1 + openLeft: false + negativeInfinity: false + to: 6.3 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "gosec_not_applicable" + + # PylintScore is N/A + - type: "StandardTestVector" + values: + - type: "ScoreValue" + score: + type: "CodeqlScore" + name: "How a project uses CodeQL" + value: 7.0 + weight: 1.0 + confidence: 10.0 + usedValues: [ ] + explanation: [ ] + - type: "ScoreValue" + score: + type: "LgtmScore" + name: "How a project addresses issues reported by LGTM" + value: 7.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "DependencyScanScore" + name: "How a project scans its dependencies for vulnerabilities" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "NoHttpToolScore" + name: "If a project uses nohttp tool" + value: 10.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "MemorySafetyTestingScore" + name: "How a project tests for memory-safety issues" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "FuzzingScore" + name: "How a project uses fuzzing" + value: 8.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "FindSecBugsScore" + name: "How a project uses FindSecBugs" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "BanditScore" + name: "How a project uses Bandit" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "GoSecScore" + name: "How a project uses GoSec" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + expectedScore: + type: "DoubleInterval" + from: 5.1 + openLeft: false + negativeInfinity: false + to: 6.3 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "pylint_not_applicable" + + # MyPyScore is N/A + - type: "StandardTestVector" + values: + - type: "ScoreValue" + score: + type: "CodeqlScore" + name: "How a project uses CodeQL" + value: 7.0 + weight: 1.0 + confidence: 10.0 + usedValues: [ ] + explanation: [ ] + - type: "ScoreValue" + score: + type: "LgtmScore" + name: "How a project addresses issues reported by LGTM" + value: 7.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "DependencyScanScore" + name: "How a project scans its dependencies for vulnerabilities" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "NoHttpToolScore" + name: "If a project uses nohttp tool" + value: 10.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "MemorySafetyTestingScore" + name: "How a project tests for memory-safety issues" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "FuzzingScore" + name: "How a project uses fuzzing" + value: 8.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "FindSecBugsScore" + name: "How a project uses FindSecBugs" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + - type: "ScoreValue" + score: + type: "BanditScore" + name: "How a project uses Bandit" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "GoSecScore" + name: "How a project uses GoSec" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true expectedScore: type: "DoubleInterval" - from: 5.3 + from: 5.1 openLeft: false negativeInfinity: false - to: 6.5 + to: 6.3 openRight: false positiveInfinity: false expectedLabel: null - alias: "gosec_not_applicable" \ No newline at end of file + alias: "mypy_not_applicable" \ No newline at end of file diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTestVectors.yml new file mode 100644 index 000000000..84b95f833 --- /dev/null +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/PylintScoreTestVectors.yml @@ -0,0 +1,180 @@ +--- +defaults: + - type: "LanguagesValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + languages: + elements: + - "PYTHON" +elements: + - type: "StandardTestVector" + values: + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + - type: "UnknownValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + expectedScore: + type: "DoubleInterval" + from: 0.0 + openLeft: false + negativeInfinity: false + to: 1.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedUnknownScore: true + alias: "all_unknown" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: false + expectedScore: + type: "DoubleInterval" + from: 0.0 + openLeft: false + negativeInfinity: false + to: 1.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_1" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: true + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: false + expectedScore: + type: "DoubleInterval" + from: 6.0 + openLeft: false + negativeInfinity: false + to: 8.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_2" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: true + expectedScore: + type: "DoubleInterval" + from: 4.0 + openLeft: false + negativeInfinity: false + to: 6.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_3" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: true + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: true + expectedScore: + type: "DoubleInterval" + from: 9.0 + openLeft: false + negativeInfinity: false + to: 10.0 + openRight: false + positiveInfinity: false + expectedLabel: null + alias: "test_vector_4" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: false + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: false + - type: "LanguagesValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + languages: + elements: + - "OTHER" + expectedScore: + type: "DoubleInterval" + from: 9.0 + openLeft: false + negativeInfinity: false + to: 10.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedNotApplicableScore: true + alias: "test_vector_5" + - type: "StandardTestVector" + values: + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + flag: true + - type: "BooleanValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + flag: true + - type: "UnknownValue" + feature: + type: "LanguagesFeature" + name: "A set of programming languages" + expectedScore: + type: "DoubleInterval" + from: 0.0 + openLeft: false + negativeInfinity: false + to: 1.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedNotApplicableScore: true + alias: "test_vector_6" diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTestVectors.yml index dce3187fb..d602fd9d0 100644 --- a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTestVectors.yml +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/StaticAnalysisScoreTestVectors.yml @@ -8,6 +8,8 @@ elements: com.sap.oss.phosphor.fosstars.model.score.oss.FindSecBugsScore: 0.0 com.sap.oss.phosphor.fosstars.model.score.oss.BanditScore: 0.0 com.sap.oss.phosphor.fosstars.model.score.oss.GoSecScore: 0.0 + com.sap.oss.phosphor.fosstars.model.score.oss.MyPyScore: 0.0 + com.sap.oss.phosphor.fosstars.model.score.oss.PylintScore: 0.0 expectedScore: type: "DoubleInterval" from: 0.0 @@ -26,6 +28,8 @@ elements: com.sap.oss.phosphor.fosstars.model.score.oss.FindSecBugsScore: 10.0 com.sap.oss.phosphor.fosstars.model.score.oss.BanditScore: 10.0 com.sap.oss.phosphor.fosstars.model.score.oss.GoSecScore: 10.0 + com.sap.oss.phosphor.fosstars.model.score.oss.MyPyScore: 10.0 + com.sap.oss.phosphor.fosstars.model.score.oss.PylintScore: 10.0 expectedScore: type: "DoubleInterval" from: 10.0 @@ -44,6 +48,8 @@ elements: com.sap.oss.phosphor.fosstars.model.score.oss.FindSecBugsScore: 2.0 com.sap.oss.phosphor.fosstars.model.score.oss.BanditScore: 2.0 com.sap.oss.phosphor.fosstars.model.score.oss.GoSecScore: 5.0 + com.sap.oss.phosphor.fosstars.model.score.oss.MyPyScore: 5.0 + com.sap.oss.phosphor.fosstars.model.score.oss.PylintScore: 5.0 expectedScore: type: "DoubleInterval" from: 5.0 @@ -62,6 +68,8 @@ elements: com.sap.oss.phosphor.fosstars.model.score.oss.FindSecBugsScore: 3.0 com.sap.oss.phosphor.fosstars.model.score.oss.BanditScore: 4.0 com.sap.oss.phosphor.fosstars.model.score.oss.GoSecScore: 4.0 + com.sap.oss.phosphor.fosstars.model.score.oss.MyPyScore: 4.0 + com.sap.oss.phosphor.fosstars.model.score.oss.PylintScore: 4.0 expectedScore: type: "DoubleInterval" from: 1.0 @@ -125,6 +133,26 @@ elements: usedValues: [] explanation: [] isNotApplicable: true + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true expectedScore: type: "DoubleInterval" from: 0.0 @@ -143,6 +171,8 @@ elements: com.sap.oss.phosphor.fosstars.model.score.oss.FindSecBugsScore: 3.0 com.sap.oss.phosphor.fosstars.model.score.oss.BanditScore: 2.0 com.sap.oss.phosphor.fosstars.model.score.oss.GoSecScore: 2.0 + com.sap.oss.phosphor.fosstars.model.score.oss.MyPyScore: 2.0 + com.sap.oss.phosphor.fosstars.model.score.oss.PylintScore: 2.0 expectedScore: type: "DoubleInterval" from: 1.0 @@ -206,6 +236,26 @@ elements: usedValues: [] explanation: [] isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false expectedScore: type: "DoubleInterval" from: 5.0 @@ -269,6 +319,26 @@ elements: usedValues: [] explanation: [] isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false expectedScore: type: "DoubleInterval" from: 5.0 @@ -332,6 +402,26 @@ elements: usedValues: [ ] explanation: [ ] isNotApplicable: true + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 4.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 4.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false expectedScore: type: "DoubleInterval" from: 3.0 @@ -395,6 +485,26 @@ elements: usedValues: [ ] explanation: [ ] isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false expectedScore: type: "DoubleInterval" from: 3.0 @@ -458,6 +568,26 @@ elements: usedValues: [ ] explanation: [ ] isNotApplicable: true + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false expectedScore: type: "DoubleInterval" from: 3.0 @@ -469,6 +599,172 @@ elements: expectedLabel: null expectedNotApplicableScore: false alias: "gosec_not_applicable" + - type: "StandardTestVector" + values: + - type: "ScoreValue" + score: + type: "CodeqlScore" + name: "How a project uses CodeQL" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "LgtmScore" + name: "How a project addresses issues reported by LGTM" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "FindSecBugsScore" + name: "How a project uses FindSecBugs" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true + - type: "ScoreValue" + score: + type: "BanditScore" + name: "How a project uses Bandit" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [ ] + explanation: [ ] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "GoSecScore" + name: "How a project uses GoSec" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [ ] + explanation: [ ] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + expectedScore: + type: "DoubleInterval" + from: 3.0 + openLeft: false + negativeInfinity: false + to: 3.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedNotApplicableScore: false + alias: "pylint_not_applicable" + - type: "StandardTestVector" + values: + - type: "ScoreValue" + score: + type: "CodeqlScore" + name: "How a project uses CodeQL" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "LgtmScore" + name: "How a project addresses issues reported by LGTM" + value: 0.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "FindSecBugsScore" + name: "How a project uses FindSecBugs" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true + - type: "ScoreValue" + score: + type: "BanditScore" + name: "How a project uses Bandit" + value: 5.0 + weight: 1.0 + confidence: 10.0 + usedValues: [ ] + explanation: [ ] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "GoSecScore" + name: "How a project uses GoSec" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [ ] + explanation: [ ] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "PylintScore" + name: "How a project uses Pylint" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: false + - type: "ScoreValue" + score: + type: "MyPyScore" + name: "How a project uses MyPy" + value: 3.0 + weight: 1.0 + confidence: 10.0 + usedValues: [] + explanation: [] + isNotApplicable: true + expectedScore: + type: "DoubleInterval" + from: 3.0 + openLeft: false + negativeInfinity: false + to: 3.0 + openRight: false + positiveInfinity: false + expectedLabel: null + expectedNotApplicableScore: false + alias: "mypy_not_applicable" - type: "StandardTestVector" values: - type: "UnknownValue" @@ -515,6 +811,22 @@ elements: feature: type: "BooleanFeature" name: "If a project runs GoSec scans" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" expectedScore: type: "DoubleInterval" from: 0.0 diff --git a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/VulnerabilityDiscoveryAndSecurityTestingScoreTestVectors.yml b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/VulnerabilityDiscoveryAndSecurityTestingScoreTestVectors.yml index 82011f035..119ae5398 100644 --- a/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/VulnerabilityDiscoveryAndSecurityTestingScoreTestVectors.yml +++ b/src/test/resources/com/sap/oss/phosphor/fosstars/model/score/oss/VulnerabilityDiscoveryAndSecurityTestingScoreTestVectors.yml @@ -96,6 +96,22 @@ elements: feature: type: "BooleanFeature" name: "If a project runs GoSec scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" expectedScore: type: "DoubleInterval" from: 0.0 @@ -218,6 +234,22 @@ elements: feature: type: "BooleanFeature" name: "If a project runs GoSec scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scans" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Pylint scan checks for commits" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scans" + - type: "UnknownValue" + feature: + type: "BooleanFeature" + name: "If a project runs Mypy scan checks for commits" expectedScore: type: "DoubleInterval" from: 0.0 diff --git a/src/test/shell/tool/github/lib.sh b/src/test/shell/tool/github/lib.sh index 03e32366c..b5e79ef71 100644 --- a/src/test/shell/tool/github/lib.sh +++ b/src/test/shell/tool/github/lib.sh @@ -29,6 +29,8 @@ declare -a project_security_default_expected_strings=( 'Figuring out if the project uses sanitizers' 'Figuring out if the project uses FindSecBugs' 'Figuring out how the project uses Bandit' + 'Figuring out how the project uses pylint' + 'Figuring out how the project uses mypy' 'Figuring out how the project uses GoSec' 'Figuring out if the project signs jar files' 'Looking for vulnerabilities in the project' @@ -40,6 +42,7 @@ declare -a project_security_default_expected_strings=( 'Figuring out if the project belongs to the Apache Software Foundation' 'Figuring out if the project uses signed commits' 'Figuring out if the project has a security team' + 'Figuring out if the project has executable binaries' 'Figuring out if the project uses nohttp' 'Figuring out if the project uses OWASP Dependency Check' 'Figuring out if the project has a bug bounty program' @@ -50,6 +53,11 @@ declare -a project_security_default_expected_strings=( 'Looking for package managers' 'Looking for programming languages that are used in the project' 'Figuring out if the project is supported by a company' + 'Does it run Pylint scans?' + 'Does it run Pylint scans on all commits?' + 'Does it run MyPy scans?' + 'Does it run MyPy scans on all commits?' + 'Does it have executable binaries?' 'Here is how the rating was calculated' 'Rating' 'Confidence' @@ -73,8 +81,10 @@ declare -a project_security_default_expected_strings=( 'Sub-score:....Project activity' 'Sub-score:....Project popularity' 'Sub-score:....Security reviews' - 'Sub-score:....How a project uses Bandit' + 'Sub-score:....Bandit score' 'Sub-score:....GoSec score' + 'Sub-score:....How a project uses Pylint' + 'Sub-score:....How a project uses MyPy' ) declare -a artifact_security_default_expected_strings=(