diff --git a/gradle/support/app_config_breakout.txt b/gradle/support/app_config_breakout.txt index 2928105212..1199881418 100644 --- a/gradle/support/app_config_breakout.txt +++ b/gradle/support/app_config_breakout.txt @@ -1,3 +1,20 @@ +This file groups ghidra test classes by the application configuration +they require. This is necessary for our parallel test execution so that we +don't group tests that will try to load different application configs. + +There are currently 4 config classes that are created by our various test +classes. A single gradle task that executes multiple tests in a single jvm cannot have +tests that try to load more than one of these: + - HeadlessGhidraApplicationConfig + - DockingApplicationConfiguration + - ApplicationConfiguration + - GhidraAppConfiguration + +Note that this does not list EVERY ghidra test class; just the classes +that are extended by other test classes. + +See testUtils.gradle for how this file is parsed/used. + ###HeadlessGhidraApplicationConfig^ AbstractGhidraHeadlessIntegrationTest ProcessorEmulatorTestAdapter diff --git a/gradle/support/testUtils.gradle b/gradle/support/testUtils.gradle index 7b8f5f2284..a3f68ba620 100644 --- a/gradle/support/testUtils.gradle +++ b/gradle/support/testUtils.gradle @@ -52,13 +52,25 @@ long getDurationFromTestReportClass(String fileContents, String fileName) { /* * Creates a map of tests to their durations, organized by the type of - * application configuration they require. This gets the mapping from the - * resource file app_config_breakout.txt. + * application configuration they require. + * + * When creating groups of tests to run, we have to ensure that we not only + * group them by duration (to make the parallelization more efficient) but also + * by the type of application config they require (to avoid a catastrophic test + * failure). + * + * This timing information is gleaned by parsing the html results of a previous + * test run. + * + * The application config information is contained in the resource file + * app_config_breakout.txt. * * eg: GhidraAppConfiguration -> DiffTestTypeAdapter, 0.135s */ def Map> getTestReport() { + // If we have already created the test report, do not waste time creating + // it again. Just return it. if (project.testReport != null) { return project.testReport; } @@ -66,85 +78,31 @@ def Map> getTestReport() { logger.debug("getTestReport: Populating 'testReport' using '$testTimeParserInputDir'") testReport = new HashMap(); - Map dockingConfigurationBucket = new HashMap(); - Map integrationConfigurationBucket = new HashMap(); - Map appConfigurationBucket = new HashMap(); - Map ghidraConfigurationBucket = new HashMap(); - Map unknownConfigurationBucket = new HashMap(); - + + List integrationConfigs = new ArrayList<>(); + List dockingConfigs = new ArrayList<>(); + List appConfigs = new ArrayList<>(); + List ghidraConfigs = new ArrayList<>(); + parseApplicationConfigs(dockingConfigs, integrationConfigs, appConfigs, ghidraConfigs); + File classesReportDir = new File(testTimeParserInputDir) if(!classesReportDir.exists()) { logger.info("getTestReport: The path '$testTimeParserInputDir' does not exist on the file system." + " Returning empty testReport map.") return Collections.emptyList(); } - + + // These are the configuration 'buckets' that each test class will be dumped + // into. Only tests in the same bucket will be run together. + Map dockingBucket = new HashMap(); + Map integrationBucket = new HashMap(); + Map appBucket = new HashMap(); + Map ghidraBucket = new HashMap(); + Map unknownBucket = new HashMap(); + int excludedHtmlFiles = 0 // counter int totalHtmlFiles = 0 String excludedHtmlFileNames = "" // for log.info summary message - - // Read in the config settings and allocate test files to - // the appropriate buckets - - List integrationConfigs = new ArrayList<>(); - List dockingConfigs = new ArrayList<>(); - List appConfigs = new ArrayList<>(); - List ghidraConfigs = new ArrayList<>(); - - File f = new File(rootProject.projectDir, "gradle/support/app_config_breakout.txt"); - String configLines = f.text; - String[] splitLines = configLines.split("###"); - for (int i=0; i @@ -187,32 +145,32 @@ def Map> getTestReport() { } if (extendsClass.isEmpty()) { - unknownConfigurationBucket.put(fqNameFromTestReport, durationInMillis); + unknownBucket.put(fqNameFromTestReport, durationInMillis); } else { if (integrationConfigs.contains(extendsClass)) { - integrationConfigurationBucket.put(fqNameFromTestReport, durationInMillis); + integrationBucket.put(fqNameFromTestReport, durationInMillis); } else if (dockingConfigs.contains(extendsClass)) { - dockingConfigurationBucket.put(fqNameFromTestReport, durationInMillis); + dockingBucket.put(fqNameFromTestReport, durationInMillis); } else if (appConfigs.contains(extendsClass)) { - appConfigurationBucket.put(fqNameFromTestReport, durationInMillis); + appBucket.put(fqNameFromTestReport, durationInMillis); } else if (ghidraConfigs.contains(extendsClass)) { - ghidraConfigurationBucket.put(fqNameFromTestReport, durationInMillis); + ghidraBucket.put(fqNameFromTestReport, durationInMillis); } else { - unknownConfigurationBucket.put(fqNameFromTestReport, durationInMillis); + unknownBucket.put(fqNameFromTestReport, durationInMillis); } } } - testReport.put("integration", integrationConfigurationBucket); - testReport.put("docking", dockingConfigurationBucket); - testReport.put("app", appConfigurationBucket); - testReport.put("ghidra", ghidraConfigurationBucket); - testReport.put("unknown", unknownConfigurationBucket); + testReport.put("integration", integrationBucket); + testReport.put("docking", dockingBucket); + testReport.put("app", appBucket); + testReport.put("ghidra", ghidraBucket); + testReport.put("unknown", unknownBucket); logger.debug("getTestReport: Added to testReport: class name = '" + fqNameFromTestReport + "' and durationInMillis = '"+ durationInMillis @@ -241,6 +199,73 @@ def Map> getTestReport() { return project.testReport } +/** + * 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