Skip to content

Flaky Test Fix - 2587#3268

Merged
krmahadevan merged 2 commits into
testng-team:masterfrom
krmahadevan:bugfix/fix_2587
Apr 26, 2026
Merged

Flaky Test Fix - 2587#3268
krmahadevan merged 2 commits into
testng-team:masterfrom
krmahadevan:bugfix/fix_2587

Conversation

@krmahadevan

@krmahadevan krmahadevan commented Apr 25, 2026

Copy link
Copy Markdown
Member

Closes #2587

Illustration:

Time --->
Freddy:  |-------------|
       100ms       900ms

Barney:       |-------------|
            300ms       1100ms

Check:

Freddy starts before Barney ends: 100 < 1100 true
Barney starts before Freddy ends: 300 < 900 true

So the windows overlap, meaning TestNG did not run Freddy completely first and Barney completely second.

Non-overlapping sequential execution would look like this:

Time --->

Freddy:  |-------------|
       100ms       900ms

Barney:                  |-------------|
                       950ms       1600ms

Check:

Freddy starts before Barney ends: 100 < 1600 true
Barney starts before Freddy ends: 950 < 900 false

So the assertion would fail.

Fixes # .

Did you remember to?

  • Add test case(s)
  • Update CHANGES.txt
  • Auto applied styling via ./gradlew autostyleApply

We encourage pull requests that:

  • Add new features to TestNG (or)
  • Fix bugs in TestNG

If your pull request involves fixing SonarQube issues then we would suggest that you please discuss this with the
TestNG-dev before you spend time working on it.

Note: For more information on contribution guidelines please make sure you refer our Contributing section for detailed set of steps.

Summary by CodeRabbit

  • Tests
    • Strengthened parallelism validation: verifies overlapping execution windows, exact per-instance invocation counts, and that each instance ran on a single, distinct thread.
    • Reworked timing checks to use interval overlap assertions rather than per-method start-time deltas for more robust concurrency verification.
  • Refactor
    • Standardized concurrency utilities and listener internals for thread-safe recording.
    • Enhanced listener tracking to record end times and thread identifiers for more reliable timing and thread assertions.

@krmahadevan krmahadevan requested a review from juherr as a code owner April 25, 2026 08:48
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Test refactor: assertions now verify per-instance execution windows overlap and per-instance single-thread usage. Listener refactor: replace TestNG concurrent helpers with JDK concurrent collections and expand Statistics to record end time and thread id (visibility changed to public).

Changes

Cohort / File(s) Summary
Test Validation Logic
testng-core/src/test/java/test/factory/issue326/IssueTest.java
Replaced per-method start-time delta checks with per-instance earliest-start / latest-end window overlap checks; assert two instances each have exactly two invocations and each instance ran on a single distinct thread. Removed old start-time helper methods.
Listener and Data Model
testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java
Replaced TestNG collection utilities with ConcurrentHashMap/CopyOnWriteArrayList; always append Statistics in afterInvocation; record thread IDs via putIfAbsent and longValue() helper. Statistics made public static, now includes endTimeInMs and threadId; constructors updated to populate new fields from ITestResult.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • juherr

Poem

🐰 I timed two hops across the test,

windows met where threads did rest,
FREDDY, BARNEY, two by two,
each on one thread, their dance true,
listeners and stats now snugly dressed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Flaky Test Fix - 2587' directly references issue 2587 and indicates the primary change is fixing a flaky test, which aligns with the PR's objective to resolve the failing test.factory.issue326.IssueTest test.
Linked Issues check ✅ Passed The PR addresses issue #2587 by refactoring test assertions to use execution window overlap checks instead of start-time delta checks, making the test deterministic for parallel execution scenarios.
Out of Scope Changes check ✅ Passed Changes are limited to the test file and its supporting listener class; all modifications directly support fixing the flaky test by making assertions account for valid parallel execution behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java (1)

21-22: Avoid String round-trip when reading the thread id; same code is duplicated in Statistics.

SampleTestClass.printer() stores the attribute as a Long (Thread.currentThread().getId()), so converting to String and re-parsing is unnecessary work and adds an avoidable failure mode (the toString() will NPE if the attribute is ever absent, with no useful message). The exact same expression is also repeated at Line 44 in the Statistics(ITestResult) constructor — extracting a tiny helper removes the duplication and keeps both call sites consistent.

♻️ Suggested refactor
   `@Override`
   public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
     String key = testResult.getInstance().toString();
     results.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(new Statistics(testResult));
