Repository: ResearchObject/ro-crate-py
Pull request #55 (2021-03-31 14:50:56)

Refer to test suites from the root dataset
Adds flexibility to testing metadata, allowing to cover cases where a "test" directory may not be present.

See https://github.com/ResearchObject/ro-crate/issues/122#issuecomment-811129313 for the background.


Patch:
diff --git a/rocrate/rocrate.py b/rocrate/rocrate.py
index 72cada4..bda6f75 100644
--- a/rocrate/rocrate.py
+++ b/rocrate/rocrate.py
@@ -364,6 +364,12 @@ class ROCrate():
             return rval
         return None
 
+    @property
+    def test_suites(self):
+        mentions = [_ for _ in (self.root_dataset['mentions'] or []) if isinstance(_, TestSuite)]
+        about = [_ for _ in (self.root_dataset['about'] or []) if isinstance(_, TestSuite)]
+        return mentions + about
+
     def resolve_id(self, id_):
         if not is_url(id_):
             id_ = urljoin(self.arcp_base_uri, id_)  # also does path normalization
@@ -503,19 +509,18 @@ class ROCrate():
         return workflow
 
     def add_test_suite(self, identifier=None, name=None, main_entity=None):
+        test_ref_prop = "mentions"
         if not main_entity:
             main_entity = self.mainEntity
             if not main_entity:
-                raise ValueError("crate does not have a main entity")
+                test_ref_prop = "about"
         suite = self.add(TestSuite(self, identifier))
         suite.name = name or suite.id.lstrip("#")
-        suite["mainEntity"] = main_entity
-        # note that a test dir is required (possibly empty) even if the suite
-        # is going to have only instances (no definitions)
-        test_dir = self.test_dir or self.add_directory(dest_path="test")
-        suite_set = set(test_dir["about"] or [])
+        if main_entity:
+            suite["mainEntity"] = main_entity
+        suite_set = set(self.test_suites)
         suite_set.add(suite)
-        test_dir["about"] = list(suite_set)
+        self.root_dataset[test_ref_prop] = list(suite_set)
         self.metadata.extra_terms.update(TESTING_EXTRA_TERMS)
         return suite
 


Test patch:
diff --git a/examples/read_test_metadata.py b/examples/read_test_metadata.py
index 546a9a2..e7e4ef3 100644
--- a/examples/read_test_metadata.py
+++ b/examples/read_test_metadata.py
@@ -36,7 +36,7 @@ PLANEMO = "https://w3id.org/ro/terms/test#PlanemoEngine"
 
 def print_suites(crate):
     print("test suites:")
-    for suite in crate.test_dir["about"]:
+    for suite in crate.test_suites:
         print(" ", suite.id)
         print("    workflow:", suite["mainEntity"].id)
         print("    instances:")
@@ -74,7 +74,7 @@ def main():
         print("planemo executable:", exe)
 
     # run a test suite
-    suite = crate.test_dir["about"][0]
+    suite = crate.test_suites[0]
     def_path = crate_dir / suite.definition.id
     workflow = suite["mainEntity"]
     workflow_path = crate_dir / workflow.id
@@ -83,7 +83,7 @@ def main():
     print("definition path:", def_path)
     print("workflow:", workflow.id)
     assert suite.definition.engine.id == PLANEMO
-    new_workflow_path = def_path.parent / workflow_path.name
+    new_workflow_path = str(def_path.parent / workflow_path.name)
     # Planemo expects the test definition in the same dir as the workflow file
     shutil.copy2(workflow_path, new_workflow_path)
     cmd = ["planemo", "test", "--engine", "docker_galaxy",
diff --git a/test/test-data/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json b/test/test-data/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json
index 4e84e70..ddde0c0 100644
--- a/test/test-data/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json
+++ b/test/test-data/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json
@@ -41,9 +41,6 @@
                 {
                     "@id": "sort-and-change-case.ga"
                 },
-                {
-                    "@id": "test/"
-                },
                 {
                     "@id": "LICENSE"
                 },
@@ -53,6 +50,11 @@
                 {
                     "@id": "test/test1/sort-and-change-case-test.yml"
                 }
+            ],
+            "mentions": [
+                {
+                    "@id": "#test1"
+                }
             ]
         },
         {
@@ -67,15 +69,6 @@
             },
             "name": "sort-and-change-case"
         },
