diff --git a/.editorconfig b/.editorconfig index 6d0be49..56ec097 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,7 +16,7 @@ ij_visual_guides = 72 [*.json] indent_size = 2 -[*.yml] +[*.y*ml] indent_size = 2 [*.md] diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f459357..fade6c3 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,36 @@ Release Notes ============= +v1.0.0-alpha.x +-------------- + +### Breaking changes + +- Updated the YAML schema to group traits/specifications under a version + number key. + [#80](https://github.com/OpenAssetIO/OpenAssetIO-TraitGen/issues/80) + +### New features + +- Added support for trait versioning. Suffixed generated + trait/specification view classes with a `_vX` and trait IDs with a `.vX` + (where `X` is a version number), except for the first version of a + trait, where the ID has no version suffix, retaining backward + compatibility. + [#80](https://github.com/OpenAssetIO/OpenAssetIO-TraitGen/issues/80) + +- Added an optional `deprecated` field to trait and specification YAML + definitions, which causes a deprecation warning/annotation to be + generated for all versions of that trait/specification. + [#80](https://github.com/OpenAssetIO/OpenAssetIO-TraitGen/issues/80) + +### Improvements + +- Updated classes without a version suffix to alias version 1, but with + a deprecation warning/annotation, for backward compatibility. + [#80](https://github.com/OpenAssetIO/OpenAssetIO-TraitGen/issues/80) + + v1.0.0-alpha.12 -------------- diff --git a/python/openassetio_traitgen/__init__.py b/python/openassetio_traitgen/__init__.py index 3d26d9d..fcecd88 100644 --- a/python/openassetio_traitgen/__init__.py +++ b/python/openassetio_traitgen/__init__.py @@ -176,10 +176,10 @@ def _log_package_declaration(package, logger): for namespace in package.traits: logger.info(f"{namespace.id}:") for trait in namespace.members: - logger.info(" - %s", trait.name) + logger.info(" - %s (v%s)", trait.name, trait.version) if package.specifications: logger.info("Specifications:") for namespace in package.specifications: logger.info(f"{namespace.id}:") for specification in namespace.members: - logger.info(" - %s", specification.id) + logger.info(" - %s (v%s)", specification.id, specification.version) diff --git a/python/openassetio_traitgen/datamodel.py b/python/openassetio_traitgen/datamodel.py index 5bc19a6..aafac31 100644 --- a/python/openassetio_traitgen/datamodel.py +++ b/python/openassetio_traitgen/datamodel.py @@ -82,6 +82,10 @@ class TraitDeclaration(NamedTuple): # A short name for the Trait that is only unique within its # namespace. name: str + # Whether this trait is deprecated. + deprecated: bool + # Version of the trait. + version: str # A user-facing description of the Trait and its purpose. description: str # User-facing hints as to the usage of this trait, in relation to @@ -115,6 +119,8 @@ class TraitReference(NamedTuple): namespace: str # The package the trait belongs to package: str + # Version of the trait + version: str # The shortest list of elements from package, namespace and name # that is required to form a unique name for this trait # relative to the specification. These should be used @@ -122,7 +128,7 @@ class TraitReference(NamedTuple): # Trait instance from a Specification, as it handles the # case where a Specification may reference two identically # named traits in different packages or namespaces. - unique_name_parts: Tuple[str] + unique_name_parts: Tuple[str, ...] class SpecificationDeclaration(NamedTuple): @@ -132,6 +138,10 @@ class SpecificationDeclaration(NamedTuple): # The unique name of Specification within its namespace. id: str + # Whether this specification is deprecated. + deprecated: bool + # Version of the specification. + version: str # A user-facing description of the Specification and its purpose. description: str # User-facing hints as to the usage of this trait, in relation to diff --git a/python/openassetio_traitgen/generators/cpp.py b/python/openassetio_traitgen/generators/cpp.py index cae3160..e66c080 100644 --- a/python/openassetio_traitgen/generators/cpp.py +++ b/python/openassetio_traitgen/generators/cpp.py @@ -17,6 +17,9 @@ A traitgen generator that outputs a C++ package based on the openassetio_traitgen PackageDefinition model. """ +import collections +import itertools + # TODO(DF): Refactor to pull out common code, then remove this # suppression. # pylint: disable=duplicate-code @@ -180,12 +183,27 @@ def __render_namespace( ) imports = [] - # Render a file per class (trait or specification). - for declaration in namespace.members: + # We group multiple versions of the same trait (or + # specification) together to render in the same header. Note + # that declarations in a namespace are already sorted + # appropriately by name, so we don't need to sort before + # applying groupby. + declarations_by_name = itertools.groupby( + namespace.members, + lambda declaration: declaration.name if kind == "traits" else declaration.id, + ) + + # Render a file per trait/specification, containing all + # versions of that trait/specification. + for name, declarations in declarations_by_name: if kind == "traits": - file_name = self.__render_trait(namespace, declaration, namespace_abs_path) + file_name = self.__render_trait( + namespace, name, tuple(declarations), namespace_abs_path + ) else: - file_name = self.__render_specification(namespace, declaration, namespace_abs_path) + file_name = self.__render_specification( + namespace, name, tuple(declarations), namespace_abs_path + ) imports.append(f"{namespace_name}/{file_name}") @@ -207,7 +225,8 @@ def __render_namespace( def __render_trait( self, namespace: NamespaceDeclaration, - declaration: TraitDeclaration, + name: str, + declarations: tuple[TraitDeclaration, ...], namespace_abs_path: str, ) -> str: """ @@ -216,24 +235,25 @@ def __render_trait( Creates a single header file containing a single trait view class. """ - cls_name = self.__env.filters["to_cpp_class_name"](declaration.name) + "Trait" + header_name = self.__env.filters["to_cpp_class_name"](name) + "Trait" self.__render_template( "trait", - os.path.join(namespace_abs_path, f"{cls_name}.hpp"), + os.path.join(namespace_abs_path, f"{header_name}.hpp"), { "package": self.__package, "namespace": namespace, - "trait": declaration, + "versions": declarations, "openassetio_abi_version": OPENASSETIO_ABI_VERSION, "traitgen_abi_version": TRAITGEN_ABI_VERSION, }, ) - return f"{cls_name}.hpp" + return f"{header_name}.hpp" def __render_specification( self, namespace: NamespaceDeclaration, - declaration: SpecificationDeclaration, + name: str, + declarations: tuple[SpecificationDeclaration, ...], namespace_abs_path: str, ) -> str: """ @@ -242,19 +262,38 @@ def __render_specification( Creates a single header file containing a single specification class. """ - cls_name = self.__env.filters["to_cpp_class_name"](declaration.id) + "Specification" + + # Properties required to interpolate when constructing #include + # directives. + TraitHeaderPathTokens = collections.namedtuple( + "TraitHeaderPathTokens", ("package", "namespace", "name") + ) + + # All versions of a given trait live in a single header. + # Extract fields required to #include the trait headers + # referenced by all versions of this specification, de-duped. + all_trait_header_path_tokens = sorted( + { + TraitHeaderPathTokens(trait_decl.package, trait_decl.namespace, trait_decl.name) + for spec_decl in declarations + for trait_decl in spec_decl.trait_set + } + ) + + header_name = self.__env.filters["to_cpp_class_name"](name) + "Specification" self.__render_template( "specification", - os.path.join(namespace_abs_path, f"{cls_name}.hpp"), + os.path.join(namespace_abs_path, f"{header_name}.hpp"), { "package": self.__package, "namespace": namespace, - "specification": declaration, + "versions": declarations, + "all_trait_header_path_tokens": all_trait_header_path_tokens, "openassetio_abi_version": OPENASSETIO_ABI_VERSION, "traitgen_abi_version": TRAITGEN_ABI_VERSION, }, ) - return f"{cls_name}.hpp" + return f"{header_name}.hpp" def __render_package_template( self, package_abs_path: str, name: str, docstring: str, imports: List[str] diff --git a/python/openassetio_traitgen/generators/python.py b/python/openassetio_traitgen/generators/python.py index b9c1573..9b86a17 100644 --- a/python/openassetio_traitgen/generators/python.py +++ b/python/openassetio_traitgen/generators/python.py @@ -181,7 +181,7 @@ def to_py_module_name(string: str): no_hypens = string.replace("-", "_") module_name = re.sub(r"[^a-zA-Z0-9_]", "_", no_hypens) if module_name != no_hypens: - logger.warning(f"Conforming '{string}' to '{module_name}' for module name") + _conform_warning(string, module_name, "module name") return module_name def to_py_class_name(string: str): @@ -190,7 +190,7 @@ def to_py_class_name(string: str): """ class_name = helpers.to_upper_camel_alnum(string) if class_name != string: - logger.warning(f"Conforming '{string}' to '{class_name}' for class name") + _conform_warning(string, class_name, "class name") validate_identifier(class_name, string) return class_name @@ -204,9 +204,7 @@ def to_py_trait_accessor_name(name_parts: List[str]): accessor_name = helpers.to_lower_camel_alnum(unique_name) # We expect the first letter to change to lowercase if accessor_name != f"{unique_name[0].lower()}{unique_name[1:]}": - logger.warning( - f"Conforming '{unique_name}' to '{accessor_name}' for trait getter name" - ) + _conform_warning(unique_name, accessor_name, "trait getter name") validate_identifier(accessor_name, unique_name) return accessor_name @@ -218,9 +216,7 @@ def to_py_var_accessor_name(string: str): """ accessor_name = helpers.to_upper_camel_alnum(string) if accessor_name != f"{string[0].upper()}{string[1:]}": - logger.warning( - f"Conforming '{string}' to '{accessor_name}' for property accessor name" - ) + _conform_warning(string, accessor_name, "property accessor name") validate_identifier(accessor_name, string) return accessor_name @@ -231,7 +227,7 @@ def to_py_var_name(string: str): """ var_name = helpers.to_lower_camel_alnum(string) if var_name != string: - logger.warning(f"Conforming '{string}' to '{var_name}' for variable name") + _conform_warning(string, var_name, "variable name") validate_identifier(var_name, string) return var_name @@ -251,6 +247,19 @@ def to_py_type(declaration_type): raise TypeError("Dictionary types are not yet supported as trait properties") return type_map[declaration_type] + def _conform_warning(original: str, conformed: str, context: str): + """ + Log a warning that an input name has been modified to conform + to a valid identifier, if the warning has not already been + logged. + """ + warning = f"Conforming '{original}' to '{conformed}' for {context}" + if warning in environment.globals["conform_warnings"]: + return + environment.globals["conform_warnings"].append(warning) + logger.warning(warning) + + environment.globals["conform_warnings"] = [] environment.filters["to_upper_camel_alnum"] = helpers.to_upper_camel_alnum environment.filters["to_py_module_name"] = to_py_module_name environment.filters["to_py_class_name"] = to_py_class_name diff --git a/python/openassetio_traitgen/parser.py b/python/openassetio_traitgen/parser.py index 4ed9c9d..54c2932 100644 --- a/python/openassetio_traitgen/parser.py +++ b/python/openassetio_traitgen/parser.py @@ -88,11 +88,14 @@ def _unpack_specifications(model: dict, package_id: str) -> List[datamodel.Names specifications = [ datamodel.SpecificationDeclaration( id=name, + deprecated=props.get("deprecated", False), + version=version_num, description=definition.get("description", "").strip(), trait_set=_unpack_trait_set(definition["traitSet"], package_id), usage=definition.get("usage", []), ) - for name, definition in data["members"].items() + for name, props in data["members"].items() + for version_num, definition in props["versions"].items() ] specifications.sort(key=_byId) @@ -134,8 +137,9 @@ def _unpack_trait_set(trait_set: List[dict], package_id: str) -> List[datamodel. package = trait.get("package", package_id) namespace = trait["namespace"] name = trait["name"] + version = trait["version"] - identifier = _build_trait_id(package, namespace, name) + identifier = _build_trait_id(package, namespace, name, version) # Check to see which of the possible combinations of reference # parts is unique for this trait. @@ -153,6 +157,7 @@ def _unpack_trait_set(trait_set: List[dict], package_id: str) -> List[datamodel. name=name, namespace=namespace, package=package, + version=version, unique_name_parts=unique_name_parts, ) ) @@ -162,7 +167,15 @@ def _unpack_trait_set(trait_set: List[dict], package_id: str) -> List[datamodel. return references -def _build_trait_id(package: str, namespace: str, name: str) -> str: +def _build_trait_id(package: str, namespace: str, name: str, version: str) -> str: + """ + Builds a trait ID from the supplied components. + + The first version "1" omits the version suffix to maintain backward + compatibility with existing traits. + """ + if version != "1": + return f"{package}:{namespace}.{name}.v{version}" return f"{package}:{namespace}.{name}" @@ -180,13 +193,16 @@ def _unpack_traits( for namespace, data in model.items(): traits = [ datamodel.TraitDeclaration( - id=_build_trait_id(package_id, namespace, name), + id=_build_trait_id(package_id, namespace, name, version_num), name=name, + deprecated=props.get("deprecated", False), + version=version_num, description=definition.get("description", "").strip(), properties=_unpack_properties(definition.get("properties", {})), usage=definition.get("usage", []), ) - for name, definition in data["members"].items() + for name, props in data["members"].items() + for version_num, definition in props["versions"].items() ] traits.sort(key=_byName) diff --git a/python/openassetio_traitgen/schema.json b/python/openassetio_traitgen/schema.json index d913c41..6eaa8e8 100644 --- a/python/openassetio_traitgen/schema.json +++ b/python/openassetio_traitgen/schema.json @@ -40,63 +40,83 @@ "description": "A single Trait. The object's key forms the name of the trait, and will be used to generate the class name (or similar) in resulting code. Trait names need to be unique within each namespace.", "additionalProperties": false, "properties": { - "description": { - "type": "string", - "description": "A description of the trait and its uses.", - "minLength": 1 + "deprecated": { + "type": "boolean", + "description": "Whether trait is deprecated." }, - "additionalProperties": false, - "properties": { + "versions": { "type": "object", - "description": "The Trait's properties.", + "description": "Versions of this trait.", "additionalProperties": false, "patternProperties": { - "^[a-z][a-zA-Z0-9]*$": { + "^[1-9][0-9]*$": { "type": "object", + "description": "A version of the trait definition.", + "additionalProperties": false, "properties": { "description": { "type": "string", - "description": "A description of the value held by the property and its use", + "description": "A description of the trait and its uses.", "minLength": 1 }, - "type": { - "description": "The type of the value held by the property", - "type": "string", - "enum": [ - "string", - "integer", - "float", - "boolean", - "dictionary" - ] + "properties": { + "type": "object", + "description": "The Trait's properties.", + "additionalProperties": false, + "patternProperties": { + "^[a-z][a-zA-Z0-9]*$": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A description of the value held by the property and its use", + "minLength": 1 + }, + "type": { + "description": "The type of the value held by the property", + "type": "string", + "enum": [ + "string", + "integer", + "float", + "boolean", + "dictionary" + ] + } + }, + "required": [ + "description", + "type" + ], + "additionalProperties": false + } + } + }, + "usage": { + "description": "The potential use cases for the Trait, these map to specific areas of the OpenAssetIO API where it makes sense to use the Trait.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "entity", + "relationship", + "locale", + "managementPolicy", + "ui", + "uiPolicy" + ] + } } }, "required": [ - "description", - "type" - ], - "additionalProperties": false + "description" + ] } } - }, - "usage": { - "description": "The potential use cases for the Trait, these map to specific areas of the OpenAssetIO API where it makes sense to use the Trait.", - "type": "array", - "items": { - "type": "string", - "enum": [ - "entity", - "relationship", - "locale", - "managementPolicy", - "ui", - "uiPolicy" - ] - } } }, "required": [ - "description" + "versions" ] } } @@ -133,60 +153,87 @@ "description": "A single Specification. The object's key forms the name of the Specification, and will be used to generate the class name (or similar) in resulting code. Specification names need to be unique within each namespace.", "additionalProperties": false, "properties": { - "description": { - "type": "string", - "description": "A description of the Specification and its uses.", - "minLength": 1 + "deprecated": { + "type": "boolean", + "description": "Whether this Specification is deprecated." }, - "traitSet": { - "description": "The Traits composed by the Specification. These can references Traits within this package, or another.", - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "package": { - "description": "The package the Trait belongs to, if it is not in the package defined by this description.", - "type": "string", - "pattern": "^[a-zA-Z0-9_-]+$" - }, - "namespace": { - "description": "The Trait's namespace", - "type": "string", - "pattern": "^[a-z][a-zA-Z0-9]*$" + "versions": { + "type": "object", + "description": "Versions of this specification.", + "additionalProperties": false, + "patternProperties": { + "^[1-9][0-9]*$": { + "type": "object", + "description": "A version of the specification definition.", + "additionalProperties": false, + "properties": { + "description": { + "type": "string", + "description": "A description of the Specification and its uses.", + "minLength": 1 + }, + "traitSet": { + "description": "The Traits composed by the Specification. These can references Traits within this package, or another.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "package": { + "description": "The package the Trait belongs to, if it is not in the package defined by this description.", + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "namespace": { + "description": "The Trait's namespace", + "type": "string", + "pattern": "^[a-z][a-zA-Z0-9]*$" + }, + "name": { + "description": "The Trait's name", + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]*$" + }, + "version": { + "description": "The Trait's version", + "type": "string", + "pattern": "^[1-9][0-9]*$" + } + }, + "required": [ + "namespace", + "name", + "version" + ] + }, + "minItems": 1 + }, + "usage": { + "description": "The potential use cases for the specification, these map to specific areas of the OpenAssetIO API where it makes sense to use supply the Specification's Trait Set.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "entity", + "relationship", + "locale", + "managementPolicy", + "ui", + "uiPolicy" + ] + } + } }, - "name": { - "description": "The Trait's name", - "type": "string", - "pattern": "^[A-Z][a-zA-Z0-9]*$" - } - }, - "required": [ - "namespace", - "name" - ] - }, - "minItems": 1 - }, - "usage": { - "description": "The potential use cases for the specification, these map to specific areas of the OpenAssetIO API where it makes sense to use supply the Specification's Trait Set.", - "type": "array", - "items": { - "type": "string", - "enum": [ - "entity", - "relationship", - "locale", - "managementPolicy", - "ui", - "uiPolicy" - ] + "required": [ + "description", + "traitSet" + ] + } } } }, "required": [ - "description", - "traitSet" + "versions" ] } } diff --git a/python/openassetio_traitgen/templates/cpp/specification.hpp.in b/python/openassetio_traitgen/templates/cpp/specification.hpp.in index b226f18..92b8c2e 100644 --- a/python/openassetio_traitgen/templates/cpp/specification.hpp.in +++ b/python/openassetio_traitgen/templates/cpp/specification.hpp.in @@ -13,45 +13,51 @@ namespace openassetio_abi = openassetio::{{ openassetio_abi_version }}; -{%- for trait in (specification.trait_set | selectattr("package", "!=", package.id) | sort) -%} +{%- for header_path_tokens in (all_trait_header_path_tokens | selectattr("package", "!=", package.id) | sort) -%} {%- if loop.first %} {% endif %} -#include <{{ trait.package | to_cpp_namespace_name }}/traits/{{ trait.namespace | to_cpp_namespace_name }}/{{ trait.name | to_cpp_class_name }}Trait.hpp> +#include <{{ header_path_tokens.package | to_cpp_namespace_name }}/traits/{{ header_path_tokens.namespace | to_cpp_namespace_name }}/{{ header_path_tokens.name | to_cpp_class_name }}Trait.hpp> {%- endfor -%} -{%- for trait in (specification.trait_set | selectattr("package", "==", package.id) | sort) -%} +{%- for header_path_tokens in (all_trait_header_path_tokens | selectattr("package", "==", package.id) | sort) -%} {%- if loop.first %} {% endif %} -#include "../../traits/{{ trait.namespace | to_cpp_namespace_name }}/{{ trait.name | to_cpp_class_name }}Trait.hpp" +#include "../../traits/{{ header_path_tokens.namespace | to_cpp_namespace_name }}/{{ header_path_tokens.name | to_cpp_class_name }}Trait.hpp" {%- endfor -%} {%- macro trait_type(trait) -%} {%- if trait.package == package.id -%} -traits::{{trait.namespace | to_cpp_namespace_name }}::{{trait.name | to_cpp_class_name }}Trait +traits::{{trait.namespace | to_cpp_namespace_name }}::{{trait.name | to_cpp_class_name }}Trait_v{{ trait.version }} {%- else -%} -{{ trait.package | to_cpp_namespace_name }}::traits::{{trait.namespace | to_cpp_namespace_name }}::{{ trait.name | to_cpp_class_name }}Trait +{{ trait.package | to_cpp_namespace_name }}::traits::{{trait.namespace | to_cpp_namespace_name }}::{{ trait.name | to_cpp_class_name }}Trait_v{{ trait.version }} {%- endif -%} {%- endmacro %} namespace {{ package.id | to_cpp_namespace_name }} { inline namespace {{ traitgen_abi_version }} { namespace specifications::{{ namespace.id | to_cpp_namespace_name }} { + +{%- for specification in versions %} +{% set classname = specification.id | to_cpp_class_name ~ 'Specification_v' ~ specification.version -%} /** * {{ specification.description | wordwrap(69, wrapstring="\n* ") | indent(1) }} {%- if specification.usage %} * Usage: {{ specification.usage | join(', ') }} +{%- endif %} +{%- if specification.deprecated %} + * + * @deprecated This specification is flagged for future removal. {%- endif %} */ -class {{ specification.id | to_cpp_class_name }}Specification { +class +{%- if specification.deprecated %} +[[deprecated("The '{{ namespace.id | to_cpp_namespace_name }}.{{ specification.id }}' specification of the '{{ package.id | to_cpp_namespace_name }}' package is deprecated.")]] +{% endif %} {{ classname }} { public: inline static const openassetio_abi::trait::TraitSet kTraitSet{ {%- for trait in specification.trait_set %} // '{{ trait.id }}' - {%- if trait.package == package.id %} - traits::{{ trait.namespace | to_cpp_namespace_name }}::{{ trait.name | to_cpp_class_name }}Trait::kId, - {%- else %} - {{ trait.package | to_cpp_namespace_name }}::traits::{{ trait.namespace | to_cpp_namespace_name }}::{{ trait.name | to_cpp_class_name }}Trait::kId, - {%- endif %} + {{ trait_type(trait) }}::kId, {%- endfor %} }; @@ -59,8 +65,8 @@ public: * Returns a new instance of the Specification, holding a new * TraitsData instance, imbued with the specification's traits. */ - static {{ specification.id | to_cpp_class_name }}Specification create() { - return {{ specification.id | to_cpp_class_name }}Specification{ + static {{ classname }} create() { + return {{ classname }}{ openassetio_abi::trait::TraitsData::make(kTraitSet)}; } @@ -73,7 +79,7 @@ public: * visible to any other specifications or traits that wrap the same * TraitsData instance. */ - explicit {{ specification.id | to_cpp_class_name }}Specification(openassetio_abi::trait::TraitsDataPtr traitsData) + explicit {{ classname }}(openassetio_abi::trait::TraitsDataPtr traitsData) : traitsData_{std::move(traitsData)} {} @@ -98,6 +104,35 @@ public: private: openassetio_abi::trait::TraitsDataPtr traitsData_; }; + +{% endfor %} +{%- set specification = versions[0] -%} +{%- set base_classname = specification.id | to_cpp_class_name ~ 'Specification' %} +/** + * {{ specification.description | wordwrap(69, wrapstring="\n* ") | indent(1) }} +{%- if specification.usage %} + * Usage: {{ specification.usage | join(', ') }} +{%- endif %} + * + * @deprecated Unversioned specification view classes are deprecated, + * please use {{ base_classname }}_v1 explicitly. + */ +class +[[deprecated("Unversioned specification view classes are deprecated, please use {{ base_classname }}_v1 explicitly.")]] +{{ base_classname }} : public {{ base_classname }}_v1 { +public: + using {{ base_classname }}_v1::{{ base_classname }}_v1; + + /** + * Returns a new instance of the Specification, holding a new + * TraitsData instance, imbued with the specification's traits. + */ + static {{ base_classname }} create() { + return {{ base_classname }}{ + openassetio_abi::trait::TraitsData::make(kTraitSet)}; + } +}; } // namespace specifications::{{ namespace.id | to_cpp_namespace_name }} } // namespace {{ traitgen_abi_version }} } // namespace {{ package.id | to_cpp_namespace_name }} + diff --git a/python/openassetio_traitgen/templates/cpp/trait.hpp.in b/python/openassetio_traitgen/templates/cpp/trait.hpp.in index 59e2bd3..281f0a6 100644 --- a/python/openassetio_traitgen/templates/cpp/trait.hpp.in +++ b/python/openassetio_traitgen/templates/cpp/trait.hpp.in @@ -22,20 +22,29 @@ namespace traits::{{ namespace.id | to_cpp_namespace_name }} { namespace property = openassetio_abi::trait::property; +{%- for trait in versions %} +{% set classname = trait.name | to_cpp_class_name ~ 'Trait_v' ~ trait.version -%} /** * {{ trait.description | wordwrap(69, wrapstring="\n* ") | indent(1) }} {%- if trait.usage %} * Usage: {{ trait.usage | join(', ') }} +{%- endif %} +{%- if trait.deprecated %} + * + * @deprecated This trait is flagged for future removal. {%- endif %} */ -class {{ trait.name | to_cpp_class_name }}Trait { +class +{%- if trait.deprecated %} +[[deprecated("The '{{ trait.id }}' trait is deprecated.")]] +{% endif %} {{ classname }} { public: static inline const openassetio_abi::trait::TraitId kId = "{{ trait.id }}"; /** * Construct this trait view, wrapping the given TraitsData instance. */ - explicit {{ trait.name | to_cpp_class_name }}Trait(openassetio_abi::trait::TraitsDataPtr traitsData) + explicit {{ classname }}(openassetio_abi::trait::TraitsDataPtr traitsData) : traitsData_{std::move(traitsData)} {} @@ -102,7 +111,7 @@ public: */ [[nodiscard]] {{ VarType }} get{{ VarMethodName }}(const {{ VarType }}& defaultValue) const { if (property::Value value; traitsData_->getTraitProperty(&value, kId, "{{ property.id }}")) { - if ({{ VarType }}* maybeOut = std::get_if<{{ VarType }}>(&value)) { + if (auto* maybeOut = std::get_if<{{ VarType }}>(&value)) { return *maybeOut; } } @@ -117,7 +126,7 @@ public: */ [[nodiscard]] std::optional<{{ VarType }}> get{{ VarMethodName }}() const { if (property::Value value; traitsData_->getTraitProperty(&value, kId, "{{ property.id }}")) { - if ({{ VarType }}* maybeOut = std::get_if<{{ VarType }}>(&value)) { + if (auto* maybeOut = std::get_if<{{ VarType }}>(&value)) { return *maybeOut; } throw std::runtime_error{"Invalid stored value type: should be '{{ VarType }}'."}; @@ -130,6 +139,26 @@ public: private: openassetio_abi::trait::TraitsDataPtr traitsData_; }; + +{% endfor %} + +{%- set trait = versions[0] %} +{%- set base_classname = trait.name | to_cpp_class_name ~ "Trait" %} +/** + * {{ trait.description | wordwrap(69, wrapstring="\n* ") | indent(1) }} +{%- if trait.usage %} + * Usage: {{ trait.usage | join(', ') }} +{%- endif %} + * + * @deprecated Unversioned trait view classes are deprecated, please use + * {{ base_classname }}_v1 explicitly. + */ +class +[[deprecated("Unversioned trait view classes are deprecated, please use {{ base_classname }}_v1 explicitly.")]] +{{ base_classname }} : public {{ base_classname }}_v1 { +public: +using {{ base_classname }}_v1::{{ base_classname }}_v1; +}; } // namespace traits::{{ namespace.id | to_cpp_namespace_name }} } // namespace {{ traitgen_abi_version }} } // namespace {{ package.id | to_cpp_namespace_name }} diff --git a/python/openassetio_traitgen/templates/python/specifications.py.in b/python/openassetio_traitgen/templates/python/specifications.py.in index 5fac579..84082b5 100644 --- a/python/openassetio_traitgen/templates/python/specifications.py.in +++ b/python/openassetio_traitgen/templates/python/specifications.py.in @@ -10,6 +10,8 @@ Specification definitions in the '{{ namespace.id }}' namespace. # WARNING: This file is auto-generated by openassetio-traitgen, do not edit. +import warnings + from openassetio.trait import TraitsData {% if imports -%} @@ -24,20 +26,24 @@ from .. import traits {% endif %} {% for specification in namespace.members %} -class {{ specification.id | to_py_class_name }}Specification: +class {{ specification.id | to_py_class_name }}Specification_v{{ specification.version }}: """ {{ specification.description | wordwrap(68) | indent(4) }} - {% if specification.usage -%} +{%- if specification.usage %} Usage: {{ specification.usage | join(', ') }} - {% endif -%} +{%- endif -%} +{%- if specification.deprecated %} + + @deprecated This specification is flagged for future removal. +{%- endif %} """ kTraitSet = { {% for trait in specification.trait_set -%} # '{{ trait.id }}' {% if trait.package == package.id -%} - traits.{{ trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait.kId, + traits.{{ trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait_v{{ trait.version }}.kId, {% else -%} - {{ trait.package | to_py_module_name }}.traits.{{ trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait.kId, + {{ trait.package | to_py_module_name }}.traits.{{ trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait_v{{ trait.version }}.kId, {% endif -%} {% endfor %} } @@ -56,6 +62,14 @@ class {{ specification.id | to_py_class_name }}Specification: """ if not isinstance(traitsData, TraitsData): raise TypeError("Specifications must be constructed with a TraitsData instance") +{%- if specification.deprecated %} + warnings.warn( + "The '{{ namespace.id | to_py_module_name }}.{{ specification.id }}' specification" + " of the '{{ package.id | to_py_module_name }}' package is deprecated.", + DeprecationWarning, + stacklevel=2 + ) +{%- endif %} self.__data = traitsData def traitsData(self): @@ -82,13 +96,34 @@ class {{ specification.id | to_py_class_name }}Specification: the data held in this instance. """ {% if trait.package == package.id -%} - return traits.{{trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait(self.traitsData()) + return traits.{{trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait_v{{ trait.version }}(self.traitsData()) {% else -%} - return {{ trait.package | to_py_module_name }}.traits.{{trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait(self.traitsData()) + return {{ trait.package | to_py_module_name }}.traits.{{trait.namespace | to_py_module_name }}.{{ trait.name | to_py_class_name }}Trait_v{{ trait.version }}(self.traitsData()) {% endif -%} - +{% endfor %} {% endfor -%} +{% for specification in namespace.members -%} +{%- if specification.version == "1" -%} +{%- set spec_basename = specification.id | to_py_class_name ~ "Specification" %} +class {{ spec_basename }}({{ spec_basename }}_v1): + """ + {{ specification.description | wordwrap(68) | indent(4) }} + {% if specification.usage -%} + Usage: {{ specification.usage | join(', ') }} + {%- endif %} + + @deprecated Unversioned specification view classes are deprecated, + please use {{ spec_basename }}_v1 explicitly. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + "Unversioned specification view classes are deprecated. Please switch from" + " {{ spec_basename }} to {{ spec_basename }}_v1.", + DeprecationWarning + ) -{% endfor -%} +{% endif -%} +{% endfor %} diff --git a/python/openassetio_traitgen/templates/python/traits.py.in b/python/openassetio_traitgen/templates/python/traits.py.in index 2886116..29abe25 100644 --- a/python/openassetio_traitgen/templates/python/traits.py.in +++ b/python/openassetio_traitgen/templates/python/traits.py.in @@ -11,16 +11,21 @@ Trait definitions in the '{{ namespace.id }}' namespace. # WARNING: This file is auto-generated by openassetio-traitgen, do not edit. from typing import Union +import warnings from openassetio.trait import TraitsData {% for trait in namespace.members %} -class {{ trait.name | to_py_class_name }}Trait: +class {{ trait.name | to_py_class_name }}Trait_v{{ trait.version }}: """ {{ trait.description | wordwrap(68) | indent(4) }} - {% if trait.usage -%} +{%- if trait.usage %} Usage: {{ trait.usage | join(', ') }} - {% endif -%} +{%- endif %} +{%- if trait.deprecated %} + + @deprecated This trait is flagged for future removal. +{%- endif %} """ kId = "{{ trait.id }}" @@ -31,6 +36,13 @@ class {{ trait.name | to_py_class_name }}Trait: @param traitsData @fqref{TraitsData}} "TraitsData" The target data that holds/will hold the traits properties. """ +{%- if trait.deprecated %} + warnings.warn( + "The '{{ trait.id }}' trait is deprecated.", + DeprecationWarning, + stacklevel=2 + ) +{%- endif %} self.__data = traitsData def isImbued(self): @@ -101,5 +113,29 @@ class {{ trait.name | to_py_class_name }}Trait: return value {% endfor %} {% endif %} +{%- endfor -%} -{% endfor -%} +{%- for trait in namespace.members %} + {%- if trait.version == "1" %} + {% set trait_basename = trait.name | to_py_class_name ~ "Trait" %} +class {{ trait_basename }}({{ trait_basename }}_v1): + """ + {{ trait.description | wordwrap(68) | indent(4) }} + {% if trait.usage -%} + Usage: {{ trait.usage | join(', ') }} + {%- endif %} + + @deprecated Unversioned trait view classes are deprecated, please + use {{ trait_basename }}_v1 explicitly. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + "Unversioned trait view classes are deprecated. Please switch from" + " {{ trait_basename }} to {{ trait_basename }}_v1.", + DeprecationWarning, + stacklevel=2 + ) + + {% endif -%} +{% endfor %} diff --git a/tests/conftest.py b/tests/conftest.py index e9ba2de..4a46c5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -114,8 +114,12 @@ def description_exotic_values(): "description": "n", "members": { "t&": { - "description": "t", - "properties": {"p$": {"type": "boolean", "description": "p"}}, + "versions": { + "1": { + "description": "t", + "properties": {"p$": {"type": "boolean", "description": "p"}}, + } + } } }, } @@ -125,8 +129,19 @@ def description_exotic_values(): "description": "", "members": { "s^": { - "description": "", - "traitSet": [{"package": "p📦p", "namespace": "t!n", "name": "t&"}], + "versions": { + "1": { + "description": "", + "traitSet": [ + { + "package": "p📦p", + "namespace": "t!n", + "name": "t&", + "version": "1", + } + ], + } + } } }, } @@ -149,6 +164,7 @@ def declaration_all(): members=[ datamodel.TraitDeclaration( id="openassetio-traitgen-test-all:aNamespace.AllProperties", + version="1", name="AllProperties", description="A trait with properties of all types.", usage=[], @@ -175,20 +191,64 @@ def declaration_all(): ), # TODO(DF): Add DICT property, once supported. ], + deprecated=False, + ), + datamodel.TraitDeclaration( + id="openassetio-traitgen-test-all:aNamespace.Deprecated", + version="1", + name="Deprecated", + description="A deprecated trait.", + usage=[], + properties=[], + deprecated=True, + ), + datamodel.TraitDeclaration( + id="openassetio-traitgen-test-all:aNamespace.MultipleVersions", + version="1", + name="MultipleVersions", + description="A trait with multiple versions, version 1.", + usage=["entity"], + properties=[ + datamodel.PropertyDeclaration( + id="oldProperty", + type=datamodel.PropertyType.STRING, + description="A deprecated string-typed property.", + ), + ], + deprecated=False, + ), + datamodel.TraitDeclaration( + id="openassetio-traitgen-test-all:aNamespace.MultipleVersions.v2", + version="2", + name="MultipleVersions", + description="A trait with multiple versions, version 2.", + usage=[], + properties=[ + datamodel.PropertyDeclaration( + id="newProperty", + type=datamodel.PropertyType.INTEGER, + description="A new int-typed property.", + ), + ], + deprecated=False, ), datamodel.TraitDeclaration( id="openassetio-traitgen-test-all:aNamespace.NoProperties", + version="1", name="NoProperties", description="Another trait, this time with no properties.", properties=[], usage=[], + deprecated=False, ), datamodel.TraitDeclaration( id="openassetio-traitgen-test-all:aNamespace.NoPropertiesMultipleUsage", + version="1", name="NoPropertiesMultipleUsage", description="Another trait, this time with multiple usage.", properties=[], usage=["entity", "relationship"], + deprecated=False, ), ], ), @@ -198,10 +258,12 @@ def declaration_all(): members=[ datamodel.TraitDeclaration( id="openassetio-traitgen-test-all:anotherNamespace.NoProperties", + version="1", name="NoProperties", description="Another NoProperties trait in a different namespace", properties=[], usage=[], + deprecated=False, ), ], ), @@ -211,8 +273,26 @@ def declaration_all(): id="test", description="Test specifications.", members=[ + datamodel.SpecificationDeclaration( + id="Deprecated", + version="1", + description="A deprecated specification.", + usage=[], + trait_set=[ + datamodel.TraitReference( + id="openassetio-traitgen-test-all:aNamespace.Deprecated", + package="openassetio-traitgen-test-all", + namespace="aNamespace", + name="Deprecated", + version="1", + unique_name_parts=("Deprecated",), + ), + ], + deprecated=True, + ), datamodel.SpecificationDeclaration( id="LocalAndExternalTrait", + version="1", description=( "A specification referencing traits in this and another package." ), @@ -223,6 +303,7 @@ def declaration_all(): package="openassetio-traitgen-test-all", namespace="aNamespace", name="NoProperties", + version="1", unique_name_parts=( "openassetio-traitgen-test-all", "aNamespace", @@ -234,6 +315,7 @@ def declaration_all(): package="openassetio-traitgen-test-traits-only", namespace="aNamespace", name="NoProperties", + version="1", unique_name_parts=( "openassetio-traitgen-test-traits-only", "aNamespace", @@ -241,9 +323,65 @@ def declaration_all(): ), ), ], + deprecated=False, + ), + datamodel.SpecificationDeclaration( + id="MultipleVersionsOfTrait", + version="1", + description=( + "Version 1 of a specification referencing version 1 of a trait." + ), + usage=["entity"], + trait_set=[ + datamodel.TraitReference( + id="openassetio-traitgen-test-all:aNamespace.MultipleVersions", + package="openassetio-traitgen-test-all", + namespace="aNamespace", + name="MultipleVersions", + version="1", + unique_name_parts=("MultipleVersions",), + ), + datamodel.TraitReference( + id="openassetio-traitgen-test-all:aNamespace.NoProperties", + package="openassetio-traitgen-test-all", + namespace="aNamespace", + name="NoProperties", + version="1", + unique_name_parts=("NoProperties",), + ), + ], + deprecated=False, + ), + datamodel.SpecificationDeclaration( + id="MultipleVersionsOfTrait", + version="2", + description=( + "Version 2 of a specification referencing version 2 of a" " trait." + ), + usage=[], + trait_set=[ + datamodel.TraitReference( + id="openassetio-traitgen-test-all:aNamespace.MultipleVersions.v2", + package="openassetio-traitgen-test-all", + namespace="aNamespace", + name="MultipleVersions", + version="2", + unique_name_parts=("MultipleVersions",), + ), + datamodel.TraitReference( + id="openassetio-traitgen-test-all:aNamespace.NoProperties", + package="openassetio-traitgen-test-all", + namespace="aNamespace", + name="NoProperties", + version="1", + unique_name_parts=("NoProperties",), + ), + ], + deprecated=False, ), datamodel.SpecificationDeclaration( id="OneExternalTrait", + version="1", description="A specification referencing traits in another package.", usage=[], trait_set=[ @@ -252,12 +390,15 @@ def declaration_all(): package="openassetio-traitgen-test-traits-only", namespace="test", name="Another", + version="1", unique_name_parts=("Another",), ), ], + deprecated=False, ), datamodel.SpecificationDeclaration( id="TwoLocalTraits", + version="1", description="A specification with two traits.", usage=[], trait_set=[ @@ -266,6 +407,7 @@ def declaration_all(): package="openassetio-traitgen-test-all", namespace="aNamespace", name="NoProperties", + version="1", unique_name_parts=("aNamespace", "NoProperties"), ), datamodel.TraitReference( @@ -273,9 +415,11 @@ def declaration_all(): package="openassetio-traitgen-test-all", namespace="anotherNamespace", name="NoProperties", + version="1", unique_name_parts=("anotherNamespace", "NoProperties"), ), ], + deprecated=False, ), ], ) @@ -299,6 +443,8 @@ def declaration_traits_only(): datamodel.TraitDeclaration( id="openassetio-traitgen-test-traits-only:aNamespace.NoProperties", name="NoProperties", + deprecated=False, + version="1", description="Yet Another No Properties Trait", properties=[], usage=["managementPolicy"], @@ -312,6 +458,8 @@ def declaration_traits_only(): datamodel.TraitDeclaration( id="openassetio-traitgen-test-traits-only:test.Another", name="Another", + deprecated=False, + version="1", description="Yet Another Trait", properties=[], usage=["managementPolicy"], @@ -339,6 +487,8 @@ def declaration_specifications_only(): members=[ datamodel.SpecificationDeclaration( id="Some", + deprecated=False, + version="1", description="Some specification", usage=[], trait_set=[ @@ -347,6 +497,7 @@ def declaration_specifications_only(): package="openassetio-traitgen-test-all", namespace="aNamespace", name="AllProperties", + version="1", unique_name_parts=("AllProperties",), ), datamodel.TraitReference( @@ -354,6 +505,7 @@ def declaration_specifications_only(): package="openassetio-traitgen-test-traits-only", namespace="test", name="Another", + version="1", unique_name_parts=("Another",), ), ], @@ -386,7 +538,9 @@ def declaration_exotic_values(): members=[ datamodel.TraitDeclaration( id="p📦p:t!n.t&", + version="1", name="t&", + deprecated=False, description="t", usage=[], properties=[ @@ -405,6 +559,8 @@ def declaration_exotic_values(): members=[ datamodel.SpecificationDeclaration( id="s^", + deprecated=False, + version="1", description="", usage=[], trait_set=[ @@ -413,6 +569,7 @@ def declaration_exotic_values(): package="p📦p", namespace="t!n", name="t&", + version="1", unique_name_parts=("t&",), ) ], @@ -442,7 +599,9 @@ def fn(package_name=None, specification_namespace=None, trait_namespace=None): members=[ datamodel.TraitDeclaration( id="some_trait", + version="1", name="some_trait", + deprecated=False, description="", usage=[], properties=[], @@ -457,6 +616,8 @@ def fn(package_name=None, specification_namespace=None, trait_namespace=None): members=[ datamodel.SpecificationDeclaration( id="some_specification", + deprecated=False, + version="1", description="", usage=[], trait_set=[ @@ -465,6 +626,7 @@ def fn(package_name=None, specification_namespace=None, trait_namespace=None): name="some_trait", namespace="some_namespace", package="some_package", + version="1", unique_name_parts=("some_trait",), ) ], diff --git a/tests/generators/cpp/cmake/StaticAnalyzers.cmake b/tests/generators/cpp/cmake/StaticAnalyzers.cmake index 48f1705..49dbe2f 100644 --- a/tests/generators/cpp/cmake/StaticAnalyzers.cmake +++ b/tests/generators/cpp/cmake/StaticAnalyzers.cmake @@ -12,6 +12,10 @@ macro(enable_clang_tidy) # compiler warning flags will cause the build to fail. list(APPEND CMAKE_CXX_CLANG_TIDY -extra-arg=-Wno-unknown-warning-option) + # Allow [[deprecated]] - we'll catch these through compiler + # warnings. + list(APPEND CMAKE_CXX_CLANG_TIDY -extra-arg=-Wno-deprecated-declarations) + # Set standard if (NOT "${CMAKE_CXX_STANDARD}" STREQUAL "") if ("${CMAKE_CXX_CLANG_TIDY_DRIVER_MODE}" STREQUAL "cl") diff --git a/tests/generators/cpp/src/CMakeLists.txt b/tests/generators/cpp/src/CMakeLists.txt index a8a5d0e..4b40036 100644 --- a/tests/generators/cpp/src/CMakeLists.txt +++ b/tests/generators/cpp/src/CMakeLists.txt @@ -4,43 +4,53 @@ find_package(OpenAssetIO REQUIRED) find_package(Catch2 REQUIRED) - #----------------------------------------------------------------------- -# Loop compile-time variants - -foreach (include_type package subpackage namespace class) +# CMake/CTest target utilities. - set(_target_name openassetio-traitgentest-${include_type}) +## +# Helper function to create a test's build target for a given include +# type. +# +# The include type determines which style of includes are used in the +# test, i.e. what level of hoisting headers - package level, namespace +# level, or class level. +function(_create_build_target target_name include_type) string(TOUPPER OPENASSETIO_TRAITGENTEST_INCLUDES_${include_type} _include_toggle) - #----------------------------------------------------------------------- + #------------------------------------------------------------------- # Target executable - add_executable(${_target_name}) - openassetio_traitgentest_set_default_target_properties(${_target_name}) + add_executable(${target_name}) + openassetio_traitgentest_set_default_target_properties(${target_name}) - target_compile_definitions(${_target_name} PRIVATE ${_include_toggle}) + target_compile_definitions(${target_name} PRIVATE ${_include_toggle}) - #----------------------------------------------------------------------- + #------------------------------------------------------------------- # Target dependencies - target_sources(${_target_name} PRIVATE main.cpp test.cpp) + target_sources(${target_name} PRIVATE main.cpp test.cpp) target_link_libraries( - ${_target_name} + ${target_name} PRIVATE OpenAssetIO::openassetio-core Catch2::Catch2 ) target_include_directories( - ${_target_name} - PRIVATE + ${target_name} + # Must make SYSTEM since e.g. DeprecatedSpecification inheriting + # from DeprecatedSpecification_v1 triggers deprecation warnings + # even if unused. + SYSTEM PRIVATE ${OPENASSETIO_TRAITGENTEST_ADDITIONAL_INCLUDE_DIRS} ) +endfunction() +#----------------------------------------------------------------------- +# Test each #include style variant. - #----------------------------------------------------------------------- - # CTest target - +foreach (include_type package subpackage namespace class) + set(_target_name openassetio-traitgentest-${include_type}) + _create_build_target(${_target_name} ${include_type}) add_test(NAME ${_target_name} COMMAND $) if (MSVC) # If OpenAssetIO was built as a shared library, then Windows @@ -51,6 +61,46 @@ foreach (include_type package subpackage namespace class) ENVIRONMENT PATH=$ ) - endif() - + endif () + # Allow [[deprecated]] - these will be tested in their own target. + target_compile_options(${_target_name} PRIVATE -Wno-deprecated-declarations) endforeach () + +#----------------------------------------------------------------------- +# Test deprecation warnings. + +set(_target_name openassetio-traitgentest-deprecations) +_create_build_target(${_target_name} package) +# Don't build this target by default - it's only useful to check that +# deprecation warnings are correctly issued, and will fail the build +# otherwise. +set_target_properties(${_target_name} PROPERTIES EXCLUDE_FROM_ALL TRUE) + +# Construct the error messages we expect to see, in order. Escape every +# character to make it a raw string match. +string(REGEX REPLACE "(.)" "\\\\\\1" _pass_regex + "Unversioned trait view classes are deprecated, please use MultipleVersionsTrait_v1" + " explicitly.") +list(APPEND _pass_regexps "${_pass_regex}") +string(REGEX REPLACE "(.)" "\\\\\\1" _pass_regex + "The 'openassetio-traitgen-test-all:aNamespace.Deprecated' trait" + " is deprecated.") +list(APPEND _pass_regexps "${_pass_regex}") +string(REGEX REPLACE "(.)" "\\\\\\1" _pass_regex + "Unversioned specification view classes are deprecated, please use" + " MultipleVersionsOfTraitSpecification_v1 explicitly.") +list(APPEND _pass_regexps "${_pass_regex}") +string(REGEX REPLACE "(.)" "\\\\\\1" _pass_regex + "The 'test.Deprecated' specification of the 'openassetio_traitgen_test_all' package is" + " deprecated.") +list(APPEND _pass_regexps "${_pass_regex}") +# Join into a single regex that matches all error messages in order. +list(JOIN _pass_regexps ".*" _pass_regexps) + +# Building the target _is_ the test. +add_test( + NAME ${_target_name} + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${_target_name} +) +# Add expected failure regex for deprecation warnings. +set_tests_properties(${_target_name} PROPERTIES PASS_REGULAR_EXPRESSION "${_pass_regexps}") diff --git a/tests/generators/cpp/src/test.cpp b/tests/generators/cpp/src/test.cpp index d779b08..e1bf064 100644 --- a/tests/generators/cpp/src/test.cpp +++ b/tests/generators/cpp/src/test.cpp @@ -18,10 +18,14 @@ #include #include #elif defined OPENASSETIO_TRAITGENTEST_INCLUDES_CLASS +#include #include +#include #include #include #include +#include +#include #include #include #include @@ -32,86 +36,170 @@ namespace openassetio_abi = openassetio::v1; +// False-positive linter errors with Catch2 macros. +// NOLINTBEGIN(bugprone-chained-comparison) + TEST_CASE("openassetio_traitgen_test_all - all expected traits are defined") { STATIC_REQUIRE( - std::is_class_v); + std::is_class_v); STATIC_REQUIRE( std::is_class_v< - openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesMultipleUsageTrait>); - STATIC_REQUIRE( - std::is_class_v); + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesMultipleUsageTrait_v1>); STATIC_REQUIRE( - std::is_class_v); + std::is_class_v); + STATIC_REQUIRE(std::is_class_v< + openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait_v1>); + STATIC_REQUIRE(std::is_class_v< + openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v1>); + STATIC_REQUIRE(std::is_class_v< + openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v2>); } TEST_CASE("openassetio_traitgen_test_all - all expected specifications are defined") { STATIC_REQUIRE(std::is_class_v); + LocalAndExternalTraitSpecification_v1>); STATIC_REQUIRE( std::is_class_v< - openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification>); + openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification_v1>); STATIC_REQUIRE( std::is_class_v< - openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification>); + openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification_v1>); + STATIC_REQUIRE(std::is_class_v); + STATIC_REQUIRE(std::is_class_v); } TEST_CASE("openassetio_traitgen_test_all - traits are under an ABI namespace") { STATIC_REQUIRE( - std::is_same_v); + std::is_same_v); STATIC_REQUIRE( std::is_same_v< - openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesMultipleUsageTrait, - openassetio_traitgen_test_all::v1::traits::aNamespace::NoPropertiesMultipleUsageTrait>); + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesMultipleUsageTrait_v1, + openassetio_traitgen_test_all::v1::traits::aNamespace:: + NoPropertiesMultipleUsageTrait_v1>); + STATIC_REQUIRE(std::is_same_v< + openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1, + openassetio_traitgen_test_all::v1::traits::aNamespace::AllPropertiesTrait_v1>); STATIC_REQUIRE( - std::is_same_v); + std::is_same_v< + openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait_v1, + openassetio_traitgen_test_all::v1::traits::anotherNamespace::NoPropertiesTrait_v1>); + STATIC_REQUIRE(std::is_same_v< + openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v1, + openassetio_traitgen_test_all::v1::traits::aNamespace::MultipleVersionsTrait_v1>); STATIC_REQUIRE(std::is_same_v< - openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait, - openassetio_traitgen_test_all::v1::traits::anotherNamespace::NoPropertiesTrait>); + openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v2, + openassetio_traitgen_test_all::v1::traits::aNamespace::MultipleVersionsTrait_v2>); } TEST_CASE("openassetio_traitgen_test_all - specifications are under an ABI namespace") { STATIC_REQUIRE( std::is_same_v< - openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification, - openassetio_traitgen_test_all::v1::specifications::test::OneExternalTraitSpecification>); - STATIC_REQUIRE( - std::is_same_v< - openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification, - openassetio_traitgen_test_all::v1::specifications::test::OneExternalTraitSpecification>); + openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification_v1, + openassetio_traitgen_test_all::v1::specifications::test:: + OneExternalTraitSpecification_v1>); STATIC_REQUIRE( std::is_same_v< - openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification, - openassetio_traitgen_test_all::v1::specifications::test::TwoLocalTraitsSpecification>); + openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification_v1, + openassetio_traitgen_test_all::v1::specifications::test:: + TwoLocalTraitsSpecification_v1>); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); } TEST_CASE("openassetio_traitgen_test_all - specifications have expected trait sets") { - CHECK(openassetio_traitgen_test_all::specifications::test::LocalAndExternalTraitSpecification:: - kTraitSet == + CHECK(openassetio_traitgen_test_all::specifications::test:: + LocalAndExternalTraitSpecification_v1::kTraitSet == openassetio_abi::trait::TraitSet{ - openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait::kId, - openassetio_traitgen_test_traits_only::traits::aNamespace::NoPropertiesTrait::kId}); - CHECK(openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification:: + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1::kId, + openassetio_traitgen_test_traits_only::traits::aNamespace::NoPropertiesTrait_v1::kId}); + CHECK(openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification_v1:: kTraitSet == openassetio_abi::trait::TraitSet{ - openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait::kId, - openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait::kId}); - CHECK(openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification:: + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1::kId, + openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait_v1::kId}); + CHECK(openassetio_traitgen_test_all::specifications::test::OneExternalTraitSpecification_v1:: kTraitSet == openassetio_abi::trait::TraitSet{ - openassetio_traitgen_test_traits_only::traits::test::AnotherTrait::kId}); + openassetio_traitgen_test_traits_only::traits::test::AnotherTrait_v1::kId}); + CHECK(openassetio_traitgen_test_all::specifications::test:: + MultipleVersionsOfTraitSpecification_v1::kTraitSet == + openassetio_abi::trait::TraitSet{ + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1::kId, + openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v1::kId, + }); + CHECK(openassetio_traitgen_test_all::specifications::test:: + MultipleVersionsOfTraitSpecification_v2::kTraitSet == + openassetio_abi::trait::TraitSet{ + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1::kId, + openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v2::kId, + }); } TEST_CASE("openassetio_traitgen_test_all - traits have expected IDs") { - CHECK(openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait::kId == + CHECK(openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1::kId == "openassetio-traitgen-test-all:aNamespace.NoProperties"); - CHECK(openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesMultipleUsageTrait::kId == - "openassetio-traitgen-test-all:aNamespace.NoPropertiesMultipleUsage"); - CHECK(openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait::kId == + CHECK( + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesMultipleUsageTrait_v1::kId == + "openassetio-traitgen-test-all:aNamespace.NoPropertiesMultipleUsage"); + CHECK(openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1::kId == "openassetio-traitgen-test-all:aNamespace.AllProperties"); - CHECK(openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait::kId == + CHECK(openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait_v1::kId == "openassetio-traitgen-test-all:anotherNamespace.NoProperties"); + CHECK(openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v1::kId == + "openassetio-traitgen-test-all:aNamespace.MultipleVersions"); + CHECK(openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v2::kId == + "openassetio-traitgen-test-all:aNamespace.MultipleVersions.v2"); +} + +SCENARIO("Unversioned trait") { + using openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait; + using openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v1; + using openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v2; + + THEN("unversioned trait is a subclass of v1") { + STATIC_REQUIRE(std::is_base_of_v); + } + + WHEN("ID of unversioned trait is retrieved") { + const auto traitId = MultipleVersionsTrait::kId; + + THEN("unversioned trait ID is the same as v1") { + CHECK(traitId == MultipleVersionsTrait_v1::kId); + CHECK(traitId != MultipleVersionsTrait_v2::kId); + } + } + + WHEN("unversioned trait is constructed") { + const auto traitsData = openassetio::trait::TraitsData::make(); + // Construct rather than using static_assert to be confident that + // the constructor is available. + + const MultipleVersionsTrait trait{traitsData}; + + THEN("unversioned trait has same members as v1") { + // Just checking that the member function exists. + CHECK(!trait.getOldProperty().has_value()); + } + } +} + +TEST_CASE("Deprecated trait causes deprecation compiler warning") { + // Ensure that the deprecated trait does indeed cause a deprecation + // warning if used. Note that to see this we must remove the + // diagnostic suppression. See + // openassetio-traitgentest-deprecations CTest target. + using openassetio_traitgen_test_all::traits::aNamespace::DeprecatedTrait_v1; + + const DeprecatedTrait_v1 trait{openassetio::trait::TraitsData::make()}; } namespace openassetio_traitgen_test_traits_only { @@ -134,19 +222,18 @@ TEST_CASE("openassetio_traitgen_test_specifications_only - no traits are defined SCENARIO("Common specification utility functions") { GIVEN("a specification constructed using its create function") { - auto specification = - openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification::create(); + auto specification = openassetio_traitgen_test_all::specifications::test:: + TwoLocalTraitsSpecification_v1::create(); WHEN("its TraitsData is retrieved") { - openassetio::trait::TraitsDataPtr traitsData; - traitsData = specification.traitsData(); + const openassetio::trait::TraitsDataPtr& traitsData = specification.traitsData(); THEN("the TraitsData is imbued with the expected traits") { - CHECK( - traitsData->traitSet() == - openassetio::trait::TraitSet{ - openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait::kId, - openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait::kId}); + CHECK(traitsData->traitSet() == + openassetio::trait::TraitSet{ + openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1::kId, + openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait_v1:: + kId}); } AND_WHEN("the retrieved TraitsData is modified") { @@ -162,12 +249,12 @@ SCENARIO("Common specification utility functions") { SCENARIO("Common trait utility functions") { GIVEN("a TraitsData and a trait view on it") { - using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; auto traitsData = openassetio::trait::TraitsData::make(); - const AllPropertiesTrait trait{traitsData}; + const AllPropertiesTrait_v1 trait{traitsData}; WHEN("trait view class method is used to query whether TraitsData is imbued") { - const bool isImbued = AllPropertiesTrait::isImbuedTo(traitsData); + const bool isImbued = AllPropertiesTrait_v1::isImbuedTo(traitsData); THEN("it is not yet imbued") { CHECK(isImbued == false); } } @@ -178,10 +265,10 @@ SCENARIO("Common trait utility functions") { } AND_GIVEN("TraitsData is imbued with the trait") { - traitsData->addTrait(AllPropertiesTrait::kId); + traitsData->addTrait(AllPropertiesTrait_v1::kId); WHEN("trait view class method is used to query whether TraitsData is imbued") { - const bool isImbued = AllPropertiesTrait::isImbuedTo(traitsData); + const bool isImbued = AllPropertiesTrait_v1::isImbuedTo(traitsData); THEN("it is imbued") { CHECK(isImbued == true); } } @@ -193,15 +280,15 @@ SCENARIO("Common trait utility functions") { } WHEN("trait view class method is used to imbue the TraitsData") { - AllPropertiesTrait::imbueTo(traitsData); + AllPropertiesTrait_v1::imbueTo(traitsData); - THEN("TraitsData is imbued") { CHECK(traitsData->hasTrait(AllPropertiesTrait::kId)); } + THEN("TraitsData is imbued") { CHECK(traitsData->hasTrait(AllPropertiesTrait_v1::kId)); } } WHEN("trait view instance method is used to imbue the TraitsData") { trait.imbue(); - THEN("TraitsData is imbued") { CHECK(traitsData->hasTrait(AllPropertiesTrait::kId)); } + THEN("TraitsData is imbued") { CHECK(traitsData->hasTrait(AllPropertiesTrait_v1::kId)); } } } } @@ -220,7 +307,8 @@ struct PropertyFixture; template <> struct PropertyFixture { - using AllPropertiesTrait = openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using AllPropertiesTrait = + openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; AllPropertiesTrait trait; @@ -243,7 +331,8 @@ struct PropertyFixture { template <> struct PropertyFixture { - using AllPropertiesTrait = openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using AllPropertiesTrait = + openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; AllPropertiesTrait trait; @@ -266,7 +355,8 @@ struct PropertyFixture { template <> struct PropertyFixture { - using AllPropertiesTrait = openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using AllPropertiesTrait = + openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; AllPropertiesTrait trait; @@ -289,7 +379,8 @@ struct PropertyFixture { template <> struct PropertyFixture { - using AllPropertiesTrait = openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using AllPropertiesTrait = + openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; AllPropertiesTrait trait; @@ -313,19 +404,19 @@ struct PropertyFixture { TEMPLATE_TEST_CASE("Property getters", "", openassetio_abi::Bool, openassetio_abi::Int, openassetio_abi::Float, openassetio_abi::Str) { - using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; using PropertyType = TestType; // Catch2-injected template param. using Fixture = PropertyFixture; GIVEN("an AllPropertiesTrait view of a fully populated TraitsData") { const openassetio_abi::trait::TraitsDataPtr traitsData = - openassetio_abi::trait::TraitsData::make({AllPropertiesTrait::kId}); + openassetio_abi::trait::TraitsData::make({AllPropertiesTrait_v1::kId}); - traitsData->setTraitProperty(AllPropertiesTrait::kId, Fixture::kPropertyKey, + traitsData->setTraitProperty(AllPropertiesTrait_v1::kId, Fixture::kPropertyKey, Fixture::kExpectedValue); - const Fixture fixture{AllPropertiesTrait{traitsData}}; + const Fixture fixture{AllPropertiesTrait_v1{traitsData}}; WHEN("property is queried without a default") { const std::optional value = fixture.getProperty(); @@ -349,9 +440,9 @@ TEMPLATE_TEST_CASE("Property getters", "", openassetio_abi::Bool, openassetio_ab GIVEN("an AllPropertiesTrait view of an imbued TraitsData with no properties set") { const openassetio_abi::trait::TraitsDataPtr traitsData = - openassetio_abi::trait::TraitsData::make({AllPropertiesTrait::kId}); + openassetio_abi::trait::TraitsData::make({AllPropertiesTrait_v1::kId}); - const Fixture fixture{AllPropertiesTrait{traitsData}}; + const Fixture fixture{AllPropertiesTrait_v1{traitsData}}; WHEN("property is queried without a default") { THEN("optional return is empty") { CHECK(!fixture.getProperty().has_value()); } @@ -365,12 +456,10 @@ TEMPLATE_TEST_CASE("Property getters", "", openassetio_abi::Bool, openassetio_ab } GIVEN("an AllPropertiesTrait view of a blank TraitsData") { - using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; - const openassetio_abi::trait::TraitsDataPtr traitsData = openassetio_abi::trait::TraitsData::make(); - const Fixture fixture{AllPropertiesTrait{traitsData}}; + const Fixture fixture{AllPropertiesTrait_v1{traitsData}}; WHEN("property is queried without a default") { THEN("optional return is empty") { CHECK(!fixture.getProperty().has_value()); } @@ -384,15 +473,13 @@ TEMPLATE_TEST_CASE("Property getters", "", openassetio_abi::Bool, openassetio_ab } GIVEN("an AllPropertiesTrait view of a TraitsData populated with unexpected types") { - using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; - const openassetio_abi::trait::TraitsDataPtr traitsData = - openassetio_abi::trait::TraitsData::make({AllPropertiesTrait::kId}); + openassetio_abi::trait::TraitsData::make({AllPropertiesTrait_v1::kId}); - traitsData->setTraitProperty(AllPropertiesTrait::kId, Fixture::kPropertyKey, + traitsData->setTraitProperty(AllPropertiesTrait_v1::kId, Fixture::kPropertyKey, Fixture::kMismatchedTypeValue); - const Fixture fixture{AllPropertiesTrait{traitsData}}; + const Fixture fixture{AllPropertiesTrait_v1{traitsData}}; WHEN("property is queried without a default") { THEN("exception is thrown") { @@ -415,7 +502,7 @@ TEMPLATE_TEST_CASE("Property getters", "", openassetio_abi::Bool, openassetio_ab TEMPLATE_TEST_CASE("Property setters", "", openassetio_abi::Bool, openassetio_abi::Int, openassetio_abi::Float, openassetio_abi::Str) { - using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; using PropertyType = TestType; // Catch2-injected template param. using Fixture = PropertyFixture; @@ -423,33 +510,35 @@ TEMPLATE_TEST_CASE("Property setters", "", openassetio_abi::Bool, openassetio_ab GIVEN("an AllPropertiesTrait view of a blank TraitsData") { const openassetio_abi::trait::TraitsDataPtr traitsData = openassetio_abi::trait::TraitsData::make(); - Fixture fixture{AllPropertiesTrait{traitsData}}; + Fixture fixture{AllPropertiesTrait_v1{traitsData}}; WHEN("property is set") { fixture.setProperty(Fixture::kExpectedValue); THEN("TraitsData contains expected value") { openassetio_abi::trait::property::Value value; - [[maybe_unused]] const bool hasProp = - traitsData->getTraitProperty(&value, AllPropertiesTrait::kId, Fixture::kPropertyKey); + [[maybe_unused]] const bool hasProp = traitsData->getTraitProperty( + &value, AllPropertiesTrait_v1::kId, Fixture::kPropertyKey); const PropertyType actualValue = std::get(value); CHECK(actualValue == Fixture::kExpectedValue); } - THEN("TraitsData has been imbued") { CHECK(traitsData->hasTrait(AllPropertiesTrait::kId)); } + THEN("TraitsData has been imbued") { + CHECK(traitsData->hasTrait(AllPropertiesTrait_v1::kId)); + } } } } SCENARIO("Moveable property setters") { - using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait; + using openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1; GIVEN("an AllPropertiesTrait view of a blank TraitsData") { const openassetio_abi::trait::TraitsDataPtr traitsData = openassetio_abi::trait::TraitsData::make(); - AllPropertiesTrait trait{traitsData}; + AllPropertiesTrait_v1 trait{traitsData}; WHEN("string property is set with a moveable string") { std::string value = "some string"; @@ -466,7 +555,7 @@ SCENARIO("Moveable property setters") { SCENARIO("Specifications providing trait views") { GIVEN("a LocalAndExternalTraitSpecification") { const auto specification = openassetio_traitgen_test_all::specifications::test:: - LocalAndExternalTraitSpecification::create(); + LocalAndExternalTraitSpecification_v1::create(); WHEN( "an openassetio-traitgen-test-traits-only:aNamespace.NoProperties trait view is " @@ -477,10 +566,9 @@ SCENARIO("Specifications providing trait views") { THEN("trait view has expected type") { STATIC_REQUIRE( std::is_same_v); + aNamespace::NoPropertiesTrait_v1>); } } - WHEN("an openassetio-traitgen-test-all:aNamespace.NoProperties trait view is requested") { const auto trait = specification.openassetioTraitgenTestAllANamespaceNoPropertiesTrait(); @@ -488,29 +576,28 @@ SCENARIO("Specifications providing trait views") { STATIC_REQUIRE( std::is_same_v< decltype(trait), - const openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait>); + const openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1>); } } } - GIVEN("a OneExternalTraitSpecification") { const auto specification = openassetio_traitgen_test_all::specifications::test:: - OneExternalTraitSpecification::create(); + OneExternalTraitSpecification_v1::create(); WHEN("an AnotherTrait trait view is requested") { const auto trait = specification.anotherTrait(); THEN("trait view has expected type") { - STATIC_REQUIRE(std::is_same_v< - decltype(trait), - const openassetio_traitgen_test_traits_only::traits::test::AnotherTrait>); + STATIC_REQUIRE( + std::is_same_v< + decltype(trait), + const openassetio_traitgen_test_traits_only::traits::test::AnotherTrait_v1>); } } } - GIVEN("a TwoLocalTraitsSpecification") { - const auto specification = - openassetio_traitgen_test_all::specifications::test::TwoLocalTraitsSpecification::create(); + const auto specification = openassetio_traitgen_test_all::specifications::test:: + TwoLocalTraitsSpecification_v1::create(); WHEN("an aNamespace.NoPropertiesTrait trait view is requested") { const auto trait = specification.aNamespaceNoPropertiesTrait(); @@ -519,25 +606,22 @@ SCENARIO("Specifications providing trait views") { STATIC_REQUIRE( std::is_same_v< decltype(trait), - const openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait>); + const openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1>); } } - WHEN("an anotherNamespace.NoPropertiesTrait trait view is requested") { const auto trait = specification.anotherNamespaceNoPropertiesTrait(); THEN("trait view has expected type") { STATIC_REQUIRE( - std::is_same_v< - decltype(trait), - const openassetio_traitgen_test_all::traits::anotherNamespace::NoPropertiesTrait>); + std::is_same_v); } } } - GIVEN("a SomeSpecification") { const auto specification = openassetio_traitgen_test_specifications_only::specifications:: - test::SomeSpecification::create(); + test::SomeSpecification_v1::create(); WHEN("an AllPropertiesTrait trait view is requested") { const auto trait = specification.allPropertiesTrait(); @@ -546,17 +630,17 @@ SCENARIO("Specifications providing trait views") { STATIC_REQUIRE( std::is_same_v< decltype(trait), - const openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait>); + const openassetio_traitgen_test_all::traits::aNamespace::AllPropertiesTrait_v1>); } } - WHEN("an AnotherTrait trait view is requested") { const auto trait = specification.anotherTrait(); THEN("trait view has expected type") { - STATIC_REQUIRE(std::is_same_v< - decltype(trait), - const openassetio_traitgen_test_traits_only::traits::test::AnotherTrait>); + STATIC_REQUIRE( + std::is_same_v< + decltype(trait), + const openassetio_traitgen_test_traits_only::traits::test::AnotherTrait_v1>); } } } @@ -567,14 +651,13 @@ SCENARIO("Specification-provided trait views updating wrapped TraitsData") { const auto traitsData = openassetio_abi::trait::TraitsData::make(); AND_GIVEN("a specification wrapping the TraitsData") { - const openassetio_traitgen_test_all::specifications::test::LocalAndExternalTraitSpecification - specification{traitsData}; + const openassetio_traitgen_test_all::specifications::test:: + LocalAndExternalTraitSpecification_v1 specification{traitsData}; THEN("TraitsData has not yet been imbued") { CHECK(!traitsData->hasTrait( - openassetio_traitgen_test_traits_only::traits::aNamespace::NoPropertiesTrait::kId)); + openassetio_traitgen_test_traits_only::traits::aNamespace::NoPropertiesTrait_v1::kId)); } - AND_GIVEN("a trait view requested from the specification") { const auto trait = specification.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait(); @@ -584,10 +667,77 @@ SCENARIO("Specification-provided trait views updating wrapped TraitsData") { THEN("the specification's TraitsData is also updated") { CHECK(traitsData->hasTrait(openassetio_traitgen_test_traits_only::traits::aNamespace:: - NoPropertiesTrait::kId)); + NoPropertiesTrait_v1::kId)); } } } } } } + +SCENARIO("Specifications using different versions of traits") { + using openassetio_traitgen_test_all::specifications::test:: + MultipleVersionsOfTraitSpecification_v1; + using openassetio_traitgen_test_all::specifications::test:: + MultipleVersionsOfTraitSpecification_v2; + GIVEN("two versions of a specification that expose different versions of the same traits") { + using openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v1; + using openassetio_traitgen_test_all::traits::aNamespace::MultipleVersionsTrait_v2; + using openassetio_traitgen_test_all::traits::aNamespace::NoPropertiesTrait_v1; + + THEN("the two specifications expose the same methods but return different versioned traits") { + STATIC_REQUIRE( + std::is_same_v, + MultipleVersionsTrait_v1>); + STATIC_REQUIRE( + std::is_same_v, + MultipleVersionsTrait_v2>); + } + + AND_GIVEN("an unversioned specification type") { + using openassetio_traitgen_test_all::specifications::test:: + MultipleVersionsOfTraitSpecification; + + THEN("the trait set of the unversioned specification matches that of v1") { + CHECK(MultipleVersionsOfTraitSpecification::kTraitSet == + MultipleVersionsOfTraitSpecification_v1::kTraitSet); + CHECK(MultipleVersionsOfTraitSpecification::kTraitSet != + MultipleVersionsOfTraitSpecification_v2::kTraitSet); + } + + WHEN("unversioned specification is constructed") { + const auto traitsData = openassetio::trait::TraitsData::make(); + // Construct rather than using static_assert to be confident that + // the constructor is available. + const MultipleVersionsOfTraitSpecification specification{traitsData}; + + THEN("unversioned specification has same members as v1") { + // Just checking that the member function exists. + CHECK(!specification.multipleVersionsTrait().getOldProperty().has_value()); + } + } + + WHEN("unversioned specification is created") { + const auto specification = MultipleVersionsOfTraitSpecification::create(); + + THEN("unversioned specification is of the correct class") { + STATIC_REQUIRE(std::is_same_v, + MultipleVersionsOfTraitSpecification>); + } + } + } + } +} + +TEST_CASE("Deprecated specification causes deprecation compiler warning") { + using openassetio_traitgen_test_all::specifications::test::DeprecatedSpecification_v1; + // Ensure that the deprecated specification does indeed cause a + // deprecation warning if used. Note that to see this we must + // remove the diagnostic suppression. See + // openassetio-traitgentest-deprecations CTest target. + const auto spec = DeprecatedSpecification_v1::create(); +} + +// NOLINTEND(bugprone-chained-comparison) diff --git a/tests/generators/test_cpp.py b/tests/generators/test_cpp.py index 434259c..157abf7 100644 --- a/tests/generators/test_cpp.py +++ b/tests/generators/test_cpp.py @@ -21,7 +21,7 @@ # pylint: disable=invalid-name,redefined-outer-name # pylint: disable=too-few-public-methods,too-many-arguments # pylint: disable=missing-class-docstring,missing-function-docstring -# pylint: disable=too-many-return-statements +# pylint: disable=too-many-return-statements,too-many-lines import logging import os @@ -120,7 +120,8 @@ def test_docstring_contains_description(self, docstring_for): "openassetio_traitgen_test_all", is_specification=False, namespace="aNamespace", - cls="NoPropertiesTrait", + name="NoPropertiesTrait", + version="1", ) == """ /** @@ -137,7 +138,8 @@ def test_has_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=False, namespace="aNamespace", - cls="NoPropertiesMultipleUsageTrait", + name="NoPropertiesMultipleUsageTrait", + version="1", ) == """ /** @@ -155,7 +157,8 @@ def test_has_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=False, namespace="aNamespace", - cls="AllPropertiesTrait", + name="AllPropertiesTrait", + version="1", ) == """ /** @@ -175,7 +178,8 @@ def test_has_prefixed_property_getters_with_default_with_expected_docstring( "openassetio_traitgen_test_all", is_specification=False, namespace="aNamespace", - cls="AllPropertiesTrait", + name="AllPropertiesTrait", + version="1", func=function_name, has_default=False, ) @@ -200,7 +204,8 @@ def test_has_prefixed_property_getters_without_default_with_expected_docstring( "openassetio_traitgen_test_all", is_specification=False, namespace="aNamespace", - cls="AllPropertiesTrait", + name="AllPropertiesTrait", + version="1", func=function_name, has_default=True, ) @@ -226,7 +231,8 @@ def test_has_prefixed_property_setters_with_expected_docstring( "openassetio_traitgen_test_all", is_specification=False, namespace="aNamespace", - cls="AllPropertiesTrait", + name="AllPropertiesTrait", + version="1", func=function_name, ) == f""" @@ -239,20 +245,144 @@ def test_has_prefixed_property_setters_with_expected_docstring( ) +class Test_cpp_package_all_traits_aNamespace_MultipleVersionsTrait: + def test_v1_has_expected_docstring(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="MultipleVersionsTrait", + version="1", + ) + == """ +/** + * A trait with multiple versions, version 1. + * Usage: entity + */ + """.strip() + ) + + def test_v1_has_expected_property(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="MultipleVersionsTrait", + version="1", + func="getOldProperty", + has_default=False, + ) + == """ + /** + * Gets the value of the oldProperty property. Returns an empty + * optional if not found or is of an unexpected type. + * + * A deprecated string-typed property. + */ +""".strip() + ) + + def test_v2_has_expected_docstring(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="MultipleVersionsTrait", + version="2", + ) + == """ +/** + * A trait with multiple versions, version 2. + */ + """.strip() + ) + + def test_v2_has_expected_property(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="MultipleVersionsTrait", + version="2", + func="getNewProperty", + has_default=False, + ) + == """ + /** + * Gets the value of the newProperty property. Returns an empty + * optional if not found or is of an unexpected type. + * + * A new int-typed property. + */ +""".strip() + ) + + def test_unversioned_has_same_docstring_as_v1_but_with_deprecation(self, docstring_for): + v1_docstring = docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="MultipleVersionsTrait", + version="1", + ) + + expected_docstring = ( + v1_docstring[:-1] + + """ + * @deprecated Unversioned trait view classes are deprecated, please use + * MultipleVersionsTrait_v1 explicitly. + */""" + ) + + actual_docstring = docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="MultipleVersionsTrait", + version="", + ) + + assert actual_docstring == expected_docstring + + +class Test_cpp_package_all_traits_aNamespace_DeprecatedTrait: + def test_docstring_contains_deprecation_warning(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=False, + namespace="aNamespace", + name="DeprecatedTrait", + version="1", + ) + == """/** + * A deprecated trait. + * + * @deprecated This trait is flagged for future removal. + */""" + ) + + class Test_cpp_package_all_specifications_test_TwoLocalTraitsSpecification: def test_docstring_contains_description(self, docstring_for): docstring_for( "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="TwoLocalTraitsSpecification", + name="TwoLocalTraitsSpecification", + version="1", ) assert ( docstring_for( "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="TwoLocalTraitsSpecification", + name="TwoLocalTraitsSpecification", + version="1", ) == """ /** @@ -267,7 +397,8 @@ def test_has_trait_getters_with_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="TwoLocalTraitsSpecification", + name="TwoLocalTraitsSpecification", + version="1", func="aNamespaceNoPropertiesTrait", ) == """ @@ -283,7 +414,8 @@ def test_has_trait_getters_with_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="TwoLocalTraitsSpecification", + name="TwoLocalTraitsSpecification", + version="1", func="anotherNamespaceNoPropertiesTrait", ) == """ @@ -302,7 +434,8 @@ def test_docstring_contains_description(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="OneExternalTraitSpecification", + name="OneExternalTraitSpecification", + version="1", ) == """ /** @@ -317,7 +450,8 @@ def test_has_trait_getters_with_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="OneExternalTraitSpecification", + name="OneExternalTraitSpecification", + version="1", func="anotherTrait", ) == """ @@ -336,7 +470,8 @@ def test_docstring_contains_description(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="LocalAndExternalTraitSpecification", + name="LocalAndExternalTraitSpecification", + version="1", ) == """ /** @@ -352,7 +487,8 @@ def test_has_trait_getters_with_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="LocalAndExternalTraitSpecification", + name="LocalAndExternalTraitSpecification", + version="1", func="openassetioTraitgenTestAllANamespaceNoPropertiesTrait", ) == """ @@ -368,7 +504,8 @@ def test_has_trait_getters_with_expected_docstring(self, docstring_for): "openassetio_traitgen_test_all", is_specification=True, namespace="test", - cls="LocalAndExternalTraitSpecification", + name="LocalAndExternalTraitSpecification", + version="1", func="openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait", ) == """ @@ -380,6 +517,124 @@ def test_has_trait_getters_with_expected_docstring(self, docstring_for): ) +class Test_cpp_package_all_specifications_test_MultipleVersionsOfTrait: + def test_v1_docstring_contains_description(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="MultipleVersionsOfTraitSpecification", + version="1", + ) + == """ +/** + * Version 1 of a specification referencing version 1 of a trait. + * Usage: entity + */ +""".strip() + ) + + def test_v1_has_trait_getter_with_expected_docstring(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="MultipleVersionsOfTraitSpecification", + version="1", + func="multipleVersionsTrait", + ) + == """ + /** + * Returns the view for the 'openassetio-traitgen-test-all:aNamespace.MultipleVersions' + * trait wrapped around the data held in this instance. + */ +""".strip() + ) + + def test_v2_docstring_contains_description(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="MultipleVersionsOfTraitSpecification", + version="2", + ) + == """ +/** + * Version 2 of a specification referencing version 2 of a trait. + */ +""".strip() + ) + + def test_v2_has_trait_getter_with_expected_docstring(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="MultipleVersionsOfTraitSpecification", + version="2", + func="multipleVersionsTrait", + ) + == """ + /** + * Returns the view for the 'openassetio-traitgen-test-all:aNamespace.MultipleVersions.v2' + * trait wrapped around the data held in this instance. + */ +""".strip() + ) + + def test_unversioned_specification_has_same_docstring_as_v1_but_with_deprecation( + self, docstring_for + ): + v1_docstring = docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="MultipleVersionsOfTraitSpecification", + version="1", + ) + + expected_docstring = ( + v1_docstring[:-1] + + """ + * @deprecated Unversioned specification view classes are deprecated, + * please use MultipleVersionsOfTraitSpecification_v1 explicitly. + */""" + ) + + actual_docstring = docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="MultipleVersionsOfTraitSpecification", + version="", + ) + + assert actual_docstring == expected_docstring + + +class Test_cpp_package_all_specifications_test_DeprecatedSpecification: + def test_docstring_contains_deprecation_warning(self, docstring_for): + assert ( + docstring_for( + "openassetio_traitgen_test_all", + is_specification=True, + namespace="test", + name="DeprecatedSpecification", + version="1", + ) + == """/** + * A deprecated specification. + * + * @deprecated This specification is flagged for future removal. + */""" + ) + + class Test_generate: def test_when_files_created_then_creation_callback_is_called( self, declaration_exotic_values, creations_exotic_values, tmp_path_factory @@ -530,13 +785,17 @@ def docstring_for(rootnode_for, cpp_language): docstring_for( "my_package", is_specification=False, namespace="my_namespace") + + The exception to this is the name and version, which must be + supplied together (or not at all). """ def fn( package_name, is_specification=None, namespace=None, - cls=None, + name=None, + version=None, func=None, has_default=None, ): @@ -546,7 +805,8 @@ def fn( specification or a trait sub-package, if any. @param namespace: Which C++ namespace to parse docstring from, if any. - @param cls: Which class to parse docstring from, if any. + @param name: The name of a trait/specification. + @param version: The version of the trait/specification. @param func: Which function within the `cls` to parse docstring from, if any. @param has_default: If True/False then signals that `func` has @@ -555,7 +815,8 @@ def fn( overload. @return: String containing docstring for selected element. """ - root_node = rootnode_for(package_name, is_specification, namespace, cls) + # pylint: disable=too-many-locals + root_node = rootnode_for(package_name, is_specification, namespace, name) if is_specification is None: # Top-level package docstring @@ -567,15 +828,16 @@ def fn( query = cpp_language.query("""(translation_unit . (comment) @docstring)""") return query.captures(root_node)[0][0].text.decode() - if cls is None: + if name is None: # Namespace docstring. query = cpp_language.query("""((comment) . (preproc_include)) @docstring""") return query.captures(root_node)[0][0].text.decode() + class_name = f"{name}_v{version}" if version else name query = cpp_language.query( f"""( (comment) @docstring . (class_specifier name: (type_identifier) @struct_name) @struct - (#eq? @struct_name "{cls}") + (#eq? @struct_name "{class_name}") )""" ) diff --git a/tests/generators/test_helpers.py b/tests/generators/test_helpers.py index f6e9056..f7d066e 100644 --- a/tests/generators/test_helpers.py +++ b/tests/generators/test_helpers.py @@ -92,6 +92,8 @@ def some_trait_declarations(): datamodel.TraitDeclaration( id="package:namespace.Name1", name="Name1", + deprecated=False, + version="1", description="A trait", properties=[], usage=[], @@ -99,6 +101,8 @@ def some_trait_declarations(): datamodel.TraitDeclaration( id="package:namespace.Name2", name="Name2", + deprecated=False, + version="1", description="Another trait", properties=[], usage=[], @@ -111,6 +115,8 @@ def some_specification_declarations(): return [ datamodel.SpecificationDeclaration( id="Specification1", + deprecated=False, + version="1", description="A specification", usage=[], trait_set=[ @@ -118,6 +124,7 @@ def some_specification_declarations(): package="packageB", namespace="namespace", name="cat", + version="1", unique_name_parts=("cat",), id="packageB:namespace.cat", ), @@ -125,6 +132,7 @@ def some_specification_declarations(): package="packageA", namespace="namespace", name="hat", + version="1", unique_name_parts=("hat",), id="packageA:namespace.hat", ), @@ -132,6 +140,8 @@ def some_specification_declarations(): ), datamodel.SpecificationDeclaration( id="Specification2", + deprecated=False, + version="1", description="Another specification", usage=[], trait_set=[ @@ -139,6 +149,7 @@ def some_specification_declarations(): package="packageB", namespace="namespace", name="cat", + version="1", unique_name_parts=("cat",), id="packageB:namespace.cat", ), @@ -146,6 +157,7 @@ def some_specification_declarations(): package="packageC", namespace="namespace", name="mouse", + version="1", unique_name_parts=("mouse",), id="packageC:namespace.mouse", ), diff --git a/tests/generators/test_python.py b/tests/generators/test_python.py index 2d12376..3d09a21 100644 --- a/tests/generators/test_python.py +++ b/tests/generators/test_python.py @@ -87,18 +87,18 @@ def test_anotherNamespace_docstring_contains_declaration_description(self, modul ) def test_aNamespace_module_traits_are_suffixed_with_Trait(self, module_all): - assert inspect.isclass(module_all.traits.aNamespace.NoPropertiesTrait) - assert inspect.isclass(module_all.traits.aNamespace.NoPropertiesMultipleUsageTrait) - assert inspect.isclass(module_all.traits.aNamespace.AllPropertiesTrait) + assert inspect.isclass(module_all.traits.aNamespace.NoPropertiesTrait_v1) + assert inspect.isclass(module_all.traits.aNamespace.NoPropertiesMultipleUsageTrait_v1) + assert inspect.isclass(module_all.traits.aNamespace.AllPropertiesTrait_v1) def test_anotherNamespace_module_traits_are_suffixed_with_Trait(self, module_all): - assert inspect.isclass(module_all.traits.anotherNamespace.NoPropertiesTrait) + assert inspect.isclass(module_all.traits.anotherNamespace.NoPropertiesTrait_v1) class Test_python_package_all_traits_aNamespace_NoPropertiesTrait: def test_docstring_contains_description(self, module_all): assert ( - module_all.traits.aNamespace.NoPropertiesTrait.__doc__ + module_all.traits.aNamespace.NoPropertiesTrait_v1.__doc__ == """ Another trait, this time with no properties. """ @@ -106,7 +106,7 @@ def test_docstring_contains_description(self, module_all): def test_kId_is_declaration_id(self, module_all): assert ( - module_all.traits.aNamespace.NoPropertiesTrait.kId + module_all.traits.aNamespace.NoPropertiesTrait_v1.kId == "openassetio-traitgen-test-all:aNamespace.NoProperties" ) @@ -114,7 +114,7 @@ def test_kId_is_declaration_id(self, module_all): class Test_python_package_all_traits_aNamespace_NoPropertiesMultipleUsageTrait: def test_has_expected_docstring(self, module_all): assert ( - module_all.traits.aNamespace.NoPropertiesMultipleUsageTrait.__doc__ + module_all.traits.aNamespace.NoPropertiesMultipleUsageTrait_v1.__doc__ == """ Another trait, this time with multiple usage. Usage: entity, relationship @@ -123,7 +123,7 @@ def test_has_expected_docstring(self, module_all): def test_kId_is_declaration_id(self, module_all): assert ( - module_all.traits.aNamespace.NoPropertiesMultipleUsageTrait.kId + module_all.traits.aNamespace.NoPropertiesMultipleUsageTrait_v1.kId == "openassetio-traitgen-test-all:aNamespace.NoPropertiesMultipleUsage" ) @@ -131,7 +131,7 @@ def test_kId_is_declaration_id(self, module_all): class Test_python_package_all_traits_aNamespace_AllPropertiesTrait: def test_has_expected_docstring(self, module_all): assert ( - module_all.traits.aNamespace.AllPropertiesTrait.__doc__ + module_all.traits.aNamespace.AllPropertiesTrait_v1.__doc__ == """ A trait with properties of all types. """ @@ -139,7 +139,7 @@ def test_has_expected_docstring(self, module_all): def test_kId_is_declaration_id(self, module_all): assert ( - module_all.traits.aNamespace.AllPropertiesTrait.kId + module_all.traits.aNamespace.AllPropertiesTrait_v1.kId == "openassetio-traitgen-test-all:aNamespace.AllProperties" ) @@ -149,7 +149,7 @@ def test_has_prefixed_property_getters_with_expected_docstring( ): property_name = f"{property_type}Property" function_name = f"get{property_type.capitalize()}Property" - function = getattr(module_all.traits.aNamespace.AllPropertiesTrait, function_name) + function = getattr(module_all.traits.aNamespace.AllPropertiesTrait_v1, function_name) assert inspect.isfunction(function) assert ( @@ -167,7 +167,7 @@ def test_has_prefixed_property_setters_with_expected_docstring( ): property_name = f"{property_type}Property" function_name = f"set{property_type.capitalize()}Property" - function = getattr(module_all.traits.aNamespace.AllPropertiesTrait, function_name) + function = getattr(module_all.traits.aNamespace.AllPropertiesTrait_v1, function_name) assert inspect.isfunction(function) assert ( @@ -195,15 +195,17 @@ def test_test_docstring_contains_declaration_description(self, module_all): ) def test_test_module_specifications_are_suffixed_with_Specification(self, module_all): - assert inspect.isclass(module_all.specifications.test.TwoLocalTraitsSpecification) - assert inspect.isclass(module_all.specifications.test.OneExternalTraitSpecification) - assert inspect.isclass(module_all.specifications.test.LocalAndExternalTraitSpecification) + assert inspect.isclass(module_all.specifications.test.TwoLocalTraitsSpecification_v1) + assert inspect.isclass(module_all.specifications.test.OneExternalTraitSpecification_v1) + assert inspect.isclass( + module_all.specifications.test.LocalAndExternalTraitSpecification_v1 + ) class Test_python_package_all_specifications_test_TwoLocalTraitsSpecification: def test_docstring_contains_description(self, module_all): assert ( - module_all.specifications.test.TwoLocalTraitsSpecification.__doc__ + module_all.specifications.test.TwoLocalTraitsSpecification_v1.__doc__ == """ A specification with two traits. """ @@ -211,21 +213,19 @@ def test_docstring_contains_description(self, module_all): def test_trait_set_composes_target_trait_kIds(self, module_all): expected = { - module_all.traits.aNamespace.NoPropertiesTrait.kId, - module_all.traits.anotherNamespace.NoPropertiesTrait.kId, + module_all.traits.aNamespace.NoPropertiesTrait_v1.kId, + module_all.traits.anotherNamespace.NoPropertiesTrait_v1.kId, } - assert module_all.specifications.test.TwoLocalTraitsSpecification.kTraitSet == expected + assert module_all.specifications.test.TwoLocalTraitsSpecification_v1.kTraitSet == expected def test_has_trait_getters_with_expected_docstring(self, module_all): - trait_one = module_all.traits.aNamespace.NoPropertiesTrait - trait_two = module_all.traits.anotherNamespace.NoPropertiesTrait + trait_one = module_all.traits.aNamespace.NoPropertiesTrait_v1 + trait_two = module_all.traits.anotherNamespace.NoPropertiesTrait_v1 + test = module_all.specifications.test - assert inspect.isfunction( - module_all.specifications.test.TwoLocalTraitsSpecification.aNamespaceNoPropertiesTrait - ) + assert inspect.isfunction(test.TwoLocalTraitsSpecification_v1.aNamespaceNoPropertiesTrait) assert ( - # pylint: disable=line-too-long - module_all.specifications.test.TwoLocalTraitsSpecification.aNamespaceNoPropertiesTrait.__doc__ + test.TwoLocalTraitsSpecification_v1.aNamespaceNoPropertiesTrait.__doc__ == f""" Returns the view for the '{trait_one.kId}' trait wrapped around the data held in this instance. @@ -233,12 +233,10 @@ def test_has_trait_getters_with_expected_docstring(self, module_all): ) assert inspect.isfunction( - # pylint: disable=line-too-long - module_all.specifications.test.TwoLocalTraitsSpecification.anotherNamespaceNoPropertiesTrait + test.TwoLocalTraitsSpecification_v1.anotherNamespaceNoPropertiesTrait ) assert ( - # pylint: disable=line-too-long - module_all.specifications.test.TwoLocalTraitsSpecification.anotherNamespaceNoPropertiesTrait.__doc__ + test.TwoLocalTraitsSpecification_v1.anotherNamespaceNoPropertiesTrait.__doc__ == f""" Returns the view for the '{trait_two.kId}' trait wrapped around the data held in this instance. @@ -249,7 +247,7 @@ def test_has_trait_getters_with_expected_docstring(self, module_all): class Test_python_package_all_specifications_test_OneExternalTraitSpecification: def test_docstring_contains_description(self, module_all): assert ( - module_all.specifications.test.OneExternalTraitSpecification.__doc__ + module_all.specifications.test.OneExternalTraitSpecification_v1.__doc__ == """ A specification referencing traits in another package. """ @@ -257,18 +255,20 @@ def test_docstring_contains_description(self, module_all): def test_trait_set_composes_target_trait_kIds(self, module_all, module_traits_only): expected = { - module_traits_only.traits.test.AnotherTrait.kId, + module_traits_only.traits.test.AnotherTrait_v1.kId, } - assert module_all.specifications.test.OneExternalTraitSpecification.kTraitSet == expected + assert ( + module_all.specifications.test.OneExternalTraitSpecification_v1.kTraitSet == expected + ) def test_has_trait_getters_with_expected_docstring(self, module_all, module_traits_only): - trait = module_traits_only.traits.test.AnotherTrait + trait = module_traits_only.traits.test.AnotherTrait_v1 assert inspect.isfunction( - module_all.specifications.test.OneExternalTraitSpecification.anotherTrait + module_all.specifications.test.OneExternalTraitSpecification_v1.anotherTrait ) assert ( - module_all.specifications.test.OneExternalTraitSpecification.anotherTrait.__doc__ + module_all.specifications.test.OneExternalTraitSpecification_v1.anotherTrait.__doc__ == f""" Returns the view for the '{trait.kId}' trait wrapped around the data held in this instance. @@ -279,7 +279,7 @@ def test_has_trait_getters_with_expected_docstring(self, module_all, module_trai class Test_python_package_all_specifications_test_LocalAndExternalTraitSpecification: def test_docstring_contains_description(self, module_all): assert ( - module_all.specifications.test.LocalAndExternalTraitSpecification.__doc__ + module_all.specifications.test.LocalAndExternalTraitSpecification_v1.__doc__ == """ A specification referencing traits in this and another package. Usage: entity, managementPolicy @@ -288,37 +288,32 @@ def test_docstring_contains_description(self, module_all): def test_trait_set_composes_target_trait_kIds(self, module_all, module_traits_only): expected = { - module_all.traits.aNamespace.NoPropertiesTrait.kId, - module_traits_only.traits.aNamespace.NoPropertiesTrait.kId, + module_all.traits.aNamespace.NoPropertiesTrait_v1.kId, + module_traits_only.traits.aNamespace.NoPropertiesTrait_v1.kId, } assert ( - module_all.specifications.test.LocalAndExternalTraitSpecification.kTraitSet == expected + module_all.specifications.test.LocalAndExternalTraitSpecification_v1.kTraitSet + == expected ) def test_has_trait_getters_with_expected_docstring(self, module_all, module_traits_only): - trait_one = module_all.traits.aNamespace.NoPropertiesTrait - trait_two = module_traits_only.traits.aNamespace.NoPropertiesTrait + trait_one = module_all.traits.aNamespace.NoPropertiesTrait_v1 + trait_two = module_traits_only.traits.aNamespace.NoPropertiesTrait_v1 + spec = module_all.specifications.test.LocalAndExternalTraitSpecification_v1 - assert inspect.isfunction( - # pylint: disable=line-too-long - module_all.specifications.test.LocalAndExternalTraitSpecification.openassetioTraitgenTestAllANamespaceNoPropertiesTrait - ) + assert inspect.isfunction(spec.openassetioTraitgenTestAllANamespaceNoPropertiesTrait) assert ( - # pylint: disable=line-too-long - module_all.specifications.test.LocalAndExternalTraitSpecification.openassetioTraitgenTestAllANamespaceNoPropertiesTrait.__doc__ + spec.openassetioTraitgenTestAllANamespaceNoPropertiesTrait.__doc__ == f""" Returns the view for the '{trait_one.kId}' trait wrapped around the data held in this instance. """ ) - assert inspect.isfunction( - # pylint: disable=line-too-long - module_all.specifications.test.LocalAndExternalTraitSpecification.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait + spec.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait ) assert ( - # pylint: disable=line-too-long - module_all.specifications.test.LocalAndExternalTraitSpecification.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait.__doc__ + spec.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait.__doc__ == f""" Returns the view for the '{trait_two.kId}' trait wrapped around the data held in this instance. @@ -338,8 +333,8 @@ def test_no_specifications_module(self, module_traits_only): module_traits_only.specifications # pylint: disable=pointless-statement def test_traits_are_suffixed_with_Trait(self, module_traits_only): - assert inspect.isclass(module_traits_only.traits.test.AnotherTrait) - assert inspect.isclass(module_traits_only.traits.aNamespace.NoPropertiesTrait) + assert inspect.isclass(module_traits_only.traits.test.AnotherTrait_v1) + assert inspect.isclass(module_traits_only.traits.aNamespace.NoPropertiesTrait_v1) class Test_python_package_specifications_only: @@ -354,7 +349,7 @@ def test_no_traits_module(self, module_specifications_only): module_specifications_only.traits # pylint: disable=pointless-statement def test_specifications_are_suffixed_with_Specification(self, module_specifications_only): - assert inspect.isclass(module_specifications_only.specifications.test.SomeSpecification) + assert inspect.isclass(module_specifications_only.specifications.test.SomeSpecification_v1) # @@ -544,6 +539,114 @@ def test_when_type_is_wrong_then_TypeError_is_raised( ) +class Test_MultipleVersionsTrait: + def test_version_1_has_expected_id(self, module_all): + assert module_all.traits.aNamespace.MultipleVersionsTrait_v1.kId == ( + "openassetio-traitgen-test-all:aNamespace.MultipleVersions" + ) + + def test_version_2_has_expected_id(self, module_all): + assert module_all.traits.aNamespace.MultipleVersionsTrait_v2.kId == ( + "openassetio-traitgen-test-all:aNamespace.MultipleVersions.v2" + ) + + def test_version_1_has_expected_docstring(self, module_all): + assert ( + module_all.traits.aNamespace.MultipleVersionsTrait_v1.__doc__ + == """ + A trait with multiple versions, version 1. + Usage: entity + """ + ) + + def test_version_2_has_expected_docstring(self, module_all): + assert ( + module_all.traits.aNamespace.MultipleVersionsTrait_v2.__doc__ + == """ + A trait with multiple versions, version 2. + """ + ) + + def test_version_1_has_expected_property(self, module_all): + assert hasattr(module_all.traits.aNamespace.MultipleVersionsTrait_v1, "getOldProperty") + assert not hasattr(module_all.traits.aNamespace.MultipleVersionsTrait_v1, "getNewProperty") + + def test_version_2_has_expected_property(self, module_all): + assert not hasattr(module_all.traits.aNamespace.MultipleVersionsTrait_v2, "getOldProperty") + assert hasattr(module_all.traits.aNamespace.MultipleVersionsTrait_v2, "getNewProperty") + + def test_unversioned_is_version_1(self, module_all): + assert issubclass( + module_all.traits.aNamespace.MultipleVersionsTrait, + module_all.traits.aNamespace.MultipleVersionsTrait_v1, + ) + + # Create an empty class and get its __dict__ keys. + builtin_attr_names = set(type("Empty", (), {}).__dict__.keys()) + + # Get attributes defined on subclass, minus builtin attrs. + user_defined_attr_names = [ + attr_name + for attr_name in module_all.traits.aNamespace.MultipleVersionsTrait.__dict__ + if attr_name not in builtin_attr_names + ] + # Ensure no overrides of the base class, other than constructor + # (for deprecation warning). + assert user_defined_attr_names == ["__init__"] + + def test_unversioned_has_same_docstring_as_version_1_but_with_deprecation(self, module_all): + assert ( + module_all.traits.aNamespace.MultipleVersionsTrait.__doc__ + == module_all.traits.aNamespace.MultipleVersionsTrait_v1.__doc__.rstrip() + + """ + + @deprecated Unversioned trait view classes are deprecated, please + use MultipleVersionsTrait_v1 explicitly. + """ + ) + assert ( + module_all.traits.aNamespace.MultipleVersionsTrait.__doc__ + != module_all.traits.aNamespace.MultipleVersionsTrait_v2.__doc__ + ) + + def test_when_unversioned_constructed_then_logs_deprecation_warning(self, module_all): + expected_warning = ( + "Unversioned trait view classes are deprecated. Please switch from" + " MultipleVersionsTrait to MultipleVersionsTrait_v1." + ) + with pytest.deprecated_call(match=expected_warning): + module_all.traits.aNamespace.MultipleVersionsTrait(TraitsData()) + + def test_when_unversioned_constructed_then_calls_base_constructor(self, module_all): + data = TraitsData() + trait = module_all.traits.aNamespace.MultipleVersionsTrait(data) + expected_value = "some string" + # Ensure TraitsData is passed through (i.e. no AttributeError). + trait.setOldProperty(expected_value) + actual_value = trait.getOldProperty() + + assert actual_value == expected_value + + +class Test_DeprecatedTrait: + def test_when_constructed_then_logs_deprecation_warning(self, module_all): + expected_warning = ( + "The 'openassetio-traitgen-test-all:aNamespace.Deprecated' trait is deprecated." + ) + with pytest.deprecated_call(match=expected_warning): + module_all.traits.aNamespace.DeprecatedTrait_v1(TraitsData()) + + def test_docstring_contains_deprecation_notice(self, module_all): + assert ( + module_all.traits.aNamespace.DeprecatedTrait_v1.__doc__ + == """ + A deprecated trait. + + @deprecated This trait is flagged for future removal. + """ + ) + + class Test_LocalAndExternalTraitSpecification: def test_external_trait_accessor_is_of_expected_type( self, local_and_external_trait_specification, module_traits_only @@ -551,7 +654,7 @@ def test_external_trait_accessor_is_of_expected_type( a_specification = local_and_external_trait_specification(TraitsData()) assert isinstance( a_specification.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait(), - module_traits_only.traits.aNamespace.NoPropertiesTrait, + module_traits_only.traits.aNamespace.NoPropertiesTrait_v1, ) def test_external_trait_instance_wraps_specifications_traits_data( @@ -561,7 +664,8 @@ def test_external_trait_instance_wraps_specifications_traits_data( a_specification = local_and_external_trait_specification(a_traits_data) a_trait = a_specification.openassetioTraitgenTestTraitsOnlyANamespaceNoPropertiesTrait() assert ( - a_trait._NoPropertiesTrait__data is a_traits_data # pylint: disable=protected-access + a_trait._NoPropertiesTrait_v1__data # pylint: disable=protected-access + is a_traits_data ) def test_package_local_trait_accessor_is_of_expected_type( @@ -570,7 +674,7 @@ def test_package_local_trait_accessor_is_of_expected_type( a_specification = local_and_external_trait_specification(TraitsData()) assert isinstance( a_specification.openassetioTraitgenTestAllANamespaceNoPropertiesTrait(), - module_all.traits.aNamespace.NoPropertiesTrait, + module_all.traits.aNamespace.NoPropertiesTrait_v1, ) def test_local_trait_instance_wraps_specifications_traits_data( @@ -580,7 +684,8 @@ def test_local_trait_instance_wraps_specifications_traits_data( a_specification = local_and_external_trait_specification(a_traits_data) a_trait = a_specification.openassetioTraitgenTestAllANamespaceNoPropertiesTrait() assert ( - a_trait._NoPropertiesTrait__data is a_traits_data # pylint: disable=protected-access + a_trait._NoPropertiesTrait_v1__data # pylint: disable=protected-access + is a_traits_data ) def test_default_constructor_raises_error(self, local_and_external_trait_specification): @@ -603,6 +708,111 @@ def test_all_traits_set_in_data(self, local_and_external_trait_specification): assert data.traitSet() == local_and_external_trait_specification.kTraitSet +class Test_MultipleVersionsOfTraitSpecification: + + def test_version_1_has_version_1_of_trait(self, module_all): + assert ( + module_all.specifications.test.MultipleVersionsOfTraitSpecification_v1.kTraitSet + == { + module_all.traits.aNamespace.MultipleVersionsTrait_v1.kId, + module_all.traits.aNamespace.NoPropertiesTrait_v1.kId, + } + ) + + def test_version_2_has_version_2_of_trait(self, module_all): + assert ( + module_all.specifications.test.MultipleVersionsOfTraitSpecification_v2.kTraitSet + == { + module_all.traits.aNamespace.MultipleVersionsTrait_v2.kId, + module_all.traits.aNamespace.NoPropertiesTrait_v1.kId, + } + ) + + def test_unversioned_is_version_1(self, module_all): + assert issubclass( + module_all.specifications.test.MultipleVersionsOfTraitSpecification, + module_all.specifications.test.MultipleVersionsOfTraitSpecification_v1, + ) + + # Create an empty class and get its __dict__ keys. + builtin_attr_names = set(type("Empty", (), {}).__dict__.keys()) + + # Get attributes defined on subclass, minus builtin attrs. + user_defined_attr_names = [ + attr_name + for attr_name in module_all.traits.aNamespace.MultipleVersionsTrait.__dict__ + if attr_name not in builtin_attr_names + ] + # Ensure no overrides of the base class, other than constructor + # (for deprecation warning). + assert user_defined_attr_names == ["__init__"] + + def test_unversioned_has_same_docstring_as_version_1_but_with_deprecation(self, module_all): + spec = module_all.specifications.test.MultipleVersionsOfTraitSpecification + spec_v1 = module_all.specifications.test.MultipleVersionsOfTraitSpecification_v1 + assert ( + spec.__doc__ + == spec_v1.__doc__.rstrip() + + """ + + @deprecated Unversioned specification view classes are deprecated, + please use MultipleVersionsOfTraitSpecification_v1 explicitly. + """ + ) + assert ( + module_all.specifications.test.MultipleVersionsOfTraitSpecification.__doc__ + != module_all.specifications.test.MultipleVersionsOfTraitSpecification_v2.__doc__ + ) + + def test_when_unversioned_constructed_then_logs_deprecation_warning(self, module_all): + expected_warning = ( + "Unversioned specification view classes are deprecated. Please switch from" + " MultipleVersionsOfTraitSpecification to MultipleVersionsOfTraitSpecification_v1." + ) + with pytest.deprecated_call(match=expected_warning): + module_all.specifications.test.MultipleVersionsOfTraitSpecification(TraitsData()) + + with pytest.deprecated_call(match=expected_warning): + module_all.specifications.test.MultipleVersionsOfTraitSpecification.create() + + def test_when_unversioned_created_then_is_correct_instance(self, module_all): + spec = module_all.specifications.test.MultipleVersionsOfTraitSpecification.create() + + assert isinstance( + spec, module_all.specifications.test.MultipleVersionsOfTraitSpecification + ) + + def test_when_unversioned_constructed_then_calls_base_constructor(self, module_all): + data = TraitsData() + spec = module_all.specifications.test.MultipleVersionsOfTraitSpecification(data) + expected_value = "some string" + # Ensure TraitsData is passed through (i.e. no AttributeError). + spec.multipleVersionsTrait().setOldProperty(expected_value) + actual_value = spec.multipleVersionsTrait().getOldProperty() + + assert actual_value == expected_value + + +class Test_DeprecatedSpecification: + def test_when_constructed_then_logs_deprecation_warning(self, module_all): + expected_warning = ( + "The 'test.Deprecated' specification of the 'openassetio_traitgen_test_all' package is" + " deprecated." + ) + with pytest.deprecated_call(match=expected_warning): + module_all.specifications.test.DeprecatedSpecification_v1(TraitsData()) + + def test_docstring_contains_deprecation_notice(self, module_all): + assert ( + module_all.specifications.test.DeprecatedSpecification_v1.__doc__ + == """ + A deprecated specification. + + @deprecated This specification is flagged for future removal. + """ + ) + + class Test_generate: def test_when_files_created_then_creation_callback_is_called( self, declaration_exotic_values, creations_exotic_values, tmp_path_factory @@ -717,7 +927,7 @@ def module_specifications_only(extended_python_path): @pytest.fixture def all_properties_trait(module_all): - return module_all.traits.aNamespace.AllPropertiesTrait + return module_all.traits.aNamespace.AllPropertiesTrait_v1 @pytest.fixture @@ -727,12 +937,12 @@ def an_empty_traitsData(): @pytest.fixture def an_all_properties_traitsData(module_all): - return TraitsData({module_all.traits.aNamespace.AllPropertiesTrait.kId}) + return TraitsData({module_all.traits.aNamespace.AllPropertiesTrait_v1.kId}) @pytest.fixture def local_and_external_trait_specification(module_all): - return module_all.specifications.test.LocalAndExternalTraitSpecification + return module_all.specifications.test.LocalAndExternalTraitSpecification_v1 @pytest.fixture @@ -759,8 +969,4 @@ def warnings_exotic_values(): (logging.WARNING, "Conforming 'p$' to 'p' for variable name"), (logging.WARNING, "Conforming 's!n' to 's_n' for module name"), (logging.WARNING, "Conforming 's^' to 'S' for class name"), - (logging.WARNING, "Conforming 't!n' to 't_n' for module name"), - (logging.WARNING, "Conforming 't&' to 'T' for class name"), - (logging.WARNING, "Conforming 't!n' to 't_n' for module name"), - (logging.WARNING, "Conforming 't&' to 'T' for class name"), ] diff --git a/tests/resources/invalid_values.yaml b/tests/resources/invalid_values.yaml index b0e7925..5564951 100644 --- a/tests/resources/invalid_values.yaml +++ b/tests/resources/invalid_values.yaml @@ -5,22 +5,27 @@ package: p-p description: p traits: - t!n: - description: n - members: - t&: - description: t - properties: - p$: - type: boolean - description: p + t!n: + description: n + members: + t&: + versions: + "1": + description: t + properties: + p$: + type: boolean + description: p specifications: - s!n: - description: n - members: - s^: - description: s - traitSet: - - namespace: t!n - name: t& + s!n: + description: n + members: + s^: + versions: + "1": + description: s + traitSet: + - namespace: t!n + name: t& + version: "1" diff --git a/tests/resources/minimal.yaml b/tests/resources/minimal.yaml index 844bb57..5036d4c 100644 --- a/tests/resources/minimal.yaml +++ b/tests/resources/minimal.yaml @@ -5,22 +5,27 @@ package: p-p description: p traits: - tn: - description: n - members: - T: - description: t - properties: - p: - type: boolean - description: p + tn: + description: n + members: + T: + versions: + "1": + description: t + properties: + p: + type: boolean + description: p specifications: - sn: - description: n - members: - S: - description: s - traitSet: - - namespace: tn - name: T + sn: + description: n + members: + S: + versions: + "1": + description: s + traitSet: + - namespace: tn + name: T + version: "1" diff --git a/tests/resources/openassetio-traitgen-test-all.yaml b/tests/resources/openassetio-traitgen-test-all.yaml index 5d07c16..243230d 100644 --- a/tests/resources/openassetio-traitgen-test-all.yaml +++ b/tests/resources/openassetio-traitgen-test-all.yaml @@ -3,71 +3,146 @@ package: openassetio-traitgen-test-all description: - Test classes to validate the integrity of the openassetio-traitgen tool. + Test classes to validate the integrity of the openassetio-traitgen tool. traits: - aNamespace: - description: A Namespace - members: - NoProperties: - description: Another trait, this time with no properties. + aNamespace: + description: A Namespace + members: + NoProperties: + versions: + "1": + description: Another trait, this time with no properties. - NoPropertiesMultipleUsage: - description: Another trait, this time with multiple usage. - usage: - - entity - - relationship + NoPropertiesMultipleUsage: + versions: + "1": + description: Another trait, this time with multiple usage. + usage: + - entity + - relationship - AllProperties: - description: A trait with properties of all types. - properties: - stringProperty: - type: string - description: A string-typed property. - intProperty: - type: integer - description: A int-typed property. - floatProperty: - type: float - description: A float-typed property. - boolProperty: - type: boolean - description: A bool-typed property. - # TODO(DF): Add dictProperty once supported. + AllProperties: + versions: + "1": + description: A trait with properties of all types. + properties: + stringProperty: + type: string + description: A string-typed property. + intProperty: + type: integer + description: A int-typed property. + floatProperty: + type: float + description: A float-typed property. + boolProperty: + type: boolean + description: A bool-typed property. + # TODO(DF): Add dictProperty once supported. - anotherNamespace: - description: Another Namespace - members: - NoProperties: - description: Another NoProperties trait in a different namespace + MultipleVersions: + versions: + "1": + description: A trait with multiple versions, version 1. + properties: + oldProperty: + type: string + description: A deprecated string-typed property. + usage: + - entity + "2": + description: A trait with multiple versions, version 2. + properties: + newProperty: + type: integer + description: A new int-typed property. + + Deprecated: + deprecated: true + versions: + "1": + description: A deprecated trait. + + anotherNamespace: + description: Another Namespace + members: + NoProperties: + versions: + "1": + description: Another NoProperties trait in a different namespace specifications: - test: - description: Test specifications. - members: - TwoLocalTraits: - description: A specification with two traits. - traitSet: - - namespace: aNamespace - name: NoProperties - - namespace: anotherNamespace - name: NoProperties + test: + description: Test specifications. + members: + TwoLocalTraits: + versions: + "1": + description: A specification with two traits. + traitSet: + - namespace: aNamespace + name: NoProperties + version: "1" + - namespace: anotherNamespace + name: NoProperties + version: "1" + + OneExternalTrait: + versions: + "1": + description: A specification referencing traits in another package. + traitSet: + - package: openassetio-traitgen-test-traits-only + namespace: test + name: Another + version: "1" + + LocalAndExternalTrait: + versions: + "1": + description: A specification referencing traits in this and another package. + traitSet: + - package: openassetio-traitgen-test-traits-only + namespace: aNamespace + name: NoProperties + version: "1" + - namespace: aNamespace + name: NoProperties + version: "1" + usage: + - entity + - managementPolicy - OneExternalTrait: - description: A specification referencing traits in another package. - traitSet: - - package: openassetio-traitgen-test-traits-only - namespace: test - name: Another + MultipleVersionsOfTrait: + versions: + "1": + description: Version 1 of a specification referencing version 1 of a trait. + traitSet: + - namespace: aNamespace + name: NoProperties + version: "1" + - namespace: aNamespace + name: MultipleVersions + version: "1" + usage: + - entity + "2": + description: Version 2 of a specification referencing version 2 of a trait. + traitSet: + - namespace: aNamespace + name: NoProperties + version: "1" + - namespace: aNamespace + name: MultipleVersions + version: "2" - LocalAndExternalTrait: - description: A specification referencing traits in this and another package. - traitSet: - - package: openassetio-traitgen-test-traits-only - namespace: aNamespace - name: NoProperties - - namespace: aNamespace - name: NoProperties - usage: - - entity - - managementPolicy + Deprecated: + deprecated: true + versions: + "1": + description: A deprecated specification. + traitSet: + - namespace: aNamespace + name: Deprecated + version: "1" diff --git a/tests/resources/openassetio-traitgen-test-specifications-only.yaml b/tests/resources/openassetio-traitgen-test-specifications-only.yaml index 09655de..937507d 100644 --- a/tests/resources/openassetio-traitgen-test-specifications-only.yaml +++ b/tests/resources/openassetio-traitgen-test-specifications-only.yaml @@ -3,19 +3,23 @@ package: openassetio-traitgen-test-specifications-only description: > - Test classes to validate the integrity of the openassetio-traitgen - tool when only specifications are defined. + Test classes to validate the integrity of the openassetio-traitgen + tool when only specifications are defined. specifications: - test: - description: More test specifications. - members: - Some: - description: Some specification - traitSet: - - package: openassetio-traitgen-test-all - namespace: aNamespace - name: AllProperties - - package: openassetio-traitgen-test-traits-only - namespace: test - name: Another + test: + description: More test specifications. + members: + Some: + versions: + "1": + description: Some specification + traitSet: + - package: openassetio-traitgen-test-all + namespace: aNamespace + name: AllProperties + version: "1" + - package: openassetio-traitgen-test-traits-only + namespace: test + name: Another + version: "1" diff --git a/tests/resources/openassetio-traitgen-test-traits-only.yaml b/tests/resources/openassetio-traitgen-test-traits-only.yaml index 0b58fa4..83ece2d 100644 --- a/tests/resources/openassetio-traitgen-test-traits-only.yaml +++ b/tests/resources/openassetio-traitgen-test-traits-only.yaml @@ -3,21 +3,25 @@ package: openassetio-traitgen-test-traits-only description: > - Test classes to validate the integrity of the openassetio-traitgen tool when only traits are - defined. + Test classes to validate the integrity of the openassetio-traitgen tool when only traits are + defined. traits: - test: - description: A namespace for testing. - members: - Another: - description: Yet Another Trait - usage: - - managementPolicy - aNamespace: - description: A namespace that overlaps with the all package - members: - NoProperties: - description: Yet Another No Properties Trait - usage: - - managementPolicy + test: + description: A namespace for testing. + members: + Another: + versions: + "1": + description: Yet Another Trait + usage: + - managementPolicy + aNamespace: + description: A namespace that overlaps with the all package + members: + NoProperties: + versions: + "1": + description: Yet Another No Properties Trait + usage: + - managementPolicy diff --git a/tests/resources/parser_load_test.yaml b/tests/resources/parser_load_test.yaml index 19184df..5eec58a 100644 --- a/tests/resources/parser_load_test.yaml +++ b/tests/resources/parser_load_test.yaml @@ -1,4 +1,4 @@ # A comment topLevelProperty: 1 nested: - property: A property + property: A property diff --git a/tests/test___init__.py b/tests/test___init__.py index 0b39a7d..769403c 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -298,14 +298,20 @@ def structure_all_log_messages(): (logging.INFO, "Package: openassetio-traitgen-test-all"), (logging.INFO, "Traits:"), (logging.INFO, "aNamespace:"), - (logging.INFO, " - AllProperties"), - (logging.INFO, " - NoProperties"), - (logging.INFO, " - NoPropertiesMultipleUsage"), + (logging.INFO, " - AllProperties (v1)"), + (logging.INFO, " - Deprecated (v1)"), + (logging.INFO, " - MultipleVersions (v1)"), + (logging.INFO, " - MultipleVersions (v2)"), + (logging.INFO, " - NoProperties (v1)"), + (logging.INFO, " - NoPropertiesMultipleUsage (v1)"), (logging.INFO, "anotherNamespace:"), - (logging.INFO, " - NoProperties"), + (logging.INFO, " - NoProperties (v1)"), (logging.INFO, "Specifications:"), (logging.INFO, "test:"), - (logging.INFO, " - LocalAndExternalTrait"), - (logging.INFO, " - OneExternalTrait"), - (logging.INFO, " - TwoLocalTraits"), + (logging.INFO, " - Deprecated (v1)"), + (logging.INFO, " - LocalAndExternalTrait (v1)"), + (logging.INFO, " - MultipleVersionsOfTrait (v1)"), + (logging.INFO, " - MultipleVersionsOfTrait (v2)"), + (logging.INFO, " - OneExternalTrait (v1)"), + (logging.INFO, " - TwoLocalTraits (v1)"), ]