-    Long threadId = Long.parseLong(testResult.getAttribute(SampleTestClass.THREAD_ID).toString());
-    threadIds.putIfAbsent(key, threadId);
+    threadIds.putIfAbsent(key, threadIdOf(testResult));
   }
@@
     public Statistics(ITestResult testResult) {
       this(
           testResult.getMethod().getMethodName(),
           testResult.getStartMillis(),
           testResult.getEndMillis(),
-          Long.parseLong(testResult.getAttribute(SampleTestClass.THREAD_ID).toString()));
+          threadIdOf(testResult));
     }
+
+  private static long threadIdOf(ITestResult testResult) {
+    return ((Number) testResult.getAttribute(SampleTestClass.THREAD_ID)).longValue();
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`
around lines 21 - 22, Both LocalTrackingListener and the Statistics(ITestResult)
constructor are converting the THREAD_ID attribute to String and reparsing it;
instead read the attribute as a Long directly, null-check it, and avoid the
toString()/parseLong() round-trip. Add a small helper (e.g.,
getThreadId(ITestResult) or a static utility) that returns a Long by fetching
testResult.getAttribute(SampleTestClass.THREAD_ID), validating non-null, and
returning the Long; replace the duplicated parsing logic in
LocalTrackingListener (where threadIds.putIfAbsent(key, threadId) is used) and
in the Statistics(ITestResult) constructor with calls to that helper so both
sites use the same safe, direct Long extraction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`:
- Around line 21-22: Both LocalTrackingListener and the Statistics(ITestResult)
constructor are converting the THREAD_ID attribute to String and reparsing it;
instead read the attribute as a Long directly, null-check it, and avoid the
toString()/parseLong() round-trip. Add a small helper (e.g.,
getThreadId(ITestResult) or a static utility) that returns a Long by fetching
testResult.getAttribute(SampleTestClass.THREAD_ID), validating non-null, and
returning the Long; replace the duplicated parsing logic in
LocalTrackingListener (where threadIds.putIfAbsent(key, threadId) is used) and
in the Statistics(ITestResult) constructor with calls to that helper so both
sites use the same safe, direct Long extraction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4dae0ff7-9bbd-45b7-851c-e336667f1888

📥 Commits

Reviewing files that changed from the base of the PR and between 88c32ff and 9ffa08a.

📒 Files selected for processing (2)
  • testng-core/src/test/java/test/factory/issue326/IssueTest.java
  • testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java

Comment thread testng-core/src/test/java/test/factory/issue326/IssueTest.java
@juherr

juherr commented Apr 25, 2026

Copy link
Copy Markdown
Member

The Nitpick comment looks good

Closes testng-team#2587

Illustration:

Time --->
Freddy:  |-------------|
       100ms       900ms

Barney:       |-------------|
            300ms       1100ms

Check:

Freddy starts before Barney ends: 100 < 1100  true
Barney starts before Freddy ends: 300 < 900   true

So the windows overlap, meaning TestNG did not run Freddy completely first and Barney completely second.

Non-overlapping sequential execution would look like this:

Time --->

Freddy:  |-------------|
       100ms       900ms

Barney:                  |-------------|
                       950ms       1600ms

Check:

Freddy starts before Barney ends: 100 < 1600  true
Barney starts before Freddy ends: 950 < 900   false

So the assertion would fail.
@krmahadevan

Copy link
Copy Markdown
Member Author

The Nitpick comment looks good

Fixed

@krmahadevan krmahadevan requested a review from juherr April 26, 2026 04:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java (1)

21-22: Unify thread‑id extraction between afterInvocation and Statistics(ITestResult).

The same attribute is read two different ways: ((Number) attr).longValue() on line 21 vs. Long.parseLong(attr.toString()) on line 44. The toString()+parseLong round‑trip is unnecessary, slightly fragile (e.g., would fail for Double/Float representations like "1.0E1"), and inconsistent with the cast used a few lines above. Prefer the Number cast in both places (or pass the already‑extracted threadId into the constructor).

♻️ Suggested change
   `@Override`
   public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
     String key = testResult.getInstance().toString();
     results.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(new Statistics(testResult));
     Long threadId = ((Number) testResult.getAttribute(SampleTestClass.THREAD_ID)).longValue();
     threadIds.putIfAbsent(key, threadId);
   }
@@
     public Statistics(ITestResult testResult) {
       this(
           testResult.getMethod().getMethodName(),
           testResult.getStartMillis(),
           testResult.getEndMillis(),
-          Long.parseLong(testResult.getAttribute(SampleTestClass.THREAD_ID).toString()));
+          ((Number) testResult.getAttribute(SampleTestClass.THREAD_ID)).longValue());
     }

