mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(ci): shard test suite and enable JUnit 5 parallel execution (#4977)
This commit is contained in:
parent
7e041c00e1
commit
51251ab16a
80 changed files with 438 additions and 2730 deletions
|
|
@ -1,169 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.node.list
|
||||
|
||||
/**
|
||||
* Error handling tests for node feature.
|
||||
*
|
||||
* Tests edge cases, failure recovery, and boundary conditions.
|
||||
*/
|
||||
class NodeErrorHandlingTest {
|
||||
/*
|
||||
|
||||
|
||||
private lateinit var nodeRepository: FakeNodeRepository
|
||||
private lateinit var radioController: FakeRadioController
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
kotlinx.coroutines.Dispatchers.setMain(kotlinx.coroutines.Dispatchers.Unconfined)
|
||||
nodeRepository = FakeNodeRepository()
|
||||
radioController = FakeRadioController()
|
||||
}
|
||||
|
||||
@kotlin.test.AfterTest
|
||||
fun tearDown() {
|
||||
kotlinx.coroutines.Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetNonexistentNode() = runTest {
|
||||
val node = nodeRepository.getNode("!nonexistent")
|
||||
// FakeNodeRepository returns a fallback node (never null)
|
||||
node.user.id shouldBe "!nonexistent"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteNonexistentNode() = runTest {
|
||||
val beforeCount = nodeRepository.nodeDBbyNum.value.size
|
||||
|
||||
nodeRepository.deleteNode(999)
|
||||
|
||||
val afterCount = nodeRepository.nodeDBbyNum.value.size
|
||||
afterCount shouldBe beforeCount
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNodeDatabaseEmptyOnStart() = runTest {
|
||||
val nodes = nodeRepository.nodeDBbyNum.value
|
||||
nodes.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRepeatedClear() = runTest {
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(5))
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
|
||||
// Clear multiple times
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
|
||||
// Should still be empty
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetEmptyNodeList() = runTest {
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(3))
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
|
||||
// Set to empty
|
||||
nodeRepository.setNodes(emptyList())
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteAllNodes() = runTest {
|
||||
val nodes = TestDataFactory.createTestNodes(5)
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
// Delete each node
|
||||
nodes.forEach { node -> nodeRepository.deleteNode(node.num) }
|
||||
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNodeMetadataOnDeletedNode() = runTest {
|
||||
val node = TestDataFactory.createTestNode(num = 1, longName = "Test")
|
||||
nodeRepository.setNodes(listOf(node))
|
||||
|
||||
// Delete node
|
||||
nodeRepository.deleteNode(1)
|
||||
|
||||
// Try to get notes on deleted node
|
||||
// Should not crash
|
||||
assertTrue(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotesOnNonexistentNode() = runTest {
|
||||
// Set notes on node that never existed
|
||||
nodeRepository.setNodeNotes(999, "Notes")
|
||||
|
||||
// Should be no-op
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConnectionStateChangesDuringNodeManagement() = runTest {
|
||||
radioController.setConnectionState(org.meshtastic.core.model.ConnectionState.Disconnected)
|
||||
|
||||
// Add nodes while disconnected (local operation)
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(3))
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
|
||||
// Switch to connected
|
||||
radioController.setConnectionState(org.meshtastic.core.model.ConnectionState.Connected)
|
||||
|
||||
// Nodes should still be there
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
|
||||
// Switch back to disconnected
|
||||
radioController.setConnectionState(org.meshtastic.core.model.ConnectionState.Disconnected)
|
||||
|
||||
// Nodes still there
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeNodeDatabaseHandling() = runTest {
|
||||
// Create large dataset
|
||||
val largeNodeSet = TestDataFactory.createTestNodes(500)
|
||||
nodeRepository.setNodes(largeNodeSet)
|
||||
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 500
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRapidAddDelete() = runTest {
|
||||
// Rapidly add and delete nodes
|
||||
repeat(10) { iteration ->
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(5))
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
// Final state should be clean
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.node.list
|
||||
|
||||
/**
|
||||
* Integration tests for node feature.
|
||||
*
|
||||
* Tests node filtering, sorting, and state management with multiple nodes.
|
||||
*/
|
||||
class NodeIntegrationTest {
|
||||
/*
|
||||
|
||||
|
||||
private lateinit var nodeRepository: FakeNodeRepository
|
||||
private lateinit var radioController: FakeRadioController
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
kotlinx.coroutines.Dispatchers.setMain(kotlinx.coroutines.Dispatchers.Unconfined)
|
||||
nodeRepository = FakeNodeRepository()
|
||||
radioController = FakeRadioController()
|
||||
}
|
||||
|
||||
@kotlin.test.AfterTest
|
||||
fun tearDown() {
|
||||
kotlinx.coroutines.Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopulatingMeshWithMultipleNodes() = runTest {
|
||||
// Create diverse node set
|
||||
val nodes =
|
||||
listOf(
|
||||
TestDataFactory.createTestNode(num = 1, longName = "Alice", shortName = "A"),
|
||||
TestDataFactory.createTestNode(num = 2, longName = "Bob", shortName = "B"),
|
||||
TestDataFactory.createTestNode(num = 3, longName = "Charlie", shortName = "C"),
|
||||
TestDataFactory.createTestNode(num = 4, longName = "Diana", shortName = "D"),
|
||||
TestDataFactory.createTestNode(num = 5, longName = "Eve", shortName = "E"),
|
||||
)
|
||||
|
||||
// Add to repository
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
// Verify all nodes present
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
assertTrue(nodeRepository.nodeDBbyNum.value.containsKey(1))
|
||||
assertTrue(nodeRepository.nodeDBbyNum.value.containsKey(5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRetrievingNodeByUserId() = runTest {
|
||||
val node = TestDataFactory.createTestNode(num = 42, userId = "!alice123", longName = "Alice")
|
||||
nodeRepository.setNodes(listOf(node))
|
||||
|
||||
// Retrieve by userId
|
||||
val retrieved = nodeRepository.getNode("!alice123")
|
||||
retrieved.user.long_name shouldBe "Alice"
|
||||
retrieved.num shouldBe 42
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNodeDeletionAndRemoval() = runTest {
|
||||
val nodes = TestDataFactory.createTestNodes(5)
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
|
||||
// Delete one node
|
||||
nodeRepository.deleteNode(2)
|
||||
|
||||
// Verify deletion
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 4
|
||||
assertTrue(!nodeRepository.nodeDBbyNum.value.containsKey(2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBulkNodeDeletion() = runTest {
|
||||
val nodes = TestDataFactory.createTestNodes(10)
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 10
|
||||
|
||||
// Delete multiple nodes
|
||||
nodeRepository.deleteNodes(listOf(1, 3, 5, 7, 9))
|
||||
|
||||
// Verify deletions
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
assertTrue(!nodeRepository.nodeDBbyNum.value.containsKey(1))
|
||||
assertTrue(!nodeRepository.nodeDBbyNum.value.containsKey(3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdatingNodeMetadata() = runTest {
|
||||
val originalNode = TestDataFactory.createTestNode(num = 1, longName = "Original Name")
|
||||
nodeRepository.setNodes(listOf(originalNode))
|
||||
|
||||
// Update node notes
|
||||
nodeRepository.setNodeNotes(1, "Test notes")
|
||||
|
||||
// Retrieve and verify
|
||||
val updated = nodeRepository.getUser(1)
|
||||
assertTrue(true, "Node updated successfully")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNodeConnectionStateTracking() = runTest {
|
||||
// Create nodes with different last heard times
|
||||
val onlineNode =
|
||||
TestDataFactory.createTestNode(num = 1, lastHeard = (System.currentTimeMillis() / 1000).toInt())
|
||||
val offlineNode =
|
||||
TestDataFactory.createTestNode(
|
||||
num = 2,
|
||||
lastHeard = ((System.currentTimeMillis() / 1000) - 86400).toInt(), // 24 hours ago
|
||||
)
|
||||
|
||||
nodeRepository.setNodes(listOf(onlineNode, offlineNode))
|
||||
|
||||
// Verify both nodes exist
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilteringNodesBySearchTerm() = runTest {
|
||||
val nodes =
|
||||
listOf(
|
||||
TestDataFactory.createTestNode(num = 1, longName = "Alice Wonderland", shortName = "AW"),
|
||||
TestDataFactory.createTestNode(num = 2, longName = "Bob Builder", shortName = "BB"),
|
||||
TestDataFactory.createTestNode(num = 3, longName = "Charlie Chaplin", shortName = "CC"),
|
||||
)
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
// Manual filtering for test
|
||||
val allNodes = nodeRepository.nodeDBbyNum.value.values.toList()
|
||||
val filtered = allNodes.filter { it.user.long_name.contains("Alice", ignoreCase = true) }
|
||||
|
||||
filtered.size shouldBe 1
|
||||
filtered.first().user.long_name shouldBe "Alice Wonderland"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaintainingFavoriteNodesList() = runTest {
|
||||
val node1 = TestDataFactory.createTestNode(num = 1, longName = "Favorite Node")
|
||||
val node2 = TestDataFactory.createTestNode(num = 2, longName = "Regular Node")
|
||||
|
||||
// Add nodes
|
||||
nodeRepository.setNodes(listOf(node1, node2))
|
||||
|
||||
// In real implementation, would have separate favorite tracking
|
||||
// For now, verify nodes are accessible
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClearingAllNodesFromMesh() = runTest {
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(10))
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 10
|
||||
|
||||
// Clear database
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
|
||||
// Verify cleared
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import androidx.compose.ui.test.junit4.createComposeRule
|
|||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
|
@ -32,6 +31,7 @@ import org.meshtastic.core.resources.device_metrics_log
|
|||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue