Test determinism: AGENTS.md rules have no enforcement, no in-repo known-flaky list, and the time-dilation contract is inconsistent across three APIs
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 45/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- github-actions, scala
- Ambito
- ci-cd, documentation, testing
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- Scala
- Stelle
- 1.7k
- Fork
- 212
- Merge medio
- 21h 21m
- PR unite (30g)
- 99
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di apache/pekko
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
-
documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
apache/pekko#1840 · 8 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Add wildcard bounds to Java DSL parameters that are needlessly invariantForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
apache/pekko#3712 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Coordinator singleton lease loss permanently stops the local ShardRegion while the node is still UpForse già presa @He-Pin l’ha presa 3 giorni fa. Apertabug cluster
Difficoltà 4/5 3-5 giorni Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
-
build enhancement github_actions
Difficoltà 3/5 1-2 giorni Idoneità per principianti 70/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di apache/pekko
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 73/100
lichess-org/lila#22298 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Python: legacy module proxies emit DeprecationWarning on introspection, so inspect.stack() warns after `import glide`Forse già presa Una pull request collegata a questa issue è aperta o già unita. Apertabug 🐞 Untriaged user issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
valkey-io/valkey-glide#7443 ·
I maintainer di solito rispondono entro 1 giorno
-
OpenAPI: PowSolutions requires pk, w and d, but a V2 solution needs only nForse già presa Una pull request collegata a questa issue è aperta o già unita. ApertaA-api C-bug S-ready-for-human
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
ergoplatform/ergo#2851 ·
I maintainer di solito rispondono entro 2 giorni
-
? - Needs Triage audit_4.2.0 feature request test
Difficoltà 2/5 1-3 ore Idoneità per principianti 87/100
NVIDIA/cudf-spark#16614 ·
I maintainer di solito rispondono entro 1 giorno