private void ingestTestBinaries() throws Exception {

		if (!resourcesCacheDir.exists() && !FileUtilities.mkdirs(resourcesCacheDir)) {
			throw new IOException("Failed to create GZF cache: " + resourcesCacheDir);
		}

		//
		// Use of TestEnv and GhidraProject
		//	 - Since we are not based upon GenericTestCase due to the manner in which test
		//     resources/programs are managed we only rely on it for its program cache
		//     which we must populate directly
		//   - GhidraProject is used for its ability to import programs, unfortunately it
		//     is a little forceful with imposing a open transaction on all programs so we
		//     take control by becoming a consumer and forcing GhidraProject to release it.
		//

		testControlBlocks = new ArrayList<>();

		List<PCodeTestFile> testFiles = null;

		Msg.info(this, "Locating " + processorDesignator + " P-Code test binaries in: " +
			resourcesTestDataDir);
		testFiles = findBinaryTestFiles(resourcesTestDataDir);

		if (testFiles.size() == 0) {
			throw new AssertException("No test binaries found");
		}

		Msg.info(this, "Processing " + testFiles.size() + " P-Code test binaries");

		int txId = -1;
		HashSet<String> duplicateTests = new HashSet<>();
		HashMap<String, PCodeTestGroup> map = new HashMap<>();
		for (PCodeTestFile testFile : testFiles) {
			String fileReferencePath = testFile.fileReferencePath; // relative resource filepath
			Program program = null;
			boolean usingCachedGZF = false;
			try {
				File absoluteGzfFilePath = null;
				boolean analyze = false; // if true program will be analyzed and cached
				if (fileReferencePath.endsWith(GZF_FILE_EXT)) {
					// TODO: this does not benefit from TestEnv DB cache
					absoluteGzfFilePath = new File(outputDir, fileReferencePath);
					program = getGzfProgram(resourcesTestDataDir, fileReferencePath); // gzf assumed to be pre-analyzed
				}
				else if (testFile.fileReferencePath.endsWith(BINARY_FILE_EXT)) {
					// check for gzf in persistent cache
					absoluteGzfFilePath =
						new File(resourcesCacheDir, fileReferencePath + GZF_FILE_EXT); // persistent cache gzf
					String gzfCachePath =
						GZF_CACHEDIR_NAME + "/" + fileReferencePath + GZF_FILE_EXT; //
					if (absoluteGzfFilePath.exists()) {
						program = getGzfProgram(outputDir, gzfCachePath);
						if (program != null && !MD5Utilities.getMD5Hash(testFile.file)
								.equals(program.getExecutableMD5())) {
							// remove obsolete GZF cache file
							env.release(program);
							program = null;
						}
						if (program == null) {
							absoluteGzfFilePath.delete();
							env.removeFromProgramCache(gzfCachePath);
						}
						else {
							usingCachedGZF = true;
						}
					}
					if (program == null) {
						// import binary from scratch - will be stored in persistent cache as gzf when done
						log(null, "Importing and Analyzing " + testFile.file);
						log(null, "Using language/compiler spec: " + language.getLanguageID() +
							" / " + compilerSpec.getCompilerSpecID());
						Class<? extends Loader> loaderClass = getLoaderClass();
						if (loaderClass != null) {
							program =
								env.getGhidraProject().importProgram(testFile.file, loaderClass);
						}
						else {
							program = env.getGhidraProject()
									.importProgram(testFile.file, language, compilerSpec);
						}
						program.addConsumer(this);
						env.getGhidraProject().close(program);
						analyze = true; // must analyze if not a gzf import
					}
					fileReferencePath = gzfCachePath; // use gzf cache when re-opening
				}
				else {
					Msg.warn(this, "Ignoring P-Code test file - unsupported file extension: " +
						fileReferencePath);
					continue;
				}
				if (program == null) {
					throw new IOException("Failed to open program: " + fileReferencePath);
				}

				txId = program.startTransaction("Analyze");

				if (!program.getLanguageID().equals(language.getLanguageID()) ||
					!program.getCompilerSpec()
							.getCompilerSpecID()
							.equals(compilerSpec.getCompilerSpecID())) {
					throw new IOException((usingCachedGZF ? "Cached " : "") +
						"Program has incorrect language/compiler spec (" + program.getLanguageID() +
						"/" + program.getCompilerSpec().getCompilerSpecID() + "): " +
						absoluteGzfFilePath);
				}

				if (analyze) {

					// discard residual ElfLoader segment data which can result in
					// duplication of data
					cleanupResidualSegmentData(program);

					log(null, "Post-Import processing of " + fileReferencePath);
					postImport(program);
				}

				PCodeTestControlBlock testControlBlock = PCodeTestControlBlock.getMainControlBlock(
					program, testFile, getRestrictedSearchSet(program), fileReferencePath,
					testInfoStruct, groupInfoStruct, analyze, logData.testResults);

				for (PCodeTestGroup testGroup : testControlBlock.getTestGroups()) {
					if (map.containsKey(testGroup.testGroupName)) {
						duplicateTests.add(testGroup.testGroupName);
					}
					else {
						map.put(testGroup.testGroupName, testGroup);
					}
				}

				testControlBlocks.add(testControlBlock);

				if (analyze) {
					log(null, "Analyzing " + fileReferencePath);
					preAnalyze(program);
					analyze(program, testControlBlock);
					postAnalyze(program);

					program.endTransaction(txId, true);
					txId = -1;

					// store to persistent cache as gzf
					Msg.info(ProcessorEmulatorTestAdapter.class,
						"Storing analyzed program in persistent cache: " + absoluteGzfFilePath);
					FileUtilities.mkdirs(absoluteGzfFilePath.getParentFile());
					env.getGhidraProject().saveAsPackedFile(program, absoluteGzfFilePath, true);
					if (!absoluteGzfFilePath.exists()) {
						throw new IOException("Failed to cache gzf file: " + absoluteGzfFilePath);
					}

					// store to short-term TestEnv cache
					env.saveToCache(fileReferencePath, (ProgramDB) program, true,
						TaskMonitor.DUMMY); // ignores .gzf extension on file name/path
				}

				// Check for program errors
				checkForProgramIssues(program, fileReferencePath, testControlBlock,
					logData.testResults);

			}
			catch (InvalidControlBlockException e) {
				throw new RuntimeException(
					"Test control block error (TestInfo structure): " + fileReferencePath, e);
			}
			catch (LanguageNotFoundException e) {
				throw new RuntimeException("Unexpected Error", e); // we already found language
			}
			catch (CancelledException e) {
				throw new RuntimeException("Unexpected Error", e); // Cancel not used
			}
			catch (DuplicateNameException e) {
				throw new RuntimeException("Test file naming conflict: " + fileReferencePath, e); // Must be avoided with naming of binary files
			}
			catch (InvalidNameException e) {
				throw new RuntimeException("Unsupported test filename: " + fileReferencePath, e); // Must be avoided with naming of binary files
			}
			catch (VersionException e) {
				throw new RuntimeException(
					"Unsupported Ghidra database version: " + fileReferencePath, e); // Must be avoided with *.gzf compatibility
			}
			catch (IOException e) {
				throw new RuntimeException(
					"IO Error during P-Code test processing: " + fileReferencePath, e);
			}
			finally {
				if (program != null) {
					if (txId != -1) {
						program.endTransaction(txId, false);
					}
					env.release(program);
				}
			}
		}

		// Sort testControlBlocks
		sortTestControlBlocks();

		// Log file digest details
		log(null, buildTestFileDigest(duplicateTests));

		if (!duplicateTests.isEmpty()) {
			log(null, "ERROR! One or more test groups have been duplicated");
			throw new RuntimeException(
				processorDesignator + " Test files contain duplicate test groups");
		}
	}
@return true if test run should fail up-front if binary contains relocation errors
Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/ProcessorEmulatorTestAdapter.java