Task Summary
Two pure workflow-operator classes have small, sharply-defined coverage gaps: AggregationOperation (common/workflow-operator/.../aggregate/AggregationOperation.scala, ~81%, 16 lines unhit) and IntervalJoinOpExec (common/workflow-operator/.../intervalJoin/IntervalJoinOpExec.scala, ~86%, 15 lines unhit). Both are dependency-free in-memory logic — no DB, no storage, no clock. Most of what is left is partial branch coverage rather than whole untested methods, so the lines below are named from codecov's line-level data; testing anything else in these files will re-cover already-green code. Extend the existing AggregationOperationSpec and IntervalOpExecSpec rather than adding new files.
Behavior to add
AggregationOperation — the 16 unhit lines are:
- The four-clause type guard in
sumAgg (138-141), minAgg (196-199) and maxAgg (221-224) — 12 of the 16 lines, and the main prize. Each is attributeType != INTEGER && != DOUBLE && != LONG && != TIMESTAMP, and the clauses are only partially exercised. For each of the three aggregations, call it with each supported type so the chain falls all the way through, and with unsupported types (STRING, BOOLEAN, BINARY) so it short-circuits at different clauses and throws UnsupportedOperationException. Assert the message names the aggregation and the offending type.
getNumericalValue's TIMESTAMP branch (249-250) — AVERAGE over a TIMESTAMP column is the only route in. Two timestamps an hour apart should average to their epoch-millis midpoint.
AveragePartialObj (29) — the case class is never constructed today, which means no test currently drives an AVERAGE partial end to end. An AVERAGE round-trip (init → iterate twice → merge → finalAgg) covers this and 249-250 together.
newAggFunc's case a: AggregationFunction => a (127) — the non-COUNT arm of the COUNT→SUM remap. Existing tests only drive the COUNT arm; assert that SUM/MIN/MAX/AVERAGE pass through unchanged.
IntervalJoinOpExec — the 15 unhit lines are:
- The
TimeIntervalType ladder in the right-bound computation (201-212) plus the per-type dispatch at 176, 186 and 196 — 11 of the 15 lines, and the main prize. Cover YEAR / MONTH / DAY / HOUR / MINUTE / SECOND, and the case None fallback at 213-214 (plusDays). Reaching None needs care: every existing test goes through the 6-argument IntervalJoinOpDesc constructor, which forces Some(...), and the field is declared var timeIntervalType: Option[TimeIntervalType] = _, i.e. initialised to null rather than None. Build a TIMESTAMP desc with timeIntervalType left unset, serialize it, and assert day semantics — whether Jackson materialises None or null here is exactly the unobvious part worth pinning.
processNumValue's bound-combination arms (144, 148) — the includeLeftBound && !includeRightBound and !includeLeftBound && includeRightBound dispatch conditions.
removeTooSmallTupleInRightCache (97) — the right-cache eviction comparison. Note the left-cache equivalent is already covered; only the right side is red.
intervalCompare (165).
Keep TIMESTAMP fixtures away from Feb 29 and DST boundaries; the interval arithmetic goes through plusYears/plusMonths calendar math.
A correctness defect in the same file (not a coverage item)
While writing the maxAgg tests, note that maxAgg seeds its accumulator with AttributeTypeUtils.minValue(attributeType) but its finaliser collapses to null when the partial equals AttributeTypeUtils.maxValue(attributeType). minAgg is consistent — it seeds maxValue and finalises against maxValue. So maxAgg has it both ways round: MAX over an empty input returns the type's minimum instead of null, and a legitimate MAX result that happens to equal the type's maximum is wrongly collapsed to null.
Those finaliser lines are already covered, so fixing this will not move the coverage number — it is a correctness fix that happens to sit next to the work. Fix it in the same PR (#6878 is the precedent for fix-plus-tests) or file it separately, but do not encode the current result as if it were intended.
Task Type
Task Summary
Two pure
workflow-operatorclasses have small, sharply-defined coverage gaps:AggregationOperation(common/workflow-operator/.../aggregate/AggregationOperation.scala, ~81%, 16 lines unhit) andIntervalJoinOpExec(common/workflow-operator/.../intervalJoin/IntervalJoinOpExec.scala, ~86%, 15 lines unhit). Both are dependency-free in-memory logic — no DB, no storage, no clock. Most of what is left is partial branch coverage rather than whole untested methods, so the lines below are named from codecov's line-level data; testing anything else in these files will re-cover already-green code. Extend the existingAggregationOperationSpecandIntervalOpExecSpecrather than adding new files.Behavior to add
AggregationOperation — the 16 unhit lines are:
sumAgg(138-141),minAgg(196-199) andmaxAgg(221-224) — 12 of the 16 lines, and the main prize. Each isattributeType != INTEGER && != DOUBLE && != LONG && != TIMESTAMP, and the clauses are only partially exercised. For each of the three aggregations, call it with each supported type so the chain falls all the way through, and with unsupported types (STRING, BOOLEAN, BINARY) so it short-circuits at different clauses and throwsUnsupportedOperationException. Assert the message names the aggregation and the offending type.getNumericalValue's TIMESTAMP branch (249-250) — AVERAGE over a TIMESTAMP column is the only route in. Two timestamps an hour apart should average to their epoch-millis midpoint.AveragePartialObj(29) — the case class is never constructed today, which means no test currently drives an AVERAGE partial end to end. An AVERAGE round-trip (init→iteratetwice →merge→finalAgg) covers this and 249-250 together.newAggFunc'scase a: AggregationFunction => a(127) — the non-COUNT arm of the COUNT→SUM remap. Existing tests only drive the COUNT arm; assert that SUM/MIN/MAX/AVERAGE pass through unchanged.IntervalJoinOpExec — the 15 unhit lines are:
TimeIntervalTypeladder in the right-bound computation (201-212) plus the per-type dispatch at 176, 186 and 196 — 11 of the 15 lines, and the main prize. Cover YEAR / MONTH / DAY / HOUR / MINUTE / SECOND, and thecase Nonefallback at 213-214 (plusDays). ReachingNoneneeds care: every existing test goes through the 6-argumentIntervalJoinOpDescconstructor, which forcesSome(...), and the field is declaredvar timeIntervalType: Option[TimeIntervalType] = _, i.e. initialised tonullrather thanNone. Build a TIMESTAMP desc withtimeIntervalTypeleft unset, serialize it, and assert day semantics — whether Jackson materialisesNoneornullhere is exactly the unobvious part worth pinning.processNumValue's bound-combination arms (144, 148) — theincludeLeftBound && !includeRightBoundand!includeLeftBound && includeRightBounddispatch conditions.removeTooSmallTupleInRightCache(97) — the right-cache eviction comparison. Note the left-cache equivalent is already covered; only the right side is red.intervalCompare(165).Keep TIMESTAMP fixtures away from Feb 29 and DST boundaries; the interval arithmetic goes through
plusYears/plusMonthscalendar math.A correctness defect in the same file (not a coverage item)
While writing the
maxAggtests, note thatmaxAggseeds its accumulator withAttributeTypeUtils.minValue(attributeType)but its finaliser collapses tonullwhen the partial equalsAttributeTypeUtils.maxValue(attributeType).minAggis consistent — it seedsmaxValueand finalises againstmaxValue. SomaxAgghas it both ways round: MAX over an empty input returns the type's minimum instead ofnull, and a legitimate MAX result that happens to equal the type's maximum is wrongly collapsed tonull.Those finaliser lines are already covered, so fixing this will not move the coverage number — it is a correctness fix that happens to sit next to the work. Fix it in the same PR (#6878 is the precedent for fix-plus-tests) or file it separately, but do not encode the current result as if it were intended.
Task Type