TestDataDescriptor (@NotNull String testName, @NotNull PsiClass psiClass, @Nullable String testDataPath) { String normalizedTestDataPath = testDataPath == null ? null : StringUtil.trimEnd(StringUtil.trimEnd(testDataPath, "/"), "\\"); // PhpStorm has tests that use '$' symbol as a file path separator, e.g. 'test$while_stmt$declaration' test // stands for '/while_smt/declaration.php' file somewhere in a test data. String possibleFileName = ContainerUtil.getLastItem(StringUtil.split(testName, "$"), testName); assert possibleFileName != null; if (possibleFileName.isEmpty()) { return TestDataDescriptor.NOTHING_FOUND; } Project project = psiClass.getProject(); ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); GotoFileModel gotoModel = new GotoFileModel(project); String possibleFilePath = testName.replace('$', '/'); Map<String, List<TestLocationDescriptor>> descriptorsByFileNames = new HashMap<>(); Module module = ReadAction.compute(() -> ModuleUtilCore.findModuleForPsiElement(psiClass)); Collection<String> fileNames = getAllFileNames(possibleFileName, gotoModel); ProgressIndicator indicator = EmptyProgressIndicator.notNullize(ProgressManager.getInstance().getProgressIndicator()); indicator.setText(DevKitBundle.message("testdata.progress.text.searching.for.test.data.files", testName)); indicator.setIndeterminate(false); int fileNamesCount = fileNames.size(); double currentIndex = 0; for (String name : fileNames) { ProgressManager.checkCanceled(); Object[] elements = gotoModel.getElementsByName(name, false, name); for (Object element : elements) { if (!(element instanceof PsiFileSystemItem psiFile)) { continue; } if (normalizedTestDataPath != null) { PsiFileSystemItem containingDirectory = psiFile.getParent(); if (containingDirectory != null) { VirtualFile directoryVirtualFile = containingDirectory.getVirtualFile(); String normalizedDirPath = StringUtil.trimEnd(StringUtil.trimEnd(directoryVirtualFile.getPath(), "/"), "\\"); if (!normalizedDirPath.startsWith(normalizedTestDataPath)) { continue; } } } VirtualFile file = psiFile.getVirtualFile(); if (file == null || fileIndex.isInSource(file) && !fileIndex.isUnderSourceRootOfType(file, JavaModuleSourceRootTypes.RESOURCES)) { continue; } String filePath = file.getPath(); if (!StringUtil.containsIgnoreCase(filePath, possibleFilePath) && !StringUtil.containsIgnoreCase(filePath, testName)) { continue; } String fileName = StringUtil.toLowerCase(PathUtil.getFileName(filePath)); int i = fileName.indexOf(StringUtil.toLowerCase(possibleFileName)); // Skip files that doesn't contain target test name and files that contain digit after target test name fragment. // Example: there are tests with names 'testEnter()' and 'testEnter2()' and we don't want test data file 'testEnter2' // to be matched to the test 'testEnter()'. if (i < 0 || (i + possibleFileName.length() < fileName.length()) && Character.isDigit(fileName.charAt(i + possibleFileName.length()))) { continue; } List<TestLocationDescriptor> currentDescriptors = new SmartList<>(); VfsUtilCore.processFilesRecursively(file, f -> { if (f.isDirectory()) return true; TestLocationDescriptor current = TestLocationDescriptor.create(possibleFileName, f, project, module); if (current != null) { currentDescriptors.add(current); } return true; }); if (!currentDescriptors.isEmpty()) { List<TestLocationDescriptor> previousDescriptors = descriptorsByFileNames.get(name); if (previousDescriptors == null) { descriptorsByFileNames.put(name, currentDescriptors); continue; } if (moreRelevantPath(currentDescriptors.get(0), previousDescriptors.get(0), psiClass)) { descriptorsByFileNames.put(name, currentDescriptors); } } } indicator.setFraction(++currentIndex / fileNamesCount); } List<TestLocationDescriptor> descriptors = ContainerUtil.flatten(descriptorsByFileNames.values()); filterDirsFromOtherModules(descriptors); return new TestDataDescriptor(descriptors); }