Also applies to: 39-45

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`
around lines 21 - 22, The thread-id extraction is inconsistent: change the code
that currently uses Long.parseLong(attr.toString()) (in the
Statistics(ITestResult) path) to use the same Number cast pattern as in
afterInvocation — i.e., cast the attribute to Number and call longValue() — or
alternatively modify the caller to pass the already-extracted threadId into the
Statistics constructor; update the Statistics(ITestResult) constructor (or its
caller) to use ((Number) attr).longValue() instead of toString()/Long.parseLong
to avoid fragile parsing and unify behavior with afterInvocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`:
- Around line 21-22: The thread-id extraction is inconsistent: change the code
that currently uses Long.parseLong(attr.toString()) (in the
Statistics(ITestResult) path) to use the same Number cast pattern as in
afterInvocation — i.e., cast the attribute to Number and call longValue() — or
alternatively modify the caller to pass the already-extracted threadId into the
Statistics constructor; update the Statistics(ITestResult) constructor (or its
caller) to use ((Number) attr).longValue() instead of toString()/Long.parseLong
to avoid fragile parsing and unify behavior with afterInvocation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52a51ba7-543b-44b7-98de-5442a1259d01

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffa08a and ebe472f.

📒 Files selected for processing (2)
  • testng-core/src/test/java/test/factory/issue326/IssueTest.java
  • testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • testng-core/src/test/java/test/factory/issue326/IssueTest.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java (1)

14-23: Concurrency primitives look correct.

ConcurrentHashMap.computeIfAbsent + CopyOnWriteArrayList.add is safe under concurrent afterInvocation calls, and putIfAbsent correctly captures the first thread observed per instance. Good replacement for the previous helpers.

Minor follow-up (optional): now that Statistics.threadId is recorded per invocation, the separate threadIds map duplicates information that can be derived from results. Consider dropping threadIds/getThreadIds() and letting callers compute thread ids from Statistics (see related comment in IssueTest.java).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`
around lines 14 - 23, The threadIds ConcurrentMap and getThreadIds() duplicate
data already captured by Statistics.threadId; remove the threadIds field and any
getThreadIds() helper, and update callers to derive thread ids from results (use
results.computeIfAbsent / the List<Statistics> entries and read
Statistics.threadId) so afterInvocation only updates results (method
afterInvocation and class-local symbols results, threadIds, getThreadIds, and
Statistics.threadId are the places to change).
testng-core/src/test/java/test/factory/issue326/IssueTest.java (1)

37-44: Redundant thread-id sources can be consolidated.

Lines 37-38 already establish each instance ran on a single distinct thread id (via Statistics.threadId). The follow-up comparison on lines 42-44 then re-reads listener.getThreadIds() (a separate map populated via putIfAbsent) for the same information. Since Statistics now carries threadId, the second source is redundant — you could derive both checks from threadIdsFor(...) and drop the getThreadIds() map from LocalTrackingListener entirely. Not a bug, just simpler and avoids two parallel sources of truth that could drift.

♻️ Possible simplification
-    // 3. The thread id for the methods that belong to each instance is the same.
-    assertThat(threadIdsFor(results, SampleTestClass.FREDDY)).hasSize(1);
-    assertThat(threadIdsFor(results, SampleTestClass.BARNEY)).hasSize(1);
-
-    // 4. Check to ensure that the thread id on which methods belonging to first instance ran
-    // is not the same as that of the methods that belong to second instance.
-    long threadIdFreddyInstance = listener.getThreadIds().get(SampleTestClass.FREDDY);
-    long threadIdBarneyInstance = listener.getThreadIds().get(SampleTestClass.BARNEY);
-    assertThat(threadIdFreddyInstance).isNotEqualTo(threadIdBarneyInstance);
+    // 3. Each instance ran on exactly one thread, and the two instances used different threads.
+    Collection<Long> freddyThreads = threadIdsFor(results, SampleTestClass.FREDDY);
+    Collection<Long> barneyThreads = threadIdsFor(results, SampleTestClass.BARNEY);
+    assertThat(freddyThreads).hasSize(1);
+    assertThat(barneyThreads).hasSize(1);
+    assertThat(freddyThreads).isNotEqualTo(barneyThreads);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testng-core/src/test/java/test/factory/issue326/IssueTest.java` around lines
37 - 44, The test repeats thread-id checks from two sources; use the existing
Statistics.threadId via threadIdsFor(results, ...) exclusively and remove the
redundant listener.getThreadIds() usage (and the backing map in
LocalTrackingListener) to avoid duplicate state. Update the assertions to
compare the single-thread-per-instance values obtained from threadIdsFor(...)
for SampleTestClass.FREDDY and SampleTestClass.BARNEY, remove reads of
listener.getThreadIds() (and corresponding putIfAbsent logic and map field in
LocalTrackingListener), and ensure any tests or code paths that relied on
LocalTrackingListener.getThreadIds() are switched to use Statistics.threadId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`:
- Around line 18-23: afterInvocation currently calls asLong for every invocation
(including configuration methods) which can NPE because THREAD_ID is only set by
SampleTestClass.printer(); update afterInvocation to skip non-test invocations
by checking the invoked method type (use IInvokedMethod.isTestMethod() or
equivalent) and return early for configuration methods so you only call
results.computeIfAbsent(...).add(...) and asLong when method.isTestMethod() is
true.

---

Nitpick comments:
In `@testng-core/src/test/java/test/factory/issue326/IssueTest.java`:
- Around line 37-44: The test repeats thread-id checks from two sources; use the
existing Statistics.threadId via threadIdsFor(results, ...) exclusively and
remove the redundant listener.getThreadIds() usage (and the backing map in
LocalTrackingListener) to avoid duplicate state. Update the assertions to
compare the single-thread-per-instance values obtained from threadIdsFor(...)
for SampleTestClass.FREDDY and SampleTestClass.BARNEY, remove reads of
listener.getThreadIds() (and corresponding putIfAbsent logic and map field in
LocalTrackingListener), and ensure any tests or code paths that relied on
LocalTrackingListener.getThreadIds() are switched to use Statistics.threadId.

In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`:
- Around line 14-23: The threadIds ConcurrentMap and getThreadIds() duplicate
data already captured by Statistics.threadId; remove the threadIds field and any
getThreadIds() helper, and update callers to derive thread ids from results (use
results.computeIfAbsent / the List<Statistics> entries and read
Statistics.threadId) so afterInvocation only updates results (method
afterInvocation and class-local symbols results, threadIds, getThreadIds, and
Statistics.threadId are the places to change).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 757749d7-2f66-4f9a-92f8-3b294b01001a

