Lab 1.2: Run Unit and Integration Tests
Background
Apache Tez has a large, well-structured test suite that spans fast unit tests, module-level
integration tests, and full mini-cluster tests built on MiniTezCluster. Understanding how to run
one test class in seconds, run a single method, read a Surefire report, and tell a real failure
from a flaky one is core contributor muscle: every patch you upload must be accompanied by a
passing, relevant test run, and reviewers will ask which tests you ran.
This lab is built directly on the clean build from Lab 1.1. If you
cannot produce BUILD SUCCESS there, stop and finish that lab first — a test run against a broken
build teaches you nothing.
One property of the Tez build you must internalize immediately: the Surefire plugin in the root
pom.xml is configured with testFailureIgnore = true. Read it yourself:
grep -n "testFailureIgnore\|forkCount\|reuseForks\|forkedProcessTimeoutInSeconds" pom.xml
That flag means a failing test does not automatically fail the Maven build with a non-zero exit
code — the reactor keeps going and reports failures in the summary. So you cannot judge a run by
BUILD SUCCESS alone; you must read the Tests run: … Failures: … Errors: … line and the
Surefire reports. This trips up every newcomer once.
Why This Lab Matters for Contributors
- You must run tests before submitting any patch, and name them in the JIRA/PR description.
- Being able to run a single test class (or method) in seconds is what makes iteration fast enough to actually fix things.
- Reading a Surefire report is the first, non-negotiable step of debugging any failure.
- Flaky tests are one of the most accessible contribution surfaces in Tez — but only once you understand how the tests are wired.
Prerequisites
-
Lab 1.1 complete:
mvn install -DskipTests -Pnouisucceeds. -
The same JDK/
protocprerequisites verified in Lab 1.1 are still on yourPATH. -
~4 GB free heap headroom; the
tez-dagsuite forks memory-hungry JVMs. -
You are inside your
tezcheckout for every command below.
How Tez Tests Are Organized
Tez tests fall into three tiers. For Levels 1–3, you live almost entirely in the first.
| Tier | Location | Runs with | Needs a cluster? |
|---|---|---|---|
| Unit tests | src/test/java/ inside each module | mvn test -pl <module> -am | No — fast, in-JVM |
| Mini-cluster integration | mostly tez-tests/src/test/java/ | mvn test -pl tez-tests -am | Spins up MiniTezCluster (heavy) |
| External-service / system | tez-ext-service-tests, CI scripts | CI / manual | Yes — not run locally |
Confirm the real unit-test classes rather than trusting this table — test names move between releases:
find tez-dag/src/test -name "Test*.java" | sed 's#.*/##' | sort | head -40
The state-machine tests in tez-dag (package org.apache.tez.dag.app.dag.impl) are the heart of
the project and the ones you will read most in Level 4:
| Test class | What it exercises | Approx. @Test count* |
|---|---|---|
TestDAGImpl | DAGImpl state machine: init, vertex bookkeeping, completion | ~43 |
TestVertexImpl | VertexImpl state machine — the single most complex test file in the project | ~102 |
TestTaskImpl | TaskImpl transitions: schedule, kill, too-many-attempts | ~37 |
TestTaskAttempt | TaskAttemptImpl transitions, failure/kill handling | (verify below) |
* Counts drift. Get the live number yourself — this is also Stretch Goal 2:
grep -c "@Test" tez-dag/src/test/java/org/apache/tez/dag/app/dag/impl/TestVertexImpl.java
Supporting test infrastructure lives in tez-dag/src/test/java/org/apache/tez/dag/app/. Confirm
what is actually there before you rely on a name:
ls tez-dag/src/test/java/org/apache/tez/dag/app/Mock*.java
# e.g. MockDAGAppMaster.java, MockClock.java — a reduced AM and a controllable clock
MockDAGAppMaster is the reduced Application Master used by TestMockDAGAppMaster to drive DAGs
in-process without a YARN connection; MockClock lets timing-sensitive tests advance time
deterministically instead of sleeping. You will meet both again when you study the
testing-framework deep-dive.
Step-by-Step Tasks
Step 1: Run All Unit Tests in tez-dag
mvn test -pl tez-dag -am -Pnoui -q 2>&1 | tail -40
Expected duration: 5–12 minutes depending on hardware. Because of testFailureIgnore=true,
scan the summary, not the exit code:
[INFO] Results:
[INFO]
[INFO] Tests run: NNNN, Failures: 0, Errors: 0, Skipped: NN
A non-zero Skipped count is normal (some tests are @Ignored or environment-gated). A non-zero
Failures/Errors is what you investigate.
Step 2: Run a Single Test Class
This is your fast inner loop. -Dtest selects the class:
mvn test -pl tez-dag -am -Pnoui -Dtest=TestDAGImpl -q
Last lines on success:
[INFO] Tests run: 43, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
On failure you will see something like:
[ERROR] Failures:
[ERROR] TestDAGImpl.testDAGInit:NNN expected:<...> but was:<...>
[ERROR] Tests run: 43, Failures: 1, Errors: 0, Skipped: 0
Note: If
-Dtest=TestDAGImplreportsNo tests were executed, you almost certainly did not build the module first.-amrebuilds upstream deps, but the module's own test classes must compile; runmvn test-compile -pl tez-dag -amonce if in doubt.
Step 3: Run a Single Test Method
The command you will run more than any other — one method after one code change:
mvn test -pl tez-dag -am -Pnoui -Dtest=TestDAGImpl#testDAGInit -q
Multiple methods and wildcards are supported by Surefire:
mvn test -pl tez-dag -am -Pnoui -Dtest='TestDAGImpl#testDAGInit+testVertexCompletion' -q
mvn test -pl tez-dag -am -Pnoui -Dtest='TestVertex*' -q # every class matching the glob
Step 4: Read the Surefire Report
Maven writes per-class results under the module's target/surefire-reports/. The .txt file
carries the full stack trace, which is almost always more useful than console output:
ls tez-dag/target/surefire-reports/
cat tez-dag/target/surefire-reports/org.apache.tez.dag.app.dag.impl.TestDAGImpl.txt
For CI-style parsing, the .xml file next to it (TEST-*.xml) is the JUnit XML a reviewer's tool
would consume. When you report a failure on JIRA, paste the relevant stack trace from the .txt,
not a screenshot of the console.
Step 5: Run Tests in tez-api
tez-api tests are fast and cluster-free — a good place to iterate. Confirm the real class names
first:
find tez-api/src/test -name "Test*.java" | sed 's#.*/##' | sort
Representative, verified classes:
| Test class | What it tests |
|---|---|
TestDAG | DAG API construction and vertex/edge wiring |
TestDAGVerify | DAG validation rules (cycles, missing edges, name clashes) |
TestDAGPlan | DAG → protobuf plan serialization round-trips |
TestTezClient | TezClient init and session lifecycle |
TestTezConfiguration | Config key defaults and scoping |
mvn test -pl tez-api -Pnoui -Dtest=TestDAGVerify -q
Step 6: Run Tests in tez-runtime-library
This module holds shuffle, sort, and I/O — the code your Lab 1.4 pipeline exercises. Confirm names (several "expected" names from older docs no longer exist):
find tez-runtime-library/src/test -name "Test*.java" | sed 's#.*/##' | sort | head -40
Verified, currently-present classes worth knowing:
| Test class | What it tests |
|---|---|
TestUnorderedPartitionedKVWriter (common/writers) | Unordered partitioned KV output writing |
TestPipelinedSorter, TestDefaultSorter (common/sort/impl) | The two sorter implementations |
TestTezMerger (common/sort/impl) | Sort-merge of spilled segments |
TestFetcher (common/shuffle and .../orderedgrouped) | Shuffle fetch logic (two implementations) |
TestShuffleScheduler (.../orderedgrouped) | Fetch scheduling and retry |
TestValuesIterator (common) | Grouped-values iteration over sorted input |
mvn test -pl tez-runtime-library -am -Pnoui -Dtest=TestValuesIterator -q
Step 7: Deliberately Break a Test, Then Read the Failure
Make failure output familiar before you hit a real one. The method getTotalVertices() exists in
DAGImpl — verify, then sabotage it in a throwaway edit:
grep -n "getTotalVertices" tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/DAGImpl.java
- In
DAGImpl.getTotalVertices(), make the bodyreturn 0;as the first statement. - Rebuild and run a class that depends on that value:
mvn test -pl tez-dag -am -Pnoui -Dtest=TestDAGImpl -q - Read both the console
[ERROR]block and the matchingsurefire-reports/*.txt. - Revert cleanly:
git checkout -- tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/DAGImpl.java
You now know exactly what a real regression will look like.
Debugging Test Failures
Attach a Debugger (IntelliJ)
Surefire exposes a debug hook. The forked test JVM suspends until your IDE attaches:
mvn test -pl tez-dag -am -Pnoui -Dtest=TestDAGImpl \
-Dmaven.surefire.debug
By default this listens on port 5005. In IntelliJ: create a Remote JVM Debug run
configuration on localhost:5005 and start it; the test proceeds once attached. To pick a
different port/options, pass the full string:
-Dmaven.surefire.debug="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006"
Turn Up Logging
Tez uses SLF4J over Log4j; test resources ship a log4j.properties per module. The cheapest way to
get more signal is to raise the level for a package during a run:
mvn test -pl tez-dag -am -Pnoui -Dtest=TestDAGImpl \
-Dtez.root.logger=DEBUG,CLA -q 2>&1 | tail -60
Then read tez-dag/target/surefire-reports/*-output.txt for the captured per-test stdout/stderr.
Where Flakiness Comes From
Most Tez flakes are one of a handful of shapes. Recognizing them is half the fix:
| Flake shape | Typical root cause |
|---|---|
| Passes alone, fails in the suite | Shared static/singleton state or a leaked thread from a prior test |
Timing-dependent (assertTrue(x.isDone()) right after an async call) | Missing wait/poll; should use a deterministic MockClock or await-condition |
| Port-in-use / bind failures | A mini-cluster test racing another for an ephemeral port |
Ordering assertions on HashMap/HashSet iteration | Non-deterministic collection order asserted as fixed |
reuseForks=false and forkCount=1 (from the root pom) mean each test class gets a fresh JVM,
which suppresses some cross-class leakage — but not intra-class shared state. When you suspect a
flake, run the class in a loop:
for i in $(seq 1 10); do
mvn test -pl tez-dag -am -Pnoui -Dtest=TestSomeFlakyThing -q 2>&1 | grep "Tests run"
done
Deliverables
-
A full
tez-dagunit run whose summary line you can read and interpret (Tests run/Failures/Errors/Skipped). -
A single-class run (
-Dtest=TestDAGImpl) and a single-method run (-Dtest=TestDAGImpl#<method>) both green. -
You located and read a real Surefire
.txtreport for at least one test class. -
You completed the "break
getTotalVertices()" exercise and reverted it cleanly. -
You ran at least one
tez-apiand onetez-runtime-librarytest by its verified name. -
You can explain why
BUILD SUCCESSalone does not prove your tests passed.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
BUILD SUCCESS but tests actually failed | testFailureIgnore=true in the root pom keeps the reactor green. | Always read the Tests run: … Failures: … Errors: … summary and the Surefire reports; don't rely on exit code. |
-Dtest=Foo → No tests were executed | The test class was never compiled, or the name/glob doesn't match. | Run mvn test-compile -pl <module> -am first; check the class exists with find … -name "Foo.java". |
A tez-tests run hangs or eats all your RAM | You invoked a MiniTezCluster integration test, which stands up YARN in-process. | For Level 1–3 stay in unit tests. If you must, give it heap via MAVEN_OPTS="-Xmx4g" and expect minutes per test. |
OutOfMemoryError / heap dump files appear | Fork heap too small for tez-dag/tez-runtime-library. | export MAVEN_OPTS="-Xmx4g". The pom already sets -XX:+HeapDumpOnOutOfMemoryError, so a .hprof will be written — delete it after. |
--add-opens / InaccessibleObjectException on an old JDK | You're running the JDK-21 test config on an older JDK. | Use the JDK the branch requires (Lab 1.1, Step 2); the pom's Surefire argLine assumes it. |
| Test fails only in the full suite, passes alone | Shared static state or a leaked thread. | Reproduce by running the two classes together; look for static mutable fields or unclosed executors. This is a genuine bug to file. |
| Surefire report directory is empty | The module never reached the test phase (compile failure upstream). | Scroll up to the first [ERROR]; fix the compile error before chasing "missing" reports. |
Checkstyle/RAT failure during mvn test | Quality gates also bind to the build lifecycle. | Run mvn checkstyle:check -pl <module> / apache-rat:check -pl <module> and fix; see Lab 1.1's table. |
Stretch Goals
-
Enumerate what touches
VertexImpl. Every test that exercises the vertex state machine:find tez-dag/src/test -name "*.java" | xargs grep -l "VertexImpl" | sed 's#.*/##' | sort -u -
Count the test surface of the biggest file.
grep -c "@Test"onTestVertexImpl.java; understand why one class needs that many cases (the vertex state machine has dozens of states and transitions — preview it in state-machines). -
Rank the slowest classes. After a full
tez-dagrun:grep -h "Time elapsed" tez-dag/target/surefire-reports/*.txt | sort -t: -k2 -rn | head -
Find the mock-driven tests. Learn the in-process AM pattern you'll extend later:
grep -rl "MockDAGAppMaster" tez-dag/src/test | sed 's#.*/##' | sort -u -
Run a quality-gate-only pass. Prove your future patch will survive review:
mvn checkstyle:check apache-rat:check -pl tez-dag -am -Pnoui
Validation / Self-check
You are done when you can answer these without notes:
- Why does a green
BUILD SUCCESSnot prove your tests passed, and which pom setting causes that? - What is the exact command to run a single test method in
tez-dag, and why do you almost always want-am? - Where does Maven write the full stack trace for a failed test, and which file extension carries the human-readable version vs. the machine-readable one?
- Name two verified test classes in
tez-runtime-libraryand say what each covers. - How do you attach IntelliJ's debugger to a forked test run, and on what default port?
- Given a test that passes alone but fails in the suite, what are the two most likely causes and how would you confirm one?
- What do
forkCount=1andreuseForks=falsein the Surefire config buy you, and what class of flakiness do they not fix?
Where to go next: with a green build (Lab 1.1) and a working test loop, you are ready to actually run a DAG in Lab 1.3 — Run a Simple Tez DAG Locally. The mocks and state machines you met here are dissected in the testing-framework and state-machines deep-dives, and become your day job in Level 2.