Skip to content

Commit cc0eedc

Browse files
m-mohrsoxofaan
andauthored
Tests for the test suite (#468)
* Add first tests from process examples * Add tests and docs * Add more tests * More tests, clean-up * Tests for arrays * Remove optional tests in favor of returns or throws * EditorConfig + fix invalid files * Add id and experimental flag to test files * Add tests for comparison processes * Add tests for reducers etc. * Add further tests * title converted to comments, fix parameter name of exp in tests * Fix test for tan * Add tests for array_apply, more NaN tests for comparisons, add required flag for sub-processes * add array_apply and array_filter tests, fix several math tests * update readme * Add first datacube test * Fix apply test * Add profile levels * Fix test in apply * Add tests for reduce_dimension and apply_dimension * Add more tests * Fix various test cases * Special no-data handling, fix tests * Add filter_bands and filter_temporal * Add mask, merge_cubes, refactor datacube object * Mark experimental processes * Add additional test for apply_dimension * Add tests for apply_kernel * Add tests for aggregate_temporal * Add tests for aggregate_temporal_period, fix aggregate_temporal * Add tests for filter_bbox, filter_spatial and mask_polygon * Use nodata type in tests properly * Add changelog entry * Fix invalid datetimes * Apply suggestions from code review Co-authored-by: Stefaan Lippens <[email protected]> * Add comment * Add consistency checks for test files and fix issues * Remove list of processes from README * Update tests/README.md Co-authored-by: Stefaan Lippens <[email protected]> * Update tests/schema/schema.json Co-authored-by: Stefaan Lippens <[email protected]> * Update tests/README.md * Update tests --------- Co-authored-by: Stefaan Lippens <[email protected]>
1 parent 02c5e04 commit cc0eedc

File tree

176 files changed

+15567
-15
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

176 files changed

+15567
-15
lines changed

.editorconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ indent_style = spaces
77
indent_size = 4
88
insert_final_newline = true
99
trim_trailing_whitespace = true
10+
11+
[*.json5]
12+
charset = utf-8
13+
end_of_line = crlf
14+
indent_style = spaces
15+
indent_size = 2
16+
insert_final_newline = true
17+
trim_trailing_whitespace = true

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- Implementation guide for implementing OGC API - Processes in openEO
12+
- Unit Tests (see folder `tests`, moved specification tests and CI tools to `dev`)
1213
- `date_difference`: Allow `week` as a unit [#506](https://github.com/Open-EO/openeo-processes/issues/506)
1314
- `export_collection`: New process
1415
- `export_workspace`: New process

absolute.json

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@
2828
}
2929
},
3030
"examples": [
31-
{
32-
"arguments": {
33-
"x": 0
34-
},
35-
"returns": 0
36-
},
3731
{
3832
"arguments": {
3933
"x": 3.5
@@ -45,12 +39,6 @@
4539
"x": -0.4
4640
},
4741
"returns": 0.4
48-
},
49-
{
50-
"arguments": {
51-
"x": -3.5
52-
},
53-
"returns": 3.5
5442
}
5543
],
5644
"links": [

apply_kernel.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "apply_kernel",
33
"summary": "Apply a spatial convolution with a kernel",
4-
"description": "Applies a 2D convolution (i.e. a focal operation with a weighted kernel) on the horizontal spatial dimensions (axes `x` and `y`) of a raster data cube.\n\nEach value in the kernel is multiplied with the corresponding pixel value and all products are summed up afterwards. The sum is then multiplied with the factor.\n\nThe process can't handle non-numerical or infinite numerical values in the data cube. Boolean values are converted to integers (`false` = 0, `true` = 1), but all other non-numerical or infinite values are replaced with zeroes by default (see parameter `replace_invalid`).\n\nFor cases requiring more generic focal operations or non-numerical values, see ``apply_neighborhood()``.",
4+
"description": "Applies a 2D convolution (i.e. a focal operation with a weighted kernel) on the horizontal spatial dimensions (axes `x` and `y`) of a raster data cube.\n\nEach value in the kernel is multiplied with the corresponding pixel value and all products are summed up afterwards. The sum is then multiplied with the factor.\n\nThe process can't handle non-numerical or infinite numerical values in the data cube. Boolean values are converted to integers (`false` = 0, `true` = 1), but all other non-numerical, NaN, no-data, or infinite values are replaced with zeroes by default (see parameter `replace_invalid`).\n\nFor cases requiring more generic focal operations or non-numerical values, see ``apply_neighborhood()``.",
55
"categories": [
66
"cubes",
77
"math > image filter"
@@ -70,7 +70,7 @@
7070
},
7171
{
7272
"name": "replace_invalid",
73-
"description": "This parameter specifies the value to replace non-numerical or infinite numerical values with. By default, those values are replaced with zeroes.",
73+
"description": "This parameter specifies the value to replace non-numerical, NaN, no-data, or infinite numerical values with. By default, those values are replaced with zeroes.",
7474
"schema": {
7575
"type": "number"
7676
},

dev/check-tests.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Ensure that each test is valid
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const JSON5 = require('json5');
6+
const ajv = require('ajv');
7+
8+
const testsDir = path.join(__dirname, '../tests');
9+
10+
const tests = fs.readdirSync(testsDir).filter(file => file.endsWith('.json5'));
11+
12+
const schemaPath = path.join(testsDir, 'schema', 'schema.json');
13+
const schemaContent = fs.readFileSync(schemaPath, 'utf8');
14+
const schema = JSON.parse(schemaContent);
15+
const validate = new ajv().compile(schema);
16+
17+
const results = {};
18+
for (const testFile of tests) {
19+
const testPath = path.join(testsDir, testFile);
20+
21+
let testData;
22+
// Ensure we can load the test file as JSON5
23+
try {
24+
testData = JSON5.parse(fs.readFileSync(testPath, 'utf8'));
25+
} catch (error) {
26+
results[testFile] = `Invalid JSON5: ${error.message}`;
27+
continue;
28+
}
29+
30+
// Ensure the file is valid against the schema
31+
if (!validate(testData)) {
32+
results[testFile] = `Schema validation failed: ${validate.errors.map(err => err.message).join(', ')}`;
33+
continue;
34+
}
35+
36+
// Make sure the id is the same as filename without extension
37+
const expectedId = path.basename(testFile, '.json5');
38+
if (testData.id !== expectedId) {
39+
results[testFile] = `ID mismatch: expected ${expectedId}, got ${testData.id}`;
40+
continue;
41+
}
42+
43+
// Check if experimental is set to the same value as in the process itself
44+
let processFile = path.join(__dirname, '../', expectedId + '.json');
45+
if (!fs.existsSync(processFile)) {
46+
processFile = path.join(__dirname, '../proposals/', expectedId + '.json');
47+
}
48+
if (fs.existsSync(processFile)) {
49+
const processData = JSON.parse(fs.readFileSync(processFile, 'utf8'));
50+
const expected = processData.experimental || false;
51+
const actual = testData.experimental || false;
52+
if (expected !== actual) {
53+
results[testFile] = `Experimental flag mismatch: expected ${expected}, got ${actual}`;
54+
continue;
55+
}
56+
}
57+
}
58+
59+
if (Object.keys(results).length > 0) {
60+
console.error('The following test files have issues:');
61+
for (const [file, error] of Object.entries(results)) {
62+
console.error(`- ${file}: ${error}`);
63+
}
64+
process.exit(1);
65+
}
66+
else {
67+
console.log('All test files are valid and match the expected schema.');
68+
}

dev/has-tests.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Ensure that each process has a corresponding file in the tests directory.
2+
// It can be empty, but it must exist to ensure people made that decision consciously.
3+
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
const processesDir = path.join(__dirname, '../');
8+
const proposalsDir = path.join(__dirname, '../proposals');
9+
const testsDir = path.join(__dirname, '../tests');
10+
11+
const processes = [
12+
...fs.readdirSync(processesDir),
13+
...fs.readdirSync(proposalsDir),
14+
]
15+
.filter(file => file.endsWith('.json'))
16+
.map(file => path.basename(file, '.json'));;
17+
const tests = fs.readdirSync(testsDir)
18+
.filter(file => file.endsWith('.json5'))
19+
.map(file => path.basename(file, '.json5'));
20+
21+
// Check which tests are missing for the processes
22+
const missingTests = processes.filter(process => !tests.includes(process));
23+
24+
if (missingTests.length > 0) {
25+
console.error('The following processes are missing tests:');
26+
missingTests.forEach(process => console.error(`- ${process}`));
27+
}
28+
29+
// Check whether there are tests for non-existing processes
30+
const extraTests = tests.filter(test => !processes.includes(test));
31+
if (extraTests.length > 0) {
32+
console.error('\nThe following tests exist without a corresponding process:');
33+
extraTests.forEach(test => console.error(`- ${test}`));
34+
}
35+
36+
// todo: add check that json5 files are valid
37+
38+
if (missingTests.length === 0 && extraTests.length === 0) {
39+
console.log('All processes have corresponding tests and vice versa.');
40+
process.exit(0);
41+
}
42+
else {
43+
process.exit(1);
44+
}

dev/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,15 @@
2323
"http-server": "^14.1.1"
2424
},
2525
"scripts": {
26-
"test": "openeo-processes-lint testConfig.json",
26+
"check-tests": "node check-tests.js",
27+
"has-tests": "node has-tests.js",
28+
"lint": "openeo-processes-lint testConfig.json",
29+
"test": "npm run has-tests && npm run check-tests && npm run lint",
2730
"generate": "concat-json-files \"../{*,proposals/*}.json\" -t \"processes.json\"",
2831
"start": "npm run generate && http-server -p 9876 -o docs.html -c-1"
32+
},
33+
"dependencies": {
34+
"ajv": "^8.17.1",
35+
"json5": "^2.2.3"
2936
}
3037
}

