Repository: SINTEF/dlite (language: python)

Issue:
Move all entities used for tests in the entities folder
There are both entities in the folder:
https://github.com/SINTEF/dlite/tree/master/bindings/python/tests

and in the folder
https://github.com/SINTEF/dlite/tree/master/bindings/python/tests/entities

Similarly entities, both valid and invalid are found here:
https://github.com/SINTEF/dlite/tree/master/bindings/python/tests/input

It is easier to find them again if they are all in the same place for the tests.



Concurrently a folder at the same level should be made for entities and entities moved there.
Move test input/output to separate directories
Currently running the tests pollutes the current directory with a lot of test output files. To avoid this, write test output to a `output` sub-directory of the test directory. 

It would also be easier to spot test input if they are moved to a `input` sub-directory of the test directory. 

Test directories that should be fixed:
- [ ] bindings/python/tests/
- [ ] bindings/fortran/tests/
- [ ] src/tests/
- [ ] src/utils/tests/
- [ ] storages/json/tests/
- [ ] storages/rdf/tests/
- [ ] tools/tests/


Discussion / hints:

PR #720 addresses this for two tests in binding/python/tests/

Fix patch:
diff --git a/bindings/python/dlite-storage.i b/bindings/python/dlite-storage.i
index 780767ce8..c7aced65e 100644
--- a/bindings/python/dlite-storage.i
+++ b/bindings/python/dlite-storage.i
@@ -52,7 +52,7 @@ Returns next instance or None if exhausted.") next;
     if (dlite_storage_iter_next($self->s, $self->state, uuid) == 0) {
       DLiteInstance *inst = dlite_instance_load($self->s, uuid);
       // Why isn't the refcount already increased?
-      dlite_instance_incref(inst);
+      if (inst) dlite_instance_incref(inst);
       return inst;
     }
     return NULL;
