-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Helper script to diff spec revisions
This isn't used anywhere yet, just for generating summaries for manual checking. But I'd like it on the record somewhere because it generates helpful PR descriptions for updates.
- Loading branch information
Showing
1 changed file
with
186 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
# | ||
# Copyright (c) 2024 Project CHIP Authors | ||
# All rights reserved. | ||
# | ||
# 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. | ||
# | ||
# This script gives a print out of the differences between the two specified spec | ||
# versions and a description of the provisional elements in the later version. | ||
# Right now, this is just in print form. The intent is to use this for new | ||
# data model XML drops to show the differences. This was also used to double-check | ||
# spec expectations before the 1.4 release and we should continue to do so going forward. | ||
|
||
import click | ||
|
||
from chip.testing.spec_parsing import build_xml_clusters, build_xml_device_types, PrebuiltDataModelDirectory | ||
from chip.testing.conformance import ConformanceDecision | ||
|
||
|
||
def get_changes(old, new): | ||
added = [e.name for id, e in new.items() if id not in old.keys()] | ||
removed = [e.name for id, e in old.items() if id not in new.keys()] | ||
same_ids = set(new.keys()).intersection(set(old.keys())) | ||
|
||
return added, removed, same_ids | ||
|
||
|
||
def str_changes(element, added, removed, change_ids, old, new): | ||
if not added and not removed and not change_ids: | ||
return [] | ||
|
||
ret = [] | ||
if added: | ||
ret.append(f'\t{element} added: {added}') | ||
if removed: | ||
ret.append(f'\t{element} removed: {removed}') | ||
if change_ids: | ||
ret.append(f'\t{element} changed:') | ||
for id in change_ids: | ||
name = old[id].name if old[id].name == new[id].name else f'{new[id].name} (previously {old[id].name})' | ||
ret.append(f'\t\t{name}') | ||
ret.append(f'\t\t\t{old[id]}') | ||
ret.append(f'\t\t\t{new[id]}') | ||
return ret | ||
|
||
|
||
def str_element_changes(element, old, new): | ||
added, removed, same_ids = get_changes(old, new) | ||
change_ids = [id for id in same_ids if old[id] != new[id] or str(old[id].conformance) != str(new[id].conformance)] | ||
return str_changes(element, added, removed, change_ids, old, new) | ||
|
||
|
||
def diff_clusters(prior_revision: PrebuiltDataModelDirectory, new_revision: PrebuiltDataModelDirectory) -> None: | ||
prior_clusters, _ = build_xml_clusters(PrebuiltDataModelDirectory.k1_3) | ||
new_clusters, _ = build_xml_clusters(PrebuiltDataModelDirectory.k1_4) | ||
|
||
additional_clusters, removed_clusters, same_cluster_ids = get_changes(prior_clusters, new_clusters) | ||
|
||
print(f'\n\nClusters newly added in {new_revision.dirname}') | ||
print(additional_clusters) | ||
print(f'\n\nClusters removed since {prior_revision.dirname}') | ||
print(removed_clusters) | ||
|
||
for cid in same_cluster_ids: | ||
new = new_clusters[cid] | ||
old = prior_clusters[cid] | ||
|
||
name = old.name if old.name == new.name else f'{new.name} (previously {old.name})' | ||
|
||
changes = [] | ||
if old.revision != new.revision: | ||
changes.append(f'\tRevision change - old: {old.revision} new: {new.revision}') | ||
changes.extend(str_element_changes('Features', old.features, new.features)) | ||
changes.extend(str_element_changes('Attributes', old.attributes, new.attributes)) | ||
changes.extend(str_element_changes('Accepted Commands', old.accepted_commands, new.accepted_commands)) | ||
changes.extend(str_element_changes('Generated Commands', old.generated_commands, new.generated_commands)) | ||
changes.extend(str_element_changes('Events', old.events, new.events)) | ||
|
||
if changes: | ||
print(f'\n\nCluster {name}') | ||
print('\n'.join(changes)) | ||
|
||
|
||
def diff_device_types(prior_revision: PrebuiltDataModelDirectory, new_revision: PrebuiltDataModelDirectory) -> None: | ||
prior_device_types, _ = build_xml_device_types(prior_revision) | ||
new_device_types, _ = build_xml_device_types(new_revision) | ||
|
||
additional_device_types, removed_device_types, same_device_type_ids = get_changes(prior_device_types, new_device_types) | ||
|
||
print(f'\n\nDevice Types newly added in {new_revision.dirname}') | ||
print(additional_device_types) | ||
print(f'\n\nDevice Types removed since {prior_revision.dirname}') | ||
print(removed_device_types) | ||
|
||
for cid in same_device_type_ids: | ||
new = new_device_types[cid] | ||
old = prior_device_types[cid] | ||
|
||
name = old.name if old.name == new.name else f'{new.name} (previously {old.name})' | ||
|
||
changes = [] | ||
if old.revision != new.revision: | ||
changes.append(f'\tRevision change - old: {old.revision} new: {new.revision}') | ||
changes.extend(str_element_changes('Server Clusters', old.server_clusters, new.server_clusters)) | ||
changes.extend(str_element_changes('Client Clusters', old.client_clusters, new.client_clusters)) | ||
|
||
if changes: | ||
print(f'\n\nDevice Type {name}') | ||
print('\n'.join(changes)) | ||
|
||
|
||
def _get_provisional(items): | ||
return [e.name for e in items if e.conformance(0, [], []).decision == ConformanceDecision.PROVISIONAL] | ||
|
||
|
||
def get_all_provisional_clusters(new_revision: PrebuiltDataModelDirectory): | ||
clusters, _ = build_xml_clusters(new_revision) | ||
|
||
provisional_clusters = [c.name for c in clusters.values() if c.is_provisional] | ||
print('\n\nProvisional Clusters') | ||
print(f'\t{sorted(provisional_clusters)}') | ||
|
||
for c in clusters.values(): | ||
features = _get_provisional(c.features.values()) | ||
attributes = _get_provisional(c.attributes.values()) | ||
accepted_commands = _get_provisional(c.accepted_commands.values()) | ||
generated_commands = _get_provisional(c.generated_commands.values()) | ||
events = _get_provisional(c.events.values()) | ||
|
||
if not features and not attributes and not accepted_commands and not generated_commands and not events: | ||
continue | ||
|
||
print(f'\n{c.name}') | ||
if features: | ||
print(f'\tProvisional features: {features}') | ||
if attributes: | ||
print(f'\tProvisional attributes: {attributes}') | ||
if accepted_commands: | ||
print(f'\tProvisional accepted commands: {accepted_commands}') | ||
if generated_commands: | ||
print(f'\tProvisional generated commands: {generated_commands}') | ||
if events: | ||
print(f'\tProvisional events: {events}') | ||
|
||
|
||
def get_all_provisional_device_types(new_revision: PrebuiltDataModelDirectory): | ||
device_types, _ = build_xml_device_types(new_revision) | ||
|
||
for d in device_types.values(): | ||
server_clusters = _get_provisional(d.server_clusters.values()) | ||
client_clusters = _get_provisional(d.client_clusters.values()) | ||
if not server_clusters and not client_clusters: | ||
continue | ||
|
||
print(f'\n{d.name}') | ||
if server_clusters: | ||
print(f'\tProvisional server clusters: {server_clusters}') | ||
if client_clusters: | ||
print(f'\tProvisional client clusters: {client_clusters}') | ||
|
||
|
||
REVISIONS = {'1.3': PrebuiltDataModelDirectory.k1_3, | ||
'1.4': PrebuiltDataModelDirectory.k1_4, 'master': PrebuiltDataModelDirectory.kMaster} | ||
|
||
|
||
@click.command() | ||
@click.argument('prior_revision', type=click.Choice(list(REVISIONS.keys()))) | ||
@click.argument('new_revision', type=click.Choice(list(REVISIONS.keys()))) | ||
def main(prior_revision: str, new_revision: str): | ||
diff_clusters(REVISIONS[prior_revision], REVISIONS[new_revision]) | ||
diff_device_types(REVISIONS[prior_revision], REVISIONS[new_revision]) | ||
get_all_provisional_clusters(REVISIONS[new_revision]) | ||
get_all_provisional_device_types(REVISIONS[new_revision]) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |