Level 5: Testing and Debugging
Up to now you have read the engine and made small, reviewed changes. From here on, no patch lands
without a test. The first question a Tez committer asks on almost every JIRA is "where's the test that
fails without your change and passes with it?" The second is "does it pass reliably?" This level makes
the Tez test suite your home: its three tiers, the DrainDispatcher-driven state-machine harness, the
MiniTezCluster + MiniDFSCluster integration rig, and the debugging discipline you use when a test —
or production — misbehaves.
Apache Tez has one of the most complete test suites in the Hadoop ecosystem: on the order of a thousand unit tests plus a full in-JVM YARN/HDFS integration harness. The single most valuable skill you build here is picking the cheapest tier that still proves your change, then making the assertion deterministic.
Note: Tez's state-machine tests are built on Hadoop's
StateMachineFactoryand a test-onlyDrainDispatcher(a subclass of YARN'sAsyncDispatcherwhoseawait()busy-loops until the event queue is empty). This lets a test fire an event and deterministically observe the resulting state — no sleeps, no races. Integration tests, by contrast, boot real YARN and HDFS in the test JVM and are where timing bugs and flakiness live. Both are covered below.
Learning Objectives
By the end of Level 5 you must be able to:
- Name the three test tiers in Tez (unit / mini-cluster / full-cluster), choose the right one for a given change, and give the Maven command that runs each.
- Explain how
MiniTezCluster(extends Hadoop'sMiniYARNCluster) andMiniDFSClusterare wired together in a@BeforeClass, and what eachserviceInit/serviceStart/serviceStopstep does. - Read and run the canonical mini-cluster integration test,
TestTezJobs#testOrderedWordCount, and distinguish what it exercises from what a unit test exercises. - Dissect the
TestVertexImplharness: how it builds aVertexImplwith mocked services and drivesVertexEvents through aDrainDispatcher, and how it asserts onVertexState. - Build a coverage matrix of which
(VertexState, VertexEventType)transitions have tests, find a gap, and write a new transition test that follows the existing patterns. - Write a full mini-cluster integration test from scratch that submits a multi-vertex DAG, asserts on
the final
DAGStatusstate, and reads backTezCounters. - Find a real
@Ignored test, reconstruct why it was disabled viagit log/git blame, and reason about what a credible "un-ignore" patch requires.
The Three Test Tiers
Everything lives in each module's src/test/java. Reach for a heavier tier only when a lighter one
cannot exercise the behavior.
# Point this at your real checkout; the labs assume $TEZ_SRC is set.
export TEZ_SRC=/path/to/tez # e.g. ~/src/tez
cd "$TEZ_SRC"
find . -name "MiniTezCluster.java" # -> tez-tests/src/test/java/org/apache/tez/test/MiniTezCluster.java
find . -name "DrainDispatcher.java" # -> tez-common/src/test/java/org/apache/tez/common/DrainDispatcher.java
| Tier | Boots | Where | Run cost | Use when |
|---|---|---|---|---|
| Unit | Nothing real — pure mocks + a DrainDispatcher | each module's src/test/java | seconds | State-machine transitions, parsers, config, counters, helper classes |
| Mini-cluster integration | MiniTezCluster (MiniYARNCluster + Tez AM) + often MiniDFSCluster | tez-tests/src/test/java | tens of seconds to minutes | End-to-end DAGs, shuffle, recovery, kill scenarios in one JVM |
| Full cluster / system | A real YARN cluster | CI / manual | minutes+ | Release validation, performance; not run locally |
flowchart TD
Q{What does your change touch?} --> U[A single class / a state transition / a parser]
Q --> M[A whole DAG: submission, shuffle, counters, recovery]
Q --> S[Scale / real-cluster behavior]
U --> UC["Unit test<br/>(TestVertexImpl, TestDAGImpl, TestTaskImpl)"]
M --> MC["Mini-cluster test<br/>(TestTezJobs, TestFaultTolerance, TestRecovery)"]
S --> SC["Full cluster<br/>(CI only)"]
Warning: Naming and location are load-bearing.
TestOrderedWordCount(tez-tests/src/main/java/org/apache/tez/mapreduce/examples/TestOrderedWordCount.java) looks like a JUnit test but is a runnableToolexample insrc/main— it has no@Testmethods, so-Dtest=TestOrderedWordCountruns nothing. The real mini-cluster JUnit test for ordered word count isTestTezJobs#testOrderedWordCountinsrc/test. Match the class you run to where it lives.
How the Mini-Cluster Is Wired
MiniTezCluster extends MiniYARNCluster. In a single JVM it starts an in-process YARN
ResourceManager and NodeManager(s); a test pairs it with a MiniDFSCluster (NameNode + DataNodes) when
it needs real HDFS. The Tez ApplicationMaster runs as a normal YARN application inside that mini-YARN.
JUnit test JVM
├── MiniDFSCluster (NameNode + N DataNodes) -> remoteFs = dfsCluster.getFileSystem()
└── MiniTezCluster (extends MiniYARNCluster)
├── ResourceManager
├── NodeManager(s) (ShuffleHandler aux-service, DefaultContainerExecutor)
└── Tez AM (DAGAppMaster) <- launched per DAG / per session as a YARN app
Inspect the real lifecycle (never trust line numbers — grep):
grep -n "extends MiniYARNCluster\|serviceInit\|serviceStart\|serviceStop\|APPJAR\|TEZ_LIB_URIS" \
"$TEZ_SRC"/tez-tests/src/test/java/org/apache/tez/test/MiniTezCluster.java
Key facts you should be able to state from that file:
| Step | What it does |
|---|---|
public static final String APPJAR = JarFinder.getJar(DAGAppMaster.class) | Locates the built Tez jar on disk; serviceInit throws TezUncheckedException if it is missing (you forgot mvn install). |
serviceInit | Sets MRConfig.FRAMEWORK_NAME, TEZ_USE_CLUSTER_HADOOP_LIBS=true, disables AM node blacklisting, copies APPJAR onto HDFS and sets TEZ_LIB_URIS, registers the ShuffleHandler NM aux-service, creates the staging dir. |
serviceStart | Writes a yarn-site.xml into the work dir and sets YARN_APPLICATION_CLASSPATH so containers can find classes. |
serviceStop | Calls waitForAppsToFinish() (up to TEZ_TEST_MINI_CLUSTER_APP_WAIT_ON_SHUTDOWN_SECS, default 30) then kills any still-running apps before super.serviceStop(). |
Required Reading
Read these deep dives before the labs; they are the conceptual backbone this level makes concrete.
- Testing Framework — the three tiers, the arrange/send/drain/assert recipe, and the do/don't patterns. Read first.
- State Machines —
StateMachineFactory,addTransition, and how an undefined(state, event)pair throwsInvalidStateTransitonException. - Vertex Lifecycle — the
VertexStategraph you will be testing. - Counters & Diagnostics —
TezCounters,TaskCounter, and how to read a failed DAG's diagnostics. - YARN Integration and Local Mode — what the mini-cluster is standing in for.
Source Areas to Inspect
| Area | Path | Why |
|---|---|---|
| Mini-cluster harness | tez-tests/src/test/java/org/apache/tez/test/MiniTezCluster.java | The rig every integration test boots |
| Canonical integration test | tez-tests/src/test/java/org/apache/tez/test/TestTezJobs.java | @BeforeClass DFS+Tez setup; testOrderedWordCount; per-IO counter assertions |
| Example DAG under test | tez-examples/src/main/java/org/apache/tez/examples/OrderedWordCount.java | The 3-vertex DAG the test submits |
| State-machine unit test | tez-dag/src/test/java/org/apache/tez/dag/app/dag/impl/TestVertexImpl.java | 100+ tests; the harness you extend in Lab 5.2 |
| State machine under test | tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java | stateMachineFactory + addTransition calls |
| Test dispatcher | tez-common/src/test/java/org/apache/tez/common/DrainDispatcher.java | Deterministic event draining |
Debugging a Test
Three tools cover almost every test-debug situation in Tez.
1. Attach a debugger to a single test method. Surefire forks a JVM per test run; pause it and attach your IDE's remote debugger on port 5005:
# Pauses the forked test JVM until a debugger attaches on 5005, then runs the one method.
cd "$TEZ_SRC"
mvn test -pl tez-dag -Dtest='TestVertexImpl#testInvalidEvent' -Dmaven.surefire.debug
# In IntelliJ: Run > Attach to Process / "Remote JVM Debug" -> localhost:5005.
# Breakpoint in VertexImpl.handle(...) and step through the transition.
2. TRACE logging on exactly the package you care about. For a mini-cluster test, raise the log level
for one package so the DAG lifecycle is visible without drowning in YARN/HDFS noise. The captured output
lands in the Surefire -output.txt:
mvn test -pl tez-tests -Dtest='TestTezJobs#testOrderedWordCount' \
-Dtez.root.logger=DEBUG,CLA 2>&1 | grep -i "vertex\|dag state"
grep -n "SUCCEEDED\|RUNNING\|Vertex.*transition" \
tez-tests/target/surefire-reports/org.apache.tez.test.TestTezJobs-output.txt | head
3. DrainDispatcher.await() — the deterministic wait. In unit tests you never Thread.sleep to
wait for an event to be processed. DrainDispatcher (a test-only AsyncDispatcher subclass) exposes
await(), which busy-loops until the event queue is empty. Fire an event, call await(), then assert —
the state is guaranteed settled. This is the single most important habit in Tez unit testing and the
subject of Lab 5.2.
Tip: The final states of the two machines you assert on: a DAG ends in
DAGStatus.StateSUCCEEDED/KILLED/FAILED/ERROR; a vertex ends inVertexStateSUCCEEDED/KILLED/FAILED/ERROR.ERRORspecifically means "the state machine received an event it had no transition for" — a bug, not a normal outcome.
Key Classes Quick Reference
| Class | Module | Role |
|---|---|---|
MiniTezCluster | tez-tests (test) | In-JVM YARN + Tez AM; extends MiniYARNCluster |
MiniDFSCluster | Hadoop | In-JVM HDFS; MiniDFSCluster.Builder(conf).numDataNodes(n).format(true).build() |
TestTezJobs | tez-tests (test) | Canonical mini-cluster tests, incl. testOrderedWordCount |
OrderedWordCount | tez-examples (main) | Tokenizer → Summation → Sorter DAG under test |
TezClient | tez-api (main) | TezClient.create(name, tezConf); submitDAG(dag) returns a DAGClient |
DAGClient | tez-api (main) | waitForCompletion(), getDAGStatus, getVertexStatus |
DAGStatus | tez-api (main) | getState(), getDAGCounters() |
TezCounters / TaskCounter | tez-api (main) | Counter values you assert on |
TestVertexImpl | tez-dag (test) | VertexImpl state-machine harness |
VertexImpl / VertexState / VertexEventType | tez-dag (main) | The state machine, its states, and its event alphabet |
DrainDispatcher | tez-common (test) | AsyncDispatcher subclass; await() drains the queue deterministically |
The Labs
| Lab | Title | Type |
|---|---|---|
| 5.1 | MiniTezCluster and TestOrderedWordCount | Read & run |
| 5.2 | Add a Missing TestVertexImpl Transition Test | Fix-it (coverage) |
| 5.3 | Build It — MiniTezCluster Integration Test | Build-it |
| 5.4 | Fix It — Un-Ignore a Flaky Test | Fix-it (flaky) |
Deliverables
Demonstrate all of the following before advancing to Level 6:
-
You ran
TestTezJobs#testOrderedWordCountto green and can explain, step by step, howMiniDFSClusterandMiniTezClusterare wired in@BeforeClass(Lab 5.1). -
A new
(VertexState, VertexEventType)transition test added toTestVertexImpl, following theDrainDispatcherharness, that passes alongside the existing suite (Lab 5.2). -
A from-scratch mini-cluster integration test that submits a multi-vertex DAG, asserts
DAGStatus.State.SUCCEEDED, and reads back aTezCountervalue you derived by hand (Lab 5.3). -
A real
@Ignored test picked from the checkout, its disable-history reconstructed fromgit log/git blame, and a written analysis of what un-ignoring it would take (Lab 5.4). -
From memory: the three tiers and their Maven commands; why
DrainDispatcher.await()beatsThread.sleep; and why an undefined(state, event)pair drives a vertex toERROR.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
-Dtest=TestOrderedWordCount | Runs nothing — it's a src/main example, not a JUnit test | Run -Dtest=TestTezJobs#testOrderedWordCount |
Running mini-cluster tests without mvn install | TezUncheckedException: TezAppJar ... not found | Build first: mvn install -DskipTests (the AM jar must exist on disk) |
Thread.sleep to wait for an event in a unit test | Flaky, slow | Use DrainDispatcher.await() — it drains the queue deterministically |
Asserting VertexState before dispatcher.await() | Assertion fires before the transition | Always await() after firing an event |
| Using a mini-cluster test for pure logic | 100× slower, harder to debug | Use TestVertexImpl-style unit tests |
@Ignore-ing a flaky test with no JIRA | No tracking; it rots and coverage silently drops | File a JIRA, link it, and treat the flake as a real bug |
Forgetting format(true) on MiniDFSCluster.Builder | Stale NameNode metadata across runs | Format on build; clean the target/*-tmpDir between runs |
How to Verify Success
cd "$TEZ_SRC"
# 1. The whole module builds and its unit tests pass.
mvn install -DskipTests -q
mvn test -pl tez-dag -Dtest=TestVertexImpl -q 2>&1 | tail -5
# 2. You can run one integration test and read its Surefire report.
mvn test -pl tez-tests -Dtest='TestTezJobs#testOrderedWordCount' -q 2>&1 | tail -10
ls tez-tests/target/surefire-reports/
# 3. You can enumerate the state machine and its test coverage.
grep -c "addTransition" tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java
grep -c "public void test" tez-dag/src/test/java/org/apache/tez/dag/app/dag/impl/TestVertexImpl.java
When you can pick the right tier, boot the mini-cluster, drive a state machine deterministically through
a DrainDispatcher, and reconstruct why a test was disabled, you have the testing fluency every Tez
committer expects.
PR Profile: Level 5 Graduate
A contributor who has completed this level ships PRs that a committer can merge with confidence:
- Every change carries a test in the right tier, named and located so Surefire actually runs it.
- State-machine changes come with a
TestVertexImpl/TestDAGImpltransition test that arranges the state, fires the event,await()s, and asserts on the resultingVertexStateand any emitted event (e.g.DAGEventType.INTERNAL_ERROR). - DAG-behavior changes come with a
MiniTezClusterintegration test that asserts the finalDAGStatusstate and a specificTezCounter, not just "it ran." - Flaky-test work is backed by archaeology (the original JIRA), a reproduction, a root-cause
classification, and evidence of N consecutive clean runs — never a silent
@Ignore.
Continue to Level 6: Hive/Tez Integration.