From d911f336ab1b8ada7bbcf04725629f03e1d5e5dd Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Sun, 22 Sep 2024 10:33:52 +0800 Subject: [PATCH] :white_check_mark: Add unit tests for NetworkUtils (#1941) --- composeApp/build.gradle.kts | 6 + .../kotlin/com/crosspaste/utils/NetUtils.kt | 2 + .../com/crosspaste/utils/ValueProvider.kt | 4 + .../com/crosspaste/utils/NetUtils.desktop.kt | 36 ++-- .../com/crosspaste/utils/NetworkUtilsTest.kt | 155 ++++++++++++++++++ 5 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 composeApp/src/desktopTest/kotlin/com/crosspaste/utils/NetworkUtilsTest.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index c7e40bc8..8080e129 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -379,6 +379,12 @@ tasks.withType { showCauses = true showStackTraces = true } + jvmArgs( + "--add-opens", + "java.base/java.net=ALL-UNNAMED", + "--add-opens", + "java.base/java.lang.reflect=ALL-UNNAMED", + ) } // region Work around temporary Compose bugs. diff --git a/composeApp/src/commonMain/kotlin/com/crosspaste/utils/NetUtils.kt b/composeApp/src/commonMain/kotlin/com/crosspaste/utils/NetUtils.kt index 34f120c3..f4b3a956 100644 --- a/composeApp/src/commonMain/kotlin/com/crosspaste/utils/NetUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/crosspaste/utils/NetUtils.kt @@ -15,4 +15,6 @@ interface NetUtils { ): Boolean fun getPreferredLocalIPAddress(): String? + + fun clearProviderCache() } diff --git a/composeApp/src/commonMain/kotlin/com/crosspaste/utils/ValueProvider.kt b/composeApp/src/commonMain/kotlin/com/crosspaste/utils/ValueProvider.kt index d105e8e6..fd5550e0 100644 --- a/composeApp/src/commonMain/kotlin/com/crosspaste/utils/ValueProvider.kt +++ b/composeApp/src/commonMain/kotlin/com/crosspaste/utils/ValueProvider.kt @@ -14,4 +14,8 @@ class ValueProvider { lastSuccessfulValue } } + + fun clear() { + lastSuccessfulValue = null + } } diff --git a/composeApp/src/desktopMain/kotlin/com/crosspaste/utils/NetUtils.desktop.kt b/composeApp/src/desktopMain/kotlin/com/crosspaste/utils/NetUtils.desktop.kt index 2f0dcde4..c071a4fe 100644 --- a/composeApp/src/desktopMain/kotlin/com/crosspaste/utils/NetUtils.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/crosspaste/utils/NetUtils.desktop.kt @@ -21,31 +21,30 @@ object DesktopNetUtils : NetUtils { private val preferredLocalIPAddress = ValueProvider() // Get all potential local IP addresses - private fun getAllLocalAddresses(): Sequence> { - val networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()) - - networkInterfaces.forEach { nic -> - logger.info { "Network interface: ${nic.name}" } - nic.interfaceAddresses.forEach { addr -> - logger.info { "\t\tInterface address: ${addr.address.hostAddress}" } - } - } - - return networkInterfaces + fun getAllLocalAddresses(): Sequence> { + return Collections.list(NetworkInterface.getNetworkInterfaces()) .asSequence() .filter { it.isUp && !it.isLoopback && !it.isVirtual } .flatMap { nic -> nic.interfaceAddresses.asSequence().map { Pair(it, nic.name) } } - .filter { (addr, _) -> + .filter { (addr, nicName) -> val address = addr.address if (address is Inet4Address) { val hostAddress = address.hostAddress - hostAddress != null && - !hostAddress.endsWith(".0") && - !hostAddress.endsWith(".1") && - !hostAddress.endsWith(".255") + val networkPrefixLength = addr.networkPrefixLength + val isLocalAddress = + hostAddress != null && + !hostAddress.endsWith(".0") && + !hostAddress.endsWith(".1") && + !hostAddress.endsWith(".255") + logger.info { + "get local address, Network interface: $nicName " + + "address: $hostAddress networkPrefixLength: $networkPrefixLength" + } + isLocalAddress } else { + logger.info { "Network interface: $nicName is not local address" } false } } @@ -122,4 +121,9 @@ object DesktopNetUtils : NetUtils { } } } + + override fun clearProviderCache() { + hostListProvider.clear() + preferredLocalIPAddress.clear() + } } diff --git a/composeApp/src/desktopTest/kotlin/com/crosspaste/utils/NetworkUtilsTest.kt b/composeApp/src/desktopTest/kotlin/com/crosspaste/utils/NetworkUtilsTest.kt new file mode 100644 index 00000000..533da85f --- /dev/null +++ b/composeApp/src/desktopTest/kotlin/com/crosspaste/utils/NetworkUtilsTest.kt @@ -0,0 +1,155 @@ +package com.crosspaste.utils + +import com.crosspaste.utils.DesktopNetUtils.getAllLocalAddresses +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import java.net.Inet4Address +import java.net.InterfaceAddress +import java.net.NetworkInterface +import java.util.Collections +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull + +class NetworkUtilsTest { + + @BeforeEach + fun setUp() { + mockkStatic(NetworkInterface::class) + } + + @AfterEach + fun tearDown() { + unmockkAll() + getNetUtils().clearProviderCache() + } + + @Test + fun testGetAllLocalAddresses() { + // Use MockK to mock NetworkInterface and related classes + mockkStatic(NetworkInterface::class) + + val nic1 = mockk() + val nic2 = mockk() + val nic3 = mockk() + + every { nic1.isUp } returns true + every { nic1.isLoopback } returns false + every { nic1.isVirtual } returns false + every { nic1.name } returns "eth0" + + every { nic2.isUp } returns true + every { nic2.isLoopback } returns false + every { nic2.isVirtual } returns false + every { nic2.name } returns "wlan0" + + every { nic3.isUp } returns true + every { nic3.isLoopback } returns false + every { nic3.isVirtual } returns false + every { nic3.name } returns "null0" + + val addr1 = mockk() + val addr2 = mockk() + val addr3 = mockk() + val inetAddr1 = mockk() + val inetAddr2 = mockk() + + every { addr1.address } returns inetAddr1 + every { addr2.address } returns inetAddr2 + every { addr3.address } returns null + every { inetAddr1.hostAddress } returns "192.168.1.8" + every { inetAddr2.hostAddress } returns "10.0.0.5" + every { addr1.networkPrefixLength } returns 24 + every { addr2.networkPrefixLength } returns 16 + every { addr3.networkPrefixLength } returns 0 + + every { nic1.interfaceAddresses } returns listOf(addr1) + every { nic2.interfaceAddresses } returns listOf(addr2) + every { nic3.interfaceAddresses } returns listOf(addr3) + + val networkInterfaces = Collections.enumeration(listOf(nic1, nic2, nic3)) + every { NetworkInterface.getNetworkInterfaces() } returns networkInterfaces + + // Execute the test + val result = getAllLocalAddresses().toList() + + // Verify the results + assertEquals(2, result.size) + assertEquals("192.168.1.8", result[0].first.hostAddress) + assertEquals(24, result[0].first.networkPrefixLength) + assertEquals("eth0", result[0].second) + assertEquals("10.0.0.5", result[1].first.hostAddress) + assertEquals(16, result[1].first.networkPrefixLength) + assertEquals("wlan0", result[1].second) + + // Verify that NetworkInterface.getNetworkInterfaces() was called + verify { NetworkInterface.getNetworkInterfaces() } + + // Verify that the interface with null address was skipped + assertFalse(result.any { it.second == "null0" }) + } + + @Test + fun `getPreferredLocalIPAddress returns correct IP when valid interfaces exist`() { + val nic1 = mockNetworkInterface("eth0", "192.168.1.100", 24) + val nic2 = mockNetworkInterface("wlan0", "192.168.2.100", 24) + + every { NetworkInterface.getNetworkInterfaces() } returns Collections.enumeration(listOf(nic1, nic2)) + + val result = DesktopNetUtils.getPreferredLocalIPAddress() + + assertEquals("192.168.1.100", result) + } + + @Test + fun `getPreferredLocalIPAddress returns null when no valid interfaces exist`() { + val nic = mockNetworkInterface("lo", "127.0.0.1", 8) + + every { NetworkInterface.getNetworkInterfaces() } returns Collections.enumeration(listOf(nic)) + + val result = DesktopNetUtils.getPreferredLocalIPAddress() + + assertNull(result) + } + + @Test + fun `getPreferredLocalIPAddress prefers eth interfaces over others`() { + val nic1 = mockNetworkInterface("wlan0", "192.168.2.100", 24) + val nic2 = mockNetworkInterface("eth0", "192.168.1.100", 24) + + every { NetworkInterface.getNetworkInterfaces() } returns Collections.enumeration(listOf(nic1, nic2)) + + val result = DesktopNetUtils.getPreferredLocalIPAddress() + + assertEquals("192.168.1.100", result) + } + + private fun mockNetworkInterface( + name: String, + ip: String, + prefixLength: Short, + ): NetworkInterface { + return mockk().apply { + every { isUp } returns true + every { isLoopback } returns false + every { isVirtual } returns false + every { this@apply.name } returns name + every { interfaceAddresses } returns + listOf( + mockk().apply { + every { address } returns + mockk().apply { + every { hostAddress } returns ip + } + every { networkPrefixLength } returns prefixLength + }, + ) + } + } +}