📥 Commits

Reviewing files that changed from the base of the PR and between ebe472f and e30a8bf.

📒 Files selected for processing (2)
  • testng-core/src/test/java/test/factory/issue326/IssueTest.java
  • testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java

Comment on lines 18 to 23
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
String key = testResult.getInstance().toString();
results.computeIfAbsent(key, k -> Lists.newArrayList()).add(new Statistics(testResult));
if (!threadIds.containsKey(key)) {
Long threadId = Long.parseLong(testResult.getAttribute(SampleTestClass.THREAD_ID).toString());
threadIds.put(key, threadId);
}
results.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(new Statistics(testResult));
Long threadId = asLong(testResult);
threadIds.putIfAbsent(key, threadId);
}

@coderabbitai coderabbitai Bot Apr 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm SampleTestClass currently has no configuration methods, so the NPE is latent rather than active.
fd -t f 'SampleTestClass.java' | xargs rg -nP '@(Before|After)(Method|Class|Suite|Test|Groups)\b'

Repository: testng-team/testng

Length of output: 458


🏁 Script executed:

# List files in issue326 directory to understand the test structure
fd -t f . testng-core/src/test/java/test/factory/issue326/

Repository: testng-team/testng

Length of output: 269


🏁 Script executed:

# Search for SampleTestClass specifically in issue326 directory
fd -t f 'SampleTestClass.java' testng-core/src/test/java/test/factory/issue326/

Repository: testng-team/testng

Length of output: 131


🏁 Script executed:

# Check the full LocalTrackingListener.java file to see the asLong implementation
cat -n testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java

Repository: testng-team/testng

Length of output: 2261


🏁 Script executed:

cat -n testng-core/src/test/java/test/factory/issue326/SampleTestClass.java

Repository: testng-team/testng

Length of output: 1763


🏁 Script executed:

# Check IInvokedMethod to see if isTestMethod() method exists
rg -t java 'isTestMethod' --type-list

Repository: testng-team/testng

Length of output: 452


🏁 Script executed:

# More specific search for IInvokedMethod interface
rg -A 10 'interface IInvokedMethod' testng-core/src/main/java/

Repository: testng-team/testng

Length of output: 44


🏁 Script executed:

# Search for IInvokedMethod interface definition
fd -t f 'IInvokedMethod.java'

Repository: testng-team/testng

