Test determinism: AGENTS.md rules have no enforcement, no in-repo known-flaky list, and the time-dilation contract is inconsistent across three APIs
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 45/100
- Tipo de issue
- Nueva funcionalidad
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- github-actions, scala
- Área
- ci-cd, documentation, testing
Línea de trabajo
Read AGENTS.md and CONTRIBUTING.md for current test rules. For part 3, inspect testkit/src/main/scala/org/apache/pekko/testkit/TestKit.scala:456,722 and actor-testkit-typed/.../TestProbeImpl.scala:161,210 to document dilation. Done: each affected method's Scaladoc states if supplied duration is dilated, cross-linked, with optional lint for double-dilation.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
AGENTS.md states clear test-determinism rules, but nothing in the build enforces any of them, and known-flaky tests are tracked only as GitHub issues. The result is measurable: 341 Thread.sleep calls in Scala sources, against a rule that says "Avoid Thread.sleep".
A second, related problem is that the testkit's time-dilation contract is inconsistent across three APIs and is documented only in Scaladoc prose, which makes dilation mistakes an easy and recurring source of flakes.
The enforcement gap
AGENTS.md, "Test Rules":
- Prefer deterministic tests over larger timeouts.
- Avoid `Thread.sleep`.
- Prefer probes, latches, stepped components, `within`, `remaining`, and `remainingOrDefault`.
- Keep `expectNoMessage` short and intentional.
- Do not weaken assertions to hide production behavior.
- Do not fix flakes only by increasing timeouts.
The only style gate in CI is build.sbt:44:
addCommandAlias("checkCodeStyle", "scalafmtCheckAll; scalafmtSbtCheck; javafmtCheckAll; +headerCheckAll")
Formatting and license headers — nothing inspects test sources for non-deterministic patterns. Meanwhile:
rg -c 'Thread\.sleep' --type scala | awk -F: '{s+=$2} END {print s}'
341
Concentration (occurrences per file):
stream-tests/.../scaladsl/HubSpec.scala 18
docs/src/test/scala/docs/stream/cookbook/RecipeAdhocSource.scala 13
cluster/.../sbr/SplitBrainResolverSpec.scala 11
actor-tests/.../util/BoundedBlockingQueueSpec.scala 11
actor-tests/.../pattern/CircuitBreakerSpec.scala 11
actor-tests/.../actor/SchedulerSpec.scala 11
stream-tests/.../scaladsl/RestartSpec.scala 9
stream-tests/.../impl/TimeoutsSpec.scala 8
pendingUntilFixed appears 3 times.
How flakiness is handled today
- Retry, but still fail —
project/PekkoDevelocityPlugin.scala:65-66:.withMaxRetries(1)and.withFlakyTestPolicy(FlakyTestPolicy.Fail), commented "preserve the original build outcome in case of flaky tests". Honest, but it does not isolate anything. - Exclusion by category, not by test —
testkit/src/test/scala/org/apache/pekko/testkit/TestTags.scala:18-23definestiming,long-running,performance,gh-exclude,gh-exclude-aeron, and CI passes-Dpekko.test.tags.exclude=gh-exclude,timing. A single flaky test in a category cannot be excluded without excluding the whole category. - Issues, closed by elapsed time —
CONTRIBUTING.md:41describes thefailedlabel and states: "When the same problem isn't seen again over a period of 6 months we assume it to be a rare flaky test or a problem that might have since been fixed, so we close the issue until it pops up again." There is no in-repo list, so nothing connects a known-flaky test to the code that is allowed to exclude or retry it.
The dilation contract is inconsistent
Three public testkit APIs treat a caller-supplied duration differently, and the difference is only discoverable by reading Scaladoc or source.
TestKit.expectMsg(max, obj) dilates — testkit/src/main/scala/org/apache/pekko/testkit/TestKit.scala:456:
def expectMsg[T](max: FiniteDuration, obj: T): T = expectMsg_internal(max.dilated, obj)
TestKit.expectNoMessage(max) does not — :722-724, where the Scaladoc carries the whole contract:
* Supplied value is not dilated.
...
def expectNoMessage(max: FiniteDuration) = {
stream-testkit expectNext(d) does not — stream-testkit/src/main/scala/org/apache/pekko/stream/testkit/StreamTestKit.scala:478-479:
def expectNext(d: FiniteDuration): I = {
val t = probe.remainingOr(d)
The typed testkit splits the same way — actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestProbeImpl.scala:161 dilates:
override def expectMessage[T <: M](max: FiniteDuration, obj: T): T = expectMessage_internal(max.dilated, obj)
while :210-211 does not:
override def expectNoMessage(max: FiniteDuration): Unit =
expectNoMessage_internal(max)
Two concrete failure modes follow. A test that passes an already-dilated duration to a dilating method gets dilation applied twice — with the CI setting -Dpekko.test.timefactor=2 (.github/workflows/build-test-prValidation.yml:93-94) a 3s budget silently becomes 12s. A test that passes a raw duration to a non-dilating method gets no relief on a slow runner and flakes only in CI. Neither is visible at the call site.
A related surprise in within
TestKit.scala:432-434 skips the upper-bound assertion when the block's last assertion was expectNoMessage:
if (!lastWasNoMsg) {
assert(diff <= max_diff, s"block took ${format(_max.unit, diff)}, exceeding ${format(_max.unit, max_diff)}")
}
The typed testkit has the same mechanism, and documents the intent — TestProbeImpl.scala:91-95:
/**
* if last assertion was expectNoMessage, disable timing failure upon within()
* block end.
*/
private var lastWasNoMessage = false
This is deliberate (expectNoMessage itself consumes the budget, so the assertion would always trip), but it means within gives no upper-bound guarantee for such a block, and that is not discoverable from within's own Scaladoc.
Proposal
Three parts, independently useful and separately mergeable:
-
An in-repo known-flaky list. One row per test: fully-qualified name, issue link, date first seen, symptom class (timing / ordering / teardown / port / resource), and owner. Deriving it from the
failedlabel would keep it honest, and it gives the tag-exclusion mechanism something finer-grained than a whole category to point at. -
A gate on newly-introduced non-deterministic patterns. A CI check that fails when a PR adds
Thread.sleep,pendingUntilFixed, or aGHExcludeTesttag to test sources, backed by a committed baseline file that exempts the existing 341 occurrences. The baseline makes the existing debt visible and bounded without requiring a big-bang cleanup, and it stops the count from growing. This can start as a shell script overrgoutput before it becomes an sbt task or a scalafix rule. -
Make the dilation contract explicit and checkable. At minimum, state on each affected method whether the supplied duration is dilated, in a consistent wording, and cross-link them. Better: a lint that flags a caller passing an already-
.dilatedduration into a dilating method, which is the double-dilation case and is mechanically detectable.
Part 3 is documentation plus an optional lint and carries no binary-compatibility risk. Parts 1 and 2 are additive CI artifacts. None of the three changes production behaviour.
Note on scope
Changing the dilation behaviour of any of these methods — for example making expectNoMessage dilate for consistency — is deliberately not proposed here. That would be a public API behaviour change affecting many existing tests and would need its own discussion.
- Lenguaje dominante
- Scala
- Estrellas
- 1.7k
- Forks
- 212
- Merge medio
- 20 h 44 min
- PR fusionados (30 d)
- 104
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de apache/pekko
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
apache/pekko#1840 · 8 comentarios ·
Los mantenedores suelen responder en 1 día
-
Add wildcard bounds to Java DSL parameters that are needlessly invariantPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 52/100
apache/pekko#3712 · 5 comentarios ·
Los mantenedores suelen responder en 1 día
-
Coordinator singleton lease loss permanently stops the local ShardRegion while the node is still UpPosiblemente ocupada @He-Pin la tomó hace 3 días. Abiertobug cluster
Dificultad 4/5 3-5 días Aptitud para principiantes 25/100
Los mantenedores suelen responder en 1 día
-
build enhancement github_actions
Dificultad 3/5 1-2 días Aptitud para principiantes 70/100
Los mantenedores suelen responder en 1 día
Todos los issues de apache/pekko
Issues similares
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 73/100
lichess-org/lila#22298 ·
Los mantenedores suelen responder en 1 día
-
Task 🔧
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
valkey-io/valkey-glide#7455 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
lunduniversity/introprog#1005 · 1 comentario ·
Los mantenedores suelen responder en 7 días