Skip to content

Add network connectivity stress test for VPN resilience#162

Merged
pappz merged 4 commits intomainfrom
feature/network-connectivity-stress-test
Apr 14, 2026
Merged

Add network connectivity stress test for VPN resilience#162
pappz merged 4 commits intomainfrom
feature/network-connectivity-stress-test

Conversation

@pappz
Copy link
Copy Markdown
Collaborator

@pappz pappz commented Apr 14, 2026

Summary

Adds an instrumented stress test that simulates real-world network disruptions and verifies that the VPN recovers automatically
Covers 10 real-device scenarios (WiFi/mobile switching, airplane mode, flapping, long outages) and 10 emulator scenarios (network disconnect, latency, speed throttling)
Auto-detects real device vs emulator and applies the appropriate network control strategy
Verifies VPN recovery by pinging a peer through the tunnel with exponential backoff

Background

Users reported that the VPN can get stuck in a disconnected state after network changes—for example, when leaving the office (WiFi → mobile) or arriving home (mobile → WiFi).

This test reproduces those conditions by randomly applying network disruptions across 20 cycles.

How to run

./gradlew assembleDebug assembleDebugAndroidTest

adb install -r -t app/build/outputs/apk/debug/app-debug.apk
adb install -r -t app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
adb shell am instrument -w -e class io.netbird.client.NetworkConnectivityStressTest \
  io.netbird.client.test/androidx.test.runner.AndroidJUnitRunner

See app/src/androidTest/README.md for full details, configuration options, and helper commands.

Summary by CodeRabbit

  • Tests

    • Added a network connectivity stress test to validate VPN resilience under randomized network disruption scenarios.
  • Documentation

    • Added documentation for running and configuring the network connectivity stress test.
  • Chores

    • Updated test dependencies and configurations to support expanded testing capabilities.

Instrumented test that simulates real-world network disruptions (WiFi/mobile
switching, airplane mode, flapping, long outages) and verifies VPN recovery
via ping. Auto-detects real device vs emulator and uses appropriate network
control strategy for each.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Warning

Rate limit exceeded

@pappz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 22 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 43 minutes and 22 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24385d20-e190-4f95-95b0-e7775e1f55b0

📥 Commits

Reviewing files that changed from the base of the PR and between 5379b7d and 6ae05a8.

📒 Files selected for processing (3)
  • .github/workflows/build-debug.yml
  • app/src/androidTest/README.md
  • app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java
📝 Walkthrough

Walkthrough

Adds a new Android instrumentation stress test that runs randomized network disruption/restore cycles to validate VPN recovery, plus supporting README docs and test dependency/version additions.

Changes

Cohort / File(s) Summary
Build & Test Dependencies
app/build.gradle.kts, gradle/libs.versions.toml
Set instrumentation runner timeout to 3,600,000 ms and added UIAutomator and Test Rules library entries/versions.
Instrumentation Test & Docs
app/src/androidTest/README.md, app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java
Added comprehensive NetworkConnectivityStressTest (randomized disruption cycles, emulator vs. device controls, ping-based recovery checks, logging) and a README documenting prerequisites, execution, emulator commands, scenarios, and log monitoring.

Sequence Diagram(s)

sequenceDiagram
    participant Test as NetworkConnectivityStressTest
    participant UI as MainActivity/UI
    participant VPN as VPN Service
    participant Network as Device/Emulator Network Control
    participant Ping as Ping Utility

    Test->>Network: Restore connectivity
    Test->>UI: Launch app & request VPN connect
    UI->>VPN: Initiate connection
    VPN-->>UI: Connection state updates
    Test->>Ping: Verify ping to PING_TARGET
    Ping-->>Test: Success

    loop NUM_CYCLES
        Test->>Network: Apply random disruption (WiFi/Data/Airplane/Emulator)
        Network-->>Test: Disruption applied
        Test->>Test: Wait + randomized delay
        Test->>Network: Restore connectivity
        Network-->>Test: Connectivity restored
        loop Recovery verification
            Test->>Ping: Single ping attempt
            Ping-->>Test: Response or timeout
        end
        Test->>Test: Record pass/fail
    end

    Test->>Network: Final restoration
    Test->>Test: Log summary
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hop through packets, wires, and nodes,
I toss the WiFi, then mend the roads,
A ping, a pause, a brave retry,
The VPN wakes and waves goodbye —
Hooray for tests that make resilience grow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding a network connectivity stress test for VPN resilience, which directly matches the primary purpose of the changeset across all modified files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/network-connectivity-stress-test

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java (3)

108-111: Consider migrating to ActivityScenarioRule in the future.