tests/README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Tests
2+
3+
This folder contains test cases for the openEO processes.
4+
5+
## Assumptions
6+
7+
The test cases assume a couple of things as they are an abstraction and not bound to specific implementations:
8+
- The JSON Schema type `number` explicitly includes the values `+Infinity`, `-Infinity` and `NaN`.
9+
- The input and output values for no-data values are `null` by default unless otherwise specified by a runner.
10+
- Input that is not valid according to the process schema must be rejected upfront, without any process evaluation attempt. Consequently, cases with schema mismatches are not covered in these tests. For example, the absolute process only tests against the data types `number` and `null`. There are no tests for a boolean or string input.
11+
- Numerical data types such as uint8 don't matter, i.e. tests don't check for overflows etc. This suite can't provide such tests as the underlying data type is not known.
12+
- If not otherwise specified for numbers, a precision of 10 decimals is checked so return values should have at least 11 decimals.
13+
14+
## Test Files
15+
16+
To allow for more data types (e.g. infinity and nan for numbers), all the files are encoded in **JSON5** instead of JSON.
17+
18+
The test files have the following schema: [schema/schema.json](./schema/schema.json)
19+
20+
### No-data values
21+
22+
No-data values have a special encoding in tests (see below).
23+
The encoding is replaced with `null` unless otherwise specified by the runners.
24+
25+
```json5
26+
{
27+
"type": "nodata"
28+
}
29+
```
30+
31+
### Datetimes
32+
33+
Datetimes as strings have a varying precision, especially regarding the milliseconds.
34+
Also, sometimes timezones are differently handled.
35+
36+
Datetimes in return values should be encoded in compliance with RFC 3339 so that the results can be compared better:
37+
38+
```json5
39+
{
40+
"type": "datetime",
41+
"value": "2020-01-01T00:00:00Z"
42+
}
43+
```
44+
45+
### External references
46+
47+
Arguments and return values can point to external files, e.g.
48+
49+
```json5
50+
{
51+
"$ref": "https://host.example/datacube.json"
52+
}
53+
```
54+
55+
The test suite can currently only load JSON and JSON5 files.
56+
57+
### Labeled arrays
58+
59+
Labeled arrays can't be represented in JSON5 and will be provided as an object instead.
60+
61+
```json5
62+
{
63+
"type": "labeled-array",
64+
"data": [
65+
{
66+
"key": "B01",
67+
"value": 1.23
68+
},
69+
{
70+
"key": "B02",
71+
"value": 0.98
72+
}
73+
// ...
74+
]
75+
}
76+
```
77+
78+
### Datacubes
79+
80+
Datacubes can't be represented in JSON5 and will be provided as an object instead.
81+
Vector datacubes are currently not supported.
82+
83+
```json5
84+
{
85+
"type": "datacube",
86+
"data": [
87+
// multi-dimensional array
88+
// can be set to `null` if the data values are irrelevant for the test.
89+
],
90+
"nodata": [
91+
NaN
92+
],
93+
"order": ["bands", "t", "y", "x"],
94+
"dimensions": {
95+
// similar to the STAC datacube extension
96+
// properties: type, axis (if type = spatial), values, and reference_system (optional)
97+
"bands": {
98+
"type": "bands",
99+
"values": ["blue","green","red","nir"]
100+
},
101+
"t": {
102+
"type": "temporal",
103+
"values": ["2020-06-01T00:00:00Z","2020-06-03T00:00:00Z","2020-06-06T00:00:00Z"]
104+
},
105+
"y": {
106+
"type": "spatial",
107+
"axis": "y",
108+
"values": [5757495.0,5757485.0,5757475.0,5757465.0],
109+
"reference_system": "EPSG:25832"
110+
},
111+
"x": {
112+
"type": "spatial",
113+
"axis": "x",
114+
"values": [404835.0,404845.0,404855.0,404865.0,404875.0],
115+
"reference_system": "EPSG:25832"
116+
}
117+
}
118+
}
119+
```

tests/absolute.json5

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"id": "absolute",
3+
"level": "L1",
4+
"tests": [
5+
{
6+
"arguments": {
7+
"x": 0
8+
},
9+
"returns": 0
10+
},
11+
{
12+
"arguments": {
13+
"x": 1
14+
},
15+
"returns": 1
16+
},
17+
{
18+
"arguments": {
19+
"x": -1
20+
},
21+
"returns": 1
22+
},
23+
{
24+
"arguments": {
25+
"x": 2.5
26+
},
27+
"returns": 2.5
28+
},
29+
{
30+
"arguments": {
31+
"x": -2.5
32+
},
33+
"returns": 2.5
34+
},
35+
{
36+
"arguments": {
37+
"x": NaN
38+
},
39+
"returns": NaN
40+
},
41+
{
42+
"arguments": {
43+
"x": Infinity
44+
},
45+
"returns": Infinity
46+
},
47+
{
48+
"arguments": {
49+
"x": -Infinity
50+
},
51+
"returns": Infinity
52+
},
53+
{
54+
"arguments": {
55+
"x": {"type": "nodata"}
56+
},
57+
"returns": {"type": "nodata"}
58+
}
59+
]
60+
}

0 commit comments

Comments
 (0)