19
19
import java .util .concurrent .Future ;
20
20
import java .util .concurrent .TimeUnit ;
21
21
import java .util .concurrent .TimeoutException ;
22
- import java .util .regex .Pattern ; // Import Pattern
22
+ import java .util .regex .Pattern ;
23
23
import java .util .stream .Collectors ;
24
24
import org .junit .jupiter .api .Assertions ;
25
25
import org .junit .jupiter .api .DynamicTest ;
@@ -47,14 +47,9 @@ private static List<String> discoverProblemNames() {
47
47
return Collections .emptyList ();
48
48
}
49
49
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.
53
50
if ("jar" .equals (resource .getProtocol ())) {
54
51
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." );
58
53
return Collections .emptyList ();
59
54
}
60
55
@@ -70,14 +65,12 @@ private static List<String> discoverProblemNames() {
70
65
return Collections .emptyList ();
71
66
}
72
67
73
- // List directories within the uvaSolutionsPath that start with 'p'
74
68
File uvaDir = uvaSolutionsPath .toFile ();
75
69
File [] problemDirs = uvaDir .listFiles (new FilenameFilter () {
76
70
@ Override
77
71
public boolean accept (File current , String name ) {
78
- // Ensure it's a directory AND matches the "p" + digits pattern
79
72
return new File (current , name ).isDirectory ()
80
- && PROBLEM_DIR_PATTERN .matcher (name ).matches ();
73
+ && PROBLEM_DIR_PATTERN .matcher (name ).matches ();
81
74
}
82
75
});
83
76
@@ -87,9 +80,9 @@ public boolean accept(File current, String name) {
87
80
}
88
81
89
82
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 ());
93
86
94
87
System .out .println ("Discovered problems: " + problems );
95
88
return problems ;
@@ -100,155 +93,149 @@ public boolean accept(File current, String name) {
100
93
101
94
@ TestFactory
102
95
Collection <DynamicTest > runMavenExecTests () {
103
- // Create a fixed-size thread pool
104
96
final ExecutorService executor = Executors .newFixedThreadPool (MAX_THREADS );
105
97
106
- // A list to hold the Futures of each submitted task
107
98
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 ();
124
116
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 " );
139
123
}
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 " );
152
136
}
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 );
170
137
} 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 ());
177
140
}
141
+ });
178
142
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 ();
185
145
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 ;
190
147
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
+ }
193
159
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 ();
197
164
}
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
+ }
198
170
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 );
224
178
}
225
- }))
226
- .collect (Collectors .toList ());
227
179
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
+
229
219
executor .shutdown ();
230
220
try {
231
- // Wait for existing tasks to terminate
232
221
if (!executor .awaitTermination (60 , TimeUnit .SECONDS )) {
233
- executor .shutdownNow (); // Forcefully terminate if not done in time
222
+ executor .shutdownNow ();
234
223
}
235
224
} catch (InterruptedException e ) {
236
225
executor .shutdownNow ();
237
- Thread .currentThread ().interrupt (); // Restore interrupt status
226
+ Thread .currentThread ().interrupt ();
238
227
}
239
228
240
229
return dynamicTests ;
241
230
}
242
231
243
- // A simple record/class to encapsulate test results
244
232
private static class TestResult {
245
233
String problemName ;
246
234
boolean success ;
247
235
String output ;
248
236
String errorOutput ;
249
- Throwable exception ; // To store any exception from the callable
237
+ Throwable exception ;
250
238
251
- // Primary constructor
252
239
public TestResult (String problemName , boolean success , String output , String errorOutput , Throwable exception ) {
253
240
this .problemName = problemName ;
254
241
this .success = success ;
@@ -259,8 +246,7 @@ public TestResult(String problemName, boolean success, String output, String err
259
246
260
247
@ Override
261
248
public String toString () {
262
- // Used by DynamicTest.dynamicTest() to name the test
263
249
return problemName ;
264
250
}
265
251
}
266
- }
252
+ }
0 commit comments