void (@Nullable Function<? super VirtualFile, Object> condition, @Nullable Function<? super VirtualFile, ? extends Iterable<VirtualFile>> iterable, @NotNull String expected, VirtualFileVisitor.Option @NotNull ... options) { MultiMap<VirtualFile, Pair<VirtualFile, String>> visitLog = MultiMap.create(); Map<VirtualFile, String> backLog = new HashMap<>(); try { Set<VirtualFile> visited = new HashSet<>(); VfsUtilCore.visitChildrenRecursively(myRoot, new VirtualFileVisitor<Integer>(options) { { setValueForChildren(0); } @Override public boolean visitFile(@NotNull VirtualFile file) { if (!visited.add(file)) { throw new AssertionError(file + " already visited"); } return true; } @Override public @NotNull Result visitFileEx(@NotNull VirtualFile file) { if (!visited.add(file)) { throw new AssertionError(file + " already visited"); } String name = file.equals(myRoot) ? "/" : file.getName(); String s = "-> " + name + " [" + getCurrentValue() + "]\n"; visitLog.putValue(file.getParent(), Pair.create(file, s)); setValueForChildren(getCurrentValue() + 1); if (condition != null) { Object result = condition.fun(file); if (result instanceof Result) return (Result)result; if (result instanceof VirtualFile) return skipTo((VirtualFile)result); } return CONTINUE; } @Override public void afterChildrenVisited(@NotNull VirtualFile file) { String name = file.equals(myRoot) ? "/" : file.getName(); String s = "<- " + name + " [" + getCurrentValue() + "]\n"; backLog.put(file, s); } @Override public @Nullable Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) { return iterable != null ? iterable.fun(file) : super.getChildrenIterable(file); } }); } catch (AbortException ignore) { } StringBuilder out = new StringBuilder(); toLog(ArrayUtil.contains(VirtualFileVisitor.SKIP_ROOT, options) ? myRoot : myRoot.getParent(), visitLog, backLog, 0, out); assertEquals(expected, out.toString()); }