Skip to main content
Version: 5.1.0 (Unmaintained)

Troubleshooting

No tests discovered​

When Runner.run() or the Maven plugin reports no Paramixel tests were found:

Common causes:

CauseFix
Missing @Paramixel.Factory annotationAdd @Paramixel.Factory to a public static no-arg method returning Spec<?> or Action<?>
Factory method has parametersFactory methods must take no arguments
Factory method is not public staticFactory methods must be public and static
Class not on the test classpathEnsure the class is compiled and included in the test classpath (Maven: src/main/java in the examples module; see Project Setup)
@Paramixel.Disabled on the factory methodRemove @Disabled to re-enable discovery
Selector filters too restrictiveCheck paramixel.match.package.regex, paramixel.match.class.regex, and paramixel.match.tag.regex — regex uses find() semantics; for exact match use ^...$

failIfNoTests behavior:

  • failIfNoTests=false (default): logs "No Paramixel tests found" and returns exit code 0.
  • failIfNoTests=true: logs to stderr and returns exit code 1, failing the build.

Null factory methods: A @Paramixel.Factory method that returns null does not produce "no tests found." Instead, it creates a Step action that throws SkipException, resulting in a SKIPPED outcome.

Classloader leaks after tests​

When the Maven plugin warns or fails about lingering non-daemon threads:

Non-daemon threads are still running after Paramixel execution;
these threads may fail when the test classloader is closed:

What the check does: The plugin identifies non-daemon threads whose context classloader is a URLClassLoader — specifically, threads that retain a reference to the test classloader. Threads without this classloader reference are not considered lingering.

Common sources of classloader leaks:

SourceFix
Thread pools not shut downCall ExecutorService.shutdown() in an after step or AutoCloseable.close()
ThreadLocal values not clearedClear thread locals in a cleanup step
Static ScheduledExecutorServiceMake it an instance field managed by Instance, or shut down in after
java.util.logging handlersReset logging handlers in cleanup

strictThreadLifecycle modes:

  • false (default): logs a WARN message, build continues.
  • true: throws MojoExecutionException, build fails.

Parallel deadlocks with nested Parallel​

Nested Parallel actions can deadlock if all scheduler worker threads are blocked waiting for child completions while child work items are queued but cannot be dequeued.

How Paramixel avoids this: The AsyncScheduler uses cooperative work-stealing in managedJoin() — threads waiting for child completions can execute queued work inline, preventing the deadlock.

Guidelines:

  • Use Parallel.parallelism(n) to limit concurrency per action.
  • Prefer isIndependent() for Parallel body children unless order matters.
  • The framework's work-stealing mechanism handles typical nesting patterns. If you encounter a hang, verify that blocking operations (e.g., CountDownLatch.await()) are not preventing cooperative scheduling.

Instance child ordering surprise​

When mixing Instance.child(String, ThrowingConsumer) consumer-shorthand calls with Instance.child(Spec<?>) spec-based calls, the consumer-shorthand children are resolved before spec-based children regardless of call order.

Example of non-obvious ordering:

Instance.of("test", MyService::new)
.child("shorthand-A", MyService::testA) // resolved first
.child(specBasedB) // resolved second
.child("shorthand-C", MyService::testC) // resolved third, but appears BEFORE specBasedB
.resolve();

Execution order: shorthand-A, shorthand-C, specBasedB.

Fix: Use a single overload type per spec:

// All consumer shorthand (declaration order preserved)
Instance.of("test", MyService::new)
.child("test-a", MyService::testA)
.child("test-b", MyService::testB)
.child("test-c", MyService::testC)
.resolve();

// All spec-based (declaration order preserved)
Instance.of("test", MyService::new)
.child(Step.of("test-a", MyService::testA))
.child(Step.of("test-b", MyService::testB))
.child(Step.of("test-c", MyService::testC))
.resolve();

Timeout not working as expected​

Timeout uses wall-clock duration and cooperative thread interruption.

What happens on timeout:

  1. The action records FAILED status with a timeout message.
  2. The child thread is interrupted.
  3. A 100ms grace period allows the child to respond to interruption.
  4. If the child still hasn't completed after the grace period, the descriptor is force-set to FAILED.

Common issues:

IssueCauseFix
Child continues after timeoutChild does not check Thread.interrupted() or is CPU-boundMake the child cooperative (check interruption, use interruptible I/O)
Timeout too earlyDuration includes scheduling overheadAccount for scheduler queue wait time in the timeout duration
Timeout doesn't fireChild completed before the deadlineThis is expected — timeout only fires when the child exceeds the deadline

Orphaned threads: If the child ignores interruption, it becomes an orphaned daemon thread. Since the scheduler uses daemon threads, orphaned threads cannot prevent JVM shutdown.

Report file not generated​

Check paramixel.report.file:

  • The path must be set for report generation to occur. When unset, no report file is generated.
  • Tilde (~) expansion is supported on Linux/macOS: ~/reports/report.json expands to $HOME/reports/report.json.
  • The parent directory is created automatically if it doesn't exist.

Report format is determined by file extension:

ExtensionFormat
.jsonJSON
.xmlXML
.html / .htmHTML
.log, .txt, otherPlain text

Common issues:

IssueFix
No report generatedSet paramixel.report.file=target/paramixel/report.json in POM config, system property, or paramixel.properties
Wrong formatChange the file extension to match the desired format
Permission deniedEnsure the process has write access to the target directory
Tilde not expanding on WindowsUse an absolute path instead: C:\reports\report.json

CI build fails intermittently​

Common causes:

CauseFix
failIfNoTests=true with selective test runsSet -Dparamixel.failIfNoTests=false when filtering by tag/package on CI
Parallelism oversubscription on limited CI runnersSet -Dparamixel.parallelism=2 or Parallel.parallelism(2) to match CI core count
Flaky external servicesUse Retry with appropriate policy, or Timeout with generous deadlines
Race conditions in test stateEnsure shared state is thread-safe; prefer Instance for per-test isolation
failureOnAbort=true (default) with environment-dependent skipsUse AbortedException intentionally; consider -Dparamixel.failureOnAbort=false if skips are expected

Discovery finds wrong tests​

Selector regex patterns use find() semantics (substring match), not matches() (full string match).

Common mistakes:

PatternMatchesIntendedFix
com.examplecom.example, com.example.tests, other.com.exampleOnly com.example and subpackagesUse Selector.packageTreeOf(ComExample.class) or ^com\.example
MyTestcom.example.MyTest, com.other.MyTestExtensionOnly com.example.MyTestUse ^com\.example\.MyTest$ or Selector.classOf(MyTest.class)
smokesmoke, smoke-tests, nosmokeOnly smokeUse ^smoke$

For exact matches, use the anchored helper methods: Selector.packageOf(), Selector.classOf(), or wrap regex with ^...$.