ActivityTestRule is deprecated in favor of ActivityScenarioRule. The current usage is acceptable given the suppression annotation, but consider migrating when updating the test infrastructure.

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

In
`@app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java`
around lines 108 - 111, The test currently uses the deprecated ActivityTestRule
(activityRule: ActivityTestRule<MainActivity>) with a suppression; replace it
with ActivityScenarioRule<MainActivity> when you migrate the test: remove the
`@SuppressWarnings`("deprecation") and ActivityTestRule declaration and instead
instantiate an ActivityScenarioRule for MainActivity (keep the rule name
activityRule or similar) so tests run with the newer ActivityScenario API;
update imports and any usages of activityRule.getActivity() to the
ActivityScenario-compatible patterns (e.g., ActivityScenarioRule and
ActivityScenario methods).

72-73: Consider making PING_TARGET configurable via test arguments.

The hardcoded IP requires code changes for different test environments. This could be made configurable via testInstrumentationRunnerArguments for easier CI/CD integration.

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

In
`@app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java`
around lines 72 - 73, Replace the hardcoded PING_TARGET in
NetworkConnectivityStressTest with a configurable value read from test
instrumentation arguments: read the "ping_target" (or similar key) from
InstrumentationRegistry.getArguments() (or
InstrumentationRegistry.getInstrumentation().getArguments()) during class setup
(e.g., `@BeforeClass`) and assign it to PING_TARGET (or a non-final static field)
with a sensible default of "100.72.116.70" if the argument is missing; update
references to use that field so CI/CD can override the target via
testInstrumentationRunnerArguments.

491-498: Iptables fallback flushes entire chains.

The fallback uses iptables -F which flushes all rules in the OUTPUT/INPUT chains, not just the DROP rules added by this test. This could remove other rules if present. For test code on a dedicated emulator this is acceptable, but be aware of this behavior.

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