-        {
-            "@id": "test/",
-            "@type": "Dataset",
-            "about": [
-                {
-                    "@id": "#test1"
-                }
-            ]
-        },
         {
             "@id": "LICENSE",
             "@type": "File"
diff --git a/test/test_test_metadata.py b/test/test_test_metadata.py
index 938b7e4..c59fdc2 100644
--- a/test/test_test_metadata.py
+++ b/test/test_test_metadata.py
@@ -86,12 +86,7 @@ def test_read(test_data_dir, helpers):
     assert test_suite.definition is test_definition
     assert test_suite["mainEntity"] is main_wf
 
-    test_dataset = crate.dereference('test/')
-    test_dataset_prop = test_dataset.properties()
-    assert test_dataset_prop['@id'] == 'test/'
-    assert test_dataset_prop['@id'] == test_dataset.id
-    assert crate.test_dir is test_dataset
-    assert set(crate.test_dir["about"]) == {test_suite}
+    assert set(crate.test_suites) == {test_suite}
 
 
 def test_create():
@@ -148,32 +143,29 @@ def test_create():
     assert test_suite.definition is test_definition
 
 
-def test_add_test_suite(test_data_dir, helpers):
+def test_add_test_suite(test_data_dir):
     top_dir = test_data_dir / "ro-crate-galaxy-sortchangecase"
     wf_path = top_dir / "sort-and-change-case.ga"
     crate = ROCrate()
-    with pytest.raises(ValueError):  # no main entity
-        crate.add_test_suite()
     wf = crate.add(ComputationalWorkflow(crate, str(wf_path), wf_path.name))
     crate.mainEntity = wf
     suites = set()
-    assert crate.test_dir is None
+    assert not crate.test_suites
     s1 = crate.add_test_suite()
-    assert crate.test_dir is not None
     assert s1["mainEntity"] is wf
     suites.add(s1)
-    assert suites == set(crate.test_dir["about"])
+    assert suites == set(crate.test_suites)
     s2 = crate.add_test_suite(identifier="test1")
     assert s2["mainEntity"] is wf
     assert s2.id == "#test1"
     suites.add(s2)
-    assert suites == set(crate.test_dir["about"])
+    assert suites == set(crate.test_suites)
     s3 = crate.add_test_suite(identifier="test2", name="Test 2")
     assert s3["mainEntity"] is wf
     assert s3.id == "#test2"
     assert s3.name == "Test 2"
     suites.add(s3)
-    assert suites == set(crate.test_dir["about"])
+    assert suites == set(crate.test_suites)
     wf2_path = top_dir / "README.md"
     wf2 = crate.add(ComputationalWorkflow(crate, wf2_path, wf2_path.name))
     s4 = crate.add_test_suite(identifier="test3", name="Foo", main_entity=wf2)
@@ -181,10 +173,14 @@ def test_add_test_suite(test_data_dir, helpers):
     assert s4.id == "#test3"
     assert s4.name == "Foo"
     suites.add(s4)
-    assert suites == set(crate.test_dir["about"])
+    assert suites == set(crate.test_suites)
+    # check filtering in test_suites property
+    crate.root_dataset["mentions"] += [wf]
+    assert set(crate.root_dataset["mentions"]) > suites
+    assert suites == set(crate.test_suites)
 
 
-def test_add_test_instance(test_data_dir, helpers):
+def test_add_test_instance(test_data_dir):
     top_dir = test_data_dir / "ro-crate-galaxy-sortchangecase"
     wf_path = top_dir / "sort-and-change-case.ga"
     crate = ROCrate()
@@ -228,7 +224,7 @@ def test_add_test_instance(test_data_dir, helpers):
 
 
 @pytest.mark.parametrize("engine,engine_version", [(None, None), ("planemo", None), ("planemo", ">=0.70")])
-def test_add_test_definition(test_data_dir, helpers, engine, engine_version):
+def test_add_test_definition(test_data_dir, engine, engine_version):
     top_dir = test_data_dir / "ro-crate-galaxy-sortchangecase"
     wf_path = top_dir / "sort-and-change-case.ga"
     def_path = top_dir / "test" / "test1" / "sort-and-change-case-test.yml"