diff --git a/bindings/python/scripts/CMakeLists.txt b/bindings/python/scripts/CMakeLists.txt
index d93eb9a3d..5ebdd5afb 100644
--- a/bindings/python/scripts/CMakeLists.txt
+++ b/bindings/python/scripts/CMakeLists.txt
@@ -32,26 +32,30 @@ install(
 # Tests
 test_success(
   dlite-validate-Person
-  dlite-validate ../tests/Person.json
+  dlite-validate ../tests/entities/Person.json
 )
 test_success(
   dlite-validate-persons
-  dlite-validate -p ../tests/Person.json -i a1d8d35f-723c-5ea1-abaf-8fc8f1d0982f
-    ../tests/persons.json
+  dlite-validate
+    -p ../tests/entities/Person.json
+    -i a1d8d35f-723c-5ea1-abaf-8fc8f1d0982f
+    ../tests/input/persons.json
 )
 test_success(
   dlite-validate-persons2
   dlite-validate
-    --storage-path=../tests
+    --storage-path=../tests/entities
     --id=Ada
     --show
-    ../tests/persons.json
+    ../tests/input/persons.json
   )
 
 # No --id for storage with multiple instances
 test_failure(
   dlite-validate-persons-fail2
-  dlite-validate --storage-path ../tests/Person.json --show ../tests/persons.json
+  dlite-validate
+    --storage-path ../tests/entities/Person.json
+    --show ../tests/input/persons.json
 )
 
 # The "dimensions" keyword is required
diff --git a/src/dlite-json.c b/src/dlite-json.c
index 18e593671..2761408c8 100644
--- a/src/dlite-json.c
+++ b/src/dlite-json.c
@@ -612,8 +612,9 @@ static DLiteInstance *parse_instance(const char *src, jsmntok_t *obj,
     if ((t = jsmn_item(src, obj, "properties"))) dims[n++] = t->size;
     if ((t = jsmn_item(src, obj, "relations")))  dims[n++] = t->size;
     if (n != meta->_ndimensions)
-      FAIL1("metadata does not confirm to schema, please check dimensions, "
-            "properties and/or relations: %s", id);
+      FAILCODE1(dliteParseError, "metadata does not confirm to schema, "
+                "please check dimensions, properties and/or relations: %s",
+                id);
 
   } else {
     if (meta->_ndimensions > 0) {
diff --git a/src/dlite-misc.c b/src/dlite-misc.c
index 919e022f1..77ab8c078 100644
--- a/src/dlite-misc.c
+++ b/src/dlite-misc.c
@@ -693,6 +693,7 @@ int dlite_err_ignored_get(DLiteErrCode code)
 {
   DLiteErrMask *mask = _dlite_err_mask_get();
   if (!mask) return 0;
+  if (code > 0 && (*mask & DLITE_ERRBIT(dliteUnknownError))) return 1;
   return *mask & DLITE_ERRBIT(code);
 }
 
diff --git a/storages/rdf/dlite-rdf.c b/storages/rdf/dlite-rdf.c
index 37572c4e4..85aa793af 100644
--- a/storages/rdf/dlite-rdf.c
+++ b/storages/rdf/dlite-rdf.c
@@ -232,6 +232,7 @@ DLiteStorage *rdf_open(const DLiteStoragePlugin *api, const char *uri,
 int rdf_close(DLiteStorage *storage)
 {
   RdfStorage *s = (RdfStorage *)storage;
+  int retval = 0;
 
   if (s->flags & dliteWritable) {
     librdf_world *world = triplestore_get_world(s->ts);
@@ -247,6 +248,7 @@ int rdf_close(DLiteStorage *storage)
     if (s->filename) {
       unsigned char *buf;
       librdf_uri *base_uri=NULL, *type_uri=NULL;
+      FILE *fp;
       if (s->base_uri)
         base_uri = librdf_new_uri(world, (unsigned char *)s->base_uri);
       if (s->type_uri)
@@ -257,10 +259,11 @@ int rdf_close(DLiteStorage *storage)
 
       if (strcmp(s->filename, "-") == 0) {
         fprintf(stdout, "%s", buf);
-      } else {
-        FILE *fp = fopen(s->filename, "w");
+      } else if ((fp = fopen(s->filename, "w"))) {
         fprintf(fp, "%s", buf);
         fclose(fp);
+      } else {
+        retval = err(dliteIOError, "cannot write rdf file: %s", s->filename);
       }
 
       if (base_uri) librdf_free_uri(base_uri);
@@ -276,7 +279,7 @@ int rdf_close(DLiteStorage *storage)
   if (s->format) free(s->format);
   if (s->mime_type) free(s->mime_type);
   if (s->type_uri) free(s->type_uri);
-  return 0;
+  return retval;
 }
 
 


Test patch:
diff --git a/bindings/python/tests/.gitignore b/bindings/python/tests/.gitignore
index 41706a5db..7d0735cd2 100644
--- a/bindings/python/tests/.gitignore
+++ b/bindings/python/tests/.gitignore
@@ -1,16 +1,2 @@
 .gdb_history
 __pycache__
-
-inst.ttl
-inst.yaml
-test.json
-test.yaml
-
-xxx.json
-xxx2.json
-yyy.json
-
-myentity.json
-persons2.json
-
-db.xml
diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt
index 2e1da655c..b6280f2ee 100644
--- a/bindings/python/tests/CMakeLists.txt
+++ b/bindings/python/tests/CMakeLists.txt
@@ -23,6 +23,7 @@ set(tests
   test_postgresql2_read
   test_ref_type
   test_pydantic
+  test_iri
 )
 
 foreach(test ${tests})
diff --git a/bindings/python/tests/Person.json b/bindings/python/tests/Person.json
deleted file mode 100644
index 97c608791..000000000
--- a/bindings/python/tests/Person.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Person",
-  "description": "A person.",
-  "dimensions": {
-    "N": "Number of skills."
-  },
-  "properties": {
-    "name": {
-      "type": "string",
-      "description": "Full name."
-    },
-    "age": {
-      "type": "float",
-      "unit": "years",
-      "description": "Age of person."
-    },
-    "skills": {
-      "type": "string",
-      "shape": ["N"],
-      "description": "List of skills."
-    }
-  }
-}
diff --git a/bindings/python/tests/entities/Invalid1.json b/bindings/python/tests/entities/Invalid1.json
index 8401f9f18..d7693cff0 100644
--- a/bindings/python/tests/entities/Invalid1.json
+++ b/bindings/python/tests/entities/Invalid1.json
@@ -1,7 +1,19 @@
 {
-  "name": "Missing version",
-  "namespace": "http://onto-ns.com/meta",
-  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
-  "dimensions": {}
-  "properties": {}
+  "uri": "http://onto-ns.com/meta/0.1/Invalid1",
+  "description": "An datamodel for an item with float type and invalid shape name.",
+  "dimensions": {
+      "nf": "Number of eigen-frequencies."
+  },
+  "properties": {
+    "name": {
+      "type": "str",
+      "description": "Name of the item."
+    },
+    "f": {
+      "type": "float64",
+      "shape": ["nf_MISPELLED"],
+      "unit": "Hz",
+      "description": "The magic eigen-frequencies of the item."
+    }
+  }
 }
diff --git a/bindings/python/tests/entities/Invalid2.json b/bindings/python/tests/entities/Invalid2.json
index 4792bc587..e0a51fdf3 100644
--- a/bindings/python/tests/entities/Invalid2.json
+++ b/bindings/python/tests/entities/Invalid2.json
@@ -1,7 +1,15 @@
 {
-  "name": "Missing namespace",
-  "version": "0.0.1",
-  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
-  "dimensions": {}
-  "properties": {}
+  "uri": "http://onto-ns.com/meta/0.1/Invalid2",
+  "description": "A datamodel with ref-types with invalid shape name...",
+  "dimensions": {
+    "nrefs": "Number of references to Ref datamodels."
+  },
+  "properties": {
+    "items": {
+      "type": "ref",
+      "$ref": "http://onto-ns.com/meta/0.1/Item",
+      "shape": ["INVALID_nrefs"],
+      "description": "List of Item datamodels."
+    }
+  }
 }
diff --git a/bindings/python/tests/entities/Invalid3.json b/bindings/python/tests/entities/Invalid3.json
index 4f91de558..cb1ed38bb 100644
--- a/bindings/python/tests/entities/Invalid3.json
+++ b/bindings/python/tests/entities/Invalid3.json
@@ -1,5 +1,19 @@
 {
-  "url": "http://onto-ns.com/meta/0.1/Invalid3",
-  "description": "Missing properties",
-  "dimensions": {}
+  "uri": "http://onto-ns.com/meta/0.1/Invalid3",
+  "description": "Invalid JSON (square bracket for dimensions instead of braces).",
+  "dimensions": [
+    "nfrequencies": "Number of eigen-frequencies."
+  ],
+  "properties": {
+    "name": {
+      "type": "str",
+      "description": "Name of the item."
+    },
+    "f": {
+      "type": "float64",
+      "shape": ["nf"],
+      "unit": "Hz",
+      "description": "The magic eigen-frequencies of the item."
+    }
+  }
 }
diff --git a/bindings/python/tests/entities/Invalid5.json b/bindings/python/tests/entities/Invalid5.json
new file mode 100644
index 000000000..8401f9f18
--- /dev/null
+++ b/bindings/python/tests/entities/Invalid5.json
@@ -0,0 +1,7 @@
+{
+  "name": "Missing version",
+  "namespace": "http://onto-ns.com/meta",
+  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
+  "dimensions": {}
+  "properties": {}
+}
diff --git a/bindings/python/tests/entities/Invalid6.json b/bindings/python/tests/entities/Invalid6.json
new file mode 100644
index 000000000..4792bc587
--- /dev/null
+++ b/bindings/python/tests/entities/Invalid6.json
@@ -0,0 +1,7 @@
+{
+  "name": "Missing namespace",
+  "version": "0.0.1",
+  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
+  "dimensions": {}
+  "properties": {}
+}
diff --git a/bindings/python/tests/entities/Invalid7.json b/bindings/python/tests/entities/Invalid7.json
new file mode 100644
index 000000000..a1d112bc7
--- /dev/null
+++ b/bindings/python/tests/entities/Invalid7.json
@@ -0,0 +1,5 @@
+{
+  "url": "http://onto-ns.com/meta/0.1/Invalid7",
+  "description": "Missing properties",
+  "dimensions": {}
+}
diff --git a/bindings/python/tests/input/Item.json b/bindings/python/tests/entities/Item.json
similarity index 100%
rename from bindings/python/tests/input/Item.json
rename to bindings/python/tests/entities/Item.json
diff --git a/bindings/python/tests/MyEntity.json b/bindings/python/tests/entities/MyEntity.json
similarity index 100%
rename from bindings/python/tests/MyEntity.json
rename to bindings/python/tests/entities/MyEntity.json
diff --git a/bindings/python/tests/entities/Person.json b/bindings/python/tests/entities/Person.json
index 7e0dab81f..97c608791 100644
--- a/bindings/python/tests/entities/Person.json
+++ b/bindings/python/tests/entities/Person.json
@@ -1,34 +1,23 @@
 {
-  "name": "Person",
-  "version": "0.1",
-  "namespace": "http://onto-ns.com/meta",
-  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
+  "uri": "http://onto-ns.com/meta/0.1/Person",
   "description": "A person.",
-  "dimensions": [
-    {
-      "name": "N",
-      "description": "Number of skills."
-    }
-  ],
-  "properties": [
-    {
-      "name": "name",
+  "dimensions": {
+    "N": "Number of skills."
+  },
+  "properties": {
+    "name": {
       "type": "string",
       "description": "Full name."
     },
-    {
-      "name": "age",
+    "age": {
       "type": "float",
       "unit": "years",
       "description": "Age of person."
     },
-    {
-      "name": "skills",
+    "skills": {
       "type": "string",
-      "dims": [
-        "N"
-      ],
+      "shape": ["N"],
       "description": "List of skills."
     }
-  ]
+  }
 }
diff --git a/bindings/python/tests/input/Ref.json b/bindings/python/tests/entities/Ref.json
similarity index 100%
rename from bindings/python/tests/input/Ref.json
rename to bindings/python/tests/entities/Ref.json
diff --git a/bindings/python/tests/SimplePerson.json b/bindings/python/tests/entities/SimplePerson.json
similarity index 100%
rename from bindings/python/tests/SimplePerson.json
rename to bindings/python/tests/entities/SimplePerson.json
diff --git a/bindings/python/tests/global_dlite_state_mod2.py b/bindings/python/tests/global_dlite_state_mod2.py
index 1476774cb..de8a8d826 100644
--- a/bindings/python/tests/global_dlite_state_mod2.py
+++ b/bindings/python/tests/global_dlite_state_mod2.py
@@ -1,15 +1,16 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
+from pathlib import Path
 
 import dlite
 from dlite import Instance, Dimension, Property, Relation
 
 assert len(dlite.istore_get_uuids()) == 3 + 3
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
 
-url = "json://" + thisdir + "/MyEntity.json"
+thisdir = Path(__file__).absolute().parent
+entitydir = thisdir / "entities"
+
+url = f"json://{entitydir}/MyEntity.json"
 
 # myentity is already defined via test_global_dlite_state, no new instance is added to istore
 myentity = Instance.from_url(url)
diff --git a/bindings/python/tests/input/Invalid1.json b/bindings/python/tests/input/Invalid1.json
deleted file mode 100644
index d7693cff0..000000000
--- a/bindings/python/tests/input/Invalid1.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Invalid1",
-  "description": "An datamodel for an item with float type and invalid shape name.",
-  "dimensions": {
-      "nf": "Number of eigen-frequencies."
-  },
-  "properties": {
-    "name": {
-      "type": "str",
-      "description": "Name of the item."
-    },
-    "f": {
-      "type": "float64",
-      "shape": ["nf_MISPELLED"],
-      "unit": "Hz",
-      "description": "The magic eigen-frequencies of the item."
-    }
-  }
-}
diff --git a/bindings/python/tests/input/Invalid2.json b/bindings/python/tests/input/Invalid2.json
deleted file mode 100644
index e0a51fdf3..000000000
--- a/bindings/python/tests/input/Invalid2.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Invalid2",
-  "description": "A datamodel with ref-types with invalid shape name...",
-  "dimensions": {
-    "nrefs": "Number of references to Ref datamodels."
-  },
-  "properties": {
-    "items": {
-      "type": "ref",
-      "$ref": "http://onto-ns.com/meta/0.1/Item",
-      "shape": ["INVALID_nrefs"],
-      "description": "List of Item datamodels."
-    }
-  }
-}
diff --git a/bindings/python/tests/input/Invalid3.json b/bindings/python/tests/input/Invalid3.json
deleted file mode 100644
index cb1ed38bb..000000000
--- a/bindings/python/tests/input/Invalid3.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Invalid3",
-  "description": "Invalid JSON (square bracket for dimensions instead of braces).",
-  "dimensions": [
-    "nfrequencies": "Number of eigen-frequencies."
-  ],
-  "properties": {
-    "name": {
-      "type": "str",
-      "description": "Name of the item."
-    },
-    "f": {
-      "type": "float64",
-      "shape": ["nf"],
-      "unit": "Hz",
-      "description": "The magic eigen-frequencies of the item."
-    }
-  }
-}
diff --git a/bindings/python/tests/persons.json b/bindings/python/tests/input/persons.json
similarity index 100%
rename from bindings/python/tests/persons.json
rename to bindings/python/tests/input/persons.json
diff --git a/bindings/python/tests/rdf.ttl b/bindings/python/tests/input/rdf.ttl
similarity index 100%
rename from bindings/python/tests/rdf.ttl
rename to bindings/python/tests/input/rdf.ttl
diff --git a/bindings/python/tests/test_ref_type.json b/bindings/python/tests/input/test_ref_type.json
similarity index 100%
rename from bindings/python/tests/test_ref_type.json
rename to bindings/python/tests/input/test_ref_type.json
diff --git a/bindings/python/tests/inst.json b/bindings/python/tests/inst.json
deleted file mode 100644
index 1aaefb7e3..000000000
--- a/bindings/python/tests/inst.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "ce592ff9-a7dc-548a-bbe5-3c53800afaf3":   {
-    "uri": "my-data",
-    "meta": "http://onto-ns.com/meta/0.1/MyEntity",
-    "dimensions": {
-      "N": 2,
-      "M": 3
-    },
-    "properties": {
-      "a-blob": "000102030405060708090a0b0c0d0e0f",
-      "a-blob-array": [["00aa00ff", "00bb00dd"], ["001100aa", "0000cc00"]],
-      "a-bool": false,
-      "a-bool-array": [true, false],
-      "an-int": 42,
-      "an-int-array": [0, 1, -1],
-      "a-float": 0,
-      "a-float64-array": [0, 3.14, -1e-6],
-      "a-fixstring": "",
-      "a-fixstring-array": [["abc", "def"], ["ghi", "jkl"]],
-      "a-string": "en af dem der red med fane",
-      "a-string-array": [[null, "a", "b"], ["abc", "a longer string", "and one that is not shorter..."]],
-      "a-relation": ["Cat", "isA", "Mammal"],
-      "a-relation-array": [["Shark", "isA", "Fish"], ["Fish", "isA", "Animal"]]
-    }
-  }
-}
diff --git a/bindings/python/tests/output/.gitignore b/bindings/python/tests/output/.gitignore
index 5c0c65f19..5b880214c 100644
--- a/bindings/python/tests/output/.gitignore
+++ b/bindings/python/tests/output/.gitignore
@@ -1,2 +1,4 @@
 *.json
 *.yaml
+*.ttl
+db.xml
diff --git a/bindings/python/tests/test_collection.py b/bindings/python/tests/test_collection.py
index 54a2286c7..72a5b7247 100755
--- a/bindings/python/tests/test_collection.py
+++ b/bindings/python/tests/test_collection.py
@@ -6,6 +6,8 @@
 
 thisdir = Path(__file__).resolve().parent
 outdir = thisdir / "output"
+indir = thisdir / "input"
+entitydir = thisdir / "entities"
 
 
 # Create collection
@@ -23,7 +25,7 @@
 assert rel is None
 
 # Create instances
-url = f"json://{thisdir}/MyEntity.json?mode=r"
+url = f"json://{entitydir}/MyEntity.json?mode=r"
 e = dlite.Instance.from_url(url)
 inst1 = dlite.Instance.from_metaid(e.uri, [3, 2])
 inst2 = dlite.Instance.from_metaid(e.uri, (3, 4), "myinst")
diff --git a/bindings/python/tests/test_entity.py b/bindings/python/tests/test_entity.py
index 76c255dc9..87aa4ad03 100755
--- a/bindings/python/tests/test_entity.py
+++ b/bindings/python/tests/test_entity.py
@@ -11,16 +11,15 @@
 
 
 thisdir = Path(__file__).absolute().parent
+outdir = thisdir / "output"
 indir = thisdir / "input"
 entitydir = thisdir / "entities"
 dlite.storage_path.append(indir / "*.json")
 dlite.storage_path.append(entitydir / "*.json")
 
-url = f"json://{thisdir}/MyEntity.json"
-
 
 # Load metadata (i.e. an instance of meta-metadata) from url
-myentity = Instance.from_url(url)
+myentity = Instance.from_url(f"json://{entitydir}/MyEntity.json")
 print(myentity.uuid)
 
 # Check some properties of the entity
@@ -32,15 +31,11 @@
 assert not myentity.is_metameta
 
 # Store the entity to a new file
-myentity.save("json://xxx.json?mode=w")
+myentity.save(f"json://{outdir}/test_entity.json?mode=w")
 
 # Try to overwrite without mode - should fail because metadata is immutable
-try:
-    myentity.save("json://xxx.json")
-except dlite.DLiteError:
-    dlite.errclr()
-else:
-    assert False, "overwriting single-entity formatted file"
+with raises(dlite.DLiteStorageSaveError):
+    myentity.save(f"json://{outdir}/test_entity.json")
 
 # Create an instance of `myentity` with dimensions 2, 3
 # For convinience, we give it an unique label "myid" that can be used
@@ -86,61 +81,51 @@
 # print(inst)
 
 # Check save and load
-inst.save("json://inst.json?mode=w")
-inst2 = Instance.from_url("json://inst.json")
+inst.save(f"json://{outdir}/test_entity_inst.json?mode=w")
+inst2 = Instance.from_url(f"json://{outdir}/test_entity_inst.json")
 blob = inst2["a-blob"]
 
 del inst2
 inst2 = Instance.from_url(
-    "json://inst.json?mode=r#46a67765-3d8b-5764-9583-3aec59a17983"
+    f"json://{outdir}/test_entity_inst.json?"
+    "mode=r#46a67765-3d8b-5764-9583-3aec59a17983"
 )
 assert inst2["a-blob"] == blob
 
 del inst2
-inst2 = Instance.from_location("json", "inst.json")
+inst2 = Instance.from_location("json", outdir / "test_entity_inst.json")
 assert inst2["a-blob"] == blob
 
 del inst2
 inst2 = Instance.from_location(
-    "json", "inst.json", id="46a67765-3d8b-5764-9583-3aec59a17983"
+    "json", outdir / "test_entity_inst.json",
+    id="46a67765-3d8b-5764-9583-3aec59a17983"
 )
 assert inst2["a-blob"] == blob
 del inst2
 
-with dlite.Storage("json", "inst.json") as s:
+with dlite.Storage("json", outdir / "test_entity_inst.json") as s:
     inst2 = dlite.Instance.from_storage(s)
 assert inst2["a-blob"] == blob
 del inst2
 
-with dlite.Storage("json", "inst.json") as s:
+with dlite.Storage("json", outdir / "test_entity_inst.json") as s:
     inst2 = s.load(id="46a67765-3d8b-5764-9583-3aec59a17983")
 assert inst2["a-blob"] == blob
 del inst2
 
 # Make sure we fail with an exception for pathetic cases
-try:
+with raises(dlite.DLiteStorageOpenError):
     Instance.from_location("json", "/", "mode=r")
-except dlite.DLiteError:
-    print('*** catched error loading "/" in read mode')
-    dlite.errclr()
 
-try:
+with raises(dlite.DLiteStorageLoadError):
     Instance.from_location("json", "/", "mode=w")
-except dlite.DLiteError:
-    print('*** catched error loading "/" in write mode')
-    dlite.errclr()
 
-try:
+with raises(dlite.DLiteStorageLoadError):
     Instance.from_location("json", "")
-except dlite.DLiteError:
-    print('*** catched error loading ""')
-    dlite.errclr()
 
-try:
+with raises(dlite.DLiteStorageLoadError):
     Instance.from_location("json", "non-existing-path...")
-except dlite.DLiteError:
-    print('*** catched error loading "non-existing-path..."')
-    dlite.errclr()
 
 
 # Test for issue #352 - improved error message for missing dimensions
@@ -154,17 +139,8 @@
   }
 }
 """
-try:
-    entity = dlite.Instance.from_json(json_repr)
-except dlite.DLiteError as exc:
-    assert str(exc) == (
-        "DLiteOtherError: metadata does not confirm to schema, please check "
-        "dimensions, properties and/or relations: "
-        "http://onto-ns.com/ex/0.1/test"
-    )
-    dlite.errclr()
-else:
-    assert False  # missing dimensions should raise an exception
+with raises(dlite.DLiteParseError):
+    dlite.Instance.from_json(json_repr)
 
 
 # Test copy
@@ -252,14 +228,14 @@
 
 
 # Test save
-inst.save("json://yyy.json?mode=w")
+inst.save(f"json://{outdir}/test_entity2.json?mode=w")
 
 try:
     import yaml
 except ImportError:
     pass
 else:
-    inst.save("yaml://yyy.yaml?mode=w")
+    inst.save(f"yaml://{outdir}/test_entity.yaml?mode=w")
 
 
 # Test metadata
@@ -304,10 +280,12 @@
 
 # Metadata schema
 schema = dlite.get_instance(dlite.ENTITY_SCHEMA)
-schema.save("entity_schema.json?mode=w;arrays=false")
-schema.meta.save("basic_metadata_schema.json?mode=w;arrays=false")
+schema.save(f"json://{outdir}/entity_schema.json?mode=w;arrays=false")
+schema.meta.save(
+    f"json://{outdir}/basic_metadata_schema.json?mode=w;arrays=false"
+)
 
-mm = dlite.Instance.from_url("json://entity_schema.json")
+mm = dlite.Instance.from_url(f"json://{outdir}/entity_schema.json")
 assert mm.uri == dlite.ENTITY_SCHEMA
 
 
@@ -317,7 +295,6 @@
 with raises(dlite.DLiteMissingInstanceError, dlite.DLiteSyntaxError):
     invalid1 = Invalid1([2], properties={"name": "a", "f": [3.14, 2.72]})
 
-
 # For issue #686
 Invalid2 = dlite.get_instance("http://onto-ns.com/meta/0.1/Invalid2")
 with raises(dlite.DLiteMissingInstanceError, dlite.DLiteSyntaxError):
@@ -325,8 +302,20 @@
 
 
 # For issue #691
-with raises(dlite.DLiteStorageOpenError):
-    Invalid3 = dlite.Instance.from_location("json", indir / "Invalid3.json")
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid3.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid4.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid5.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid6.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid7.json")
 
 
 # For issue #702
diff --git a/bindings/python/tests/test_factory.py b/bindings/python/tests/test_factory.py
index 541b59ccf..5a32d7434 100755
--- a/bindings/python/tests/test_factory.py
+++ b/bindings/python/tests/test_factory.py
@@ -7,7 +7,9 @@
 
 thisdir = Path(__file__).resolve().parent
 rootdir = thisdir.parent.parent.parent
+indir = thisdir / "input"
 outdir = thisdir / "output"
+entitydir = thisdir / "entities"
 
 
 class Person:
@@ -20,17 +22,15 @@ def __repr__(self):
         return "Person(%r, %r, %r)" % (self.name, self.age, list(self.skills))
 
 
-url = f"json://{thisdir}/Person.json"
-
 print("-- create: ExPerson")
-ExPerson = dlite.classfactory(Person, url=url)
+ExPerson = dlite.classfactory(Person, url=f"json://{entitydir}/Person.json")
 
 print("-- create: person1")
 person1 = Person("Jack Daniel", 42, ["distilling", "tasting"])
 
 print("-- create: person2")
 person2 = ExPerson("Jack Daniel", 42, ["distilling", "tasting"])
-person2.dlite_inst.save("json", f"{outdir}/persons.json", "mode=w")
+person2.dlite_inst.save("json", outdir / "persons.json", "mode=w")
 
 # Print json-representation of person2 using dlite
 print(person2.dlite_inst.asjson(indent=2))
diff --git a/bindings/python/tests/test_global_dlite_state.py b/bindings/python/tests/test_global_dlite_state.py
index 849fcc351..bae1b2ac5 100644
--- a/bindings/python/tests/test_global_dlite_state.py
+++ b/bindings/python/tests/test_global_dlite_state.py
@@ -1,11 +1,16 @@
-import os
+from pathlib import Path
 import importlib
 import dlite
 from dlite import Instance
 
 from global_dlite_state_mod1 import assert_exists_in_module
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
+
+thisdir = Path(__file__).absolute().parent
+outdir = thisdir / "output"
+indir = thisdir / "input"
+entitydir = thisdir / "entities"
+#dlite.storage_path.append(entitydir / "*.json")
 
 assert len(dlite.istore_get_uuids()) == 3  # 3 Hardcoded dlite instances
 
@@ -17,7 +22,7 @@
 # Must exist in imported dlite in different module (mod1)
 assert_exists_in_module(coll.uuid)
 
-url = "json://" + thisdir + "/MyEntity.json" + "?mode=r"
+url = f"json://{entitydir}/MyEntity.json" + "?mode=r"
 e = Instance.from_url(url)  # (2)
 assert len(dlite.istore_get_uuids()) == 3 + 2
 
@@ -32,7 +37,7 @@
 
 # Use compile and exec with dlite defined in globals
 env = globals().copy()
-filename = os.path.join(thisdir, "global_dlite_state_mod2.py")
+filename = thisdir / "global_dlite_state_mod2.py"
 
 with open(filename) as fd:
     exec(compile(fd.read(), filename, "exec"), env)
diff --git a/bindings/python/tests/test_iri.py b/bindings/python/tests/test_iri.py
index 0ce438a09..869faed68 100644
--- a/bindings/python/tests/test_iri.py
+++ b/bindings/python/tests/test_iri.py
@@ -1,14 +1,14 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
+from pathlib import Path
 
 import dlite
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
 
-url = "json://" + thisdir + "/MyEntity.json" + "?mode=r"
+thisdir = Path(__file__).parent.absolute()
+entitydir = thisdir / "entities"
+dlite.storage_path.append(f"{entitydir}/*.json")
 
-E = dlite.Instance.from_url(url)
+E = dlite.Instance.from_url(url=f"json://{entitydir}/MyEntity.json?mode=r")
 E.iri = "http://emmo.info/emmo/EMMO_Physical"
 E.iri = None
 E.iri = "http://emmo.info/emmo/EMMO_Physical"
diff --git a/bindings/python/tests/test_mapping.py b/bindings/python/tests/test_mapping.py
index f58e61886..168f0b555 100755
--- a/bindings/python/tests/test_mapping.py
+++ b/bindings/python/tests/test_mapping.py
@@ -6,11 +6,12 @@
 
 # Configure search paths
 thisdir = Path(__file__).parent.absolute()
-dlite.storage_path.append(f"{thisdir}/*.json")
+entitydir = thisdir / "entities"
+dlite.storage_path.append(f"{entitydir}/*.json")
 dlite.python_mapping_plugin_path.append(f"{thisdir}/python-mapping-plugins")
 
 # Create an instance of Person
-Person = dlite.Instance.from_url(f"json:{thisdir}/Person.json?mode=r")
+Person = dlite.Instance.from_url(f"json:{entitydir}/Person.json?mode=r")
 person = Person(dimensions=[2])
 person.name = "Neil Armstrong"
 person.age = 39
diff --git a/bindings/python/tests/test_postgresql1_write.py b/bindings/python/tests/test_postgresql1_write.py
index 88825ed20..62e9398cd 100644
--- a/bindings/python/tests/test_postgresql1_write.py
+++ b/bindings/python/tests/test_postgresql1_write.py
@@ -9,6 +9,9 @@
 
 # Paths
 thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
+outdir = thisdir / "output"
+entitydir = thisdir / "entities"
 
 
 def parse_pgconf():
@@ -54,14 +57,14 @@ def ping_server(server="localhost", port=5432, timeout=3):
 ping_server()
 
 # Add metadata to search path
-dlite.storage_path.append(f"{thisdir}/Person.json")
-dlite.storage_path.append(f"{thisdir}/SimplePerson.json")
+dlite.storage_path.append(f"{entitydir}/Person.json")
+dlite.storage_path.append(f"{entitydir}/SimplePerson.json")
 
 # Load dataset
 host, user, database, password = parse_pgconf()
 inst = dlite.Instance.from_location(
     "json",
-    f"{thisdir}/persons.json",
+    f"{indir}/persons.json",
     id="Cleopatra",
 )
 
diff --git a/bindings/python/tests/test_python_storage.py b/bindings/python/tests/test_python_storage.py
index e0802daa1..903ac6fe9 100755
--- a/bindings/python/tests/test_python_storage.py
+++ b/bindings/python/tests/test_python_storage.py
@@ -29,6 +29,8 @@
 
 # thisdir = os.path.abspath(os.path.dirname(__file__))
 thisdir = Path(__file__).resolve().parent
+outdir = thisdir / "output"
+entitydir = thisdir / "entities"
 
 
 def equal_rdf_files(path1, path2):
@@ -46,21 +48,18 @@ def equal_rdf_files(path1, path2):
 
 
 # Test JSON
-
-url = "json://" + os.path.join(thisdir, "Person.json")
-Person = dlite.Instance.from_url(url)
-
+Person = dlite.Instance.from_url(f"json://{entitydir}/Person.json")
 person = Person(dims=[2])
 person.name = "Ada"
 person.age = 12.5
 person.skills = ["skiing", "jumping"]
 
 print("=== saving...")
-with dlite.Storage("json", "test.json", "mode=w") as s:
+with dlite.Storage("json", outdir / "test_python_storage.json", "mode=w") as s:
     s.save(person)
 
 print("=== loading...", person.uuid)
-with dlite.Storage("json", "test.json", "mode=r") as s:
+with dlite.Storage("json", outdir / "test_python_storage.json", "mode=r") as s:
     inst = s.load(id=person.uuid)
 
 
@@ -68,11 +67,11 @@ def equal_rdf_files(path1, path2):
 person2.name = "Berry"
 person2.age = 24.3
 person2.skills = ["eating", "sleeping", "reading"]
-with dlite.Storage("json://test.json") as s:
+with dlite.Storage(f"json://{outdir}/test_python_storage.json") as s:
     s.save(person2)
 
 
-s = dlite.Storage("json://test.json")
+s = dlite.Storage(f"json://{outdir}/test_python_storage.json")
 uuids = s.get_uuids()
 del s
 del uuids
diff --git a/bindings/python/tests/test_rdf.py b/bindings/python/tests/test_rdf.py
index ab35e88f5..485ab633c 100644
--- a/bindings/python/tests/test_rdf.py
+++ b/bindings/python/tests/test_rdf.py
@@ -12,9 +12,10 @@
 
 
 thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
 
 id = "http://onto-ns.com/data#my_test_instance"
-inst = from_rdf(thisdir / "rdf.ttl", id=id)
+inst = from_rdf(indir / "rdf.ttl", id=id)
 
 # Serialise `inst` to string `s`
 s = to_rdf(
@@ -25,6 +26,6 @@
 )
 
 # Check that content matches original serialisation
-with open(thisdir / "rdf.ttl", "r") as f:
+with open(indir / "rdf.ttl", "r") as f:
     orig = f.read()
 assert s == orig
diff --git a/bindings/python/tests/test_ref_type.py b/bindings/python/tests/test_ref_type.py
index d970c2f3e..5d7e62c5b 100644
--- a/bindings/python/tests/test_ref_type.py
+++ b/bindings/python/tests/test_ref_type.py
@@ -4,8 +4,9 @@
 
 
 thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
 
-dlite.storage_path.append(thisdir / "test_ref_type.json")
+dlite.storage_path.append(indir / "test_ref_type.json")
 
 Top = dlite.get_instance("http://onto-ns.com/meta/0.1/Top")
 Middle = dlite.get_instance("http://onto-ns.com/meta/0.1/Middle")
diff --git a/bindings/python/tests/test_storage.py b/bindings/python/tests/test_storage.py
index 7c7be74ba..4b93c394b 100755
--- a/bindings/python/tests/test_storage.py
+++ b/bindings/python/tests/test_storage.py
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
+from pathlib import Path
 
 import dlite
 from dlite.testutils import raises
@@ -12,10 +11,13 @@
     HAVE_YAML = False
 
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
-outdir = f"{thisdir}/output"
+thisdir = Path(__file__).absolute().parent
+outdir = thisdir / "output"
+indir = thisdir / "input"
+entitydir = thisdir / "entities"
+dlite.storage_path.append(entitydir / "*.json")
 
-url = "json://" + thisdir + "/MyEntity.json"
+url = f"json://{entitydir}/MyEntity.json"
 
 
 # Load metadata (i.e. an instance of meta-metadata) from url
@@ -36,7 +38,7 @@
 inst["a-bool-array"] = True, False
 
 # Test Storage.save()
-with dlite.Storage("json", f"{outdir}/tmp.json", "mode=w") as s:
+with dlite.Storage("json", outdir / "test_storage_tmp.json", "mode=w") as s:
     s.save(inst)
 
 # Test query
@@ -44,18 +46,18 @@
 
 # Test json
 print("--- testing json")
-myentity.save(f"json://{outdir}/myentity.json?mode=w")
-inst.save(f"json://{outdir}/inst.json?mode=w")
+myentity.save(f"json://{outdir}/test_storage_myentity.json?mode=w")
+inst.save(f"json://{outdir}/test_storage_inst.json?mode=w")
 del inst
-inst = dlite.Instance.from_url(f"json://{outdir}/inst.json#my-data")
+inst = dlite.Instance.from_url(f"json://{outdir}/test_storage_inst.json#my-data")
 
 
 # Test yaml
 if HAVE_YAML:
     print('--- testing yaml')
-    inst.save(f"yaml://{outdir}/inst.yaml?mode=w")
+    inst.save(f"yaml://{outdir}/test_storage_inst.yaml?mode=w")
     del inst
-    inst = dlite.Instance.from_url(f"yaml://{outdir}/inst.yaml#my-data")
+    inst = dlite.Instance.from_url(f"yaml://{outdir}/test_storage_inst.yaml#my-data")
 
     # test help()
     expected = """\
@@ -74,7 +76,9 @@
             - `single`: Whether the input is assumed to be in single-entity form.
                 If "auto" (default) the form will be inferred automatically.
 """
-    s = dlite.Storage("yaml", f"{outdir}/inst.yaml", options="mode=a")
+    s = dlite.Storage(
+        "yaml", outdir / "test_storage_inst.yaml", options="mode=a"
+    )
     assert s.help().strip() == expected.strip()
 
     # Test delete()
@@ -95,13 +99,27 @@
 # Test rdf
 try:
     print("--- testing rdf")
-    inst.save("rdf:db.xml?mode=w;store=file;filename=inst.ttl;format=turtle")
-except dlite.DLiteError:
+    inst.save(
+        f"rdf:{outdir}/db.xml?mode=w;"
+        "store=file;"
+        f"filename={outdir}/test_storage_inst.ttl;"
+        "format=turtle"
+    )
+except (dlite.DLiteUnsupportedError, dlite.DLiteStorageOpenError):
     print("    skipping rdf test")
 else:
     # del inst
     # FIXME: read from inst.ttl not db.xml
-    inst3 = dlite.Instance.from_url("rdf://db.xml#my-data")
+    inst3 = dlite.Instance.from_url("rdf://{outdir}/db.xml#my-data")
+
+    with raises(dlite.DLiteIOError):
+        inst.save(
+            f"rdf://{outdir}/db.xml?mode=w;"
+            "store=file;"
+            f"filename={outdir}/non-existing/test_storage_inst.ttl;"
+            "format=turtle"
+        )
+
 
 
 # Tests for issue #587
@@ -113,8 +131,7 @@
     inst.to_bytes("json")
 
 
-dlite.storage_path.append(thisdir)
-s = dlite.Storage("json", f"{thisdir}/persons.json", "mode=r")
+s = dlite.Storage("json", f"{indir}/persons.json", "mode=r")
 
 
 #assert len(list(s.instances())) == 5
diff --git a/bindings/python/tests/test_utils.py b/bindings/python/tests/test_utils.py
index cd2c3351a..d346bf5d8 100644
--- a/bindings/python/tests/test_utils.py
+++ b/bindings/python/tests/test_utils.py
@@ -14,9 +14,10 @@
 
 
 thisdir = Path(__file__).absolute().parent
-dlite.storage_path.append(thisdir / "input" / "*.json")
+entitydir = thisdir / "entities"
+dlite.storage_path.append(entitydir / "*.json")
 
-with open(thisdir / "Person.json", "rt") as f:
+with open(entitydir / "Person.json", "rt") as f:
     d = json.load(f)
 Person = dlite.utils.instance_from_dict(d)
 
@@ -67,9 +68,7 @@
 print(inst)
 
 
-url = "json://" + os.path.join(thisdir, "Person.json")
-Person = dlite.Instance.from_url(url)
-
+Person = dlite.Instance.from_url(f"json://{entitydir}/Person.json")
 person = Person([2])
 person.name = "Ada"
 person.age = 12.5
diff --git a/bindings/python/testutils.py b/bindings/python/testutils.py
index b60da1309..0e09156a9 100644
--- a/bindings/python/testutils.py
+++ b/bindings/python/testutils.py
@@ -49,6 +49,7 @@ def __exit__(self, exc_type, exc_value, tb):
         # was raised
         for exc in self.exceptions:
             if issubclass(exc_type, exc):
+                dlite.errclr()
                 return True
 
         raise UnexpectedExceptionError(