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 -Pnoui succeeds.
  • The same JDK/protoc prerequisites verified in Lab 1.1 are still on your PATH.
  • ~4 GB free heap headroom; the tez-dag suite forks memory-hungry JVMs.
  • You are inside your tez checkout 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.

TierLocationRuns withNeeds a cluster?
Unit testssrc/test/java/ inside each modulemvn test -pl <module> -amNo — fast, in-JVM
Mini-cluster integrationmostly tez-tests/src/test/java/mvn test -pl tez-tests -amSpins up MiniTezCluster (heavy)
External-service / systemtez-ext-service-tests, CI scriptsCI / manualYes — 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 classWhat it exercisesApprox. @Test count*
TestDAGImplDAGImpl state machine: init, vertex bookkeeping, completion~43
TestVertexImplVertexImpl state machine — the single most complex test file in the project~102
TestTaskImplTaskImpl transitions: schedule, kill, too-many-attempts~37
TestTaskAttemptTaskAttemptImpl 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=TestDAGImpl reports No tests were executed, you almost certainly did not build the module first. -am rebuilds upstream deps, but the module's own test classes must compile; run mvn test-compile -pl tez-dag -am once 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 classWhat it tests
TestDAGDAG API construction and vertex/edge wiring
TestDAGVerifyDAG validation rules (cycles, missing edges, name clashes)
TestDAGPlanDAG → protobuf plan serialization round-trips
TestTezClientTezClient init and session lifecycle
TestTezConfigurationConfig 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 classWhat 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
  1. In DAGImpl.getTotalVertices(), make the body return 0; as the first statement.
  2. Rebuild and run a class that depends on that value:
    mvn test -pl tez-dag -am -Pnoui -Dtest=TestDAGImpl -q
    
  3. Read both the console [ERROR] block and the matching surefire-reports/*.txt.
  4. 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 shapeTypical root cause
Passes alone, fails in the suiteShared 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 failuresA mini-cluster test racing another for an ephemeral port
Ordering assertions on HashMap/HashSet iterationNon-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-dag unit 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 .txt report for at least one test class.
  • You completed the "break getTotalVertices()" exercise and reverted it cleanly.
  • You ran at least one tez-api and one tez-runtime-library test by its verified name.
  • You can explain why BUILD SUCCESS alone does not prove your tests passed.

Troubleshooting

SymptomCauseFix
BUILD SUCCESS but tests actually failedtestFailureIgnore=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 executedThe 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 RAMYou 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 appearFork 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 JDKYou'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 aloneShared 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 emptyThe 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 testQuality 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

  1. 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
    
  2. Count the test surface of the biggest file. grep -c "@Test" on TestVertexImpl.java; understand why one class needs that many cases (the vertex state machine has dozens of states and transitions — preview it in state-machines).

  3. Rank the slowest classes. After a full tez-dag run:

    grep -h "Time elapsed" tez-dag/target/surefire-reports/*.txt | sort -t: -k2 -rn | head
    
  4. 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
    
  5. 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:

  1. Why does a green BUILD SUCCESS not prove your tests passed, and which pom setting causes that?
  2. What is the exact command to run a single test method in tez-dag, and why do you almost always want -am?
  3. 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?
  4. Name two verified test classes in tez-runtime-library and say what each covers.
  5. How do you attach IntelliJ's debugger to a forked test run, and on what default port?
  6. Given a test that passes alone but fails in the suite, what are the two most likely causes and how would you confirm one?
  7. What do forkCount=1 and reuseForks=false in 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.