/* ### * IP: GHIDRA * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.regex.*; import groovy.io.FileType; import java.lang.reflect.Constructor; import java.lang.*; import java.io.*; ext.integrationConfigs = new ArrayList<>(); ext.dockingConfigs = new ArrayList<>(); ext.appConfigs = new ArrayList<>(); ext.ghidraConfigs = new ArrayList<>(); /* * Checks if html test report for an individual test class has a valid name. */ boolean hasValidTestReportClassName(String name) { return name.endsWith("Test.html") && !name.contains("Suite") } /** * Parses the file containing the mapping of test classes to application configs and assigns those * classes to the appropriate lists. */ def parseApplicationConfigs(List dockingConfigs, List integrationConfigs, List appConfigs, List ghidraConfigs) { File breakoutFile = new File(rootProject.projectDir, "gradle/support/app_config_breakout.txt"); String configLines = breakoutFile.text; // Ignore everything up to the first "###" (everything before that // is documentation) configLines = configLines.substring(configLines.indexOf("###")); String[] splitLines = configLines.split("###"); for (int i=0; i getTestsForSubProject(SourceDirectorySet sourceDirectorySet) { def testsForSubProject = new HashMap(); int includedClassFilesNotInTestReport = 0 // class in sourceSet but not in test report, 'bumped' to first task int includedClassFilesInTestReport = 0 // class in sourceSet and in test report int excludedClassFilesBadName = 0 // excluded class in sourceSet with invalid name int excludedClassFilesCategory = 0 // excluded class in sourceSet with @Category annotation int excludedClassAllTestsIgnored = 0 // excluded class in sourceSet with test report duration of 0 logger.debug("getTestsForSubProject: Found " + sourceDirectorySet.files.size() + " file(s) in source set to process.") // Read in the config file that indicates which base test classes are associated with // which application configs. This is not a comprehensive list of all test classes // in Ghidra - it's the list of all classes that are extended in Ghidra. parseApplicationConfigs(dockingConfigs, integrationConfigs, appConfigs, ghidraConfigs); // "Buckets" that delineate which test classes should be run together. List dockingBucket = new ArrayList(); List integrationBucket = new ArrayList(); List appBucket = new ArrayList(); List ghidraBucket = new ArrayList(); List unknownBucket = new ArrayList(); for (File file : sourceDirectorySet.getFiles()) { logger.debug("getTestsForSubProject: Found file in sourceSet = " + file.name) // Must have a valid class name if(!hasValidTestClassName(file.name)) { logger.debug("getTestsForSubProject: Excluding file '" + file.name + "' based on name.") excludedClassFilesBadName++ continue } String fileContents = file.text // Must not have a Category annotation if (hasCategoryExcludes(fileContents)) { logger.debug("getTestsForSubProject: Found category exclude for '" + file.name + "'. Excluding this class from running.") excludedClassFilesCategory++ continue } // Get any extending class so we can see what bucket it belongs to. Do this // by grabbing the next word after "extends". Pattern p = Pattern.compile("extends\\W+(\\w+)"); Matcher m = p.matcher(fileContents); String extendsClass = ""; while (m.find()) { extendsClass = m.group(1); break; } // Get full package name of the class. Pattern p2 = Pattern.compile("package\\s+([a-zA_Z_][\\.\\w]*);"); Matcher m2 = p2.matcher(fileContents); String packageName = ""; while (m2.find()) { packageName = m2.group(1); break; } // Construct a var of the form ".". This will // be stored in the appropriate bucket and be used when creating test // tasks later on. String className = packageName + "." + file.name className = className.replace(".java", "") if (extendsClass.isEmpty()) { unknownBucket.add(className); } else { if (integrationConfigs.contains(extendsClass)) { integrationBucket.add(className); } else if (dockingConfigs.contains(extendsClass)) { dockingBucket.add(className); } else if (appConfigs.contains(extendsClass)) { appBucket.add(className); } else if (ghidraConfigs.contains(extendsClass)) { ghidraBucket.add(className); } else { unknownBucket.add(className); } } } Map testBuckets = new HashMap(); testBuckets.put("docking", dockingBucket) testBuckets.put("integration", integrationBucket) testBuckets.put("app", appBucket) testBuckets.put("ghidra", ghidraBucket) testBuckets.put("unknown", unknownBucket) logger.debug("integration bucket: " + integrationBucket) logger.debug("docking bucket: " + dockingBucket) logger.debug("app bucket: " + appBucket) logger.debug("ghidra bucket: " + ghidraBucket) logger.debug("unknown bucket: " + unknownBucket) return testBuckets; } /********************************************************************************* * Determines if test task creation should be skipped for parallelCombinedTestReport task. *********************************************************************************/ def boolean shouldSkipTestTaskCreation(Project subproject) { if (!parallelMode) { logger.debug("shouldSkipTestTaskCreation: Skip task creation for $subproject.name. Not in parallel mode.") return true } if (!subproject.hasProperty("sourceSets")) { logger.debug("shouldSkipTestTaskCreation: subproject $subproject.name has no sourceSet property.") return true } if (subproject.sourceSets.findByName("test") == null || subproject.sourceSets.test.java.files.isEmpty()) { logger.debug("shouldSkipTestTaskCreation: Skip task creation for $subproject.name. No test sources.") return true } if (subproject.findProperty("excludeFromParallelTests") ?: false) { logger.debug("shouldSkipTestTaskCreation: Skip task creation for $subproject.name." + " 'excludeFromParallelTests' found.") return true } if (subproject.findProperty("repoToTest")) { subproject.ext.repoToTest = subproject.getProperty('repoToTest') } else { subproject.ext.repoToTest = "ALL_REPOS" } if (!subproject.ext.repoToTest.equals("ALL_REPOS") && !subproject.getProjectDir().toString().contains("/" + repoToTest + "/")) { return true } return false } /********************************************************************************* * Determines if integrationTest task creation should be skipped for parallelCombinedTestReport task. *********************************************************************************/ def boolean shouldSkipIntegrationTestTaskCreation(Project subproject) { if (!parallelMode) { logger.debug("shouldSkipIntegrationTestTaskCreation: Skip task creation for $subproject.name." + " Not in parallel mode.") return true } if (!subproject.hasProperty("sourceSets")) { logger.debug("shouldSkipIntegrationTestTaskCreation: subproject $subproject.name has no sourceSet property.") return true } if (subproject.sourceSets.findByName("integrationTest") == null || subproject.sourceSets.integrationTest.java.files.isEmpty()) { logger.debug("shouldSkipIntegrationTestTaskCreation: Skip task creation for $subproject.name." + " No integrationTest sources.") return true } if (subproject.findProperty("excludeFromParallelIntegrationTests") ?: false) { logger.debug("shouldSkipIntegrationTestTaskCreation: Skip task creation for $subproject.name." + "'excludeFromParallelIntegrationTests' found.") return true } if (subproject.findProperty("repoToTest")) { subproject.ext.repoToTest = subproject.getProperty('repoToTest') } else { subproject.ext.repoToTest = "ALL_REPOS" } if (!subproject.ext.repoToTest.equals("ALL_REPOS") && !subproject.getProjectDir().toString().contains("/" + repoToTest + "/")) { return true } return false } /********************************************************************************* * Gets the path to the last archived test report. This is used by the * 'parallelCombinedTestReport' task when no -PtestTimeParserInputDir is supplied * via cmd line. *********************************************************************************/ def String getLastArchivedReport(String reportArchivesPath) { // skip configuration for this property if not in parallelMode if (!parallelMode) { logger.debug("getLastArchivedReport: not in 'parallelMode'. Skipping.") return "" } File reportArchiveDir = new File(reportArchivesPath); logger.info("getLastArchivedReport: searching for test report to parse in " + reportArchivesPath) if(!reportArchiveDir.exists()) { logger.info("getLastArchivedReport: '$reportArchiveDir' does not exist.") return "" } // filter for report archive directories. File[] files = reportArchiveDir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.startsWith("reports_"); } }); assert (files != null && files.size() > 0) : """Could not find test report archives in '$reportArchiveDir'. because no -PtestTimeParserInputDir= supplied via cmd line""" logger.debug("getLastArchivedReport: found " + files.size() + " archived report directories in '" + reportArchiveDir.getPath() + "'.") // Sort by lastModified date. The last modified directory will be first. files = files.sort{-it.lastModified()} logger.debug("getLastArchivedReport: selecting report archive to parse: " + files[0].getAbsolutePath()) return files[0].getAbsolutePath() } ext { getTestsForSubProject = this.&getTestsForSubProject // export this utility method to project shouldSkipTestTaskCreation = this.&shouldSkipTestTaskCreation shouldSkipIntegrationTestTaskCreation = this.&shouldSkipIntegrationTestTaskCreation getLastArchivedReport = this.&getLastArchivedReport }