1919import java .util .concurrent .Future ;
2020import java .util .concurrent .TimeUnit ;
2121import java .util .concurrent .TimeoutException ;
22- import java .util .regex .Pattern ; // Import Pattern
22+ import java .util .regex .Pattern ;
2323import java .util .stream .Collectors ;
2424import org .junit .jupiter .api .Assertions ;
2525import org .junit .jupiter .api .DynamicTest ;
@@ -47,14 +47,9 @@ private static List<String> discoverProblemNames() {
4747 return Collections .emptyList ();
4848 }
4949
50- // Handle JAR paths correctly. If it's in a JAR, resource.toURI() will be "jar:file:/..."
51- // We need to resolve the actual file system path if running from an unzipped structure,
52- // or return an empty list if it's purely in a JAR and cannot be listed as a directory.
5350 if ("jar" .equals (resource .getProtocol ())) {
5451 System .err .println (
55- "Cannot discover problems dynamically from within a JAR file. Please ensure 'src/main/java' is accessible on the file system during testing." );
56- // In a real scenario, you might have a pre-defined list for JAR runs,
57- // or expect tests to be run against expanded directories.
52+ "Cannot discover problems dynamically from within a JAR file. Please ensure 'src/main/java' is accessible on the file system during testing." );
5853 return Collections .emptyList ();
5954 }
6055
@@ -70,14 +65,12 @@ private static List<String> discoverProblemNames() {
7065 return Collections .emptyList ();
7166 }
7267
73- // List directories within the uvaSolutionsPath that start with 'p'
7468 File uvaDir = uvaSolutionsPath .toFile ();
7569 File [] problemDirs = uvaDir .listFiles (new FilenameFilter () {
7670 @ Override
7771 public boolean accept (File current , String name ) {
78- // Ensure it's a directory AND matches the "p" + digits pattern
7972 return new File (current , name ).isDirectory ()
80- && PROBLEM_DIR_PATTERN .matcher (name ).matches ();
73+ && PROBLEM_DIR_PATTERN .matcher (name ).matches ();
8174 }
8275 });
8376
@@ -87,9 +80,9 @@ public boolean accept(File current, String name) {
8780 }
8881
8982 List <String > problems = Arrays .stream (problemDirs )
90- .map (File ::getName )
91- .sorted () // Optional: Sort for consistent order
92- .collect (Collectors .toList ());
83+ .map (File ::getName )
84+ .sorted ()
85+ .collect (Collectors .toList ());
9386
9487 System .out .println ("Discovered problems: " + problems );
9588 return problems ;
@@ -100,155 +93,149 @@ public boolean accept(File current, String name) {
10093
10194 @ TestFactory
10295 Collection <DynamicTest > runMavenExecTests () {
103- // Create a fixed-size thread pool
10496 final ExecutorService executor = Executors .newFixedThreadPool (MAX_THREADS );
10597
106- // A list to hold the Futures of each submitted task
10798 List <Future <TestResult >> futures = PROBLEMS .stream ()
108- .map (problem -> {
109- // Create a Callable for each problem
110- Callable <TestResult > task = () -> {
111- Thread .currentThread ().setName ("Problem-Runner-" + problem ); // Name thread for better logging
112- String command = String .format ("mvn exec:exec -Dproblem=%s" , problem );
113- System .out .println (Thread .currentThread ().getName () + ": Executing command for " + problem
114- + ": " + command );
115-
116- Process process ;
117- try {
118- process = Runtime .getRuntime ().exec (command );
119- } catch (Exception e ) {
120- // If process execution itself fails
121- return new TestResult (
122- problem , false , "" , "Failed to execute command: " + e .getMessage (), e );
123- }
99+ .map (problem -> {
100+ Callable <TestResult > task = () -> {
101+ Thread .currentThread ().setName ("Problem-Runner-" + problem );
102+ String command = String .format ("mvn exec:exec -Dproblem=%s" , problem );
103+ System .out .println (Thread .currentThread ().getName () + ": Executing command for " + problem
104+ + ": " + command );
105+
106+ Process process ;
107+ try {
108+ process = Runtime .getRuntime ().exec (command );
109+ } catch (Exception e ) {
110+ return new TestResult (
111+ problem , false , "" , "Failed to execute command: " + e .getMessage (), e );
112+ }
113+
114+ StringBuilder output = new StringBuilder ();
115+ StringBuilder errorOutput = new StringBuilder ();
124116
125- StringBuilder output = new StringBuilder ();
126- StringBuilder errorOutput = new StringBuilder ();
127-
128- // Use separate threads to consume streams to prevent deadlock
129- Thread outputGobbler = new Thread (() -> {
130- try (BufferedReader reader =
131- new BufferedReader (new InputStreamReader (process .getInputStream ()))) {
132- String line ;
133- while ((line = reader .readLine ()) != null ) {
134- output .append (line ).append ("\n " );
135- }
136- } catch (Exception e ) {
137- System .err .println (Thread .currentThread ().getName () + ": Error reading output for "
138- + problem + ": " + e .getMessage ());
117+ Thread outputGobbler = new Thread (() -> {
118+ try (BufferedReader reader =
119+ new BufferedReader (new InputStreamReader (process .getInputStream ()))) {
120+ String line ;
121+ while ((line = reader .readLine ()) != null ) {
122+ output .append (line ).append ("\n " );
139123 }
140- });
141-
142- Thread errorGobbler = new Thread (() -> {
143- try ( BufferedReader errorReader =
144- new BufferedReader ( new InputStreamReader ( process . getErrorStream ()))) {
145- String line ;
146- while (( line = errorReader . readLine ()) != null ) {
147- errorOutput . append ( line ). append ( " \n " );
148- }
149- } catch ( Exception e ) {
150- System . err . println ( Thread . currentThread (). getName ()
151- + ": Error reading error output for " + problem + ": " + e . getMessage () );
124+ } catch ( Exception e ) {
125+ System . err . println ( Thread . currentThread (). getName () + ": Error reading output for "
126+ + problem + ": " + e . getMessage ());
127+ }
128+ });
129+
130+ Thread errorGobbler = new Thread (() -> {
131+ try ( BufferedReader errorReader =
132+ new BufferedReader ( new InputStreamReader ( process . getErrorStream ()))) {
133+ String line ;
134+ while (( line = errorReader . readLine ()) != null ) {
135+ errorOutput . append ( line ). append ( " \n " );
152136 }
153- });
154-
155- outputGobbler .start ();
156- errorGobbler .start ();
157-
158- int exitCode ;
159- try {
160- exitCode = process .waitFor ();
161- outputGobbler .join (); // Ensure all output is consumed
162- errorGobbler .join (); // Ensure all error output is consumed
163- } catch (InterruptedException e ) {
164- process .destroyForcibly (); // Ensure subprocess is terminated if interrupted
165- outputGobbler .join (100 ); // Give gobblers a moment, but don't hang
166- errorGobbler .join (100 );
167- Thread .currentThread ().interrupt (); // Restore interrupted status
168- return new TestResult (
169- problem , false , output .toString (), "Test interrupted (likely timed out)" , e );
170137 } catch (Exception e ) {
171- return new TestResult (
172- problem ,
173- false ,
174- output .toString (),
175- "Error waiting for process: " + e .getMessage (),
176- e );
138+ System .err .println (Thread .currentThread ().getName ()
139+ + ": Error reading error output for " + problem + ": " + e .getMessage ());
177140 }
141+ });
178142
179- boolean success = (exitCode == 0 );
180- return new TestResult (problem , success , output .toString (), errorOutput .toString (), null );
181- };
182- return executor .submit (task ); // Submit the task to the executor
183- })
184- .collect (Collectors .toList ());
143+ outputGobbler .start ();
144+ errorGobbler .start ();
185145
186- // Create DynamicTests to check the results of the submitted tasks
187- Collection <DynamicTest > dynamicTests = futures .stream ()
188- .map (future -> DynamicTest .dynamicTest ("Test problem: " + future .toString (), () -> {
189- TestResult result = null ;
146+ boolean completed ;
190147 try {
191- // Wait for each task to complete with the defined timeout
192- result = future .get (TEST_TIMEOUT .toSeconds (), TimeUnit .SECONDS );
148+ completed = process .waitFor (TEST_TIMEOUT .toSeconds (), TimeUnit .SECONDS );
149+ outputGobbler .join (100 );
150+ errorGobbler .join (100 );
151+ } catch (InterruptedException e ) {
152+ process .destroyForcibly ();
153+ outputGobbler .join (100 );
154+ errorGobbler .join (100 );
155+ Thread .currentThread ().interrupt ();
156+ return new TestResult (
157+ problem , false , output .toString (), "Test interrupted" , e );
158+ }
193159
194- System .out .println ("Test " + result .problemName + " completed. Output:\n " + result .output );
195- if (!result .errorOutput .isEmpty ()) {
196- System .err .println ("Test " + result .problemName + " error output:\n " + result .errorOutput );
160+ if (!completed ) {
161+ process .destroy ();
162+ if (process .isAlive ()) {
163+ process .destroyForcibly ();
197164 }
165+ outputGobbler .join (100 );
166+ errorGobbler .join (100 );
167+ return new TestResult (
168+ problem , false , output .toString (), "Process timed out after " + TEST_TIMEOUT .toSeconds () + " seconds" , null );
169+ }
198170
199- Assertions .assertTrue (
200- result .success ,
201- "Maven command failed for problem: " + result .problemName + "\n Error output:\n "
202- + result .errorOutput );
203-
204- } catch (TimeoutException e ) {
205- // This handles the case where the Callable itself exceeds the timeout
206- future .cancel (true ); // Attempt to interrupt the running task
207- Assertions .fail (
208- "Test for problem " + (result != null ? result .problemName : "unknown" )
209- + " timed out after " + TEST_TIMEOUT .toSeconds () + " seconds." ,
210- e );
211- } catch (InterruptedException e ) {
212- Thread .currentThread ().interrupt (); // Restore interrupt status
213- Assertions .fail (
214- "Test for problem " + (result != null ? result .problemName : "unknown" )
215- + " was interrupted." ,
216- e );
217- } catch (ExecutionException e ) {
218- // The actual exception thrown by the Callable is wrapped here
219- Throwable cause = e .getCause ();
220- Assertions .fail (
221- "An error occurred during execution for problem "
222- + (result != null ? result .problemName : "unknown" ) + ": " + cause .getMessage (),
223- cause );
171+ int exitCode ;
172+ try {
173+ exitCode = process .exitValue ();
174+ } catch (IllegalThreadStateException e ) {
175+ process .destroyForcibly ();
176+ return new TestResult (
177+ problem , false , output .toString (), "Process did not terminate properly" , e );
224178 }
225- }))
226- .collect (Collectors .toList ());
227179
228- // Crucial: Shut down the executor after all tests have been processed
180+ boolean success = (exitCode == 0 );
181+ return new TestResult (problem , success , output .toString (), errorOutput .toString (), null );
182+ };
183+ return executor .submit (task );
184+ })
185+ .collect (Collectors .toList ());
186+
187+ Collection <DynamicTest > dynamicTests = futures .stream ()
188+ .map (future -> DynamicTest .dynamicTest ("Test problem: " + future .toString (), () -> {
189+ TestResult result = null ;
190+ try {
191+ result = future .get ();
192+
193+ System .out .println ("Test " + result .problemName + " completed. Output:\n " + result .output );
194+ if (!result .errorOutput .isEmpty ()) {
195+ System .err .println ("Test " + result .problemName + " error output:\n " + result .errorOutput );
196+ }
197+
198+ Assertions .assertTrue (
199+ result .success ,
200+ "Maven command failed for problem: " + result .problemName + "\n Error output:\n "
201+ + result .errorOutput );
202+
203+ } catch (InterruptedException e ) {
204+ Thread .currentThread ().interrupt ();
205+ Assertions .fail (
206+ "Test for problem " + (result != null ? result .problemName : "unknown" )
207+ + " was interrupted." ,
208+ e );
209+ } catch (ExecutionException e ) {
210+ Throwable cause = e .getCause ();
211+ Assertions .fail (
212+ "An error occurred during execution for problem "
213+ + (result != null ? result .problemName : "unknown" ) + ": " + cause .getMessage (),
214+ cause );
215+ }
216+ }))
217+ .collect (Collectors .toList ());
218+
229219 executor .shutdown ();
230220 try {
231- // Wait for existing tasks to terminate
232221 if (!executor .awaitTermination (60 , TimeUnit .SECONDS )) {
233- executor .shutdownNow (); // Forcefully terminate if not done in time
222+ executor .shutdownNow ();
234223 }
235224 } catch (InterruptedException e ) {
236225 executor .shutdownNow ();
237- Thread .currentThread ().interrupt (); // Restore interrupt status
226+ Thread .currentThread ().interrupt ();
238227 }
239228
240229 return dynamicTests ;
241230 }
242231
243- // A simple record/class to encapsulate test results
244232 private static class TestResult {
245233 String problemName ;
246234 boolean success ;
247235 String output ;
248236 String errorOutput ;
249- Throwable exception ; // To store any exception from the callable
237+ Throwable exception ;
250238
251- // Primary constructor
252239 public TestResult (String problemName , boolean success , String output , String errorOutput , Throwable exception ) {
253240 this .problemName = problemName ;
254241 this .success = success ;
@@ -259,8 +246,7 @@ public TestResult(String problemName, boolean success, String output, String err
259246
260247 @ Override
261248 public String toString () {
262- // Used by DynamicTest.dynamicTest() to name the test
263249 return problemName ;
264250 }
265251 }
266- }
252+ }
0 commit comments