From 2ff14e758ecf4cd907fbc150ca0e9824f7032270 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 13 Sep 2024 14:59:38 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20`ids`=20option=20for=20`needi?= =?UTF-8?q?mport`=20(#1292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A more performant alternative to the `filter` option. This PR also adds some other performance optimisations; caching the schema load and not reading the import file twice (during validation) Also, add to and improve the `needimport` tests --- docs/directives/needimport.rst | 14 +- sphinx_needs/directives/needimport.py | 39 +- sphinx_needs/needsfile.py | 30 +- tests/__snapshots__/test_needimport.ambr | 2342 ++++++++++++++++++ tests/doc_test/import_doc/conf.py | 2 + tests/doc_test/import_doc/index.rst | 11 + tests/doc_test/import_doc_invalid/index.rst | 7 - tests/{test_import.py => test_needimport.py} | 133 +- tests/test_needimport_noindex.py | 23 - 9 files changed, 2503 insertions(+), 98 deletions(-) create mode 100644 tests/__snapshots__/test_needimport.ambr rename tests/{test_import.py => test_needimport.py} (66%) delete mode 100644 tests/test_needimport_noindex.py diff --git a/docs/directives/needimport.rst b/docs/directives/needimport.rst index 0a34f9b65..e1372a03a 100644 --- a/docs/directives/needimport.rst +++ b/docs/directives/needimport.rst @@ -56,13 +56,25 @@ In most cases this should be the latest available version. tags ~~~~ -You can attach tags to existing tags of imported needs using the ``:tags:`` option. +You can attach tags to existing tags of imported needs using the ``:tags:`` option +(as a comma-separated list). This may be useful to mark easily imported needs and to create specialised filters for them. +ids +~~~ + +.. versionadded:: 3.1.0 + +You can use the ``:ids:`` option to import only the needs with the given ids +(as a comma-separated list). +This is useful if you want to import only a subset of the needs from the JSON file. + filter ~~~~~~ You can use the ``:filter:`` option to imports only the needs which pass the filter criteria. +This is a string that is evaluated as a Python expression, +it is less performant than the ``:ids:`` option, but more flexible. Please read :ref:`filter` for more information. diff --git a/sphinx_needs/directives/needimport.py b/sphinx_needs/directives/needimport.py index 27ed1abdb..435239ff7 100644 --- a/sphinx_needs/directives/needimport.py +++ b/sphinx_needs/directives/needimport.py @@ -19,7 +19,7 @@ from sphinx_needs.defaults import string_to_boolean from sphinx_needs.filter_common import filter_single_need from sphinx_needs.logging import log_warning -from sphinx_needs.needsfile import check_needs_file +from sphinx_needs.needsfile import SphinxNeedsFileException, check_needs_data from sphinx_needs.utils import add_doc, import_prefix_link_edit, logger @@ -37,6 +37,7 @@ class NeedimportDirective(SphinxDirective): "version": directives.unchanged_required, "hide": directives.flag, "collapse": string_to_boolean, + "ids": directives.unchanged_required, "filter": directives.unchanged_required, "id_prefix": directives.unchanged_required, "tags": directives.unchanged_required, @@ -56,10 +57,6 @@ def run(self) -> Sequence[nodes.Node]: filter_string = self.options.get("filter") id_prefix = self.options.get("id_prefix", "") - tags = self.options.get("tags", []) - if len(tags) > 0: - tags = [tag.strip() for tag in re.split("[;,]", tags)] - need_import_path = self.arguments[0] # check if given arguemnt is downloadable needs.json path @@ -115,7 +112,14 @@ def run(self) -> Sequence[nodes.Node]: f"Could not load needs import file {correct_need_import_path}" ) - errors = check_needs_file(correct_need_import_path) + try: + with open(correct_need_import_path) as needs_file: + needs_import_list = json.load(needs_file) + except (OSError, json.JSONDecodeError) as e: + # TODO: Add exception handling + raise SphinxNeedsFileException(correct_need_import_path) from e + + errors = check_needs_data(needs_import_list) if errors.schema: logger.info( f"Schema validation errors detected in file {correct_need_import_path}:" @@ -123,13 +127,6 @@ def run(self) -> Sequence[nodes.Node]: for error in errors.schema: logger.info(f' {error.message} -> {".".join(error.path)}') - try: - with open(correct_need_import_path) as needs_file: - needs_import_list = json.load(needs_file) - except json.JSONDecodeError as e: - # TODO: Add exception handling - raise e - if version is None: try: version = needs_import_list["current_version"] @@ -146,6 +143,13 @@ def run(self) -> Sequence[nodes.Node]: needs_config = NeedsSphinxConfig(self.config) data = needs_import_list["versions"][version] + + if ids := self.options.get("ids"): + id_list = [i.strip() for i in ids.split(",") if i.strip()] + data["needs"] = { + key: data["needs"][key] for key in id_list if key in data["needs"] + } + # TODO this is not exactly NeedsInfoType, because the export removes/adds some keys needs_list: dict[str, NeedsInfoType] = data["needs"] if schema := data.get("needs_schema"): @@ -184,8 +188,13 @@ def run(self) -> Sequence[nodes.Node]: needs_list = needs_list_filtered # tags update - for need in needs_list.values(): - need["tags"] = need["tags"] + tags + if tags := [ + tag.strip() + for tag in re.split("[;,]", self.options.get("tags", "")) + if tag.strip() + ]: + for need in needs_list.values(): + need["tags"] = need["tags"] + tags import_prefix_link_edit(needs_list, id_prefix, needs_config.extra_links) diff --git a/sphinx_needs/needsfile.py b/sphinx_needs/needsfile.py index ec78e4e85..9ee9663c4 100644 --- a/sphinx_needs/needsfile.py +++ b/sphinx_needs/needsfile.py @@ -11,6 +11,7 @@ import sys from copy import deepcopy from datetime import datetime +from functools import lru_cache from typing import Any, Iterable from jsonschema import Draft7Validator @@ -242,21 +243,38 @@ def check_needs_file(path: str) -> Errors: :param path: File path to a needs.json file :return: Dict, with error reports """ - schema_path = os.path.join(os.path.dirname(__file__), "needsfile.json") - with open(schema_path) as schema_file: - needs_schema = json.load(schema_file) - with open(path) as needs_file: try: - needs_data = json.load(needs_file) + data = json.load(needs_file) except json.JSONDecodeError as e: raise SphinxNeedsFileException( f'Problems loading json file "{path}". ' f"Maybe it is empty or has an invalid json format. Original exception: {e}" ) + return check_needs_data(data) + + +@lru_cache +def _load_schema() -> dict[str, Any]: + schema_path = os.path.join(os.path.dirname(__file__), "needsfile.json") + with open(schema_path) as schema_file: + return json.load(schema_file) # type: ignore[no-any-return] + + +def check_needs_data(data: Any) -> Errors: + """ + Checks a given json-file, if it passes our needs.json structure tests. + + Current checks: + * Schema validation + + :param data: Loaded needs.json file + :return: Dict, with error reports + """ + needs_schema = _load_schema() validator = Draft7Validator(needs_schema) - schema_errors = list(validator.iter_errors(needs_data)) + schema_errors = list(validator.iter_errors(data)) # In future there may be additional types of validations. # So lets already use a class for all errors diff --git a/tests/__snapshots__/test_needimport.ambr b/tests/__snapshots__/test_needimport.ambr new file mode 100644 index 000000000..7e2e5e47b --- /dev/null +++ b/tests/__snapshots__/test_needimport.ambr @@ -0,0 +1,2342 @@ +# name: test_import_builder[test_app0] + dict({ + 'current_version': '1.0', + 'versions': dict({ + '1.0': dict({ + 'filters': dict({ + }), + 'filters_amount': 0, + 'needs': dict({ + 'IMPL_01': dict({ + 'description': 'Incoming links of this spec: :need_incoming:`IMPL_01`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Implementation for specification', + 'id': 'IMPL_01', + 'links': list([ + 'OWN_ID_123', + ]), + 'links_back': list([ + 'T_C3893', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'IMPL_01', + 'title': 'Implementation for specification', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'OWN_ID_123': dict({ + 'description': 'Outgoing links of this spec: :need_outgoing:`OWN_ID_123`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification of a requirement', + 'id': 'OWN_ID_123', + 'links': list([ + 'R_F4722', + ]), + 'links_back': list([ + 'IMPL_01', + 'T_C3893', + 'filter_IMPL_01', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'OWN_ID_123', + 'title': 'Specification of a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'REQ_001': dict({ + 'description': ''' + This is an awesome requirement and it includes a nice title, + a given id, a tag and this text as description. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'REQ_001', + 'links_back': list([ + 'S_503A1', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'example', + ]), + 'target_id': 'REQ_001', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'REQ_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test requirement 1', + 'id': 'REQ_1', + 'layout': '', + 'section_name': 'FILTERED', + 'sections': list([ + 'FILTERED', + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'REQ_1', + 'title': 'Test requirement 1', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'ROLES_REQ_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Sliced Bread', + 'id': 'ROLES_REQ_1', + 'links_back': list([ + 'ROLES_REQ_2', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'ROLES_REQ_1', + 'title': 'Sliced Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'ROLES_REQ_2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Butter on Bread', + 'id': 'ROLES_REQ_2', + 'links': list([ + 'ROLES_REQ_1', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'ROLES_REQ_2', + 'title': 'Butter on Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'R_22EB2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement B', + 'id': 'R_22EB2', + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'B', + 'filter', + ]), + 'target_id': 'R_22EB2', + 'title': 'Requirement B', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'R_2A9D0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement A', + 'id': 'R_2A9D0', + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'A', + 'filter', + ]), + 'target_id': 'R_2A9D0', + 'title': 'Requirement A', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'R_F4722': dict({ + 'description': ''' + This is my **first** requirement!! + + .. note:: You can use any rst code inside it :) + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'R_F4722', + 'links_back': list([ + 'OWN_ID_123', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'requirement', + 'test', + 'awesome', + ]), + 'target_id': 'R_F4722', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'SPEC_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test specification 1', + 'id': 'SPEC_1', + 'layout': '', + 'section_name': 'FILTERED', + 'sections': list([ + 'FILTERED', + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'SPEC_1', + 'title': 'Test specification 1', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'S_01A67': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification B', + 'id': 'S_01A67', + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'B', + 'filter', + ]), + 'target_id': 'S_01A67', + 'title': 'Specification B', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'S_503A1': dict({ + 'description': ''' + We haven't set an **ID** here, so sphinxcontrib-needs + will generated one for us. + + But we have **set a link** to our first requirement and + also a *status* is given. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Spec for a requirement', + 'id': 'S_503A1', + 'links': list([ + 'REQ_001', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'done', + 'tags': list([ + 'important', + 'example', + ]), + 'target_id': 'S_503A1', + 'title': 'Spec for a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'S_D70B0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification A', + 'id': 'S_D70B0', + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'A', + 'filter', + ]), + 'target_id': 'S_D70B0', + 'title': 'Specification A', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'TEST_01': dict({ + 'description': 'TEST IMPORT TITLE', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'TEST IMPORT DESCRIPTION', + 'id': 'TEST_01', + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'target_id': 'TEST_01', + 'title': 'TEST IMPORT DESCRIPTION', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'T_5CCAA': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test 1', + 'id': 'T_5CCAA', + 'parent_needs_back': list([ + 'T_C3893', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'awesome', + 'filter', + ]), + 'target_id': 'T_5CCAA', + 'title': 'Test 1', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'T_C3893': dict({ + 'description': ''' + This test checks :need:`impl_01` for :need:`OWN_ID_123` inside a + Python 2.7 environment. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test for XY', + 'id': 'T_C3893', + 'links': list([ + 'OWN_ID_123', + 'IMPL_01', + ]), + 'parent_need': 'T_5CCAA', + 'parent_needs': list([ + 'T_5CCAA', + ]), + 'section_name': 'TEST DOCUMENT IMPORT', + 'sections': list([ + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'implemented', + 'tags': list([ + 'test', + 'user_interface', + 'python27', + ]), + 'target_id': 'T_C3893', + 'title': 'Test for XY', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'collapsed_IMPL_01': dict({ + 'description': 'Incoming links of this spec: :need_incoming:`collapsed_IMPL_01`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Implementation for specification', + 'id': 'collapsed_IMPL_01', + 'links': list([ + 'collapsed_OWN_ID_123', + ]), + 'links_back': list([ + 'collapsed_T_C3893', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_IMPL_01', + 'title': 'Implementation for specification', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'collapsed_OWN_ID_123': dict({ + 'description': 'Outgoing links of this spec: :need_outgoing:`collapsed_OWN_ID_123`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification of a requirement', + 'id': 'collapsed_OWN_ID_123', + 'links': list([ + 'collapsed_R_F4722', + ]), + 'links_back': list([ + 'collapsed_IMPL_01', + 'collapsed_T_C3893', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_OWN_ID_123', + 'title': 'Specification of a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'collapsed_REQ_001': dict({ + 'description': ''' + This is an awesome requirement and it includes a nice title, + a given id, a tag and this text as description. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'collapsed_REQ_001', + 'links_back': list([ + 'collapsed_S_503A1', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'example', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_REQ_001', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'collapsed_ROLES_REQ_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Sliced Bread', + 'id': 'collapsed_ROLES_REQ_1', + 'links_back': list([ + 'collapsed_ROLES_REQ_2', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_ROLES_REQ_1', + 'title': 'Sliced Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'collapsed_ROLES_REQ_2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Butter on Bread', + 'id': 'collapsed_ROLES_REQ_2', + 'links': list([ + 'collapsed_ROLES_REQ_1', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_ROLES_REQ_2', + 'title': 'Butter on Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'collapsed_R_22EB2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement B', + 'id': 'collapsed_R_22EB2', + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'B', + 'filter', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_R_22EB2', + 'title': 'Requirement B', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'collapsed_R_2A9D0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement A', + 'id': 'collapsed_R_2A9D0', + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'A', + 'filter', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_R_2A9D0', + 'title': 'Requirement A', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'collapsed_R_F4722': dict({ + 'description': ''' + This is my **first** requirement!! + + .. note:: You can use any rst code inside it :) + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'collapsed_R_F4722', + 'links_back': list([ + 'collapsed_OWN_ID_123', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'requirement', + 'test', + 'awesome', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_R_F4722', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'collapsed_S_01A67': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification B', + 'id': 'collapsed_S_01A67', + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'B', + 'filter', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_S_01A67', + 'title': 'Specification B', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'collapsed_S_503A1': dict({ + 'description': ''' + We haven't set an **ID** here, so sphinxcontrib-needs + will generated one for us. + + But we have **set a link** to our first requirement and + also a *status* is given. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Spec for a requirement', + 'id': 'collapsed_S_503A1', + 'links': list([ + 'collapsed_REQ_001', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'done', + 'tags': list([ + 'important', + 'example', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_S_503A1', + 'title': 'Spec for a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'collapsed_S_D70B0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification A', + 'id': 'collapsed_S_D70B0', + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'A', + 'filter', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_S_D70B0', + 'title': 'Specification A', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'collapsed_TEST_01': dict({ + 'description': 'TEST IMPORT TITLE', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'TEST IMPORT DESCRIPTION', + 'id': 'collapsed_TEST_01', + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_TEST_01', + 'title': 'TEST IMPORT DESCRIPTION', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'collapsed_T_5CCAA': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test 1', + 'id': 'collapsed_T_5CCAA', + 'parent_needs_back': list([ + 'collapsed_T_C3893', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'awesome', + 'filter', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_T_5CCAA', + 'title': 'Test 1', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'collapsed_T_C3893': dict({ + 'description': ''' + This test checks :need:`impl_01` for :need:`collapsed_OWN_ID_123` inside a + Python 2.7 environment. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test for XY', + 'id': 'collapsed_T_C3893', + 'links': list([ + 'collapsed_OWN_ID_123', + 'collapsed_IMPL_01', + ]), + 'parent_need': 'collapsed_T_5CCAA', + 'parent_needs': list([ + 'collapsed_T_5CCAA', + ]), + 'section_name': 'COLLAPSED', + 'sections': list([ + 'COLLAPSED', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'implemented', + 'tags': list([ + 'test', + 'user_interface', + 'python27', + 'should', + 'be', + 'collapsed', + ]), + 'target_id': 'collapsed_T_C3893', + 'title': 'Test for XY', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'filter_IMPL_01': dict({ + 'description': 'Incoming links of this spec: :need_incoming:`filter_IMPL_01`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Implementation for specification', + 'id': 'filter_IMPL_01', + 'links': list([ + 'OWN_ID_123', + ]), + 'section_name': 'FILTERED', + 'sections': list([ + 'FILTERED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + ]), + 'target_id': 'filter_IMPL_01', + 'title': 'Implementation for specification', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'hidden_IMPL_01': dict({ + 'description': 'Incoming links of this spec: :need_incoming:`hidden_IMPL_01`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Implementation for specification', + 'id': 'hidden_IMPL_01', + 'links': list([ + 'hidden_OWN_ID_123', + ]), + 'links_back': list([ + 'hidden_T_C3893', + ]), + 'parent_need': 'hidden_TEST_01', + 'parent_needs': list([ + 'hidden_TEST_01', + ]), + 'parent_needs_back': list([ + 'hidden_OWN_ID_123', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'hidden', + ]), + 'target_id': 'hidden_IMPL_01', + 'title': 'Implementation for specification', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'hidden_OWN_ID_123': dict({ + 'description': 'Outgoing links of this spec: :need_outgoing:`hidden_OWN_ID_123`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification of a requirement', + 'id': 'hidden_OWN_ID_123', + 'links': list([ + 'hidden_R_F4722', + ]), + 'links_back': list([ + 'hidden_IMPL_01', + 'hidden_T_C3893', + ]), + 'parent_need': 'hidden_IMPL_01', + 'parent_needs': list([ + 'hidden_IMPL_01', + ]), + 'parent_needs_back': list([ + 'hidden_REQ_001', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'hidden', + ]), + 'target_id': 'hidden_OWN_ID_123', + 'title': 'Specification of a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'hidden_REQ_001': dict({ + 'description': ''' + This is an awesome requirement and it includes a nice title, + a given id, a tag and this text as description. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'hidden_REQ_001', + 'links_back': list([ + 'hidden_S_503A1', + ]), + 'parent_need': 'hidden_OWN_ID_123', + 'parent_needs': list([ + 'hidden_OWN_ID_123', + ]), + 'parent_needs_back': list([ + 'hidden_ROLES_REQ_1', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'example', + 'hidden', + ]), + 'target_id': 'hidden_REQ_001', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'hidden_ROLES_REQ_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Sliced Bread', + 'id': 'hidden_ROLES_REQ_1', + 'links_back': list([ + 'hidden_ROLES_REQ_2', + ]), + 'parent_need': 'hidden_REQ_001', + 'parent_needs': list([ + 'hidden_REQ_001', + ]), + 'parent_needs_back': list([ + 'hidden_ROLES_REQ_2', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'hidden', + ]), + 'target_id': 'hidden_ROLES_REQ_1', + 'title': 'Sliced Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'hidden_ROLES_REQ_2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Butter on Bread', + 'id': 'hidden_ROLES_REQ_2', + 'links': list([ + 'hidden_ROLES_REQ_1', + ]), + 'parent_need': 'hidden_ROLES_REQ_1', + 'parent_needs': list([ + 'hidden_ROLES_REQ_1', + ]), + 'parent_needs_back': list([ + 'hidden_R_22EB2', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'hidden', + ]), + 'target_id': 'hidden_ROLES_REQ_2', + 'title': 'Butter on Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'hidden_R_22EB2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement B', + 'id': 'hidden_R_22EB2', + 'parent_need': 'hidden_ROLES_REQ_2', + 'parent_needs': list([ + 'hidden_ROLES_REQ_2', + ]), + 'parent_needs_back': list([ + 'hidden_R_2A9D0', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'B', + 'filter', + 'hidden', + ]), + 'target_id': 'hidden_R_22EB2', + 'title': 'Requirement B', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'hidden_R_2A9D0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement A', + 'id': 'hidden_R_2A9D0', + 'parent_need': 'hidden_R_22EB2', + 'parent_needs': list([ + 'hidden_R_22EB2', + ]), + 'parent_needs_back': list([ + 'hidden_R_F4722', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'A', + 'filter', + 'hidden', + ]), + 'target_id': 'hidden_R_2A9D0', + 'title': 'Requirement A', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'hidden_R_F4722': dict({ + 'description': ''' + This is my **first** requirement!! + + .. note:: You can use any rst code inside it :) + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'hidden_R_F4722', + 'links_back': list([ + 'hidden_OWN_ID_123', + ]), + 'parent_need': 'hidden_R_2A9D0', + 'parent_needs': list([ + 'hidden_R_2A9D0', + ]), + 'parent_needs_back': list([ + 'hidden_S_01A67', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'requirement', + 'test', + 'awesome', + 'hidden', + ]), + 'target_id': 'hidden_R_F4722', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'hidden_S_01A67': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification B', + 'id': 'hidden_S_01A67', + 'parent_need': 'hidden_R_F4722', + 'parent_needs': list([ + 'hidden_R_F4722', + ]), + 'parent_needs_back': list([ + 'hidden_S_503A1', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'B', + 'filter', + 'hidden', + ]), + 'target_id': 'hidden_S_01A67', + 'title': 'Specification B', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'hidden_S_503A1': dict({ + 'description': ''' + We haven't set an **ID** here, so sphinxcontrib-needs + will generated one for us. + + But we have **set a link** to our first requirement and + also a *status* is given. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Spec for a requirement', + 'id': 'hidden_S_503A1', + 'links': list([ + 'hidden_REQ_001', + ]), + 'parent_need': 'hidden_S_01A67', + 'parent_needs': list([ + 'hidden_S_01A67', + ]), + 'parent_needs_back': list([ + 'hidden_S_D70B0', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'done', + 'tags': list([ + 'important', + 'example', + 'hidden', + ]), + 'target_id': 'hidden_S_503A1', + 'title': 'Spec for a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'hidden_S_D70B0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification A', + 'id': 'hidden_S_D70B0', + 'parent_need': 'hidden_S_503A1', + 'parent_needs': list([ + 'hidden_S_503A1', + ]), + 'parent_needs_back': list([ + 'hidden_T_5CCAA', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'A', + 'filter', + 'hidden', + ]), + 'target_id': 'hidden_S_D70B0', + 'title': 'Specification A', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'hidden_TEST_01': dict({ + 'description': 'TEST IMPORT TITLE', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'TEST IMPORT DESCRIPTION', + 'id': 'hidden_TEST_01', + 'parent_needs_back': list([ + 'hidden_IMPL_01', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'hidden', + ]), + 'target_id': 'hidden_TEST_01', + 'title': 'TEST IMPORT DESCRIPTION', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'hidden_T_5CCAA': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test 1', + 'id': 'hidden_T_5CCAA', + 'parent_need': 'hidden_S_D70B0', + 'parent_needs': list([ + 'hidden_S_D70B0', + ]), + 'parent_needs_back': list([ + 'hidden_T_C3893', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'awesome', + 'filter', + 'hidden', + ]), + 'target_id': 'hidden_T_5CCAA', + 'title': 'Test 1', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'hidden_T_C3893': dict({ + 'description': ''' + This test checks :need:`impl_01` for :need:`hidden_OWN_ID_123` inside a + Python 2.7 environment. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test for XY', + 'id': 'hidden_T_C3893', + 'links': list([ + 'hidden_OWN_ID_123', + 'hidden_IMPL_01', + ]), + 'parent_need': 'hidden_T_5CCAA', + 'parent_needs': list([ + 'hidden_T_5CCAA', + ]), + 'section_name': 'HIDDEN', + 'sections': list([ + 'HIDDEN', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'implemented', + 'tags': list([ + 'test', + 'user_interface', + 'python27', + 'hidden', + ]), + 'target_id': 'hidden_T_C3893', + 'title': 'Test for XY', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'ids_ROLES_REQ_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Sliced Bread', + 'id': 'ids_ROLES_REQ_1', + 'links_back': list([ + 'ids_ROLES_REQ_2', + ]), + 'section_name': 'FILTERED', + 'sections': list([ + 'FILTERED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + ]), + 'target_id': 'ids_ROLES_REQ_1', + 'title': 'Sliced Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'ids_ROLES_REQ_2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Butter on Bread', + 'id': 'ids_ROLES_REQ_2', + 'links': list([ + 'ids_ROLES_REQ_1', + ]), + 'section_name': 'FILTERED', + 'sections': list([ + 'FILTERED', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + ]), + 'target_id': 'ids_ROLES_REQ_2', + 'title': 'Butter on Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'small2_TEST_03': dict({ + 'description': 'AAA', + 'docname': 'subdoc/filter', + 'external_css': 'external_link', + 'full_title': 'AAA', + 'id': 'small2_TEST_03', + 'section_name': 'Filter', + 'sections': list([ + 'Filter', + ]), + 'status': 'open', + 'target_id': 'small2_TEST_03', + 'title': 'AAA', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'small_TEST_02': dict({ + 'description': 'small_TEST_02', + 'docname': 'subdoc/filter', + 'external_css': 'external_link', + 'full_title': 'TEST_02 DESCRIPTION', + 'id': 'small_TEST_02', + 'section_name': 'Filter', + 'sections': list([ + 'Filter', + ]), + 'status': 'open', + 'tags': list([ + 'test_02', + 'test', + ]), + 'target_id': 'small_TEST_02', + 'title': 'TEST_02 DESCRIPTION', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'small_abs_path_TEST_02': dict({ + 'description': 'small_abs_path_TEST_02', + 'docname': 'subdoc/abs_path_import', + 'external_css': 'external_link', + 'full_title': 'TEST_02 DESCRIPTION', + 'id': 'small_abs_path_TEST_02', + 'section_name': 'Absolute path import test', + 'sections': list([ + 'Absolute path import test', + ]), + 'status': 'open', + 'tags': list([ + 'test_02', + 'test', + ]), + 'target_id': 'small_abs_path_TEST_02', + 'title': 'TEST_02 DESCRIPTION', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'small_depr_rel_path_TEST_01': dict({ + 'description': 'small_depr_rel_path_TEST_01', + 'docname': 'subdoc/deprecated_rel_path_import', + 'external_css': 'external_link', + 'full_title': 'TEST_01 DESCRIPTION', + 'id': 'small_depr_rel_path_TEST_01', + 'section_name': 'Deprecated Relative path import test', + 'sections': list([ + 'Deprecated Relative path import test', + ]), + 'target_id': 'small_depr_rel_path_TEST_01', + 'title': 'TEST_01 DESCRIPTION', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'small_rel_path_TEST_01': dict({ + 'description': 'small_rel_path_TEST_01', + 'docname': 'subdoc/rel_path_import', + 'external_css': 'external_link', + 'full_title': 'TEST_01 DESCRIPTION', + 'id': 'small_rel_path_TEST_01', + 'section_name': 'Relative path import test', + 'sections': list([ + 'Relative path import test', + ]), + 'target_id': 'small_rel_path_TEST_01', + 'title': 'TEST_01 DESCRIPTION', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'test_IMPL_01': dict({ + 'description': 'Incoming links of this spec: :need_incoming:`test_IMPL_01`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Implementation for specification', + 'id': 'test_IMPL_01', + 'links': list([ + 'test_OWN_ID_123', + ]), + 'links_back': list([ + 'test_T_C3893', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + 'new_tag', + ]), + 'target_id': 'test_IMPL_01', + 'title': 'Implementation for specification', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'test_OWN_ID_123': dict({ + 'description': 'Outgoing links of this spec: :need_outgoing:`test_OWN_ID_123`.', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification of a requirement', + 'id': 'test_OWN_ID_123', + 'links': list([ + 'test_R_F4722', + ]), + 'links_back': list([ + 'test_IMPL_01', + 'test_T_C3893', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + 'new_tag', + ]), + 'target_id': 'test_OWN_ID_123', + 'title': 'Specification of a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'test_REQ_001': dict({ + 'description': ''' + This is an awesome requirement and it includes a nice title, + a given id, a tag and this text as description. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'test_REQ_001', + 'links_back': list([ + 'test_S_503A1', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'example', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_REQ_001', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'test_ROLES_REQ_1': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Sliced Bread', + 'id': 'test_ROLES_REQ_1', + 'links_back': list([ + 'test_ROLES_REQ_2', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + 'new_tag', + ]), + 'target_id': 'test_ROLES_REQ_1', + 'title': 'Sliced Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'test_ROLES_REQ_2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Butter on Bread', + 'id': 'test_ROLES_REQ_2', + 'links': list([ + 'test_ROLES_REQ_1', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + 'new_tag', + ]), + 'target_id': 'test_ROLES_REQ_2', + 'title': 'Butter on Bread', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'test_R_22EB2': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement B', + 'id': 'test_R_22EB2', + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'B', + 'filter', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_R_22EB2', + 'title': 'Requirement B', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'test_R_2A9D0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Requirement A', + 'id': 'test_R_2A9D0', + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'A', + 'filter', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_R_2A9D0', + 'title': 'Requirement A', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'test_R_F4722': dict({ + 'description': ''' + This is my **first** requirement!! + + .. note:: You can use any rst code inside it :) + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'My first requirement', + 'id': 'test_R_F4722', + 'links_back': list([ + 'test_OWN_ID_123', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'requirement', + 'test', + 'awesome', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_R_F4722', + 'title': 'My first requirement', + 'type': 'req', + 'type_name': 'Requirement', + }), + 'test_S_01A67': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification B', + 'id': 'test_S_01A67', + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'open', + 'tags': list([ + 'B', + 'filter', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_S_01A67', + 'title': 'Specification B', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'test_S_503A1': dict({ + 'description': ''' + We haven't set an **ID** here, so sphinxcontrib-needs + will generated one for us. + + But we have **set a link** to our first requirement and + also a *status* is given. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Spec for a requirement', + 'id': 'test_S_503A1', + 'links': list([ + 'test_REQ_001', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'done', + 'tags': list([ + 'important', + 'example', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_S_503A1', + 'title': 'Spec for a requirement', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'test_S_D70B0': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Specification A', + 'id': 'test_S_D70B0', + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'closed', + 'tags': list([ + 'A', + 'filter', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_S_D70B0', + 'title': 'Specification A', + 'type': 'spec', + 'type_name': 'Specification', + }), + 'test_TEST_01': dict({ + 'description': 'TEST IMPORT TITLE', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'TEST IMPORT DESCRIPTION', + 'id': 'test_TEST_01', + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'imported', + 'new_tag', + ]), + 'target_id': 'test_TEST_01', + 'title': 'TEST IMPORT DESCRIPTION', + 'type': 'impl', + 'type_name': 'Implementation', + }), + 'test_T_5CCAA': dict({ + 'description': '', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test 1', + 'id': 'test_T_5CCAA', + 'parent_needs_back': list([ + 'test_T_C3893', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'tags': list([ + 'awesome', + 'filter', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_T_5CCAA', + 'title': 'Test 1', + 'type': 'test', + 'type_name': 'Test Case', + }), + 'test_T_C3893': dict({ + 'description': ''' + This test checks :need:`impl_01` for :need:`test_OWN_ID_123` inside a + Python 2.7 environment. + ''', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test for XY', + 'id': 'test_T_C3893', + 'links': list([ + 'test_OWN_ID_123', + 'test_IMPL_01', + ]), + 'parent_need': 'test_T_5CCAA', + 'parent_needs': list([ + 'test_T_5CCAA', + ]), + 'section_name': 'TEST', + 'sections': list([ + 'TEST', + 'TEST DOCUMENT IMPORT', + ]), + 'status': 'implemented', + 'tags': list([ + 'test', + 'user_interface', + 'python27', + 'imported', + 'new_tag', + ]), + 'target_id': 'test_T_C3893', + 'title': 'Test for XY', + 'type': 'test', + 'type_name': 'Test Case', + }), + }), + 'needs_amount': 66, + 'needs_defaults_removed': True, + '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', + }), + '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', + ]), + }), + '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', + }), + }), + }), + }) +# --- +# name: test_needimport_needs_json_download[test_app0] + dict({ + 'IMP_TEST_101': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'collapse': False, + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content': 'IMP_TEST_101 DESCRIPTION', + 'created_at': '', + 'delete': False, + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'TEST_101 TITLE', + 'has_dead_links': False, + 'has_forbidden_dead_links': False, + 'hide': False, + 'id': 'IMP_TEST_101', + 'id_complete': 'IMP_TEST_101', + 'id_parent': 'IMP_TEST_101', + 'id_prefix': '', + 'is_external': False, + 'is_modified': False, + 'is_need': True, + 'is_part': False, + 'jinja_content': False, + 'layout': None, + 'lineno': 14, + 'lineno_content': None, + 'links': list([ + ]), + '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': 'TEST DOCUMENT NEEDIMPORT DOWNLOAD NEEDS JSON', + 'sections': list([ + 'TEST DOCUMENT NEEDIMPORT DOWNLOAD NEEDS JSON', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + 'ext_test', + ]), + 'target_id': 'IMP_TEST_101', + 'template': None, + 'title': 'TEST_101 TITLE', + 'type': 'impl', + 'type_color': '#DF744A', + 'type_name': 'Implementation', + 'type_prefix': 'IM_', + 'type_style': 'node', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'SPEC_1': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'collapse': False, + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content': 'other.', + 'created_at': '', + 'delete': False, + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'A Spec', + 'has_dead_links': False, + 'has_forbidden_dead_links': False, + 'hide': False, + 'id': 'SPEC_1', + 'id_complete': 'SPEC_1', + 'id_parent': 'SPEC_1', + 'id_prefix': '', + 'is_external': False, + 'is_modified': False, + 'is_need': True, + 'is_part': False, + 'jinja_content': False, + 'layout': '', + 'lineno': 9, + 'lineno_content': 12, + 'links': list([ + ]), + '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': 'TEST DOCUMENT NEEDIMPORT DOWNLOAD NEEDS JSON', + 'sections': list([ + 'TEST DOCUMENT NEEDIMPORT DOWNLOAD NEEDS JSON', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'SPEC_1', + 'template': None, + 'title': 'A Spec', + 'type': 'spec', + 'type_color': '#FEDCD2', + 'type_name': 'Specification', + 'type_prefix': 'SP_', + 'type_style': 'node', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'STORY_1': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'collapse': False, + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content': 'some.', + 'created_at': '', + 'delete': False, + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'A story', + 'has_dead_links': False, + 'has_forbidden_dead_links': False, + 'hide': False, + 'id': 'STORY_1', + 'id_complete': 'STORY_1', + 'id_parent': 'STORY_1', + 'id_prefix': '', + 'is_external': False, + 'is_modified': False, + 'is_need': True, + 'is_part': False, + 'jinja_content': False, + 'layout': '', + 'lineno': 4, + 'lineno_content': 7, + 'links': list([ + ]), + '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': 'TEST DOCUMENT NEEDIMPORT DOWNLOAD NEEDS JSON', + 'sections': list([ + 'TEST DOCUMENT NEEDIMPORT DOWNLOAD NEEDS JSON', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'STORY_1', + 'template': None, + 'title': 'A story', + 'type': 'story', + 'type_color': '#BFD8D2', + 'type_name': 'User Story', + 'type_prefix': 'US_', + 'type_style': 'node', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + }) +# --- diff --git a/tests/doc_test/import_doc/conf.py b/tests/doc_test/import_doc/conf.py index 4c044331f..1e6501b34 100644 --- a/tests/doc_test/import_doc/conf.py +++ b/tests/doc_test/import_doc/conf.py @@ -60,3 +60,5 @@ {% endif -%} """ + +needs_json_remove_defaults = True diff --git a/tests/doc_test/import_doc/index.rst b/tests/doc_test/import_doc/index.rst index f79ace4e3..216c4f5a5 100644 --- a/tests/doc_test/import_doc/index.rst +++ b/tests/doc_test/import_doc/index.rst @@ -24,6 +24,17 @@ TEST :id_prefix: test_ :tags: imported; new_tag +FILTERED +-------- +.. needimport:: needs_test.json + :id_prefix: filter_ + :tags: imported + :filter: id == 'IMPL_01' + +.. needimport:: needs_test.json + :id_prefix: ids_ + :tags: imported + :ids: ROLES_REQ_1,ROLES_REQ_2 diff --git a/tests/doc_test/import_doc_invalid/index.rst b/tests/doc_test/import_doc_invalid/index.rst index 3974d5c95..f0f6d14d2 100644 --- a/tests/doc_test/import_doc_invalid/index.rst +++ b/tests/doc_test/import_doc_invalid/index.rst @@ -30,13 +30,6 @@ No_NEEDS :id_prefix: no_ :tags: imported; new_tag -.. toctree:: - - subdoc/filter - subdoc/abs_path_import - subdoc/rel_path_import - subdoc/deprecated_rel_path_import - .. req:: Test requirement 1 :id: REQ_1 diff --git a/tests/test_import.py b/tests/test_needimport.py similarity index 66% rename from tests/test_import.py rename to tests/test_needimport.py index b3fad7319..9d10b1c2d 100644 --- a/tests/test_import.py +++ b/tests/test_needimport.py @@ -1,22 +1,34 @@ import json -import subprocess +import os from pathlib import Path import pytest import responses +from sphinx.util.console import strip_colors from syrupy.filters import props from sphinx_needs.data import SphinxNeedsData +from sphinx_needs.directives.needimport import NeedimportException +from sphinx_needs.needsfile import SphinxNeedsFileException @pytest.mark.parametrize( "test_app", - [{"buildername": "html", "srcdir": "doc_test/import_doc"}], + [{"buildername": "html", "srcdir": "doc_test/import_doc", "no_plantuml": True}], indirect=True, ) def test_import_json(test_app): app = test_app app.build() + warnings = strip_colors( + app._warning.getvalue().replace( + str(app.srcdir) + os.sep + "subdoc" + os.sep, "srcdir/subdoc/" + ) + ).splitlines() + assert warnings == [ + "srcdir/subdoc/deprecated_rel_path_import.rst:6: WARNING: Deprecation warning: Relative path must be relative to the current document in future, not to the conf.py location. Use a starting '/', like '/needs.json', to make the path relative to conf.py. [needs]" + ] + html = Path(app.outdir, "index.html").read_text() assert "TEST IMPORT TITLE" in html assert "TEST_01" in html @@ -59,35 +71,21 @@ def test_import_json(test_app): @pytest.mark.parametrize( "test_app", - [{"buildername": "html", "srcdir": "doc_test/import_doc_invalid"}], - indirect=True, -) -def test_json_schema_console_check(test_app): - """Checks the console output for hints about json schema validation errors""" - import os - import subprocess - - app = test_app - - srcdir = Path(app.srcdir) - out_dir = os.path.join(srcdir, "_build") - out = subprocess.run( - ["sphinx-build", "-b", "html", srcdir, out_dir], capture_output=True - ) - - assert "Schema validation errors detected" in str(out.stdout) - - -@pytest.mark.parametrize( - "test_app", - [{"buildername": "html", "srcdir": "doc_test/import_doc_invalid"}], + [ + { + "buildername": "html", + "srcdir": "doc_test/import_doc_invalid", + "no_plantuml": True, + } + ], indirect=True, ) -def test_json_schema_file_check(test_app): +def test_json_schema_check(test_app): """Checks that an invalid json-file gets normally still imported and is used as normal (if possible)""" - app = test_app - app.build() - html = Path(app.outdir, "index.html").read_text() + test_app.build() + assert test_app._warning.getvalue() == "" + assert "Schema validation errors detected" in test_app._status.getvalue() + html = Path(test_app.outdir, "index.html").read_text() assert "TEST_01" in html assert "test_TEST_01" in html assert "new_tag" in html @@ -95,13 +93,18 @@ def test_json_schema_file_check(test_app): @pytest.mark.parametrize( "test_app", - [{"buildername": "html", "srcdir": "doc_test/import_doc_empty"}], + [ + { + "buildername": "html", + "srcdir": "doc_test/import_doc_empty", + "no_plantuml": True, + } + ], indirect=True, ) def test_empty_file_check(test_app): """Checks that an empty needs.json throws an exception""" app = test_app - from sphinx_needs.needsfile import SphinxNeedsFileException with pytest.raises(SphinxNeedsFileException): app.build() @@ -109,22 +112,28 @@ def test_empty_file_check(test_app): @pytest.mark.parametrize( "test_app", - [{"buildername": "html", "srcdir": "doc_test/non_exists_file_import"}], + [ + { + "buildername": "html", + "srcdir": "doc_test/non_exists_file_import", + "no_plantuml": True, + } + ], indirect=True, ) def test_import_non_exists_json(test_app): # Check non exists file import - try: - app = test_app + app = test_app + with pytest.raises(ReferenceError) as err: app.build() - except ReferenceError as err: - assert err.args[0].startswith("Could not load needs import file") - assert "non_exists_file_import" in err.args[0] + + assert err.value.args[0].startswith("Could not load needs import file") + assert "non_exists_file_import" in err.value.args[0] @pytest.mark.parametrize( "test_app", - [{"buildername": "needs", "srcdir": "doc_test/import_doc"}], + [{"buildername": "needs", "srcdir": "doc_test/import_doc", "no_plantuml": True}], indirect=True, ) def test_import_builder(test_app, snapshot): @@ -137,7 +146,13 @@ def test_import_builder(test_app, snapshot): @pytest.mark.parametrize( "test_app", - [{"buildername": "needs", "srcdir": "doc_test/doc_needimport_download_needs_json"}], + [ + { + "buildername": "needs", + "srcdir": "doc_test/doc_needimport_download_needs_json", + "no_plantuml": True, + } + ], indirect=True, ) def test_needimport_needs_json_download(test_app, snapshot): @@ -210,18 +225,44 @@ def test_needimport_needs_json_download(test_app, snapshot): { "buildername": "needs", "srcdir": "doc_test/doc_needimport_download_needs_json_negative", + "no_plantuml": True, } ], indirect=True, ) def test_needimport_needs_json_download_negative(test_app): - app = test_app - src_dir = Path(app.srcdir) - out_dir = Path(app.outdir) - output = subprocess.run( - ["sphinx-build", "-M", "html", src_dir, out_dir], capture_output=True - ) + with pytest.raises(NeedimportException) as err: + test_app.build() assert ( - "NeedimportException: Getting http://my_wrong_name_company.com/docs/v1/remote-needs.json didn't work." - in output.stderr.decode("utf-8") + "Getting http://my_wrong_name_company.com/docs/v1/remote-needs.json didn't work." + in err.value.args[0] ) + + +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "latex", + "srcdir": "doc_test/doc_needimport_noindex", + "no_plantuml": True, + } + ], + indirect=True, +) +def test_doc_needimport_noindex(test_app): + app = test_app + app.build() + warnings = strip_colors( + app._warning.getvalue().replace(str(app.srcdir) + os.sep, "srcdir/") + ).splitlines() + assert warnings == [ + "srcdir/needimport.rst:6: WARNING: Need 'TEST_01' has unknown outgoing link 'SPEC_1' in field 'links' [needs.link_outgoing]" + ] + + latex_path = str(Path(app.outdir, "needstestdocs.tex")) + latex = Path(latex_path).read_text() + + assert Path(latex_path).exists() + assert len(latex) > 0 + assert "AAA" in latex diff --git a/tests/test_needimport_noindex.py b/tests/test_needimport_noindex.py deleted file mode 100644 index b6aaacf87..000000000 --- a/tests/test_needimport_noindex.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import sys -from pathlib import Path - -import pytest - - -@pytest.mark.parametrize( - "test_app", - [{"buildername": "latex", "srcdir": "doc_test/doc_needimport_noindex"}], - indirect=True, -) -def test_doc_needimport_noindex(test_app): - app = test_app - app.build() - - latex_path = str(Path(app.outdir, "needstestdocs.tex")) - latex = Path(latex_path).read_text() - print(f"in path {app.outdir}", sys.stderr) - - assert os.path.exists(latex_path) - assert len(latex) > 0 - assert "AAA" in latex