Refactor: Introduce TestableCallback for EthernetStateListener

The monolithic EthernetStateListener has been refactored and split
into two specialized test classes:

- TestInterfaceStateListener: For interface state callbacks.
- TestEthernetStateListener: For ethernet state callbacks.

Both new classes now extend TestableCallback, providing a standardized
and more robust way to manage and verify listener interactions in
unit tests. This improves testability and clarity of test code.

This is a no-op, test-only change.

Flag: EXEMPT refactor
Test: atest CtsNetTestCases:android.net.cts.EthernetManagerTest
Bug: 410731080
Change-Id: I667e3607954dcb073b2732b16bedfbc71a70bb39
This commit is contained in:
Junyu Lai 2025-07-01 18:15:51 +08:00
commit 4c7ba24f57
2 changed files with 203 additions and 214 deletions

View file

@ -1,147 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.testutils
import android.net.EthernetManager.ETHERNET_STATE_DISABLED
import android.net.EthernetManager.ETHERNET_STATE_ENABLED
import android.net.EthernetManager.InterfaceStateListener
import android.net.EthernetManager.ROLE_CLIENT
import android.net.EthernetManager.ROLE_NONE
import android.net.EthernetManager.ROLE_SERVER
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
import java.util.function.IntConsumer
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
private const val TIMEOUT_MS = 10_000L
private const val NO_CALLBACK_TIMEOUT_MS = 500L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(
IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE,
null,
null
)
/**
* A listener for both ethernet interface state and ethernet feature state.
*
* This class can be used to track both onInterfaceStateChanged and onEthernetStateChanged
* callbacks from EthernetManager.
*/
open class EthernetStateListener private constructor(
private val history: ArrayTrackRecord<Event>
) : InterfaceStateListener, IntConsumer, TrackRecord<EthernetStateListener.Event> by history {
constructor() : this(ArrayTrackRecord())
val events = history.newReadHead()
sealed class Event {
data class InterfaceStateChanged(
val iface: String,
val state: Int,
val role: Int,
val configuration: IpConfiguration?
) : Event() {
override fun toString(): String {
val stateString = when (state) {
STATE_ABSENT -> "STATE_ABSENT"
STATE_LINK_UP -> "STATE_LINK_UP"
STATE_LINK_DOWN -> "STATE_LINK_DOWN"
else -> state.toString()
}
val roleString = when (role) {
ROLE_NONE -> "ROLE_NONE"
ROLE_CLIENT -> "ROLE_CLIENT"
ROLE_SERVER -> "ROLE_SERVER"
else -> role.toString()
}
return ("InterfaceStateChanged(iface=$iface, state=$stateString, " +
"role=$roleString, ipConfig=$configuration)")
}
}
data class EthernetStateChanged(val state: Int) : Event() {
override fun toString(): String {
val stateString = when (state) {
ETHERNET_STATE_ENABLED -> "ETHERNET_STATE_ENABLED"
ETHERNET_STATE_DISABLED -> "ETHERNET_STATE_DISABLED"
else -> state.toString()
}
return "EthernetStateChanged(state=$stateString)"
}
}
}
override fun onInterfaceStateChanged(
iface: String,
state: Int,
role: Int,
cfg: IpConfiguration?
) {
add(Event.InterfaceStateChanged(iface, state, role, cfg))
}
override fun accept(state: Int) {
add(Event.EthernetStateChanged(state))
}
fun <T : Event> expectCallback(expected: T): T {
val event = events.poll(TIMEOUT_MS)
assertEquals(expected, event)
return event as T
}
fun expectCallback(ifaceName: String, state: Int, role: Int) {
expectCallback(createChangeEvent(ifaceName, state, role))
}
fun expectCallback(state: Int) {
expectCallback(Event.EthernetStateChanged(state))
}
private fun createChangeEvent(iface: String, state: Int, role: Int) =
Event.InterfaceStateChanged(
iface,
state,
role,
if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null
)
fun eventuallyExpect(expected: Event) {
val cb = events.poll(TIMEOUT_MS) { it == expected }
assertNotNull(cb, "Never received expected $expected. Received: ${events.backtrace()}")
}
fun eventuallyExpect(ifaceName: String, state: Int, role: Int) {
eventuallyExpect(createChangeEvent(ifaceName, state, role))
}
fun eventuallyExpect(state: Int) {
eventuallyExpect(Event.EthernetStateChanged(state))
}
fun assertNoCallback() {
val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
assertNull(cb, "Expected no callback but got $cb")
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.testutils
import android.net.EthernetManager.ETHERNET_STATE_DISABLED
import android.net.EthernetManager.ETHERNET_STATE_ENABLED
import android.net.EthernetManager.InterfaceStateListener
import android.net.EthernetManager.ROLE_CLIENT
import android.net.EthernetManager.ROLE_NONE
import android.net.EthernetManager.ROLE_SERVER
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration
import com.android.net.module.util.TestableCallback
import com.android.testutils.TestEthernetStateListener.EthernetStateChanged
import com.android.testutils.TestInterfaceStateListener.InterfaceStateChanged
import java.util.function.IntConsumer
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(
IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE,
null,
null
)
/**
* A test listener for interface state.
*/
class TestInterfaceStateListener : TestableCallback<InterfaceStateChanged>(),
InterfaceStateListener {
data class InterfaceStateChanged(
val iface: String,
val state: Int,
val role: Int,
val configuration: IpConfiguration?
) {
override fun toString(): String {
val stateString = when (state) {
STATE_ABSENT -> "STATE_ABSENT"
STATE_LINK_UP -> "STATE_LINK_UP"
STATE_LINK_DOWN -> "STATE_LINK_DOWN"
else -> state.toString()
}
val roleString = when (role) {
ROLE_NONE -> "ROLE_NONE"
ROLE_CLIENT -> "ROLE_CLIENT"
ROLE_SERVER -> "ROLE_SERVER"
else -> role.toString()
}
return ("InterfaceStateChanged(iface=$iface, state=$stateString, " +
"role=$roleString, ipConfig=$configuration)")
}
}
override fun onInterfaceStateChanged(
iface: String,
state: Int,
role: Int,
configuration: IpConfiguration?,
) {
history.add(InterfaceStateChanged(iface, state, role, configuration))
}
fun expectInterfaceStateChanged(
iface: String,
state: Int,
role: Int,
timeoutMs: Long = defaultTimeoutMs
): InterfaceStateChanged = expect(timeoutMs = timeoutMs) {
val expectedConfig = if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null
it.iface == iface && it.state == state && it.role == role &&
it.configuration == expectedConfig
}
fun eventuallyExpectInterfaceStateChanged(
iface: String,
state: Int,
role: Int,
timeoutMs: Long = defaultTimeoutMs
): InterfaceStateChanged = eventuallyExpect(timeoutMs = timeoutMs) {
val expectedConfig = if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null
it.iface == iface && it.state == state && it.role == role &&
it.configuration == expectedConfig
}
}
/**
* A test listener for ethernet state.
*/
class TestEthernetStateListener : TestableCallback<EthernetStateChanged>(), IntConsumer {
data class EthernetStateChanged(val state: Int) {
override fun toString(): String {
val stateString = when (state) {
ETHERNET_STATE_ENABLED -> "ETHERNET_STATE_ENABLED"
ETHERNET_STATE_DISABLED -> "ETHERNET_STATE_DISABLED"
else -> state.toString()
}
return "EthernetStateChanged(state=$stateString)"
}
}
override fun accept(state: Int) {
history.add(EthernetStateChanged(state))
}
}