1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // Multi-Phasic Applications: SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.plugin
.multivm
;
12 import cc
.squirreljme
.plugin
.util
.TestResultOutputStream
;
13 import java
.io
.IOException
;
14 import java
.nio
.charset
.StandardCharsets
;
15 import java
.util
.ArrayList
;
16 import java
.util
.List
;
19 import java
.util
.TreeMap
;
20 import java
.util
.TreeSet
;
21 import java
.util
.concurrent
.TimeUnit
;
22 import java
.util
.concurrent
.atomic
.AtomicReference
;
23 import java
.util
.function
.Consumer
;
24 import org
.gradle
.api
.internal
.tasks
.testing
.DefaultTestClassDescriptor
;
25 import org
.gradle
.api
.internal
.tasks
.testing
.DefaultTestDescriptor
;
26 import org
.gradle
.api
.internal
.tasks
.testing
.DefaultTestMethodDescriptor
;
27 import org
.gradle
.api
.internal
.tasks
.testing
.DefaultTestSuiteDescriptor
;
28 import org
.gradle
.api
.internal
.tasks
.testing
.TestClassProcessor
;
29 import org
.gradle
.api
.internal
.tasks
.testing
.TestClassRunInfo
;
30 import org
.gradle
.api
.internal
.tasks
.testing
.TestCompleteEvent
;
31 import org
.gradle
.api
.internal
.tasks
.testing
.TestResultProcessor
;
32 import org
.gradle
.api
.internal
.tasks
.testing
.TestStartEvent
;
33 import org
.gradle
.api
.tasks
.testing
.TestOutputEvent
;
34 import org
.gradle
.api
.tasks
.testing
.TestResult
;
35 import org
.gradle
.internal
.id
.IdGenerator
;
38 * Processor for test classes.
42 public class VMTestFrameworkTestClassProcessor
43 implements TestClassProcessor
46 protected final Map
<String
, Set
<VMTestFrameworkTestClass
>> runTests
=
49 /** Test result output. */
50 protected final AtomicReference
<TestResultProcessor
> resultProcessor
=
51 new AtomicReference
<>();
53 /** The tests that are available. */
54 protected final Map
<String
, CandidateTestFiles
> availableTests
;
56 /** The ID generator to use. */
57 protected final IdGenerator
<?
> idGenerator
;
59 /** The name of this project. */
60 protected final String projectName
;
62 /** The run parameters. */
63 protected final Map
<String
, TestRunParameters
> runParameters
;
65 /** The thread that is running tests. */
66 private volatile Thread _runningThread
;
68 /** Stop running tests? */
69 private volatile boolean _stopNow
;
71 /** The final test result. */
72 private volatile TestResult
.ResultType _finalResult
=
73 TestResult
.ResultType
.SKIPPED
;
76 * Initializes the processor.
78 * @param __availableTests The tests that are available.
79 * @param __projectName The name of the project.
80 * @param __runParameters The run parameters.
81 * @throws NullPointerException On null arguments.
84 public VMTestFrameworkTestClassProcessor(
85 Map
<String
, CandidateTestFiles
> __availableTests
,
86 IdGenerator
<?
> __idGenerator
, String __projectName
,
87 Map
<String
, TestRunParameters
> __runParameters
)
88 throws NullPointerException
90 if (__availableTests
== null || __idGenerator
== null ||
91 __projectName
== null || __runParameters
== null)
92 throw new NullPointerException("NARG");
94 this.availableTests
= __availableTests
;
95 this.idGenerator
= __idGenerator
;
96 this.projectName
= __projectName
;
97 this.runParameters
= __runParameters
;
105 public void startProcessing(TestResultProcessor __resultProcessor
)
107 // Store this for late
108 synchronized (this.resultProcessor
)
110 this.resultProcessor
.set(__resultProcessor
);
119 public void processTestClass(TestClassRunInfo __testClass
)
121 // Build test name for later usage
122 String test
= __testClass
.getTestClassName();
123 VMTestFrameworkTestClass testClass
=
124 new VMTestFrameworkTestClass(test
);
126 // Was there a match at all?
127 boolean match
= false;
129 Map
<String
, Set
<VMTestFrameworkTestClass
>> runTests
= this.runTests
;
130 for (String availableTest
: this.availableTests
.keySet())
132 VMTestFrameworkTestClass available
=
133 new VMTestFrameworkTestClass(availableTest
);
135 // Belong to a different class, ignore completely
136 if (!testClass
.className
.equals(available
.className
))
139 // Is this pure match?
140 boolean currentMatch
= false;
141 currentMatch
= testClass
.normal
.equals(available
.normal
);
143 // Is there a variant match?
144 if (!currentMatch
&& testClass
.variant
!= null)
145 currentMatch
= testClass
.variant
.equals(available
.variant
);
147 // We have a primary sub-variant which matches, but we asked for no
148 // secondary sub-variant and there is one... we want to grab it
149 if (!currentMatch
&& testClass
.primarySubVariant
!= null)
150 currentMatch
= testClass
.primarySubVariant
151 .equals(available
.primarySubVariant
) &&
152 testClass
.secondarySubVariant
== null &&
153 available
.secondarySubVariant
!= null;
155 // We did not ask for a variant but there is one, so include the
157 if (!currentMatch
&& testClass
.variant
== null &&
158 available
.variant
!= null)
161 // Matched, so add the test
163 runTests
.computeIfAbsent(testClass
.className
,
164 (__k
) -> new TreeSet
<>())
165 .add(new VMTestFrameworkTestClass(availableTest
));
167 // Matching emits a match
168 match
|= currentMatch
;
171 // If there is no match and a variant was specified, we likely want
172 // a slightly different parameter
173 if (!match
&& testClass
.variant
!= null)
175 // Remember class for later, sort by classes all together
176 runTests
.computeIfAbsent(testClass
.className
,
177 (__k
) -> new TreeSet
<>()).add(testClass
);
188 // Stop is a bit of a misnomer, it means stop processing and then run
191 // Remember thread for later stop
194 this._runningThread
= Thread
.currentThread();
197 AtomicReference
<TestResultProcessor
> resultProcessor
=
198 this.resultProcessor
;
200 // Suite for the entire project group
201 IdGenerator
<?
> idGenerator
= this.idGenerator
;
202 DefaultTestSuiteDescriptor suiteDesc
=
203 new DefaultTestSuiteDescriptor(
204 idGenerator
.generateId(),
208 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
209 (__rp
) -> __rp
.started(suiteDesc
,
210 new TestStartEvent(System
.currentTimeMillis())));
212 // Go through and actually run all the tests
213 for (Map
.Entry
<String
, Set
<VMTestFrameworkTestClass
>> byClass
:
214 this.runTests
.entrySet())
216 // Do not run any more classes?
224 DefaultTestClassDescriptor classDesc
=
225 new DefaultTestClassDescriptor(idGenerator
.generateId(),
226 byClass
.getKey(), byClass
.getKey());
227 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
228 (__rp
) -> __rp
.started(classDesc
,
229 new TestStartEvent(System
.currentTimeMillis(),
230 suiteDesc
.getId())));
232 // Go through all tests in this class
233 TestResult
.ResultType classResult
= TestResult
.ResultType
.SKIPPED
;
234 for (VMTestFrameworkTestClass testName
: byClass
.getValue())
236 // Do not run any more tests?
243 // We do not know how to run the test, so do nothing
244 if (this.runParameters
.get(testName
.normal
) == null)
247 // Run test and get its result
248 TestResult
.ResultType result
= this.__runSingleTest(suiteDesc
,
249 testName
, classDesc
);
251 // Update class based result
252 classResult
= VMTestFrameworkTestClassProcessor
253 .calculateResult(classResult
, result
);
255 // Change the global suite test result here
256 this._finalResult
= VMTestFrameworkTestClassProcessor
257 .calculateResult(this._finalResult
, result
);
260 // Mark class as completed
261 TestResult
.ResultType finalClassResult
= classResult
;
262 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
263 (__rp
) -> __rp
.completed(classDesc
.getId(),
264 new TestCompleteEvent(System
.currentTimeMillis(),
268 // If failed, emit a throwable... if we do not do this then Gradle does
269 // not care if a test failed and will just continue on happily like
270 // nothing ever happened
271 TestResult
.ResultType finalResult
= this._finalResult
;
272 if (finalResult
== TestResult
.ResultType
.FAILURE
)
273 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
274 (__rp
) -> __rp
.failure(suiteDesc
.getId(),
275 VMTestFrameworkTestClassProcessor
276 .messageThrow("Tests have failed.")));
278 // Use the final result from all the test runs
279 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
280 (__rp
) -> __rp
.completed(suiteDesc
.getId(),
281 new TestCompleteEvent(System
.currentTimeMillis(),
290 public void stopNow()
292 // Signal that tests should stop
296 this._stopNow
= true;
299 synchronized (this.resultProcessor
)
301 this.resultProcessor
.set(null);
304 // Interrupt thread quickly
305 Thread runningThread
= this._runningThread
;
306 if (runningThread
!= null)
307 runningThread
.interrupt();
309 // Make sure to notify on all monitors
315 * Runs the test and gives the result of it.
317 * @param __suiteDesc The suite descriptor.
318 * @param __testName The name of the test.
319 * @param __classDesc The owning class descriptor, since this is a group.
320 * @return The result of the test.
323 @SuppressWarnings("UseOfProcessBuilder")
324 private TestResult
.ResultType
__runSingleTest(
325 DefaultTestSuiteDescriptor __suiteDesc
,
326 VMTestFrameworkTestClass __testName
,
327 DefaultTestClassDescriptor __classDesc
)
329 Map
<String
, TestRunParameters
> runParameters
= this.runParameters
;
330 AtomicReference
<TestResultProcessor
> resultProcessor
=
331 this.resultProcessor
;
332 IdGenerator
<?
> idGenerator
= this.idGenerator
;
334 // Default to failure
335 TestResult
.ResultType testResult
= TestResult
.ResultType
.FAILURE
;
337 // Threads for processing stream data
338 Process process
= null;
339 Thread stdOutThread
= null;
340 Thread stdErrThread
= null;
342 // Make sure process and thread are killed at the end
345 // Check to see if we are stopping testing
348 // If we are forcing a stop, mark as failure
350 return TestResult
.ResultType
.FAILURE
;
353 // Get parameters for this test run
354 TestRunParameters runTest
= runParameters
.get(__testName
.normal
);
356 // Setup process to run
357 ProcessBuilder builder
= new ProcessBuilder();
359 // The command we are executing
360 builder
.command(runTest
.getCommandLine());
362 // Redirect all the outputs we have
363 builder
.redirectOutput(ProcessBuilder
.Redirect
.PIPE
);
364 builder
.redirectError(ProcessBuilder
.Redirect
.PIPE
);
367 DefaultTestDescriptor methodDesc
= new DefaultTestMethodDescriptor(
368 idGenerator
.generateId(), __testName
.className
,
369 (__testName
.variant
== null ?
"test" :
370 String
.format("test[%s]", __testName
.variant
)));
371 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
372 (__rp
) -> __rp
.started(methodDesc
,
373 new TestStartEvent(System
.currentTimeMillis(),
374 __classDesc
.getId())));
379 process
= builder
.start();
383 catch (IOException __e
)
385 // Failed to start test
386 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
387 (__rp
) -> __rp
.failure(__suiteDesc
.getId(),
388 new Throwable("Failed to start test.")));
390 throw new RuntimeException(__e
);
393 // Setup listening buffer threads
394 VMTestOutputBuffer stdOut
= new VMTestOutputBuffer(
395 process
.getInputStream(),
396 new TestResultOutputStream(resultProcessor
, methodDesc
.getId(),
397 TestOutputEvent
.Destination
.StdOut
),
399 VMTestOutputBuffer stdErr
= new VMTestOutputBuffer(
400 process
.getErrorStream(),
401 new TestResultOutputStream(resultProcessor
, methodDesc
.getId(),
402 TestOutputEvent
.Destination
.StdErr
),
405 // Setup threads for reading standard output and error
406 stdOutThread
= new Thread(stdOut
, "stdOutReader");
407 stdErrThread
= new Thread(stdErr
, "stdErrReader");
409 // Start threads so console lines can be read as they appear
410 stdOutThread
.start();
411 stdErrThread
.start();
413 // Run the test, default to failed exit code
415 boolean isDebugging
= VMTestTaskAction
.isDebugging();
416 for (long startTime
= System
.nanoTime();;)
419 // How much time is left? If debugging time never runs
421 long timeLeft
= Math
.max(VMTestWorkAction
.TEST_TIMEOUT
-
422 (System
.nanoTime() - startTime
),
423 (isDebugging ?
1000L : 0L));
425 // Did we run out of time?
426 // Wait for however long this takes to complete
427 if (timeLeft
<= 0 || this._stopNow
||
428 process
.waitFor(timeLeft
, TimeUnit
.NANOSECONDS
))
430 exitCode
= process
.exitValue();
435 // We got interrupted or ran out of time, make sure the process
436 // stops, and we continue with the result
437 catch (IllegalThreadStateException
|InterruptedException
440 // Forcibly destroy the process if it is alive
441 if (process
.isAlive())
442 process
.destroyForcibly();
444 // Make sure the threads process their output
445 stdOutThread
.interrupt();
446 stdErrThread
.interrupt();
449 // Force completion of the read thread, we cannot continue if
450 // the other thread is currently working...
451 stdOut
.getBytes(stdOutThread
);
452 byte[] stdErrBytes
= stdErr
.getBytes(stdErrThread
);
454 // The result determines whether this succeeded, skipped, or
460 testResult
= TestResult
.ResultType
.SUCCESS
;
463 // Skipped is just two
465 testResult
= TestResult
.ResultType
.SKIPPED
;
468 // Treat anything else as failure
470 testResult
= TestResult
.ResultType
.FAILURE
;
474 // If failed, emit a throwable...
475 TestResult
.ResultType finalResult
= testResult
;
476 if (finalResult
== TestResult
.ResultType
.FAILURE
)
477 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
478 (__rp
) -> __rp
.failure(methodDesc
.getId(),
479 VMTestFrameworkTestClassProcessor
480 .messageThrow("Test failed: " +
481 __testName
.normal
)));
483 // Mark method as completed
484 VMTestFrameworkTestClassProcessor
.resultAction(resultProcessor
,
485 (__rp
) -> __rp
.completed(methodDesc
.getId(),
486 new TestCompleteEvent(System
.currentTimeMillis(),
490 // Interrupt read/write threads and kill the process if it is alive
493 // If our test process is still alive, stop it
495 if (process
.isAlive())
496 process
.destroyForcibly();
498 // Stop the standard output thread from running
499 if (stdOutThread
!= null)
500 stdOutThread
.interrupt();
502 // Stop the standard error thread from running
503 if (stdErrThread
!= null)
504 stdErrThread
.interrupt();
511 * Calculates the test result.
513 * @param __input The input result.
514 * @param __modifier The modifier to the result.
515 * @return The new result that should be used.
518 public static TestResult
.ResultType
calculateResult(
519 TestResult
.ResultType __input
, TestResult
.ResultType __modifier
)
521 if (__input
== null || __modifier
== null)
522 throw new NullPointerException("NARG");
524 // Depends on our target result
527 // Keep the old state, this does not cause a change
531 // Change skipped to success, but nothing else
533 if (__input
== TestResult
.ResultType
.SKIPPED
)
537 // Otherwise, always mark failure
542 throw new Error("OOPS");
546 * Generates a throwable useful for printing the output.
548 * @param __output The output.
549 * @return The throwable to use for the message.
552 public static Throwable
messageThrow(byte[] __output
)
554 return VMTestFrameworkTestClassProcessor
.messageThrow(
555 (__output
== null || __output
.length
<= 0 ?
"" :
556 new String(__output
, StandardCharsets
.UTF_8
)));
560 * Generates a throwable useful for printing the output.
562 * @param __output The output.
563 * @return The throwable to use for the message.
566 private static Throwable
messageThrow(String __output
)
568 return new VMTestFrameworkThrowableOutput(__output
);
572 * Runs the given test result processor action, assuming that it is still
573 * a valid one. When {@link #stopNow()} then any processor that did exist
574 * must not be used ever again.
576 * @param __atom The atomic to check on.
577 * @param __doThis Do this action.
578 * @throws NullPointerException On null arguments.
581 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
582 public static void resultAction(
583 AtomicReference
<TestResultProcessor
> __atom
,
584 Consumer
<TestResultProcessor
> __doThis
)
585 throws NullPointerException
587 if (__atom
== null || __doThis
== null)
588 throw new NullPointerException("NARG");
590 synchronized (__atom
)
592 TestResultProcessor processor
= __atom
.get();
593 if (processor
!= null)
594 __doThis
.accept(processor
);