Skip to content

Commit 1a4913e

Browse files
committed
Add working set tests that are ignored by default
1 parent 9aeba4d commit 1a4913e

2 files changed

Lines changed: 196 additions & 0 deletions

File tree

rewrite-csharp/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ tasks.withType<Test> {
9090

9191
maxParallelForks = 1
9292
maxHeapSize = "8g"
93+
// Exclude working-set tests by default; run with -PincludeWorkingSet to include
94+
useJUnitPlatform {
95+
if (!project.hasProperty("includeWorkingSet")) {
96+
excludeTags("workingSet")
97+
}
98+
}
9399
// Add timeout to identify hanging tests
94100
systemProperty("junit.jupiter.execution.timeout.default", "30s")
95101
// Show test names as they run

rewrite-csharp/src/test/java/org/openrewrite/csharp/rpc/CSharpParseProjectTest.java

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.junit.jupiter.api.AfterEach;
1919
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Tag;
2021
import org.junit.jupiter.api.Test;
2122
import org.junit.jupiter.api.Timeout;
2223
import org.junit.jupiter.api.io.TempDir;
@@ -28,11 +29,14 @@
2829
import java.io.IOException;
2930
import java.nio.file.Files;
3031
import java.nio.file.Path;
32+
import java.time.Duration;
3133
import java.nio.file.Paths;
3234
import java.util.List;
3335
import java.util.concurrent.TimeUnit;
36+
import java.util.stream.Collectors;
3437

3538
import static org.assertj.core.api.Assertions.assertThat;
39+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
3640

3741
/**
3842
* Integration tests for C# ParseSolution RPC.
@@ -304,4 +308,190 @@ public class Program
304308
String sourcePath = sf.getSourcePath().toString();
305309
assertThat(sourcePath).startsWith("src");
306310
}
311+
312+
// ---- Individual solution tests for targeted debugging ----
313+
314+
private static final Path WORKING_SET = Paths.get("C:/Projects/moderneinc/moderne-cli/working-set-csharp");
315+
316+
// MonoGame solutions (FieldAccess→NameTree cast failures)
317+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
318+
void parseMonoGameAndroid() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Framework.Android.sln")); }
319+
320+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
321+
void parseMonoGameDesktopGL() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Framework.DesktopGL.sln")); }
322+
323+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
324+
void parseMonoGameiOS() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Framework.iOS.sln")); }
325+
326+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
327+
void parseMonoGameToolsLinux() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Tools.Linux.sln")); }
328+
329+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
330+
void parseMonoGameToolsMac() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Tools.Mac.sln")); }
331+
332+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
333+
void parseMonoGameToolsWindows() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Tools.Windows.sln")); }
334+
335+
@Tag("workingSet") @Test @Timeout(value = 600, unit = TimeUnit.SECONDS)
336+
void parseMonoGameWindowsDX() { parseSingleSolution(WORKING_SET.resolve("Game Framework/MonoGame/MonoGame/MonoGame.Framework.WindowsDX.sln")); }
337+
338+
// Large solutions — need extended timeouts
339+
@Tag("workingSet") @Test @Timeout(value = 1200, unit = TimeUnit.SECONDS)
340+
void parseFlowLauncher() { parseSingleSolution(WORKING_SET.resolve("Desktop App/Flow-Launcher/Flow.Launcher/Flow.Launcher.sln")); }
341+
342+
@Tag("workingSet") @Test @Timeout(value = 1200, unit = TimeUnit.SECONDS)
343+
void parseBulkCrapUninstaller() { parseSingleSolution(WORKING_SET.resolve("Desktop App/Klocman/Bulk-Crap-Uninstaller/BulkCrapUninstaller.sln")); }
344+
345+
@Tag("workingSet") @Test @Timeout(value = 1200, unit = TimeUnit.SECONDS)
346+
void parseJellyfin() { parseSingleSolution(WORKING_SET.resolve("Web API/jellyfin/jellyfin/Jellyfin.sln")); }
347+
348+
@Tag("workingSet") @Test @Timeout(value = 1200, unit = TimeUnit.SECONDS)
349+
void parseILSpy() { parseSingleSolution(WORKING_SET.resolve("Developer Tool/icsharpcode/ILSpy/ILSpy.sln")); }
350+
351+
@Tag("workingSet") @Test @Timeout(value = 1200, unit = TimeUnit.SECONDS)
352+
void parseBitwarden() { parseSingleSolution(WORKING_SET.resolve("Web API/bitwarden/server/bitwarden-server.sln")); }
353+
354+
/**
355+
* Parses a single solution file with a fresh RPC instance and 10-minute timeout.
356+
* Skipped via assumeTrue if the solution file doesn't exist on this machine.
357+
*/
358+
private void parseSingleSolution(Path solutionPath) {
359+
assumeTrue(Files.exists(solutionPath), "Solution not found: " + solutionPath);
360+
Path rootDir = solutionPath.getParent();
361+
362+
// Restart RPC with extended timeout for large solutions
363+
CSharpRewriteRpc.shutdownCurrent();
364+
CSharpRewriteRpc.setFactory(
365+
CSharpRewriteRpc.builder()
366+
.csharpServerEntry(findCSharpServerEntry())
367+
.traceRpcMessages(false)
368+
.timeout(Duration.ofMinutes(20))
369+
.log(Paths.get("/tmp/csharp-rpc-project.log"))
370+
);
371+
factoryConfigured = true;
372+
rpc = CSharpRewriteRpc.getOrStart();
373+
374+
InMemoryExecutionContext ctx = new InMemoryExecutionContext(t -> {
375+
System.err.println(" Execution error: " + t.getMessage());
376+
t.printStackTrace(System.err);
377+
});
378+
379+
List<SourceFile> sourceFiles = rpc.parseSolution(solutionPath, rootDir, ctx).toList();
380+
381+
int parseErrors = 0;
382+
for (SourceFile sf : sourceFiles) {
383+
if (sf instanceof ParseError pe) {
384+
parseErrors++;
385+
System.err.println(" PARSE ERROR: " + sf.getSourcePath());
386+
System.err.println(" " + pe.getText());
387+
}
388+
}
389+
390+
System.out.println(" Parsed " + sourceFiles.size() + " files" +
391+
(parseErrors > 0 ? ", " + parseErrors + " parse errors" : ""));
392+
393+
assertThat(sourceFiles).as("Should parse at least one file").isNotEmpty();
394+
assertThat(parseErrors).as("Parse errors").isZero();
395+
}
396+
397+
// ---- Full working set sweep ----
398+
399+
/**
400+
* Discovers and parses all .sln/.slnx files under WORKING_SET_ROOT.
401+
* Set the system property "workingSetRoot" to override the default path.
402+
* Skipped automatically if the root directory doesn't exist on this machine.
403+
*/
404+
@Tag("workingSet")
405+
@Test
406+
@Timeout(value = 3600, unit = TimeUnit.SECONDS)
407+
void parseWorkingSetSolution() throws IOException {
408+
String rootProperty = System.getProperty("workingSetRoot",
409+
"C:/Projects/moderneinc/moderne-cli/working-set-csharp");
410+
Path workingSetRoot = Paths.get(rootProperty);
411+
assumeTrue(Files.isDirectory(workingSetRoot),
412+
"Working set root not found: " + workingSetRoot);
413+
414+
// Find all .sln and .slnx files
415+
List<Path> solutionFiles;
416+
try (var walk = Files.walk(workingSetRoot)) {
417+
solutionFiles = walk
418+
.filter(p -> {
419+
String name = p.getFileName().toString().toLowerCase();
420+
return name.endsWith(".sln") || name.endsWith(".slnx");
421+
})
422+
.sorted()
423+
.collect(Collectors.toList());
424+
}
425+
426+
System.out.println("Found " + solutionFiles.size() + " solution files under " + workingSetRoot);
427+
assumeTrue(!solutionFiles.isEmpty(), "No .sln/.slnx files found under " + workingSetRoot);
428+
429+
int totalFiles = 0;
430+
int totalParseErrors = 0;
431+
int totalSolutions = 0;
432+
int failedSolutions = 0;
433+
434+
for (int i = 0; i < solutionFiles.size(); i++) {
435+
Path solutionPath = solutionFiles.get(i);
436+
Path rootDir = solutionPath.getParent();
437+
String relative = workingSetRoot.relativize(solutionPath).toString();
438+
439+
System.out.println("\n[" + (i + 1) + "/" + solutionFiles.size() + "] Parsing: " + relative);
440+
System.out.flush();
441+
442+
// Restart RPC for each solution to avoid state leaks and OOM
443+
CSharpRewriteRpc.shutdownCurrent();
444+
CSharpRewriteRpc.setFactory(
445+
CSharpRewriteRpc.builder()
446+
.csharpServerEntry(findCSharpServerEntry())
447+
.traceRpcMessages(false)
448+
.timeout(Duration.ofMinutes(20))
449+
.log(Paths.get("/tmp/csharp-rpc-project.log"))
450+
);
451+
factoryConfigured = true;
452+
rpc = CSharpRewriteRpc.getOrStart();
453+
454+
InMemoryExecutionContext ctx = new InMemoryExecutionContext(t -> {
455+
System.err.println(" Execution error: " + t.getMessage());
456+
t.printStackTrace(System.err);
457+
});
458+
459+
try {
460+
List<SourceFile> sourceFiles = rpc.parseSolution(solutionPath, rootDir, ctx).toList();
461+
462+
int parseErrors = 0;
463+
for (SourceFile sf : sourceFiles) {
464+
if (sf instanceof ParseError pe) {
465+
parseErrors++;
466+
System.err.println(" PARSE ERROR: " + sf.getSourcePath());
467+
System.err.println(" " + pe.getText());
468+
}
469+
}
470+
471+
totalFiles += sourceFiles.size();
472+
totalParseErrors += parseErrors;
473+
totalSolutions++;
474+
475+
System.out.println(" OK: " + sourceFiles.size() + " files" +
476+
(parseErrors > 0 ? ", " + parseErrors + " parse errors" : ""));
477+
} catch (Exception e) {
478+
failedSolutions++;
479+
System.err.println(" FAILED: " + e.getClass().getSimpleName() + ": " + e.getMessage());
480+
e.printStackTrace(System.err);
481+
}
482+
System.out.flush();
483+
System.err.flush();
484+
}
485+
486+
System.out.println("\n========== SUMMARY ==========");
487+
System.out.println("Solutions found: " + solutionFiles.size());
488+
System.out.println("Solutions parsed: " + totalSolutions);
489+
System.out.println("Solutions failed: " + failedSolutions);
490+
System.out.println("Total files: " + totalFiles);
491+
System.out.println("Parse errors: " + totalParseErrors);
492+
System.out.println("=============================");
493+
494+
assertThat(failedSolutions).as("Solutions that failed to parse").isZero();
495+
assertThat(totalParseErrors).as("Total parse errors across all solutions").isZero();
496+
}
307497
}

0 commit comments

Comments
 (0)