Skip to content

Commit

Permalink
Merge branch 'feature/unittest--controller-tests-data_model_v2' of ht…
Browse files Browse the repository at this point in the history
…tps://github.com/feasel0/connectedhomeip into feature/unittest--controller-tests-data_model_v2
  • Loading branch information
feasel0 committed May 9, 2024
2 parents e636e16 + 4748f50 commit 515db18
Show file tree
Hide file tree
Showing 25 changed files with 839 additions and 977 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.databinding.AddressUpdateFragmentBinding
import com.google.chip.chiptool.util.DeviceIdUtil
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -55,6 +54,8 @@ class AddressUpdateFragment : ICDCheckInCallback, Fragment() {

private val handler = Handler(Looper.getMainLooper())

private var externalICDCheckInMessageCallback: ICDCheckInMessageCallback? = null

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand Down Expand Up @@ -128,11 +129,12 @@ class AddressUpdateFragment : ICDCheckInCallback, Fragment() {
}

val cluster = ChipClusters.IcdManagementCluster(devicePtr, 0)
val duration = suspendCoroutine { cont ->
val retDuration = suspendCoroutine { cont ->
cluster.stayActiveRequest(
object : ChipClusters.IcdManagementCluster.StayActiveResponseCallback {
override fun onError(error: Exception) {
cont.resumeWithException(error)
Log.d(TAG, "onError", error)
cont.resume(0L)
}

override fun onSuccess(promisedActiveDuration: Long) {
Expand All @@ -143,12 +145,11 @@ class AddressUpdateFragment : ICDCheckInCallback, Fragment() {
)
}
isSendingStayActiveCommand = false
return duration
return retDuration
}

private fun updateUIForICDInteractionSwitch(isEnabled: Boolean): Boolean {
val isICD =
deviceController.icdClientInfo.firstOrNull { info -> info.peerNodeId == deviceId } != null
val isICD = isICDDevice()
if (isEnabled && !isICD) {
binding.icdInteractionSwitch.isChecked = false
return false
Expand Down Expand Up @@ -188,7 +189,13 @@ class AddressUpdateFragment : ICDCheckInCallback, Fragment() {
return binding.deviceIdEd.text.toString().toULong(16)
}

fun isICDDevice(): Boolean {
return deviceController.icdClientInfo.firstOrNull { info -> info.peerNodeId == deviceId } !=
null
}

override fun notifyCheckInMessage(info: ICDClientInfo) {
externalICDCheckInMessageCallback?.notifyCheckInMessage()
if (info.peerNodeId != icdDeviceId) {
return
}
Expand All @@ -206,6 +213,14 @@ class AddressUpdateFragment : ICDCheckInCallback, Fragment() {
}
}

fun setNotifyCheckInMessageCallback(callback: ICDCheckInMessageCallback?) {
externalICDCheckInMessageCallback = callback
}

interface ICDCheckInMessageCallback {
fun notifyCheckInMessage()
}

private fun turnOnActiveMode() {
requireActivity().runOnUiThread {
binding.icdProgressBar.max = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import matter.tlv.AnonymousTag
import matter.tlv.TlvReader
import matter.tlv.TlvWriter

class WildcardFragment : Fragment() {
class WildcardFragment : Fragment(), AddressUpdateFragment.ICDCheckInMessageCallback {
private var _binding: WildcardFragmentBinding? = null
private val binding
get() = _binding!!
Expand All @@ -63,6 +63,23 @@ class WildcardFragment : Fragment() {
private val invokePath = ArrayList<InvokeElement>()
private val subscribeIdList = ArrayList<ULong>()

data class ReadICDConfig(val isFabricFiltered: Boolean, val eventMin: Long?)

data class SubscribeICDConfig(
val minInterval: Int,
val maxInterval: Int,
val keepSubscriptions: Boolean,
val isFabricFiltered: Boolean,
val eventMin: Long?
)

data class WriteInvokeICDConfig(val timedRequestTimeoutMs: Int, val imTimeoutMs: Int)

private var readICDConfig: ReadICDConfig? = null
private var subscribeICDConfig: SubscribeICDConfig? = null
private var writeICDConfig: WriteInvokeICDConfig? = null
private var invokeICDConfig: WriteInvokeICDConfig? = null

private val reportCallback =
object : ReportCallback {
override fun onError(
Expand Down Expand Up @@ -173,6 +190,45 @@ class WildcardFragment : Fragment() {
return binding.root
}

override fun onResume() {
super.onResume()
addressUpdateFragment.setNotifyCheckInMessageCallback(this)
}

override fun onPause() {
addressUpdateFragment.setNotifyCheckInMessageCallback(null)
super.onPause()
}

override fun notifyCheckInMessage() {
Log.d(TAG, "notifyCheckInMessage")
if (attributePath.isNotEmpty() || eventPath.isNotEmpty()) {
if (binding.readRadioBtn.isChecked && readICDConfig != null) {
scope.launch { read(readICDConfig!!.isFabricFiltered, readICDConfig!!.eventMin) }
} else if (binding.subscribeRadioBtn.isChecked && subscribeICDConfig != null) {
scope.launch {
subscribe(
subscribeICDConfig!!.minInterval,
subscribeICDConfig!!.maxInterval,
subscribeICDConfig!!.keepSubscriptions,
subscribeICDConfig!!.isFabricFiltered,
subscribeICDConfig!!.eventMin
)
}
}
} else if (
binding.writeRadioBtn.isChecked && writePath.isNotEmpty() && writeICDConfig != null
) {
scope.launch { write(writeICDConfig!!.timedRequestTimeoutMs, writeICDConfig!!.imTimeoutMs) }
} else if (
binding.invokeRadioBtn.isChecked && invokePath.isNotEmpty() && invokeICDConfig != null
) {
scope.launch {
invoke(invokeICDConfig!!.timedRequestTimeoutMs, invokeICDConfig!!.imTimeoutMs)
}
}
}

private fun setVisibilityEachView(radioBtnId: Int) {
val readBtnOn = (radioBtnId == R.id.readRadioBtn)
val subscribeBtnOn = (radioBtnId == R.id.subscribeRadioBtn)
Expand Down Expand Up @@ -520,7 +576,12 @@ class WildcardFragment : Fragment() {
if (eventPath.isNotEmpty() && eventMinEd.text.isNotBlank()) {
eventMin = eventMinEd.text.toString().toULong().toLong()
}
read(isFabricFilteredEd.selectedItem.toString().toBoolean(), eventMin)
if (addressUpdateFragment.isICDDevice()) {
readICDConfig =
ReadICDConfig(isFabricFilteredEd.selectedItem.toString().toBoolean(), eventMin)
} else {
read(isFabricFilteredEd.selectedItem.toString().toBoolean(), eventMin)
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
Expand All @@ -537,18 +598,23 @@ class WildcardFragment : Fragment() {
dialogView.findViewById<EditText>(R.id.timedRequestTimeoutEd).text.toString()
val imTimeout = dialogView.findViewById<EditText>(R.id.imTimeoutEd).text.toString()
scope.launch {
write(
val timedRequestTimeoutInt =
if (timedRequestTimeoutMs.isEmpty()) {
0
} else {
timedRequestTimeoutMs.toInt()
},
}
val imTimeoutInt =
if (imTimeout.isEmpty()) {
0
} else {
imTimeout.toInt()
}
)
if (addressUpdateFragment.isICDDevice()) {
writeICDConfig = WriteInvokeICDConfig(timedRequestTimeoutInt, imTimeoutInt)
} else {
write(timedRequestTimeoutInt, imTimeoutInt)
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
Expand Down Expand Up @@ -588,13 +654,24 @@ class WildcardFragment : Fragment() {
if (eventPath.isNotEmpty() && eventMinEd.text.isNotBlank()) {
eventMin = eventMinEd.text.toString().toULong().toLong()
}
subscribe(
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt(),
keepSubscriptionsSp.selectedItem.toString().toBoolean(),
isFabricFilteredSp.selectedItem.toString().toBoolean(),
eventMin,
)
if (addressUpdateFragment.isICDDevice()) {
subscribeICDConfig =
SubscribeICDConfig(
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt(),
keepSubscriptionsSp.selectedItem.toString().toBoolean(),
isFabricFilteredSp.selectedItem.toString().toBoolean(),
eventMin
)
} else {
subscribe(
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt(),
keepSubscriptionsSp.selectedItem.toString().toBoolean(),
isFabricFilteredSp.selectedItem.toString().toBoolean(),
eventMin
)
}
} else {
Log.e(TAG, "minInterval or maxInterval is empty!")
}
Expand All @@ -614,18 +691,23 @@ class WildcardFragment : Fragment() {
dialogView.findViewById<EditText>(R.id.timedRequestTimeoutEd).text.toString()
val imTimeout = dialogView.findViewById<EditText>(R.id.imTimeoutEd).text.toString()
scope.launch {
invoke(
val timedRequestTimeoutInt =
if (timedRequestTimeoutMs.isEmpty()) {
0
} else {
timedRequestTimeoutMs.toInt()
},
}
val imTimeoutInt =
if (imTimeout.isEmpty()) {
0
} else {
imTimeout.toInt()
}
)
if (addressUpdateFragment.isICDDevice()) {
invokeICDConfig = WriteInvokeICDConfig(timedRequestTimeoutInt, imTimeoutInt)
} else {
invoke(timedRequestTimeoutInt, imTimeoutInt)
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
Expand Down
1 change: 1 addition & 0 deletions kotlin-detect-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ complexity:
- "**/src/controller/java/tests/matter/tlv/TlvReaderTest.kt"
LargeClass:
excludes:
- "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt"
- "**/src/controller/java/generated/java/**/*"
- "**/src/controller/java/tests/matter/tlv/TlvReadWriteTest.kt"
- "**/src/controller/java/tests/matter/jsontlv/JsonToTlvToJsonTest.kt"
Expand Down
89 changes: 88 additions & 1 deletion scripts/tests/run_tv_casting_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
# The maximum amount of time to commission the Linux tv-casting-app and the tv-app before timeout.
COMMISSIONING_STAGE_MAX_WAIT_SEC = 10

# The maximum amount of time to test that the launchURL is sent from the Linux tv-casting-app and received on the tv-app before timeout.
TEST_LAUNCHURL_MAX_WAIT_SEC = 10

# File names of logs for the Linux tv-casting-app and the Linux tv-app.
LINUX_TV_APP_LOGS = 'Linux-tv-app-logs.txt'
LINUX_TV_CASTING_APP_LOGS = 'Linux-tv-casting-app-logs.txt'
Expand Down Expand Up @@ -303,10 +306,80 @@ def validate_commissioning_success(tv_casting_app_info: Tuple[subprocess.Popen,

if 'PROMPT USER: commissioning success' in tv_app_line:
logging.info('Commissioning success noted on the Linux tv-app output:')
logging.info(tv_app_line)
return True


def parse_tv_app_output_for_launchUrl_msg_success(tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]):
"""Parse the Linux tv-app output for the relevant string indicating that the launchUrl was received."""

tv_app_process, linux_tv_app_log_file = tv_app_info

start_wait_time = time.time()

while True:
# Check if we exceeded the maximum wait time to parse the Linux tv-app output for the string related to the launchUrl.
if time.time() - start_wait_time > COMMISSIONING_STAGE_MAX_WAIT_SEC:
logging.error(
'The relevant launchUrl string was not found in the Linux tv-app process within the timeout.')
return False

tv_app_line = tv_app_process.stdout.readline()

if tv_app_line:
linux_tv_app_log_file.write(tv_app_line)
linux_tv_app_log_file.flush()

if 'ContentLauncherManager::HandleLaunchUrl TEST CASE ContentURL=https://www.test.com/videoid DisplayString=Test video' in tv_app_line:
logging.info('Found the launchUrl in the Linux tv-app output:')
logging.info(tv_app_line.rstrip('\n'))
return True


def parse_tv_casting_app_output_for_launchUrl_msg_success(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]):
"""Parse the Linux tv-casting-app output for relevant strings indicating that the launchUrl was sent."""

tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info

continue_parsing_invoke_response_msg_block = False
found_example_data_msg = False
start_wait_time = time.time()

while True:
# Check if we exceeded the maximum wait time to parse the Linux tv-casting-app output for strings related to the launchUrl.
if time.time() - start_wait_time > TEST_LAUNCHURL_MAX_WAIT_SEC:
logging.error(
'The relevant launchUrl strings were not found in the Linux tv-casting-app process within the timeout.')
return False

tv_casting_line = tv_casting_app_process.stdout.readline()

if tv_casting_line:
linux_tv_casting_app_log_file.write(tv_casting_line)
linux_tv_casting_app_log_file.flush()

if 'InvokeResponseMessage =' in tv_casting_line:
logging.info('Found the InvokeResponseMessage block in the Linux tv-casting-app output:')
logging.info(tv_casting_line.rstrip('\n'))
continue_parsing_invoke_response_msg_block = True

elif continue_parsing_invoke_response_msg_block:
# Sanity check for `exampleData` in the `InvokeResponseMessage` block.
if 'exampleData' in tv_casting_line:
found_example_data_msg = True

elif 'Received Command Response Data' in tv_casting_line:
if not found_example_data_msg:
logging.error('The `exampleData` string was not found in the `InvokeResponseMessage` block.')
return False

logging.info('Found the `Received Command Response Data` string in the Linux tv-casting-app output:')
logging.info(tv_casting_line.rstrip('\n'))
return True

logging.info(tv_casting_line.rstrip('\n'))


def test_discovery_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]) -> Optional[str]:
"""Parse the output of the Linux tv-casting-app to find a valid commissioner."""
tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info
Expand Down Expand Up @@ -347,7 +420,7 @@ def test_discovery_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_
logging.info(valid_vendor_id)
logging.info(valid_product_id)
logging.info(valid_device_type)
logging.info('Discovery success!')
logging.info('Discovery success!\n')
break

return valid_discovered_commissioner
Expand All @@ -372,6 +445,18 @@ def test_commissioning_fn(valid_discovered_commissioner_number, tv_casting_app_i
handle_casting_failure('Commissioning', log_paths)


def test_launchUrl_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]):
"""Test that the Linux tv-casting-app sent the launchUrl and that the Linux tv-app received the launchUrl."""

if not parse_tv_app_output_for_launchUrl_msg_success(tv_app_info, log_paths):
handle_casting_failure('Testing launchUrl', log_paths)

if not parse_tv_casting_app_output_for_launchUrl_msg_success(tv_casting_app_info, log_paths):
handle_casting_failure('Testing launchUrl', log_paths)

logging.info('Testing launchUrl success!\n')


@click.command()
@click.option('--tv-app-rel-path', type=str, default='out/tv-app/chip-tv-app', help='Path to the Linux tv-app executable.')
@click.option('--tv-casting-app-rel-path', type=str, default='out/tv-casting-app/chip-tv-casting-app', help='Path to the Linux tv-casting-app executable.')
Expand Down Expand Up @@ -420,6 +505,8 @@ def test_casting_fn(tv_app_rel_path, tv_casting_app_rel_path):

test_commissioning_fn(valid_discovered_commissioner_number, tv_casting_app_info, tv_app_info, log_paths)

test_launchUrl_fn(tv_casting_app_info, tv_app_info, log_paths)


if __name__ == '__main__':

Expand Down
Loading

0 comments on commit 515db18

Please sign in to comment.