blob: b2806c8084b6ee302fc17bb622e912b5547044ab [file] [log] [blame] [view]
# Designing Clusters for Testing and Portability
## Unit Testable, Modular Cluster Design
When designing new clusters, consider the following approach:
- Separate the cluster logic from the on-the-wire data model
- Server vs. ClusterLogic
- Makes the cluster logic unit-testable without generating TLV.
- Separate the basic cluster logic from code that is platform- or
device-specific.
- ClusterLogic uses a ClusterDriver
- Makes the cluster logic portable between platforms / manufacturers
- Removes necessity of overriding global singleton functions like
PostAttributeChangeCallback.
General approach:
![](./img/unit_testable_clusters.png)
Class proposal:
![](./img/unit_testable_clusters_all_classes.png)
### ClusterServer
![](./img/unit_testable_clusters_server.png)
The ClusterServerClass is a **Very** light wrapper over ClusterLogic. It
translates Interaction Model wire format handling into API calls for cluster
logic methods.
This class implements both the AttributeAccessInterface and the CommandHandler
interfaces so ClusterLogic properly handles data dependencies between commands
and attributes.
An example code snippet showing the translation of the TLV into API calls to the
ClusterLogic class:
```
CHIP_ERROR DiscoBallServer::Read(const ConcreteReadAttributePath & aPath,
AttributeValueEncoder & aEncoder)
{
DiscoBallClusterLogic * cluster = FindEndpoint(aPath.mEndpointId);
VerifyOrReturnError(cluster != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint));
switch (aPath.mAttributeId)
{
case Clusters::DiscoBall::Attributes::Run::Id:
return aEncoder.Encode(cluster->GetRunAttribute());
}
}
```
### ClusterLogic
![](./img/unit_testable_clusters_logic.png)
The ClusterLogic class is for all the code that is SHARED between platforms. It
does NOT include any TLV parsing or direct calls to Ember/IM/LogEvent etc.
The class should include attribute getters/setters and handlers for all
commands.
The class receives plain data Matter requests from ClusterServer class,
performs required common actions, and calls driver class to perform platform- or
hardware-specific actions. It also receives driver updates (e.g.
application-driven value changes) from the ClusterDriver class and updates state
as appropriate.
The class should handle spec-requirements for:
- Range checking (CONSTRAINT_ERROR)
- Attribute and metadata storage (persistent or in-memory)
- Data dependencies between commands and attributes
- Event generation / dirty attributes (callback to server)
- Calling driver when platform or hardware interactions are required
API recommendation:
- Maintain all cluster state in a separate data-only class.
- Provider getters/setters for application logic to use.
- Implement handlers for all commands, conditional on features.
- Let the caller provide (inject) dependencies. Avoid explicit memory
management.
### ClusterDriver
Implements hardware or platform-specific actions required on cluster
interactions or when application wants to report state changes.
![](./img/unit_testable_clusters_driver.png)
### MatterContext
The ClusterLogic class must not directly use global resource because they cannot
be isolated for testing. Instead, the MatterContext holds pointers to Matter
stack objects, which can be be injected / faked for testing. This includes -
Wrapper over IM Engine interface functions for marking attributes dirty, and
logging events. - Storage - Anything you would normally access with
Server::GetInstance()
![](./img/unit_testable_clusters_context.png)
### ClusterDriver
The ClusterDriver is called by the ClusterLogic class and is used to translate
attribute changes and commands into application actions. It also reports
external changes back to the ClusterLogic class.
The API design for this class will vary by the cluster, but it is generally
recommended to use a generic API where possible, so the API ports easily to
other platforms, for example an attribute changed callback with the changes
listed. It is important to be careful about the design and revisit this early if
issues arise.
## Unit testing with the modular cluster design
### ClusterLogic class
The unit test instantiates the ClusterLogic, provides MatterContext and
ClusterDriver instance with fakes/mocks for testing.
Unit test against the API, and check the fakes/mocks to ensure they are being
called as appropriate.
As with all unit tests, prefer testing for behavior rather than implementation
details.
Important tests to consider:
- Initialization and initial attribute correctness.
- Errors for out-of-range on all attribute setters and command handlers.
- All spec-defined error conditions, especially ones that are difficult to
trigger.
- Data dependencies between commands and attributes.
- Incoming actions in different states (stopped, running, etc).
- Calls out to storage for persistent attributes.
- Calls out to driver for changes as appropriate.
- Driver error reporting.
- Event generation and dirty attribute marking, including attributes that are
changed from the driver side.
- Others - very dependent on the cluster.
# Unit testing ClusterServer
- Best to have the lightest wrapping possible
- If the wrapper is light, the code can be covered by integration or unit
tests, or a combination.
- Correctness can mostly be validated by inspection if its trivial.
- Important tests
- Errors when ClusterLogic instances arent properly registered.
- Flow through to ClusterLogic for all reads/writes/commands.
- Can unit test this class by generating the TLV / path for input, parsing the
TLV output.
# Unit testing existing clusters
- Important for clusters where there are multiple configurations that cannot
easily be represented with example apps
- Option 1
- Refactor the cluster logic to be unit-testable.
- Option 2
- Test at AttributeAccessInterface boundary.
- Instantiate or access the cluster server instance in the test.
- Read / Write TLV and use TLV encode/decode functions to verify
correctness.
- See TestPowerSourceCluster for an example of how to do this
- Additional test coverage on clusters, especially for hard to trigger
conditions, is important. However, **dont let perfection be the enemy of
progress** .