diff --git a/docs/builders.rst b/docs/builders.rst index 6743e0942..06850967b 100644 --- a/docs/builders.rst +++ b/docs/builders.rst @@ -38,26 +38,6 @@ version(s) inside the **needs.json**. If you generate and store/archive (e.g. in git) the **needs.json** file every time you raise your documentation version, you will get a nice history data. -.. _filter_export: - -Exporting filters -+++++++++++++++++ - -.. versionadded:: 0.3.11 - -The results and filter configuration of a filter based directive, like :ref:`needlist`, :ref:`needtable` -or :ref:`needflow` gets exported, if the option :ref:`export_id` is used in the related directive. - -This allows to export specified filter results only. - -.. code-block:: rst - - .. needtable:: - :status: open - :filter: "test" in tags - :export_id: filter_01 - - .. _needs_builder_format: Format @@ -81,16 +61,6 @@ See also :ref:`needs_json_exclude_fields`, :ref:`needs_json_remove_defaults`, an "versions": { "1.0": { "created": "2017-07-03T11:54:42.433868", - "filters": { - "FILTER_1": { - "amount": 1, - "export_id": "FILTER_1", - "filter": "", - "result": ["IMPL_01"], - "status": [], - "tags": "", - "types": [] - }, "needs_schema": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -138,16 +108,6 @@ See also :ref:`needs_json_exclude_fields`, :ref:`needs_json_remove_defaults`, an }, "1.5": { "created": "2017-07-03T16:10:31.633425", - "filters": { - "FILTER_1": { - "amount": 1, - "export_id": "FILTER_1", - "filter": "", - "result": ["IMPL_01"], - "status": [], - "tags": "", - "types": [] - }, "needs_schema": { "id": { "description": "ID of the data.", diff --git a/docs/changelog.rst b/docs/changelog.rst index c7f6e95f5..a5ef90ff6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -788,7 +788,7 @@ custom css definitions you need to update them. ------ * Improvement: Added config option :ref:`needs_extra_links` to define additional link types like *blocks*, *tested by* and more. Supports also style configuration and custom presentation names for links. -* Improvement: Added :ref:`export_id` option for filter directives to export results of filters to ``needs.json``. +* Improvement: Added :ref:`!export_id` option for filter directives to export results of filters to ``needs.json``. * Improvement: Added config option :ref:`needs_flow_show_links` and related needflow option :ref:`needflow_show_link_names`. * Improvement: Added config option :ref:`needs_flow_link_types` and related needflow option :ref:`needflow_link_types`. * Bugfix: Unicode handling for Python 2.7 fixed. (:issue:`86`) diff --git a/docs/conf.py b/docs/conf.py index 11fdac7bb..2750ba993 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -288,6 +288,7 @@ # -- Options for Needs extension --------------------------------------- needs_debug_measurement = "READTHEDOCS" in os.environ # run on CI +needs_debug_filters = True needs_types = [ # Architecture types diff --git a/docs/configuration.rst b/docs/configuration.rst index 72500a19d..ec31f60d5 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -533,6 +533,15 @@ If set to False, the filter results contains the original need fields and any ma needs_allow_unsafe_filters = True +.. _needs_filter_max_time: + +needs_filter_max_time +~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.0.0 + +If set, warn if any :ref:`filter processing ` call takes longer than the given time in seconds. + .. _needs_flow_engine: needs_flow_engine @@ -2177,3 +2186,13 @@ See :ref:`runtime_debugging` for details. To activate it, set it to ``True``:: needs_debug_measurement = True + +.. _needs_debug_filters: + +needs_debug_filters +~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.0.0 + +If set to ``True``, all calls to :ref:`filter processing ` will be logged to a ``debug_filters.jsonl`` file in the build output directory, +appending a single-line JSON for each filter call. diff --git a/docs/filter.rst b/docs/filter.rst index 3eb084f76..05dcba849 100644 --- a/docs/filter.rst +++ b/docs/filter.rst @@ -212,6 +212,17 @@ To improve performance, certain common patterns are identified and optimized by Also filters containing ``and`` will be split into multiple filters and evaluated separately for the above patterns. For example, ``type == 'spec' and other == 'value'`` will first be filtered performantly by ``type == 'spec'`` and then the remaining needs will be filtered by ``other == 'value'``. +To guard against long running filters, the :ref:`needs_filter_max_time` configuration option can be used to set a maximum time limit for filter evaluation. + +To debug which filters are being used across your project and their run times, you can enable the :ref:`needs_debug_filters` configuration option. + +.. _export_id: +.. deprecated:: 4.0.0 + + The directive option ``export_id`` was previously used to export filter information in the ``needs.json`` file. + This is deprecated and will be removed in a future version. + Instead use the above :ref:`needs_debug_filters` configuration option. + .. _re_search: search @@ -234,23 +245,6 @@ The second parameter should be one of the above variables(status, id, content, . .. needlist:: :filter: search("([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", title) -.. _export_id: - -export_id -~~~~~~~~~ - -.. versionadded:: 0.3.11 - -If set, the filter results get exported to needs.json, if the builder :ref:`needs_builder` is used:: - - .. needtable:: - :status: open - :filter: "test" in tags - :export_id: filter_01 - -See :ref:`filter_export` for more details. - - .. _filter_code: Filter code diff --git a/sphinx_needs/builder.py b/sphinx_needs/builder.py index 9acbd179b..e8e7ea21c 100644 --- a/sphinx_needs/builder.py +++ b/sphinx_needs/builder.py @@ -9,7 +9,7 @@ from sphinx_needs.config import NeedsSphinxConfig from sphinx_needs.data import SphinxNeedsData -from sphinx_needs.logging import get_logger, log_warning +from sphinx_needs.logging import get_logger from sphinx_needs.needsfile import NeedsList LOGGER = get_logger(__name__) @@ -25,10 +25,6 @@ class NeedsBuilder(Builder): where all documents are post-transformed, to improve performance. It is assumed all need data is already read in the read phase, and the post-processing of the data is done in the finish phase. - - However, if the ``export_id`` option is set for any directives, - the write phase is still run, since this filter data is currently only added there. - A warning is issued in this case. """ name = "needs" @@ -45,15 +41,7 @@ def write( updated_docnames: Sequence[str], method: str = "update", ) -> None: - if not SphinxNeedsData(self.env).has_export_filters: - return - log_warning( - LOGGER, - "At least one use of `export_id` directive option, requires a slower build", - "build", - None, - ) - return super().write(build_docnames, updated_docnames, method) + return def finish(self) -> None: from sphinx_needs.filter_common import filter_needs_view @@ -61,7 +49,6 @@ def finish(self) -> None: data = SphinxNeedsData(self.env) needs = data.get_needs_view() needs_config = NeedsSphinxConfig(self.env.config) - filters = data.get_or_create_filters() version = getattr(self.env.config, "version", "unset") needs_list = NeedsList(self.env.config, self.outdir, self.srcdir) @@ -92,10 +79,6 @@ def finish(self) -> None: for need in filtered_needs: needs_list.add_need(version, need) - for need_filter in filters.values(): - if need_filter["export_id"]: - needs_list.add_filter(version, need_filter) - try: needs_list.write_json() except Exception as e: diff --git a/sphinx_needs/config.py b/sphinx_needs/config.py index 53ee2853c..37cb85891 100644 --- a/sphinx_needs/config.py +++ b/sphinx_needs/config.py @@ -384,6 +384,10 @@ def __setattr__(self, name: str, value: Any) -> None: default=False, metadata={"rebuild": "html", "types": (bool,)} ) """Allow filters to change the need data.""" + filter_max_time: int | float | None = field( + default=None, metadata={"rebuild": "html", "types": (type(None), int, float)} + ) + """Warn if a filter runs for longer than this time (in seconds).""" flow_engine: Literal["plantuml", "graphviz"] = field( default="plantuml", metadata={"rebuild": "env", "types": (str,)} ) @@ -525,9 +529,6 @@ def __setattr__(self, name: str, value: Any) -> None: ) """Jinja context for rendering templates""" - debug_measurement: bool = field( - default=False, metadata={"rebuild": "html", "types": (bool,)} - ) # add config for needs_id_builder build_json_per_id: bool = field( default=False, metadata={"rebuild": "html", "types": (bool,)} @@ -536,6 +537,15 @@ def __setattr__(self, name: str, value: Any) -> None: default="needs_id", metadata={"rebuild": "html", "types": (str,)} ) + debug_measurement: bool = field( + default=False, metadata={"rebuild": "html", "types": (bool,)} + ) + """If True, log runtime information for various functions.""" + debug_filters: bool = field( + default=False, metadata={"rebuild": "html", "types": (bool,)} + ) + """If True, log filter processing runtime information.""" + @classmethod def add_config_values(cls, app: Sphinx) -> None: """Add all config values to the Sphinx application.""" diff --git a/sphinx_needs/data.py b/sphinx_needs/data.py index 0ba3e6562..b6b62b553 100644 --- a/sphinx_needs/data.py +++ b/sphinx_needs/data.py @@ -33,23 +33,6 @@ LOGGER = getLogger(__name__) -class NeedsFilterType(TypedDict): - filter: str - """Filter string.""" - status: list[str] - tags: list[str] - types: list[str] - amount: int - export_id: str - """If set, the filter is exported with this ID in the needs.json file.""" - origin: str - """Origin of the request (e.g. needlist, needtable, needflow).""" - location: str - """Location of the request (e.g. "docname:lineno")""" - runtime: float - """Time take to run filter (seconds).""" - - class NeedsPartType(TypedDict): """Data for a single need part.""" @@ -529,7 +512,6 @@ class NeedsFilteredBaseType(NeedsBaseDataType): sort_by: None | str filter_code: list[str] filter_func: None | str - export_id: str filter_warning: str | None """If set, the filter is exported with this ID in the needs.json file.""" @@ -776,29 +758,6 @@ def get_needs_view(self) -> NeedsView: self.env._needs_view = NeedsView._from_needs(self._env_needs) return self.env._needs_view - @property - def has_export_filters(self) -> bool: - """Whether any filters have export IDs.""" - try: - return self.env.needs_filters_export_id - except AttributeError: - return False - - @has_export_filters.setter - def has_export_filters(self, value: bool) -> None: - self.env.needs_filters_export_id = value - - def get_or_create_filters(self) -> dict[str, NeedsFilterType]: - """Get all filters, mapped by ID. - - This is lazily created and cached in the environment. - """ - try: - return self.env.needs_all_filters - except AttributeError: - self.env.needs_all_filters = {} - return self.env.needs_all_filters - def get_or_create_docs(self) -> dict[str, list[str]]: """Get mapping of need category to docnames containing the need. @@ -897,13 +856,6 @@ def merge_data( this_data = SphinxNeedsData(env) other_data = SphinxNeedsData(other) - # update filters - if other_data.has_export_filters: - this_data.has_export_filters = True - # merge these just to be safe, - # although actually all should be added in the write phase - this_data.get_or_create_filters().update(other_data.get_or_create_filters()) - # Update needs needs = this_data._env_needs other_needs = other_data._env_needs diff --git a/sphinx_needs/debug.py b/sphinx_needs/debug.py index 1d50f8a7f..dbf05751a 100644 --- a/sphinx_needs/debug.py +++ b/sphinx_needs/debug.py @@ -17,8 +17,6 @@ from jinja2 import Environment, PackageLoader, select_autoescape from sphinx.application import Sphinx -from sphinx_needs.data import SphinxNeedsData - TIME_MEASUREMENTS: dict[str, Any] = {} # Stores the timing results EXECUTE_TIME_MEASUREMENTS = ( False # Will be used to de/activate measurements. Set during a Sphinx Event @@ -157,18 +155,6 @@ def _print_timing_results(app: Sphinx) -> None: print(f' max: {value["max"]:2f}') print(f' min: {value["min"]:2f} \n') - # print 10 slowest filters - filters = sorted( - SphinxNeedsData(app.env).get_or_create_filters().values(), - key=lambda x: x["runtime"], - reverse=True, - ) - if filters: - print("Slowest need filters:") - for filter in filters[:10]: - print(f'{filter["location"]}: {filter["runtime"]:2f}s ({filter["origin"]})') - print("") - def _store_timing_results_json(app: Sphinx, build_data: dict[str, Any]) -> None: json_result_path = os.path.join(str(app.outdir), "debug_measurement.json") @@ -180,16 +166,6 @@ def _store_timing_results_json(app: Sphinx, build_data: dict[str, Any]) -> None: print(f"Timing measurement results (JSON) stored under {json_result_path}") -def _store_filter_results_json(app: Sphinx) -> None: - json_result_path = os.path.join(str(app.outdir), "debug_filters.json") - - data = SphinxNeedsData(app.env).get_or_create_filters() - - with open(json_result_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4) - print(f"Filter results (JSON) stored under {json_result_path}") - - def _store_timing_results_html(app: Sphinx, build_data: dict[str, Any]) -> None: jinja_env = Environment( loader=PackageLoader("sphinx_needs"), autoescape=select_autoescape() @@ -213,5 +189,4 @@ def process_timing(app: Sphinx, _exception: Exception | None) -> None: _print_timing_results(app) _store_timing_results_json(app, build_data) - _store_filter_results_json(app) _store_timing_results_html(app, build_data) diff --git a/sphinx_needs/filter_common.py b/sphinx_needs/filter_common.py index 63178d451..cf02be760 100644 --- a/sphinx_needs/filter_common.py +++ b/sphinx_needs/filter_common.py @@ -6,7 +6,9 @@ from __future__ import annotations import ast +import json import re +from pathlib import Path from timeit import default_timer as timer from types import CodeType from typing import Any, Iterable, TypedDict, overload @@ -22,7 +24,6 @@ NeedsFilteredBaseType, NeedsInfoType, NeedsMutable, - SphinxNeedsData, ) from sphinx_needs.debug import measure_time, measure_time_func from sphinx_needs.logging import log_warning @@ -39,7 +40,6 @@ class FilterAttributesType(TypedDict): sort_by: str filter_code: list[str] filter_func: str | None - export_id: str filter_warning: str | None """If set, the filter is exported with this ID in the needs.json file.""" @@ -81,9 +81,13 @@ def collect_filter_attributes(self) -> FilterAttributesType: if isinstance(types, str): types = [typ.strip() for typ in re.split(";|,", types)] - if self.options.get("export_id", ""): - # this is used by needs builders - SphinxNeedsData(self.env).has_export_filters = True + if "export_id" in self.options: + log_warning( + log, + "The 'export_id' option is deprecated, instead use the `needs_debug_filters` configuration.", + "deprecated", + location=self.get_location(), + ) # Add the need and all needed information collected_filter_options: FilterAttributesType = { @@ -94,7 +98,6 @@ def collect_filter_attributes(self) -> FilterAttributesType: "sort_by": self.options.get("sort_by"), "filter_code": self.content, "filter_func": self.options.get("filter-func"), - "export_id": self.options.get("export_id", ""), "filter_warning": self.options.get("filter_warning"), } return collected_filter_options @@ -123,8 +126,12 @@ def process_filters( start = timer() needs_config = NeedsSphinxConfig(app.config) + # filter string to record (will be joined by 'and') + full_filter: list[str] = [] + # check if include external needs if not include_external: + full_filter.append("is_external == False") needs_view = needs_view.filter_is_external(False) # Check if external filter code is defined @@ -149,13 +156,22 @@ def process_filters( # TODO these may not be correct for parts filtered_needs = needs_view if filter_data["status"]: + full_filter.append(f"status in {filter_data['status']!r}") filtered_needs = filtered_needs.filter_statuses(filter_data["status"]) if filter_data["tags"]: + full_filter.append( + " or ".join(f"{tag!r} in tags" for tag in filter_data["tags"]) + ) filtered_needs = filtered_needs.filter_has_tag(filter_data["tags"]) if filter_data["types"]: + full_filter.append( + f"type in {filter_data['types']!r} or type_name in {filter_data['types']!r}" + ) filtered_needs = filtered_needs.filter_types( filter_data["types"], or_type_names=True ) + if filter_data["filter"]: + full_filter.append(filter_data["filter"]) # Get need by filter string found_needs = filter_needs_parts( @@ -169,6 +185,7 @@ def process_filters( found_dirty_needs: list[NeedsInfoType] = [] if filter_code: # code from content + full_filter.append(filter_code) # TODO better context type context: dict[str, NeedsAndPartsListView] = { "needs": needs_view.to_list_with_parts(), @@ -177,6 +194,7 @@ def process_filters( exec(filter_code, context) found_dirty_needs = context["results"] # type: ignore[assignment] elif ff_result: # code from external file + full_filter.append(ff_result.sig) args = [] if ff_result.args: args = ff_result.args.split(",") @@ -222,21 +240,36 @@ def process_filters( ) return [] - # Store basic filter configuration and result global list. - # Needed mainly for exporting the result to needs.json (if builder "needs" is used). - filter_list = SphinxNeedsData(app.env).get_or_create_filters() - - filter_list[filter_data["target_id"]] = { - "origin": origin, - "location": f"{location.source}:{location.line}", - "filter": filter_data["filter"] or "", - "status": filter_data["status"], - "tags": filter_data["tags"], - "types": filter_data["types"], - "export_id": filter_data["export_id"].upper(), - "amount": len(found_needs), - "runtime": timer() - start, - } + duration = timer() - start + + if ( + needs_config.filter_max_time is not None + and duration > needs_config.filter_max_time + ): + log_warning( + log, + f"Filtering took {duration:.3f}s, which is longer than the configured maximum of {needs_config.filter_max_time}s.", + "filter", + location=location, + ) + + if needs_config.debug_filters and full_filter: + # Store basic filter configuration and result global list. + # Needed mainly for exporting the result to needs.json (if builder "needs" is used). + json_line = json.dumps( + { + "origin": origin, + "source": str(location.source) if location.source else None, + "line": location.line, + "filter": full_filter[0] + if len(full_filter) == 1 + else " and ".join(f"({f})" for f in full_filter), + "needs_count": len(found_needs), + "runtime": duration, + } + ) + with Path(str(app.outdir), "debug_filters.jsonl").open("a") as f: + f.write(json_line + "\n") return found_needs diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py index 09e56951f..74211449a 100644 --- a/sphinx_needs/needs.py +++ b/sphinx_needs/needs.py @@ -1,5 +1,7 @@ from __future__ import annotations +import contextlib +from pathlib import Path from timeit import default_timer as timer # Used for timing measurements from typing import Any, Callable, Dict, List, Type @@ -497,7 +499,6 @@ def prepare_env(app: Sphinx, env: BuildEnvironment, _docname: str) -> None: """ needs_config = NeedsSphinxConfig(app.config) data = SphinxNeedsData(env) - data.get_or_create_filters() data.get_or_create_docs() services = data.get_or_create_services() @@ -578,6 +579,10 @@ def prepare_env(app: Sphinx, env: BuildEnvironment, _docname: str) -> None: debug.START_TIME = timer() # Store the rough start time of Sphinx build debug.EXECUTE_TIME_MEASUREMENTS = True + if needs_config.debug_filters: + with contextlib.suppress(FileNotFoundError): + Path(str(app.outdir), "debug_filters.jsonl").unlink() + def check_configuration(_app: Sphinx, config: Config) -> None: """Checks the configuration for invalid options. diff --git a/sphinx_needs/needsfile.py b/sphinx_needs/needsfile.py index 9ee9663c4..c2d9a92c7 100644 --- a/sphinx_needs/needsfile.py +++ b/sphinx_needs/needsfile.py @@ -18,7 +18,7 @@ from sphinx.config import Config from sphinx_needs.config import NEEDS_CONFIG, NeedsSphinxConfig -from sphinx_needs.data import NeedsCoreFields, NeedsFilterType, NeedsInfoType +from sphinx_needs.data import NeedsCoreFields, NeedsInfoType from sphinx_needs.logging import get_logger, log_warning log = get_logger(__name__) @@ -170,16 +170,6 @@ def add_need(self, version: str, need_info: NeedsInfoType) -> None: self.needs_list["versions"][version]["needs"] ) - def add_filter(self, version: str, need_filter: NeedsFilterType) -> None: - self.update_or_add_version(version) - writable_filters = {**need_filter} - self.needs_list["versions"][version]["filters"][ - need_filter["export_id"].upper() - ] = writable_filters - self.needs_list["versions"][version]["filters_amount"] = len( - self.needs_list["versions"][version]["filters"] - ) - def wipe_version(self, version: str) -> None: if version in self.needs_list["versions"]: del self.needs_list["versions"][version] diff --git a/tests/__snapshots__/test_basic_doc/test_build_html[test_app0].doctree.xml b/tests/__snapshots__/test_basic_doc/test_build_html[test_app0].doctree.xml index e50cbfc5a..2a9947506 100644 --- a/tests/__snapshots__/test_basic_doc/test_build_html[test_app0].doctree.xml +++ b/tests/__snapshots__/test_basic_doc/test_build_html[test_app0].doctree.xml @@ -7,4 +7,4 @@ - + diff --git a/tests/__snapshots__/test_export_id.ambr b/tests/__snapshots__/test_export_id.ambr deleted file mode 100644 index fc2f784a3..000000000 --- a/tests/__snapshots__/test_export_id.ambr +++ /dev/null @@ -1,1221 +0,0 @@ -# name: test_export_id[test_app0] - dict({ - 'current_version': '1.0', - 'versions': dict({ - '1.0': dict({ - 'filters': dict({ - 'FLOW_1': dict({ - 'amount': 10, - 'export_id': 'FLOW_1', - 'filter': '', - 'origin': 'needflow', - 'status': list([ - ]), - 'tags': list([ - ]), - 'types': list([ - ]), - }), - 'FLOW_2': dict({ - 'amount': 5, - 'export_id': 'FLOW_2', - 'filter': 'is_need is False or type != "story"', - 'origin': 'needflow', - 'status': list([ - ]), - 'tags': list([ - ]), - 'types': list([ - ]), - }), - 'LIST_1': dict({ - 'amount': 10, - 'export_id': 'LIST_1', - 'filter': '', - 'origin': 'needlist', - 'status': list([ - ]), - 'tags': list([ - ]), - 'types': list([ - ]), - }), - 'TABLE_1': dict({ - 'amount': 10, - 'export_id': 'TABLE_1', - 'filter': '', - 'origin': 'needtable', - 'status': list([ - ]), - 'tags': list([ - ]), - 'types': list([ - ]), - }), - 'TABLE_2': dict({ - 'amount': 3, - 'export_id': 'TABLE_2', - 'filter': '"test" in type', - 'origin': 'needtable', - 'status': list([ - ]), - 'tags': list([ - ]), - 'types': list([ - ]), - }), - }), - 'filters_amount': 5, - 'needs': dict({ - 'REQ_001': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - 'REQ_003', - ]), - 'blocks_back': list([ - 'REQ_005', - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'My requirement', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'REQ_001', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - 'REQ_002', - 'REQ_004', - 'REQ_003', - ]), - 'links_back': list([ - 'REQ_005', - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Stories', - 'sections': list([ - 'Stories', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'REQ_001', - 'template': None, - 'tests': list([ - ]), - 'tests_back': list([ - 'TEST_001', - 'TEST_002', - ]), - 'title': 'My requirement', - 'type': 'story', - 'type_name': 'User Story', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'REQ_002': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - ]), - 'blocks_back': list([ - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'My requirement 2', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'REQ_002', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - ]), - 'links_back': list([ - 'REQ_001', - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Stories', - 'sections': list([ - 'Stories', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'REQ_002', - 'template': None, - 'tests': list([ - ]), - 'tests_back': list([ - ]), - 'title': 'My requirement 2', - 'type': 'story', - 'type_name': 'User Story', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'REQ_003': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - ]), - 'blocks_back': list([ - 'REQ_001', - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'My requirement 3', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'REQ_003', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - ]), - 'links_back': list([ - 'REQ_001', - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Stories', - 'sections': list([ - 'Stories', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'REQ_003', - 'template': None, - 'tests': list([ - ]), - 'tests_back': list([ - 'TEST_001', - ]), - 'title': 'My requirement 3', - 'type': 'story', - 'type_name': 'User Story', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'REQ_004': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - ]), - 'blocks_back': list([ - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'My requirement 4', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'REQ_004', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - ]), - 'links_back': list([ - 'REQ_001', - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Stories', - 'sections': list([ - 'Stories', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'REQ_004', - 'template': None, - 'tests': list([ - ]), - 'tests_back': list([ - ]), - 'title': 'My requirement 4', - 'type': 'story', - 'type_name': 'User Story', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'REQ_005': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - 'REQ_001', - ]), - 'blocks_back': list([ - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': ''' - :need_part:`(1) awesome part` - - :need_part:`(cool) a cool part` - ''', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'Req 5', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'REQ_005', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - 'REQ_001', - 'REQ_001', - ]), - 'links_back': list([ - 'TEST_003', - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - '1': dict({ - 'content': ' awesome part', - 'id': '1', - 'links': list([ - ]), - 'links_back': list([ - 'TEST_003', - ]), - 'tests_back': list([ - 'TEST_003', - ]), - }), - 'cool': dict({ - 'content': ' a cool part', - 'id': 'cool', - 'links': list([ - ]), - 'links_back': list([ - ]), - 'tests_back': list([ - 'TEST_003', - ]), - }), - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Stories', - 'sections': list([ - 'Stories', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'REQ_005', - 'template': None, - 'tests': list([ - ]), - 'tests_back': list([ - 'TEST_003', - ]), - 'title': 'Req 5', - 'type': 'story', - 'type_name': 'User Story', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'TEST_001': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - ]), - 'blocks_back': list([ - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'Test of requirements', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'TEST_001', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - ]), - 'links_back': list([ - 'TEST_002', - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Tests', - 'sections': list([ - 'Tests', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'TEST_001', - 'template': None, - 'tests': list([ - 'REQ_001', - 'REQ_003', - ]), - 'tests_back': list([ - ]), - 'title': 'Test of requirements', - 'type': 'test', - 'type_name': 'Test Case', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'TEST_002': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - ]), - 'blocks_back': list([ - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'Test of requirements2', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'TEST_002', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - 'TEST_001', - ]), - 'links_back': list([ - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Tests', - 'sections': list([ - 'Tests', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'TEST_002', - 'template': None, - 'tests': list([ - 'REQ_001', - ]), - 'tests_back': list([ - ]), - 'title': 'Test of requirements2', - 'type': 'test', - 'type_name': 'Test Case', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - 'TEST_003': dict({ - 'arch': dict({ - }), - 'avatar': '', - 'blocks': list([ - ]), - 'blocks_back': list([ - ]), - 'closed_at': '', - 'completion': '', - 'constraints': list([ - ]), - 'constraints_passed': True, - 'constraints_results': dict({ - }), - 'created_at': '', - 'delete': False, - 'description': '', - 'docname': 'index', - 'doctype': '.rst', - 'duration': '', - 'external_css': 'external_link', - 'external_url': None, - 'full_title': 'Test of requirements 5', - 'has_dead_links': False, - 'has_forbidden_dead_links': False, - 'id': 'TEST_003', - 'id_prefix': '', - 'is_external': False, - 'is_modified': False, - 'is_need': True, - 'is_part': False, - 'jinja_content': False, - 'layout': '', - 'links': list([ - 'REQ_005.1', - ]), - 'links_back': list([ - ]), - 'max_amount': '', - 'max_content_lines': '', - 'modifications': 0, - 'params': '', - 'parent_need': '', - 'parent_needs': list([ - ]), - 'parent_needs_back': list([ - ]), - 'parts': dict({ - }), - 'post_template': None, - 'pre_template': None, - 'prefix': '', - 'query': '', - 'section_name': 'Tests', - 'sections': list([ - 'Tests', - 'EXTRA LINKS DOCUMENT', - ]), - 'service': '', - 'signature': '', - 'specific': '', - 'status': None, - 'style': None, - 'tags': list([ - ]), - 'target_id': 'TEST_003', - 'template': None, - 'tests': list([ - 'REQ_005.1', - 'REQ_005.cool', - ]), - 'tests_back': list([ - ]), - 'title': 'Test of requirements 5', - 'type': 'test', - 'type_name': 'Test Case', - 'updated_at': '', - 'url': '', - 'url_postfix': '', - 'user': '', - }), - }), - 'needs_amount': 8, - 'needs_schema': dict({ - '$schema': 'http://json-schema.org/draft-07/schema#', - 'properties': dict({ - 'arch': dict({ - 'additionalProperties': dict({ - 'type': 'string', - }), - 'default': dict({ - }), - 'description': 'Mapping of uml key to uml content.', - 'field_type': 'core', - 'type': 'object', - }), - 'avatar': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'blocks': dict({ - 'default': list([ - ]), - 'description': 'Link field', - 'field_type': 'links', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'blocks_back': dict({ - 'default': list([ - ]), - 'description': 'Backlink field', - 'field_type': 'backlinks', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'closed_at': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'completion': dict({ - 'default': '', - 'description': 'Added for needgantt functionality', - 'field_type': 'extra', - 'type': 'string', - }), - 'constraints': dict({ - 'default': list([ - ]), - 'description': 'List of constraint names, which are defined for this need.', - 'field_type': 'core', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'constraints_error': dict({ - 'default': '', - 'description': 'An error message set if any constraint failed, and `error_message` field is set in config.', - 'field_type': 'core', - 'type': 'string', - }), - 'constraints_passed': dict({ - 'default': True, - 'description': 'True if all constraints passed, False if any failed, None if not yet checked.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'constraints_results': dict({ - 'additionalProperties': dict({ - 'type': 'object', - }), - 'default': dict({ - }), - 'description': 'Mapping of constraint name, to check name, to result.', - 'field_type': 'core', - 'type': 'object', - }), - 'created_at': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'delete': dict({ - 'default': False, - 'description': 'If true, the need is deleted entirely.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'docname': dict({ - 'default': None, - 'description': 'Name of the document where the need is defined (None if external).', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'doctype': dict({ - 'default': '.rst', - 'description': "Type of the document where the need is defined, e.g. '.rst'.", - 'field_type': 'core', - 'type': 'string', - }), - 'duration': dict({ - 'default': '', - 'description': 'Added for needgantt functionality', - 'field_type': 'extra', - 'type': 'string', - }), - 'external_css': dict({ - 'default': '', - 'description': 'CSS class name, added to the external reference.', - 'field_type': 'core', - 'type': 'string', - }), - 'external_url': dict({ - 'default': None, - 'description': 'URL of the need, if it is an external need.', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'full_title': dict({ - 'default': '', - 'description': 'Title of the need, of unlimited length.', - 'field_type': 'core', - 'type': 'string', - }), - 'has_dead_links': dict({ - 'default': False, - 'description': 'True if any links reference need ids that are not found in the need list.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'has_forbidden_dead_links': dict({ - 'default': False, - 'description': 'True if any links reference need ids that are not found in the need list, and the link type does not allow dead links.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'id': dict({ - 'description': 'ID of the data.', - 'field_type': 'core', - 'type': 'string', - }), - 'id_prefix': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'is_external': dict({ - 'default': False, - 'description': 'If true, no node is created and need is referencing external url.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'is_modified': dict({ - 'default': False, - 'description': 'Whether the need was modified by needextend.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'is_need': dict({ - 'default': True, - 'description': 'Whether the need is a need.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'is_part': dict({ - 'default': False, - 'description': 'Whether the need is a part.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'jinja_content': dict({ - 'default': False, - 'description': 'Whether the content should be pre-processed by jinja.', - 'field_type': 'core', - 'type': 'boolean', - }), - 'layout': dict({ - 'default': None, - 'description': 'Key of the layout, which is used to render the need.', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'links': dict({ - 'default': list([ - ]), - 'description': 'Link field', - 'field_type': 'links', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'links_back': dict({ - 'default': list([ - ]), - 'description': 'Backlink field', - 'field_type': 'backlinks', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'max_amount': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'max_content_lines': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'modifications': dict({ - 'default': 0, - 'description': 'Number of modifications by needextend.', - 'field_type': 'core', - 'type': 'integer', - }), - 'params': dict({ - 'default': '', - 'description': 'Added by service open-needs', - 'field_type': 'extra', - 'type': 'string', - }), - 'parent_need': dict({ - 'default': '', - 'description': 'Simply the first parent id.', - 'field_type': 'core', - 'type': 'string', - }), - 'parent_needs': dict({ - 'default': list([ - ]), - 'description': 'Link field', - 'field_type': 'links', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'parent_needs_back': dict({ - 'default': list([ - ]), - 'description': 'Backlink field', - 'field_type': 'backlinks', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'parts': dict({ - 'additionalProperties': dict({ - 'type': 'object', - }), - 'default': dict({ - }), - 'description': "Mapping of parts, a.k.a. sub-needs, IDs to data that overrides the need's data", - 'field_type': 'core', - 'type': 'object', - }), - 'post_content': dict({ - 'default': '', - 'description': 'Post-content of the need.', - 'field_type': 'core', - 'type': 'string', - }), - 'post_template': dict({ - 'default': None, - 'description': 'Post-template of the need.', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'pre_content': dict({ - 'default': '', - 'description': 'Pre-content of the need.', - 'field_type': 'core', - 'type': 'string', - }), - 'pre_template': dict({ - 'default': None, - 'description': 'Pre-template of the need.', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'prefix': dict({ - 'default': '', - 'description': 'Added by service open-needs', - 'field_type': 'extra', - 'type': 'string', - }), - 'query': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'section_name': dict({ - 'default': '', - 'description': 'Simply the first section.', - 'field_type': 'core', - 'type': 'string', - }), - 'sections': dict({ - 'default': list([ - ]), - 'description': 'Sections of the need.', - 'field_type': 'core', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'service': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'signature': dict({ - 'default': '', - 'description': 'Derived from a docutils desc_name node.', - 'field_type': 'core', - 'type': 'string', - }), - 'specific': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'status': dict({ - 'default': None, - 'description': 'Status of the need.', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'style': dict({ - 'default': None, - 'description': 'Comma-separated list of CSS classes (all appended by `needs_style_`).', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'tags': dict({ - 'default': list([ - ]), - 'description': 'List of tags.', - 'field_type': 'core', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'target_id': dict({ - 'description': 'ID of the data.', - 'field_type': 'core', - 'type': 'string', - }), - 'template': dict({ - 'default': None, - 'description': 'Template of the need.', - 'field_type': 'core', - 'type': list([ - 'string', - 'null', - ]), - }), - 'tests': dict({ - 'default': list([ - ]), - 'description': 'Link field', - 'field_type': 'links', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'tests_back': dict({ - 'default': list([ - ]), - 'description': 'Backlink field', - 'field_type': 'backlinks', - 'items': dict({ - 'type': 'string', - }), - 'type': 'array', - }), - 'title': dict({ - 'description': 'Title of the need, trimmed to a maximum length.', - 'field_type': 'core', - 'type': 'string', - }), - 'type': dict({ - 'default': '', - 'description': 'Type of the need.', - 'field_type': 'core', - 'type': 'string', - }), - 'type_name': dict({ - 'default': '', - 'description': 'Name of the type.', - 'field_type': 'core', - 'type': 'string', - }), - 'updated_at': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'url': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - 'url_postfix': dict({ - 'default': '', - 'description': 'Added by service open-needs', - 'field_type': 'extra', - 'type': 'string', - }), - 'user': dict({ - 'default': '', - 'description': 'Added by service github-issues', - 'field_type': 'extra', - 'type': 'string', - }), - }), - 'type': 'object', - }), - }), - }), - }) -# --- diff --git a/tests/doc_test/doc_export_id/conf.py b/tests/doc_test/doc_export_id/conf.py deleted file mode 100644 index d2a706459..000000000 --- a/tests/doc_test/doc_export_id/conf.py +++ /dev/null @@ -1,65 +0,0 @@ -version = "1.0" - -extensions = ["sphinx_needs", "sphinxcontrib.plantuml"] - -# note, the plantuml executable command is set globally in the test suite -plantuml_output_format = "svg" - -needs_types = [ - { - "directive": "story", - "title": "User Story", - "prefix": "US_", - "color": "#BFD8D2", - "style": "node", - }, - { - "directive": "spec", - "title": "Specification", - "prefix": "SP_", - "color": "#FEDCD2", - "style": "node", - }, - { - "directive": "impl", - "title": "Implementation", - "prefix": "IM_", - "color": "#DF744A", - "style": "node", - }, - { - "directive": "test", - "title": "Test Case", - "prefix": "TC_", - "color": "#DCB239", - "style": "node", - }, -] - -needs_extra_links = [ - { - "option": "links", - "incoming": "is linked by", - "outgoing": "links to", - "copy": False, - "style": "#black", - "style_part": "dotted,#black", - }, - { - "option": "blocks", - "incoming": "is blocked by", - "outgoing": "blocks", - "copy": True, - "style": "bold,#AA0000", - }, - { - "option": "tests", - "incoming": "is tested by", - "outgoing": "tests", - "copy": False, - "style": "dashed,#00AA00", - "style_part": "dotted,#00AA00", - }, -] - -needs_flow_link_types = ["links", "tests"] diff --git a/tests/doc_test/doc_export_id/index.rst b/tests/doc_test/doc_export_id/index.rst deleted file mode 100644 index 87a310a1b..000000000 --- a/tests/doc_test/doc_export_id/index.rst +++ /dev/null @@ -1,98 +0,0 @@ -EXTRA LINKS DOCUMENT -==================== - -Stories -------- - -.. story:: My requirement - :id: REQ_001 - :links: REQ_002, REQ_004 - :blocks: REQ_003 - -.. story:: My requirement 2 - :id: REQ_002 - -.. story:: My requirement 3 - :id: REQ_003 - -.. story:: My requirement 4 - :id: REQ_004 - -.. story:: Req 5 - :id: REQ_005 - :links: REQ_001 - :blocks: REQ_001 - - :need_part:`(1) awesome part` - - :need_part:`(cool) a cool part` - - -Tests ------ - -.. test:: Test of requirements - :id: TEST_001 - :tests: REQ_001, REQ_003 - -.. test:: Test of requirements2 - :id: TEST_002 - :links: TEST_001 - :tests: REQ_001 - -.. test:: Test of requirements 5 - :id: TEST_003 - :links: REQ_005.1 - :tests: REQ_005.1,REQ_005.cool - - -Lists ------ - -.. needlist:: - -.. needlist:: - :export_id: list_1 - - -Tables ------- - -.. needtable:: - :status: open - -.. needtable:: - :export_id: table_1 - -.. needtable:: - :filter: "test" in type - :export_id: table_2 - -Flow ----- - -.. needflow:: - -.. needflow:: - :export_id: flow_1 - -.. needflow:: - :export_id: flow_2 - :filter: is_need is False or type != "story" - - - -.. uml:: - - node test - node test_2 - - node spec - cloud spec_1 as s1 [[https.spiegel.de]] #ffcc00 { - - node spec_1.2 as s11 - } - - test --> s1 - test_2 --> s11 - diff --git a/tests/doc_test/doc_measure_time/conf.py b/tests/doc_test/doc_measure_time/conf.py index a4b1d4e5f..ff61c91f9 100644 --- a/tests/doc_test/doc_measure_time/conf.py +++ b/tests/doc_test/doc_measure_time/conf.py @@ -13,6 +13,7 @@ plantuml_output_format = "svg" needs_debug_measurement = True +needs_debug_filters = True needs_id_regex = "^[A-Za-z0-9_]" diff --git a/tests/doc_test/doc_measure_time/index.rst b/tests/doc_test/doc_measure_time/index.rst index a3812deae..c9db58bff 100644 --- a/tests/doc_test/doc_measure_time/index.rst +++ b/tests/doc_test/doc_measure_time/index.rst @@ -47,6 +47,7 @@ Filtering --------- .. needtable:: + :export_id: needtable_1 :filter: status == "open" .. needtable:: diff --git a/tests/test_export_id.py b/tests/test_export_id.py deleted file mode 100644 index 77302267d..000000000 --- a/tests/test_export_id.py +++ /dev/null @@ -1,31 +0,0 @@ -import json -import os -from pathlib import Path - -import pytest -from syrupy.filters import props - - -@pytest.mark.parametrize( - "test_app", - [{"buildername": "needs", "srcdir": "doc_test/doc_export_id"}], - indirect=True, -) -def test_export_id(test_app, snapshot): - app = test_app - app.build() - needs_data = json.loads(Path(app.outdir, "needs.json").read_text()) - assert needs_data == snapshot( - exclude=props("created", "project", "location", "runtime") - ) - - -@pytest.mark.parametrize( - "test_app", - [{"buildername": "html", "srcdir": "doc_test/doc_export_id"}], - indirect=True, -) -def test_export_id_html(test_app): - app = test_app - app.build() - assert not os.path.exists(os.path.join(app.outdir, "needs.json")) diff --git a/tests/test_measure_time.py b/tests/test_measure_time.py index ea9a27ff9..af3a7d20d 100644 --- a/tests/test_measure_time.py +++ b/tests/test_measure_time.py @@ -1,4 +1,8 @@ +import os +from pathlib import Path + import pytest +from sphinx.util.console import strip_colors @pytest.mark.parametrize( @@ -9,4 +13,12 @@ def test_measure_time(test_app): app = test_app app.build() - # html = (app.outdir / "index.html").read_text() + warnings = strip_colors( + app._warning.getvalue().replace(str(app.srcdir) + os.sep, "srcdir/") + ).splitlines() + assert warnings == [ + "srcdir/index.rst:49: WARNING: The 'export_id' option is deprecated, instead use the `needs_debug_filters` configuration. [needs.deprecated]" + ] + outdir = Path(str(app.outdir)) + assert outdir.joinpath("debug_measurement.json").exists() + assert outdir.joinpath("debug_filters.jsonl").exists()