Back to the drill cs.jdoner.me · lesson · exam craft, all 100 points

Nine green tests, and a zero on the same problem

A submission can pass every test the grader runs and still score nothing. It can also fail to compile in a way no test ever reports, because the tests never get to run. Before you write a line, you need to know the six ways a part can land, and which of them passing tests can hide. That is the whole job of the Zero Gauntlet: read a short piece of practice code, name its fate, then read the rule. This page is the logic behind each verdict.

The six verdicts

Every graded part lands on exactly one of these. Four of them are a zero. Two of the four zeros happen with every test green, which is why passing tests alone never tells you the whole story.

Full credit

Compiles, stops on its own, passes the hidden tests, stays in the fence, breaks no stated rule.

Does not compile · 0

javac rejects the file. The tests never run, and the part scores 0.

Endless loop or recursion · 0

A loop that never ends, or recursion with no reachable base case, which ends in a StackOverflowError. The part scores 0.

Fence edit · 0

A change outside the markers or in a given file. The part scores 0, even with a correct body.

Broken rule

Every test green, but a person takes points: an API sort on Sorting, a loop on Recursion, a field assigned directly.

Fails a test

Compiles and finishes, but a hidden assertion does not match. Only the failed points are lost.

Does not compile: the first gate

A part that does not compile scores 0, and nothing else about it matters, because the grader never runs a file that will not build. This is the cheapest zero to avoid and the easiest to miss under pressure. Five shapes show up on this exam:

int cmp = this.last.compareTo(that.last) // missing semicolon
inputFile = new Scanner(file); // FileNotFoundException not caught
return false; // inside a void method
// a non-void method with a branch that never returns
import java.awt.List; // wrong List; c.get and c.size vanish

The Scanner line is the subtle one. new Scanner(File) throws FileNotFoundException, which is a checked exception (the compiler forces you to handle it). Calling exists() first does not change that; the code still needs a try/catch, and the fence forbids adding throws to the signature, so catching is the only route. Compile every module the moment you finish it, and read the first error before you touch anything else.

Endless loop or recursion: the packet’s own example

Code that never returns scores 0, and the instructions name a missing base case in a recursive method as the example. A test that never finishes cannot pass, and an endless run also stalls the whole grading pass. Three recursion traps and one loop trap cover it:

return findLargest(array, i++, largest); // i++ hands down the OLD i
return findLargest(array, i, largest); // i never moves; no base case is reachable
while (cur.next != null) { ... } // nothing advances cur

The i++ case is the trap that looks correct. i++ evaluates to the value of i before the increment, so the next call receives the same i and the stack of calls never shrinks. Use i + 1. A StackOverflowError is an Error, not an Exception, so a test’s catch (Exception) never catches it; the run dies outright.

The fence: the region you own

You write only between //startMethod and //endMethod, in only the file the problem names. Changing the fences, a given comment, a given signature, another file, a data file, or a project setting scores 0 for that part, even when the body between the markers is perfect. Two edits look harmless and are not:

public static boolean openInputFile(String name) throws FileNotFoundException {
    //startMethod ← the throws was added above this line: 0 for the part

return "Shape name: " + name + "\n" + ... // editing Shape.toString: 0 for the part

Adding throws to the signature is tempting because it makes the Scanner compile, but the signature sits above the fence and is given. Editing Shape.toString because the console output looks wrong is the other one; Shape.java is marked do not change, and its \r\n line endings are part of the expected strings. When local output looks wrong, the mistake is in your code, not in the given file. Find the two fences before you type, and stay between them.

Broken rule: green tests, points still lost

Two rules are applied by a person after the automated tests run, and the JUnit files cannot see either one. On the Sorting problem, any Java API sort scores 0 even with all nine tests green: Arrays.sort, Collections.sort, List.sort, Stream.sorted. Copying the data into an array or an ArrayList first and sorting that does not help; the zero is on the API call, not on where the data sits. On the Recursion problems, any loop scores 0 even with every test green: for, while, do, or a decorative recursive call whose return value is discarded while a library sort does the real work.

A third case is a dock rather than a full zero. The Circle spec says do not assign the fields directly; use setRadius, which validates. Writing this.radius = radius passes both inheritance tests, but a person reading it takes points, and a negative radius would slip past the validation the setter provides. Before you submit Sorting, search your region for .sort(. Before you submit Recursion, search it for for, while, and do.

Fails a test: the exact-string world

This is the honest failure: it compiles, it finishes, and a hidden assertion does not match, so only the failed points are lost. Most failures here are one character. Every graded output string ends each line with \r\n, including the last line; typing \n or System.lineSeparator() instead fails every non-empty assertion while the console still looks right. Doubles print at full raw precision through string concatenation (3.141592653589793, never 3.14), so a rounded constant fails an exact-string test.

Structural failures round it out. A reverseArray with left != right as the base case passes on odd lengths and fails on even ones: the two pointers step past each other, (0,3) then (1,2) then (2,1), are never equal, and the call after the crossing reads one past the end and throws ArrayIndexOutOfBoundsException. That is an Exception, so the test’s catch (Exception) turns it into a failed test. The crossing-safe base case is left < right, which stops whether the pointers meet (odd length) or cross (even length). Starting a dummy-head list scan at this.head instead of this.head.next puts the sentinel’s null data in min.data, and cur.data.compareTo(null) throws a NullPointerException the test converts to a failure. A clone() that returns this matches on toString yet fails the assertion that the clone’s head.next is a different node, because a real clone allocates its own nodes. And two sibling methods can disagree on purpose: closeInputFile sets an errorMsg when it is not open, but closeOutputFile must leave errorMsg null, so copying the input side into the output side fails one test.

The blind spot the tests do not check

Passing every official test is not the same as being correct. The seven compareTo tests each change exactly one field and hold the others equal, so a version that compares first name before last name passes all seven while its priority is backwards. The only way to catch it is a pair where two fields disagree: compare (Adam, Zeta) with (Zoe, Alpha). Last name should win, so the answer must be positive (Zeta is after Alpha), but the first-then-last version returns negative because Adam comes before Zoe. Build one adversarial pair like this for any ordering you write; it tests the thing the grader skips.

The grading model: the Main is a mirror

Grading is done by hidden JUnit files whose names end in Tests. You never see them during the exam. What you do have is a *Main.java file for each problem, whose expected console output sits in a block comment at the bottom. You run the Main, then compare your output against that comment, line for line. Matching the Main is necessary but not sufficient: the hidden tests check more than the Main prints, so a Main that matches perfectly can still lose points. Treat a green Main as the floor, then remember the real tests are out of view.

Modules are independent

Each module is compiled and graded on its own. A compile error in Sorting scores 0 for Sorting and does nothing to your Recursion score. This cuts both ways: one broken file never drags down the others, so submit every module you finished, and never delete working code to make an unrelated file build.

Full credit is a real answer

About one card in six is clean: it compiles, it stops, it passes the tests, it stays in the fence, and it breaks no rule. If reading every card makes you distrust a correct one, you will talk yourself out of points that were already yours. Calibrate on the clean cards. Knowing what a correct submission looks like is how you recognize the traps.

The walk-in checklist, run on every module
  • It compiles.
  • It stops on its own: no endless loop, no endless recursion.
  • Nothing changed outside the two fences, and no given file was touched.
  • No Java API sort anywhere in the Sorting problem.
  • No loop anywhere in the Recursion problems.
  • The console output matches each Main’s comment block, line for line.

These six are the list the four-hour plan on the APE hub opens with. The last card of the drill builds this list from memory.

Zero Gauntlet drill APE hub Min Scan, the Sorting algorithm