Length of output: 123


🏁 Script executed:

# Check if isTestMethod exists in the codebase
rg -t java 'isTestMethod'

Repository: testng-team/testng

Length of output: 4353


🏁 Script executed:

# View the IInvokedMethod interface to understand isTestMethod
cat -n testng-core-api/src/main/java/org/testng/IInvokedMethod.java

Repository: testng-team/testng

Length of output: 771


Latent NPE risk in asLong when afterInvocation is called for configuration methods.

afterInvocation fires for both test methods and configuration methods. Currently, SampleTestClass.printer() sets THREAD_ID only in test methods (test1, test2). If a configuration method (@BeforeMethod, @AfterMethod, etc.) is added later, afterInvocation will invoke asLong, which casts getAttribute(THREAD_ID) without a null check. Since configuration methods never call printer(), the attribute will be absent, causing NullPointerException or ClassCastException.

Guard against this by returning early for non-test methods:

Proposed fix
  `@Override`
  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+   if (!method.isTestMethod()) {
+     return;
+   }
    String key = testResult.getInstance().toString();
    results.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(new Statistics(testResult));
    Long threadId = asLong(testResult);
    threadIds.putIfAbsent(key, threadId);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
String key = testResult.getInstance().toString();
results.computeIfAbsent(key, k -> Lists.newArrayList()).add(new Statistics(testResult));
if (!threadIds.containsKey(key)) {
Long threadId = Long.parseLong(testResult.getAttribute(SampleTestClass.THREAD_ID).toString());
threadIds.put(key, threadId);
}
results.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(new Statistics(testResult));
Long threadId = asLong(testResult);
threadIds.putIfAbsent(key, threadId);
}
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
if (!method.isTestMethod()) {
return;
}
String key = testResult.getInstance().toString();
results.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(new Statistics(testResult));
Long threadId = asLong(testResult);
threadIds.putIfAbsent(key, threadId);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`
around lines 18 - 23, afterInvocation currently calls asLong for every
invocation (including configuration methods) which can NPE because THREAD_ID is
only set by SampleTestClass.printer(); update afterInvocation to skip non-test
invocations by checking the invoked method type (use
IInvokedMethod.isTestMethod() or equivalent) and return early for configuration
methods so you only call results.computeIfAbsent(...).add(...) and asLong when
method.isTestMethod() is true.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is a valid concern in the general context, its not relevant here because test.factory.issue326.SampleTestClass#SampleTestClass does not have any configuration methods.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krmahadevan, that's a fair point — since test.factory.issue326.SampleTestClass is a tightly scoped test fixture with no configuration methods, the risk is indeed not applicable here. I'll note this for future reviews.

(╯°□°)╯


✏️ Learnings added
Learnt from: krmahadevan
Repo: testng-team/testng PR: 3268
File: testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java:18-23
Timestamp: 2026-04-26T11:56:08.426Z
Learning: In `testng-core/src/test/java/test/factory/issue326/LocalTrackingListener.java`, the `afterInvocation` method does not need a guard for configuration methods (e.g., `method.isTestMethod()` check) because the associated test fixture `test.factory.issue326.SampleTestClass` is a tightly scoped test class with no `BeforeMethod`/`AfterMethod`/`BeforeClass`/`AfterClass` configuration methods. The `THREAD_ID` attribute is always set by `SampleTestClass.printer()` for all invocations in this context.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: krmahadevan
Repo: testng-team/testng PR: 3181
File: testng-core/src/test/java/test/invocationcount/issue3180/RetryAnalyzer.java:6-9
Timestamp: 2024-09-30T05:21:21.304Z
Learning: In test code for the TestNG project, it's acceptable to hardcode parameters like `retryLimit` since they don't need to be configurable.

Learnt from: krmahadevan
Repo: testng-team/testng PR: 3181
File: testng-core/src/test/java/test/invocationcount/issue3180/RetryAnalyzer.java:6-9
Timestamp: 2024-10-09T09:04:04.750Z
Learning: In test code for the TestNG project, it's acceptable to hardcode parameters like `retryLimit` since they don't need to be configurable.

@juherr juherr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm but have a look at coderabbit review

@krmahadevan krmahadevan merged commit 9941b29 into testng-team:master Apr 26, 2026
10 checks passed
@krmahadevan

Copy link
Copy Markdown
Member Author

Lgtm but have a look at coderabbit review

Its not relevant to this PR in specific because the sample class in question does not have configuration methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

issue326.IssueTest: Expecting: <1007L> to be less than or equal to: <1000L>

2 participants