Revision control

++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ import QtPositioning
++ import QtQml
++ import QtQuick
++ import QtTest
++
++ TestCase {
++ name: "SatelliteSource"
++
++ id: top
++
++ Component {
++ id: defaultSource
++ SatelliteSource {
++ }
++ }
++
++ Component {
++ id: sourceWithParameters
++ SatelliteSource {
++ name: "satellitesource.test"
++ PluginParameter { name: "satellitesystem"; value: GeoSatelliteInfo.GALILEO }
++ }
++ }
++
++ Component {
++ id: sourceWithIncompleteParameter
++ SatelliteSource {
++ name: "satellitesource.test"
++ PluginParameter {
++ objectName: "incompleteProperty"
++ name: "satellitesystem"
++ }
++ }
++ }
++
++ SignalSpy { id: inViewSpy; signalName: "satellitesInViewChanged" }
++ SignalSpy { id: inUseSpy; signalName: "satellitesInUseChanged" }
++ SignalSpy { id: activeSpy; signalName: "activeChanged" }
++ SignalSpy { id: validSpy; signalName: "validityChanged" }
++ SignalSpy { id: updateIntervalSpy; signalName: "updateIntervalChanged" }
++ SignalSpy { id: errorSpy; signalName: "sourceErrorChanged" }
++ SignalSpy { id: nameSpy; signalName: "nameChanged" }
++
++ function test_default_construct() {
++ var source = defaultSource.createObject(top, {})
++ // empty name resulted in picking a default source, which is valid
++ compare(source.valid, true)
++ compare(source.active, false)
++ verify(source.name !== "")
++ compare(source.sourceError, SatelliteSource.NoError)
++ compare(source.satellitesInView.length, 0)
++ compare(source.satellitesInUse.length, 0)
++ }
++
++ function test_unknown_plugin() {
++ var source = defaultSource.createObject(top, { name: "invalid_plugin" })
++ compare(source.valid, false)
++ compare(source.active, false)
++ compare(source.name, "invalid_plugin")
++ compare(source.sourceError, SatelliteSource.NoError)
++ compare(source.satellitesInView.length, 0)
++ compare(source.satellitesInUse.length, 0)
++ }
++
++ function test_backend_properties() {
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++
++ var inView = source.backendProperty("desiredInViewCount")
++ var inUse = source.backendProperty("desiredInUseCount")
++ var useElevation = source.backendProperty("useElevation")
++ var useAzimuth = source.backendProperty("useAzimuth")
++ var unknownParam = source.backendProperty("unknown")
++
++ // check defaults
++ compare(inView, 5)
++ compare(inUse, 3)
++ compare(useElevation, false)
++ compare(useAzimuth, false)
++ compare(unknownParam, undefined)
++
++ // modify
++ compare(source.setBackendProperty("desiredInViewCount", 10), true)
++ compare(source.setBackendProperty("desiredInUseCount", 5), true)
++ compare(source.setBackendProperty("useElevation", true), true)
++ compare(source.setBackendProperty("useAzimuth", true), true)
++ compare(source.setBackendProperty("unknown", "val"), false)
++
++ // check that parameters are updated
++ inView = source.backendProperty("desiredInViewCount")
++ inUse = source.backendProperty("desiredInUseCount")
++ useElevation = source.backendProperty("useElevation")
++ useAzimuth = source.backendProperty("useAzimuth")
++
++ compare(inView, 10)
++ compare(inUse, 5)
++ compare(useElevation, true)
++ compare(useAzimuth, true)
++ }
++
++ function test_regular_updates() {
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ inViewSpy.target = source
++ inUseSpy.target = source
++ inViewSpy.clear()
++ inUseSpy.clear()
++
++ var expectedInView = source.backendProperty("desiredInViewCount")
++ var expectedInUse = source.backendProperty("desiredInUseCount")
++ verify(expectedInView > 0)
++ verify(expectedInUse > 0)
++
++ source.start()
++ tryCompare(inViewSpy, "count", 1)
++ tryCompare(inUseSpy, "count", 1)
++
++ compare(source.satellitesInView.length, expectedInView)
++ compare(source.satellitesInUse.length, expectedInUse)
++
++ for (var idx = 0; idx < expectedInView; ++idx) {
++ var satInView = source.satellitesInView[idx]
++ compare(satInView.satelliteSystem, GeoSatelliteInfo.GPS)
++ compare(satInView.satelliteIdentifier, idx + 1)
++ compare(satInView.signalStrength, 5 * (idx + 1))
++ compare(satInView.hasAttribute(GeoSatelliteInfo.Elevation), false)
++ compare(satInView.attribute(GeoSatelliteInfo.Elevation), -1)
++ compare(satInView.hasAttribute(GeoSatelliteInfo.Azimuth), false)
++ compare(satInView.attribute(GeoSatelliteInfo.Azimuth), -1)
++
++ if (idx < expectedInUse) {
++ var satInUse = source.satellitesInUse[idx]
++ compare(satInUse, satInView)
++ }
++ }
++ }
++
++ function test_update_with_plugin_parameters_and_properties() {
++ var source = sourceWithParameters.createObject(top, {})
++ inViewSpy.target = source
++ inUseSpy.target = source
++ inViewSpy.clear()
++ inUseSpy.clear()
++
++ source.setBackendProperty("useElevation", true)
++ source.setBackendProperty("useAzimuth", true)
++
++ var expectedInView = source.backendProperty("desiredInViewCount")
++ var expectedInUse = source.backendProperty("desiredInUseCount")
++
++ source.update()
++ tryCompare(inViewSpy, "count", 1)
++ tryCompare(inUseSpy, "count", 1)
++
++ for (var idx = 0; idx < expectedInView; ++idx) {
++ var satInView = source.satellitesInView[idx]
++ compare(satInView.satelliteSystem, GeoSatelliteInfo.GALILEO)
++ compare(satInView.satelliteIdentifier, idx + 1)
++ compare(satInView.signalStrength, 5 * (idx + 1))
++ compare(satInView.hasAttribute(GeoSatelliteInfo.Elevation), true)
++ compare(satInView.attribute(GeoSatelliteInfo.Elevation), 3.0 * (idx + 1))
++ compare(satInView.hasAttribute(GeoSatelliteInfo.Azimuth), true)
++ compare(satInView.attribute(GeoSatelliteInfo.Azimuth), 0.5 * (idx + 1))
++
++ if (idx < expectedInUse) {
++ var satInUse = source.satellitesInUse[idx]
++ compare(satInUse, satInView)
++ }
++ }
++ }
++
++ function test_signals() {
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ activeSpy.target = source
++ activeSpy.clear()
++ validSpy.target = source
++ validSpy.clear()
++ updateIntervalSpy.target = source
++ updateIntervalSpy.clear()
++ nameSpy.target = source
++ nameSpy.clear()
++ errorSpy.target = source
++ errorSpy.clear()
++
++ compare(source.valid, true)
++
++ source.updateInterval = 1000
++ tryCompare(updateIntervalSpy, "count", 1)
++ compare(source.updateInterval, 1000)
++
++ source.active = true
++ tryCompare(activeSpy, "count", 1)
++ compare(source.active, true)
++
++ // should trigger an error, because it's less than minimumUpdateInterval
++ source.update(100)
++ tryCompare(errorSpy, "count", 1)
++ compare(source.sourceError, SatelliteSource.UpdateTimeoutError)
++
++ source.name = "invalid_plugin"
++ tryCompare(nameSpy, "count", 1)
++ compare(source.name, "invalid_plugin")
++ tryCompare(activeSpy, "count", 2)
++ compare(source.active, false)
++ tryCompare(validSpy, "count", 1)
++ compare(source.valid, false)
++ }
++
++ function test_create_active() {
++ // create a default source, active by default - should be working fine
++ var source = defaultSource.createObject(top, { active: true })
++ inViewSpy.target = source
++ inViewSpy.clear()
++ inUseSpy.target = source
++ inUseSpy.clear()
++
++ tryCompare(source, "active", true)
++ tryCompare(source, "valid", true)
++
++ tryVerify(function() { return inViewSpy.count > 0 && inUseSpy.count > 0 })
++
++ verify(source.satellitesInView.length > 0)
++ verify(source.satellitesInUse.length > 0)
++
++ // create an invalid source, active by default - should fail
++ var invalidSource = defaultSource.createObject(top, { name: "invalid", active: true })
++ tryCompare(invalidSource, "active", false)
++ tryCompare(invalidSource, "valid", false)
++ }
++
++ function test_set_plugin() {
++ // When creating with no name specified, some default plugin will
++ // be chosen
++ var source = defaultSource.createObject(top, {})
++ nameSpy.target = source
++ nameSpy.clear()
++ activeSpy.target = source
++ activeSpy.clear()
++
++ compare(source.valid, true)
++ var defaultPluginName = source.name
++
++ source.active = true
++ tryCompare(activeSpy, "count", 1)
++
++ // check that setting an empty name will do nothing, as we are already
++ // using the default
++ source.name = ""
++ compare(nameSpy.count, 0)
++ compare(source.name, defaultPluginName)
++ compare(source.active, true)
++
++ // Set another valid plugin name. Source will be set to inactive state,
++ // but will be valid
++ var otherPluginName = (defaultPluginName === "satellitesource.test")
++ ? "dummy.source" : "satellitesource.test";
++ source.name = otherPluginName
++ compare(nameSpy.count, 1)
++ compare(source.name, otherPluginName)
++ tryCompare(activeSpy, "count", 2)
++ compare(source.active, false)
++ compare(source.valid, true)
++
++ source.active = true
++ tryCompare(activeSpy, "count", 3)
++ compare(source.active, true)
++
++ // Set invalid name. Source will be set to inactive state, and will be
++ // invalid
++ source.name = "invalid_name"
++ compare(nameSpy.count, 2)
++ compare(source.name, "invalid_name")
++ tryCompare(activeSpy, "count", 4)
++ compare(source.active, false)
++ compare(source.valid, false)
++ }
++
++ function test_update_after_start() {
++ // When update() is called after start(), it should not invalidate any
++ // state. The active state must still be true when the single update()
++ // is completed.
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ errorSpy.target = source
++ errorSpy.clear()
++ inViewSpy.target = source
++ inViewSpy.clear()
++ compare(source.active, false)
++
++ source.start()
++ tryCompare(source, "active", true)
++
++ source.update(300) // default update interval == 200
++ tryVerify(function() { return errorSpy.count > 0 || inViewSpy.count > 0 })
++
++ // at this point update() is completed, source should still be active
++ compare(source.active, true)
++
++ source.stop()
++ tryCompare(source, "active", false)
++ }
++
++ function test_start_after_update() {
++ // When start() is called after update(), the source should remain
++ // active even when the single update is completed.
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ errorSpy.target = source
++ errorSpy.clear()
++ inViewSpy.target = source
++ inViewSpy.clear()
++ compare(source.active, false)
++
++ source.update(300)
++ tryCompare(source, "active", true)
++
++ source.start()
++ tryVerify(function() { return errorSpy.count > 0 || inViewSpy.count > 0 })
++
++ // at this point update() is completed, source should still be active
++ compare(source.active, true)
++
++ source.stop()
++ tryCompare(source, "active", false)
++ }
++
++ function test_stop_after_update() {
++ // When stop() is called after update(), and the update() is still in
++ // progress, the source should remain active until the update()
++ // is completed.
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ errorSpy.target = source
++ errorSpy.clear()
++ inViewSpy.target = source
++ inViewSpy.clear()
++ compare(source.active, false)
++
++ source.start()
++ tryCompare(source, "active", true)
++
++ source.update(300) // default update interval == 200
++ source.stop();
++ compare(source.active, true)
++
++ tryVerify(function() { return errorSpy.count > 0 || inViewSpy.count > 0 })
++ // at this point update() is completed, source should switch to inactive
++ compare(source.active, false)
++ }
++
++ function test_start_stop_after_update() {
++ // Calling start() and stop() after update(), while still waiting for
++ // the update() to complete, should still result in the source to be
++ // active until the update() is completed.
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ errorSpy.target = source
++ errorSpy.clear()
++ inViewSpy.target = source
++ inViewSpy.clear()
++ compare(source.active, false)
++
++ source.update(300)
++ tryCompare(source, "active", true)
++
++ source.start()
++ source.stop()
++ compare(source.active, true)
++
++ tryVerify(function() { return errorSpy.count > 0 || inViewSpy.count > 0 })
++ // at this point update() is completed, source should switch to inactive
++ compare(source.active, false)
++ }
++
++ function test_update_timed_out() {
++ // This test checks that source resets to inactive state when the single
++ // update() request times out without providing the position info
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ errorSpy.target = source
++ errorSpy.clear()
++ compare(source.active, false)
++
++ source.update(50) // too small interval
++ compare(source.active, true)
++
++ tryCompare(errorSpy, "count", 1)
++ compare(source.active, false)
++ }
++
++ function test_update_with_start_timed_out() {
++ // This test checks that if single update() times out, but the regular
++ // updates are running, we still remain in active state.
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ errorSpy.target = source
++ errorSpy.clear()
++ compare(source.active, false)
++
++ source.start()
++ source.update(100) // too small interval
++ tryCompare(source, "active", true)
++
++ tryCompare(errorSpy, "count", 1)
++ compare(source.active, true) // still active
++
++ source.stop()
++ tryCompare(source, "active", false)
++ }
++
++ function test_start_update_stop_with_no_intervals() {
++ // This test checks that calls start() -> update() -> stop() without
++ // any waits between them will result in expected behavior.
++ // Specifically, the source should remain active until it gets
++ // the satellite updates.
++ var source = defaultSource.createObject(top, { name: "satellitesource.test" })
++ inViewSpy.target = source
++ inViewSpy.clear()
++ compare(source.active, false)
++
++ source.start()
++ source.update(300)
++ source.stop()
++ tryCompare(source, "active", true)
++
++ tryCompare(inViewSpy, "count", 1)
++ // after the update is received, the source switches to inactive state
++ compare(source.active, false)
++ }
++
++ function test_start_when_incomplete() {
++ var source = sourceWithIncompleteParameter.createObject(top, {})
++ inViewSpy.target = source
++ inViewSpy.clear()
++ var parameterObj = findChild(source, "incompleteProperty")
++
++ source.start() // does nothing, as the parameter is not initialized
++ compare(source.active, false)
++
++ // init parameter
++ parameterObj.value = GeoSatelliteInfo.BEIDOU
++ // source created - should start providing updates
++ tryCompare(source, "active", true)
++
++ tryVerify(function() { return inViewSpy.count > 0 })
++
++ source.stop()
++ tryCompare(source, "active", false)
++ }
++
++ function test_start_stop_when_incomplete() {
++ var source = sourceWithIncompleteParameter.createObject(top, {})
++ var parameterObj = findChild(source, "incompleteProperty")
++
++ source.start() // does nothing, as the parameter is not initialized
++ compare(source.active, false)
++ source.stop() // calling stop() should invalidate request for start
++
++ // init parameter
++ parameterObj.value = GeoSatelliteInfo.BEIDOU
++ // source still should be inactive
++ tryCompare(source, "active", false)
++ }
++
++ function test_update_when_incomplete() {
++ var source = sourceWithIncompleteParameter.createObject(top, {})
++ inViewSpy.target = source
++ inViewSpy.clear()
++ var parameterObj = findChild(source, "incompleteProperty")
++
++ source.update() // does nothing, as the parameter is not initialized
++ compare(source.active, false)
++
++ // init parameter
++ parameterObj.value = GeoSatelliteInfo.BEIDOU
++ // source created - should provide exactly 1 update
++ tryCompare(source, "active", true)
++
++ tryVerify(function() { return inViewSpy.count == 1 })
++ tryCompare(source, "active", false)
++ }
++ }
++