skip to content

In Allure 2, a `categories.json` rule whose `messageRegex` is `RuntimeException` catches nothing, while `.*RuntimeException.*` catches the failures you meant — why?

level: middleimportance: must knowfreq 56%

answer

  1. the pattern is not a search
  2. end to end, not somewhere inside
  3. why every shipped example starts with .*
  4. the dot has to cross newlines

basics

~20 s

Allure matches the whole string rather than searching inside it: the pattern must cover the entire failure message end to end. A bare token matches only a message that is exactly that token, so shipped examples wrap patterns in .* instead.

solid answer

~40 s

The pattern is applied to the **whole** text, not searched for within it. So a `messageRegex` of `RuntimeException` matches only a message that is precisely the string `RuntimeException`, never `RuntimeException: connection reset`. Wrapping it as `.*RuntimeException.*` is what turns it into the containment test people expect, and it is why every rule in Allure's own shipped example is written that way. The pattern is compiled with `Pattern.DOTALL`, so `.` also crosses the newlines inside a multi-line message or stack trace — without that flag a trailing `.*` would stop at the first line break and long traces would never match. ANSI colour codes are stripped from the text before matching, so escape sequences neither need to appear in your pattern nor can be matched by it. Nothing warns you when a rule matches nothing.

code

json · 10 lines
json
[
  {
    "name": "Never fires",
    "messageRegex": "RuntimeException"
  },
  {
    "name": "Fires as intended",
    "messageRegex": ".*RuntimeException.*"
  }
]

go deeper

for a junior

Know that these patterns are matched against the whole text, so wrap the fragment you are looking for in .* on both sides rather than typing an exception name on its own.

for a middle

Explain the difference between matching and searching, and say why the dot-matches-newline flag is required for multi-line messages and for a trace condition naming a deep frame.

for a senior

Show how you diagnose a rule that never fires: nothing logs it, so regenerate a kept results directory with the pattern deliberately loosened and compare bucket contents rather than re-reading the regex.

for a principal

Be ready to say who reviews rule patterns and against what evidence, since a silently non-matching rule looks exactly like a failure mode that stopped happening.

## Matching, not searching Allure 2 tests a `categories.json` pattern against the **entire** message or trace: the regular expression is compiled and then asked whether it matches the whole input, not whether it occurs somewhere inside it. Most regex tools people touch daily — `grep`, an editor's find box, a language's search function — do the opposite, and that mismatch of expectation is the single biggest source of silently dead rules in this file. Against a real message of `RuntimeException: connection reset by peer`: | `messageRegex` | matches? | why | |---|---|---| | `RuntimeException` | no | the input has more text after the token, so the whole string is not covered | | `^RuntimeException$` | no | the anchors change nothing; the tail is still unmatched | | `RuntimeException.*` | no | the tail is handled but nothing accounts for text appearing before the token | | `.*RuntimeException.*` | **yes** | the wildcards absorb everything on either side | | `.*connection reset.*` | **yes** | any distinguishing fragment works, wrapped the same way | That is why every rule in Allure's own shipped example file looks like `".*SomeText.*"`. It is not a stylistic tic; it is the minimum form that behaves the way a reader assumes it does. ## Why the dot has to cross newlines The pattern is compiled with `Pattern.DOTALL`. In default regex behaviour `.` matches any character **except** a line terminator, so a trailing `.*` stops dead at the first newline. Failure messages are frequently multi-line and stack traces always are. Without the flag, `.*RuntimeException.*` would fail against a message whose first line is a summary and whose remaining lines are the cause chain, purely because the wildcard could not reach past line one. With `DOTALL`, `.*` spans the whole blob, and a `traceRegex` can name a package that appears twenty frames down. The practical consequence: **one pattern can be written against a whole multi-line trace**, and you should not try to be clever with per-line anchors. ## What text is actually matched Two things are worth knowing about the input side: 1. **ANSI colour codes are stripped from both the message and the trace before matching.** Suites that colourise console output embed escape sequences in that text, and they are removed first. You therefore never need to account for them in a pattern — and equally, you cannot write a rule that keys on the colouring. 2. **A rule with a `messageRegex` requires a message to exist.** A result with no message text cannot satisfy a message condition, so a rule naming a message pattern skips results that carry only a trace, and the same holds in reverse. ## Why this failure is invisible A rule that never matches produces no error, no warning and no log line. The report generates perfectly; the bucket simply has nothing in it. And an empty bucket has two indistinguishable explanations: - the pattern is wrong, or - the failure mode it describes did not occur on this run. That ambiguity is what makes this worth interviewing on. The way out is to test the rule rather than read it: 1. **Keep a results directory** from a run you know contains the failure you are targeting. 2. **Loosen the pattern deliberately** — for instance to `.*` with only the status you expect — and regenerate. If the bucket now fills, the mechanism is fine and your pattern was the problem. 3. **Tighten one fragment at a time**, regenerating between steps, until the bucket holds what you meant and nothing more. 4. **Prefer a stable fragment** — an exception type name, a distinctive phrase — over anything carrying a host name, a port, a timestamp or an identifier that changes from run to run. Generation is cheap and repeatable because the results are already on disk, so this loop costs seconds and needs no test execution at all. ## Writing patterns that keep working - **Wrap in `.*` unless you genuinely want an exact-equality test.** Exact equality against a failure message is almost never what anyone means. - **Put framework and package names in `traceRegex`, not `messageRegex`.** The message is the human-facing sentence and it changes when a library rewords it; the frame list is more stable. - **Avoid embedding values that vary per run.** A pattern containing a generated identifier matches exactly one historical run. - **Do not lean on `^` and `$`.** With a whole-string match they add nothing, and reaching for them signals a mental model that will produce dead rules elsewhere in the file.

  • You want a rule to fire only when a specific package appears deep in the stack. Which field do you use?
    `traceRegex`, wrapped in `.*` on both sides. The trace is matched as one whole multi-line string with the dot-matches-newline flag on, so a package name twenty frames down is reachable. Leave the message condition off that rule unless you also need it, since every condition you supply must hold for the rule to match.
  • Why should a pattern avoid host names, ports and generated identifiers?
    Because a whole-string match is brittle in exactly that direction: the wildcards forgive surrounding text but not a changed fragment inside your literal. A rule containing a value that differs per run matches the one run you copied it from and nothing afterwards, and it stops matching silently, with no warning at generation time.

saying these in an interview costs you the question

  • Assumes the pattern is searched for inside the message
  • Adds ^ and $ expecting them to change the match
  • Thinks a non-matching rule produces a warning at generation
  • Copies a whole failure message, timestamps and all, into the pattern