In
`@app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java`
around lines 491 - 498, The current fallback in the network disconnect/connect
branches of the command handling uses "iptables -A OUTPUT -j DROP" and later
"iptables -F OUTPUT"/"iptables -F INPUT", which flushes entire chains; change
this to add a distinguishable rule (for example add the DROP rule with a unique
comment) when executing the disconnect path in the block handling
command.equals("network disconnect"), and on the connect path
(command.equals("network connect")) remove only that specific rule (use iptables
-D with the same matching parameters or match the comment) instead of using
iptables -F; update the executeShellCommand invocations accordingly so the test
only removes the DROP rules it added rather than flushing INPUT/OUTPUT chains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java`:
- Around line 463-500: The sendEmulatorConsoleCommand method leaks resources if
an exception occurs after creating the Socket/BufferedReader/PrintWriter; change
it to use try-with-resources so the Socket, BufferedReader and PrintWriter are
declared in the try(...) header and will be auto-closed (remove the explicit
socket.close()), keep the existing logic that reads the banner via readUntilOK,
authenticates with EMULATOR_AUTH_TOKEN, sends the command, logs the response,
and retains the catch block that logs the error and falls back to
executeShellCommand for "network disconnect"/"network connect"; ensure
EMULATOR_CONSOLE_PORT is still used and no behavior changes otherwise.

In `@app/src/androidTest/README.md`:
- Around line 114-116: Update the fenced code block containing "=== Results:
18/20 passed, 2 failed ===" to include a language specifier (e.g., replace the
opening ``` with ```text) so the block becomes ```text ... ``` to enable proper
syntax highlighting and satisfy markdown lint rules.

In `@gradle/libs.versions.toml`:
- Around line 7-8: Update the testRules version entry from "1.6.1" to "1.7.0" in
libs.versions.toml (leave uiautomator at "2.3.0"), and make the same change for
any other duplicate testRules entries in the file (e.g., the other occurrence
referenced in the review) so all testRules declarations are consistently 1.7.0.

---

Nitpick comments:
In
`@app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java`:
- Around line 108-111: The test currently uses the deprecated ActivityTestRule
(activityRule: ActivityTestRule<MainActivity>) with a suppression; replace it
with ActivityScenarioRule<MainActivity> when you migrate the test: remove the
`@SuppressWarnings`("deprecation") and ActivityTestRule declaration and instead
instantiate an ActivityScenarioRule for MainActivity (keep the rule name
activityRule or similar) so tests run with the newer ActivityScenario API;
update imports and any usages of activityRule.getActivity() to the
ActivityScenario-compatible patterns (e.g., ActivityScenarioRule and
ActivityScenario methods).
- Around line 72-73: Replace the hardcoded PING_TARGET in
NetworkConnectivityStressTest with a configurable value read from test
instrumentation arguments: read the "ping_target" (or similar key) from
InstrumentationRegistry.getArguments() (or
InstrumentationRegistry.getInstrumentation().getArguments()) during class setup
(e.g., `@BeforeClass`) and assign it to PING_TARGET (or a non-final static field)
with a sensible default of "100.72.116.70" if the argument is missing; update
references to use that field so CI/CD can override the target via
testInstrumentationRunnerArguments.
- Around line 491-498: The current fallback in the network disconnect/connect
branches of the command handling uses "iptables -A OUTPUT -j DROP" and later
"iptables -F OUTPUT"/"iptables -F INPUT", which flushes entire chains; change
this to add a distinguishable rule (for example add the DROP rule with a unique
comment) when executing the disconnect path in the block handling
command.equals("network disconnect"), and on the connect path
(command.equals("network connect")) remove only that specific rule (use iptables
-D with the same matching parameters or match the comment) instead of using
iptables -F; update the executeShellCommand invocations accordingly so the test
only removes the DROP rules it added rather than flushing INPUT/OUTPUT chains.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5eacabf4-4fa2-456a-a59f-dd50e323fcd9

📥 Commits

Reviewing files that changed from the base of the PR and between 625510f and 0ace50c.

📒 Files selected for processing (4)
  • app/build.gradle.kts
  • app/src/androidTest/README.md
  • app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java
  • gradle/libs.versions.toml

Comment thread app/src/androidTest/README.md Outdated
Comment thread gradle/libs.versions.toml Outdated
- Use try-with-resources in sendEmulatorConsoleCommand to prevent socket leak
- Bump testRules version from 1.6.1 to 1.7.0
- Add language specifier to fenced code block in README
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java`:
- Around line 318-320: The DisruptionType entry creates a mismatch: the label
"EMU: Extreme latency (5s delay)" but the applied delay uses
emuNetworkDelay("10000") (10s). Fix by making the label and value consistent —
either change the label to "(10s delay)" or change the emuNetworkDelay argument
to "5000"; update the DisruptionType line that references DisruptionType(...)
and the emuNetworkDelay(...) calls accordingly so the human-readable label
matches the configured delay.
- Around line 501-514: The readUntilOK method currently swallows exceptions and
returns partial output; change it to fail fast: in readUntilOK(BufferedReader
reader) stop swallowing exceptions—propagate IOExceptions (or wrap and throw a
RuntimeException) instead of returning partial text, and after the loop if EOF
is reached without a line containing "OK" (or if a line contains "KO") throw an
exception indicating the command/auth failed and include the collected output in
the exception message so callers can detect failure; keep the same loop and
StringBuilder logic but remove the empty catch and add explicit failure throws
when "KO" or no "OK" is observed.
- Around line 164-196: The current cycle applies a network disruption with
disruption.apply.run() but only calls disruption.restore.run() in the normal
path, so if an exception occurs the network isn't restored; change the per-cycle
control flow so that after calling disruption.apply.run() you always restore in
a finally block (e.g., use try { apply; ... verify; } finally { try {
disruption.restore.run(); } catch(Exception restoreEx) { log/handle restore
failure } }), keep existing logging and passCount/failCount updates (from
waitForPingSuccess and exception handling) but ensure restore is executed even
when apply or the verification throws; reference disruption.apply,
disruption.restore, waitForPingSuccess and the per-cycle exception handling to
locate where to wrap the restore.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41421730-1e36-497f-ae3a-8624c11b9148

📥 Commits

Reviewing files that changed from the base of the PR and between 0ace50c and 5379b7d.

📒 Files selected for processing (3)
  • app/src/androidTest/README.md
  • app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java
  • gradle/libs.versions.toml
✅ Files skipped from review due to trivial changes (1)
  • app/src/androidTest/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • gradle/libs.versions.toml

Comment thread app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java Outdated
Comment thread app/src/androidTest/java/io/netbird/client/NetworkConnectivityStressTest.java Outdated
pappz added 2 commits April 14, 2026 15:35
…error handling

- Always call disruption.restore in finally block to prevent stale network state
- Fix EMU latency label from 5s to 10s to match actual delay value
- Make readUntilOK propagate IOException and detect KO/EOF failures
This test requires a configured VPN and real network interfaces,
so it cannot run on the CI emulator. Run it manually on a real device.
@pappz pappz merged commit 029585a into main Apr 14, 2026
7 checks passed
@pappz pappz deleted the feature/network-connectivity-stress-test branch April 14, 2026 14:02
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.

2 participants