20 |
20 |
from jsonschema import ValidationError
|
21 |
21 |
|
22 |
22 |
from hydrilla import util as hydrilla_util
|
|
23 |
from hydrilla.util._util import _major_version_re
|
23 |
24 |
from hydrilla.builder import build, _version, local_apt
|
24 |
25 |
from hydrilla.builder.common_errors import *
|
25 |
26 |
|
... | ... | |
60 |
61 |
|
61 |
62 |
del src_files['report.spdx']
|
62 |
63 |
|
|
64 |
expected_source_copyright = [{
|
|
65 |
'file': 'report.spdx',
|
|
66 |
'sha256': sha256_hashes['report.spdx']
|
|
67 |
}, {
|
|
68 |
'file': 'LICENSES/CC0-1.0.txt',
|
|
69 |
'sha256': sha256_hashes['LICENSES/CC0-1.0.txt']
|
|
70 |
}]
|
|
71 |
|
63 |
72 |
expected_resources = [{
|
64 |
73 |
'$schema': 'https://hydrilla.koszko.org/schemas/api_resource_description-1.schema.json',
|
65 |
74 |
'source_name': 'hello',
|
66 |
|
'source_copyright': [{
|
67 |
|
'file': 'report.spdx',
|
68 |
|
'sha256': sha256_hashes['report.spdx']
|
69 |
|
}, {
|
70 |
|
'file': 'LICENSES/CC0-1.0.txt',
|
71 |
|
'sha256': sha256_hashes['LICENSES/CC0-1.0.txt']
|
72 |
|
}],
|
|
75 |
'source_copyright': expected_source_copyright,
|
73 |
76 |
'type': 'resource',
|
74 |
77 |
'identifier': 'helloapple',
|
75 |
78 |
'long_name': 'Hello Apple',
|
... | ... | |
89 |
92 |
}, {
|
90 |
93 |
'$schema': 'https://hydrilla.koszko.org/schemas/api_resource_description-1.schema.json',
|
91 |
94 |
'source_name': 'hello',
|
92 |
|
'source_copyright': [{
|
93 |
|
'file': 'report.spdx',
|
94 |
|
'sha256': sha256_hashes['report.spdx']
|
95 |
|
}, {
|
96 |
|
'file': 'LICENSES/CC0-1.0.txt',
|
97 |
|
'sha256': sha256_hashes['LICENSES/CC0-1.0.txt']
|
98 |
|
}],
|
|
95 |
'source_copyright': expected_source_copyright,
|
99 |
96 |
'type': 'resource',
|
100 |
97 |
'identifier': 'hello-message',
|
101 |
98 |
'long_name': 'Hello Message',
|
... | ... | |
114 |
111 |
expected_mapping = {
|
115 |
112 |
'$schema': 'https://hydrilla.koszko.org/schemas/api_mapping_description-1.schema.json',
|
116 |
113 |
'source_name': 'hello',
|
117 |
|
'source_copyright': [{
|
118 |
|
'file': 'report.spdx',
|
119 |
|
'sha256': sha256_hashes['report.spdx']
|
120 |
|
}, {
|
121 |
|
'file': 'LICENSES/CC0-1.0.txt',
|
122 |
|
'sha256': sha256_hashes['LICENSES/CC0-1.0.txt']
|
123 |
|
}],
|
|
114 |
'source_copyright': expected_source_copyright,
|
124 |
115 |
'type': 'mapping',
|
125 |
116 |
'identifier': 'helloapple',
|
126 |
117 |
'long_name': 'Hello Apple',
|
... | ... | |
141 |
132 |
expected_source_description = {
|
142 |
133 |
'$schema': 'https://hydrilla.koszko.org/schemas/api_source_description-1.schema.json',
|
143 |
134 |
'source_name': 'hello',
|
144 |
|
'source_copyright': [{
|
145 |
|
'file': 'report.spdx',
|
146 |
|
'sha256': sha256_hashes['report.spdx']
|
147 |
|
}, {
|
148 |
|
'file': 'LICENSES/CC0-1.0.txt',
|
149 |
|
'sha256': sha256_hashes['LICENSES/CC0-1.0.txt']
|
150 |
|
}],
|
|
135 |
'source_copyright': expected_source_copyright,
|
151 |
136 |
'source_archives': {
|
152 |
137 |
'zip': {
|
153 |
138 |
'sha256': '!!!!value to fill during test!!!!',
|
... | ... | |
155 |
140 |
},
|
156 |
141 |
'upstream_url': 'https://git.koszko.org/hydrilla-source-package-example',
|
157 |
142 |
'definitions': [{
|
|
143 |
'type': 'mapping',
|
|
144 |
'identifier': 'helloapple',
|
|
145 |
'long_name': 'Hello Apple',
|
|
146 |
'version': [2021, 11, 10],
|
|
147 |
}, {
|
158 |
148 |
'type': 'resource',
|
159 |
149 |
'identifier': 'helloapple',
|
160 |
150 |
'long_name': 'Hello Apple',
|
... | ... | |
164 |
154 |
'identifier': 'hello-message',
|
165 |
155 |
'long_name': 'Hello Message',
|
166 |
156 |
'version': [2021, 11, 10],
|
167 |
|
}, {
|
168 |
|
'type': 'mapping',
|
169 |
|
'identifier': 'helloapple',
|
170 |
|
'long_name': 'Hello Apple',
|
171 |
|
'version': [2021, 11, 10],
|
172 |
157 |
}],
|
173 |
158 |
'generated_by': expected_generated_by
|
174 |
159 |
}
|
175 |
160 |
|
176 |
|
expected = [*expected_resources, expected_mapping, expected_source_description]
|
|
161 |
expected = [expected_mapping, *expected_resources, expected_source_description]
|
|
162 |
expected_items = expected[:3]
|
177 |
163 |
|
178 |
164 |
def run_reuse(command, **kwargs):
|
179 |
165 |
"""
|
... | ... | |
212 |
198 |
class MockedPiggybacked:
|
213 |
199 |
"""Minimal mock of Piggybacked object."""
|
214 |
200 |
package_license_files = [PurePosixPath('.apt-root/.../copyright')]
|
215 |
|
package_must_depend = [{'identifier': 'apt-common-licenses'}]
|
|
201 |
resource_must_depend = [{'identifier': 'apt-common-licenses'}]
|
216 |
202 |
|
217 |
203 |
def resolve_file(path):
|
218 |
204 |
"""
|
219 |
|
For each path that starts with '.apt-root' return a valid
|
220 |
|
dummy file path.
|
|
205 |
For each path that starts with '.apt-root' return a valid dummy file
|
|
206 |
path.
|
221 |
207 |
"""
|
222 |
208 |
if path.parts[0] != '.apt-root':
|
223 |
209 |
return None
|
... | ... | |
289 |
275 |
@variant_maker
|
290 |
276 |
def sample_source_add_comments(monkeypatch, sample_source):
|
291 |
277 |
"""Add index.json comments that should be preserved."""
|
292 |
|
for dictionary in (index_obj, expected_source_description):
|
|
278 |
for dictionary in index_obj, *index_obj['definitions'], *expected:
|
293 |
279 |
monkeypatch.setitem(dictionary, 'comment', 'index.json comment')
|
294 |
280 |
|
295 |
|
for i, dicts in enumerate(zip(index_obj['definitions'], expected)):
|
296 |
|
for dictionary in dicts:
|
297 |
|
monkeypatch.setitem(dictionary, 'comment', 'index.json comment')
|
298 |
|
|
299 |
281 |
@variant_maker
|
300 |
282 |
def sample_source_remove_spdx(monkeypatch, sample_source):
|
301 |
283 |
"""Remove spdx report generation."""
|
302 |
284 |
monkeypatch.delitem(index_obj, 'reuse_generate_spdx_report')
|
303 |
285 |
|
304 |
|
for obj, key in [
|
305 |
|
(index_obj, 'copyright'),
|
306 |
|
*((definition, 'source_copyright') for definition in expected)
|
307 |
|
]:
|
308 |
|
new_list = [r for r in obj[key] if r['file'] != 'report.spdx']
|
309 |
|
monkeypatch.setitem(obj, key, new_list)
|
|
286 |
pred = lambda ref: ref['file'] != 'report.spdx'
|
|
287 |
copy_refs_in = list(filter(pred, index_obj['copyright']))
|
|
288 |
monkeypatch.setitem(index_obj, 'copyright', copy_refs_in)
|
|
289 |
|
|
290 |
copy_refs_out = list(filter(pred, expected_source_copyright))
|
|
291 |
for obj in expected:
|
|
292 |
monkeypatch.setitem(obj, 'source_copyright', copy_refs_out)
|
310 |
293 |
|
311 |
294 |
monkeypatch.delitem(dist_files, 'report.spdx')
|
312 |
295 |
|
... | ... | |
325 |
308 |
@variant_maker
|
326 |
309 |
def sample_source_remove_script(monkeypatch, sample_source):
|
327 |
310 |
"""Use default value ([]) for 'scripts' property in one of the resources."""
|
328 |
|
monkeypatch.delitem(index_obj['definitions'][1], 'scripts')
|
|
311 |
monkeypatch.delitem(index_obj['definitions'][2], 'scripts')
|
329 |
312 |
|
330 |
313 |
monkeypatch.setitem(expected_resources[1], 'scripts', [])
|
331 |
314 |
|
... | ... | |
335 |
318 |
@variant_maker
|
336 |
319 |
def sample_source_remove_payloads(monkeypatch, sample_source):
|
337 |
320 |
"""Use default value ({}) for 'payloads' property in mapping."""
|
338 |
|
monkeypatch.delitem(index_obj['definitions'][2], 'payloads')
|
|
321 |
monkeypatch.delitem(index_obj['definitions'][0], 'payloads')
|
339 |
322 |
|
340 |
323 |
monkeypatch.setitem(expected_mapping, 'payloads', {})
|
341 |
324 |
|
... | ... | |
363 |
346 |
if k != 'payloads')
|
364 |
347 |
monkeypatch.setitem(processed, 'spurious_property', 'some_value')
|
365 |
348 |
|
|
349 |
@variant_maker
|
|
350 |
def sample_source_make_version_2(monkeypatch, sample_source,
|
|
351 |
expected_documents_to_modify=[]):
|
|
352 |
"""Increase sources' schema version from to 2."""
|
|
353 |
for obj in index_obj, *expected_documents_to_modify:
|
|
354 |
monkeypatch.setitem(obj, '$schema', obj['$schema'].replace('1', '2'))
|
|
355 |
|
|
356 |
@variant_maker
|
|
357 |
def sample_source_cors_bypass_ignored(monkeypatch, sample_source, value=True):
|
|
358 |
"""
|
|
359 |
Specify CORS bypass permissions in sources, but keep sources' schema version
|
|
360 |
at 1.
|
|
361 |
"""
|
|
362 |
for definition in index_obj['definitions']:
|
|
363 |
monkeypatch.setitem(definition, 'permissions', {'cors_bypass': value})
|
|
364 |
|
|
365 |
@variant_maker
|
|
366 |
def sample_source_cors_bypass(monkeypatch, sample_source):
|
|
367 |
"""Specify CORS bypass permissions in sources."""
|
|
368 |
sample_source_cors_bypass_ignored(monkeypatch, sample_source, value=True)
|
|
369 |
sample_source_make_version_2(monkeypatch, sample_source, expected_items)
|
|
370 |
|
|
371 |
for obj in expected_items:
|
|
372 |
monkeypatch.setitem(obj, 'permissions', {'cors_bypass': True})
|
|
373 |
|
|
374 |
@variant_maker
|
|
375 |
def sample_source_cors_bypass_defaults(monkeypatch, sample_source):
|
|
376 |
"""
|
|
377 |
Specify CORS bypass permissions in sources but use the default value
|
|
378 |
("False").
|
|
379 |
"""
|
|
380 |
sample_source_cors_bypass_ignored(monkeypatch, sample_source, value=False)
|
|
381 |
sample_source_make_version_2(monkeypatch, sample_source)
|
|
382 |
|
|
383 |
@variant_maker
|
|
384 |
def sample_source_req_mappings_ignored(monkeypatch, sample_source,
|
|
385 |
value=[{'identifier': 'mapping-dep'}]):
|
|
386 |
"""
|
|
387 |
Specify dependencies on mappings, but keep sources' schema version at 1.
|
|
388 |
"""
|
|
389 |
for definition in index_obj['definitions']:
|
|
390 |
monkeypatch.setitem(definition, 'required_mappings', value);
|
|
391 |
|
|
392 |
@variant_maker
|
|
393 |
def sample_source_req_mappings(monkeypatch, sample_source):
|
|
394 |
"""Specify dependencies on mappings."""
|
|
395 |
sample_source_req_mappings_ignored(monkeypatch, sample_source)
|
|
396 |
sample_source_make_version_2(monkeypatch, sample_source, expected_items)
|
|
397 |
|
|
398 |
for obj in expected_items:
|
|
399 |
monkeypatch.setitem(obj, 'required_mappings',
|
|
400 |
[{'identifier': 'mapping-dep'}])
|
|
401 |
|
|
402 |
@variant_maker
|
|
403 |
def sample_source_req_mappings_defaults(monkeypatch, sample_source):
|
|
404 |
"""Specify dependencies of a mapping, but use the default value ("[]")."""
|
|
405 |
sample_source_req_mappings_ignored(monkeypatch, sample_source, value=[])
|
|
406 |
sample_source_make_version_2(monkeypatch, sample_source)
|
|
407 |
|
|
408 |
@variant_maker
|
|
409 |
def sample_source_combined_def(monkeypatch, sample_source):
|
|
410 |
"""Define mapping and resource together."""
|
|
411 |
sample_source_make_version_2(monkeypatch, sample_source)
|
|
412 |
|
|
413 |
mapping_def = index_obj['definitions'][0]
|
|
414 |
resource_defs = index_obj['definitions'][1:3]
|
|
415 |
|
|
416 |
item_defs_shortened = [mapping_def, resource_defs[1]]
|
|
417 |
monkeypatch.setitem(index_obj, 'definitions', item_defs_shortened)
|
|
418 |
|
|
419 |
monkeypatch.setitem(mapping_def, 'type', 'mapping_and_resource')
|
|
420 |
|
|
421 |
new_mapping_ver = [*expected_mapping['version'], 1]
|
|
422 |
monkeypatch.setitem(mapping_def, 'revision', 1)
|
|
423 |
monkeypatch.setitem(expected_mapping, 'version', new_mapping_ver)
|
|
424 |
|
|
425 |
for prop in 'scripts', 'dependencies':
|
|
426 |
monkeypatch.setitem(mapping_def, prop, resource_defs[0][prop])
|
|
427 |
|
|
428 |
monkeypatch.setitem(expected_resources[0], 'uuid', mapping_def['uuid'])
|
|
429 |
monkeypatch.setitem(expected_resources[0], 'description',
|
|
430 |
mapping_def['description'])
|
|
431 |
|
|
432 |
monkeypatch.setitem(expected_source_description['definitions'][0],
|
|
433 |
'version', new_mapping_ver)
|
|
434 |
|
|
435 |
@variant_maker
|
|
436 |
def sample_source_minmax_haketilo_ver_ignored(monkeypatch, sample_source,
|
|
437 |
min_ver=[1, 2], max_ver=[1, 2]):
|
|
438 |
"""
|
|
439 |
Specify version constraints on Haketilo, but keep sources' schema version at
|
|
440 |
1.
|
|
441 |
"""
|
|
442 |
mapping_def = index_obj['definitions'][0]
|
|
443 |
monkeypatch.setitem(mapping_def, 'min_haketilo_version', min_ver)
|
|
444 |
monkeypatch.setitem(mapping_def, 'max_haketilo_version', max_ver)
|
|
445 |
|
|
446 |
@variant_maker
|
|
447 |
def sample_source_minmax_haketilo_ver(monkeypatch, sample_source):
|
|
448 |
"""Specify version constraints on Haketilo."""
|
|
449 |
sample_source_minmax_haketilo_ver_ignored(monkeypatch, sample_source)
|
|
450 |
sample_source_make_version_2(monkeypatch, sample_source, [expected_mapping])
|
|
451 |
|
|
452 |
monkeypatch.setitem(expected_mapping, 'min_haketilo_version', [1, 2])
|
|
453 |
monkeypatch.setitem(expected_mapping, 'max_haketilo_version', [1, 2])
|
|
454 |
|
|
455 |
@variant_maker
|
|
456 |
def sample_source_minmax_haketilo_ver_default(monkeypatch, sample_source):
|
|
457 |
"""Specify version constraints on Haketilo, but use default values."""
|
|
458 |
sample_source_minmax_haketilo_ver_ignored(monkeypatch, sample_source,
|
|
459 |
min_ver=[1], max_ver=[65536])
|
|
460 |
sample_source_make_version_2(monkeypatch, sample_source)
|
|
461 |
|
366 |
462 |
piggyback_archive_names = [
|
367 |
463 |
'apt/something.deb',
|
368 |
464 |
'apt/something.orig.tar.gz',
|
... | ... | |
371 |
467 |
]
|
372 |
468 |
|
373 |
469 |
@variant_maker
|
374 |
|
def sample_source_add_piggyback(monkeypatch, sample_source,
|
375 |
|
extra_build_args={}):
|
376 |
|
"""Add piggybacked foreign system packages."""
|
|
470 |
def sample_source_add_piggyback_ignored(monkeypatch, sample_source,
|
|
471 |
extra_build_args={}):
|
|
472 |
"""
|
|
473 |
Add piggybacked foreign system packages, but keep sources' schema version at
|
|
474 |
1.
|
|
475 |
"""
|
377 |
476 |
old_build = build.Build
|
378 |
477 |
new_build = lambda *a, **kwa: old_build(*a, **kwa, **extra_build_args)
|
379 |
478 |
monkeypatch.setattr(build, 'Build', new_build)
|
... | ... | |
384 |
483 |
'packages': ['somelib=1.0'],
|
385 |
484 |
'dependencies': False
|
386 |
485 |
})
|
387 |
|
schema = 'https://hydrilla.koszko.org/schemas/package_source-2.schema.json'
|
388 |
|
monkeypatch.setitem(index_obj, '$schema', schema)
|
|
486 |
|
|
487 |
@variant_maker
|
|
488 |
def sample_source_add_piggyback(monkeypatch, sample_source,
|
|
489 |
extra_build_args={}):
|
|
490 |
"""Add piggybacked foreign system packages."""
|
|
491 |
sample_source_add_piggyback_ignored\
|
|
492 |
(monkeypatch, sample_source, extra_build_args)
|
|
493 |
|
|
494 |
sample_source_make_version_2(monkeypatch, sample_source)
|
389 |
495 |
|
390 |
496 |
new_refs = {}
|
391 |
497 |
for name in '.apt-root/.../copyright', '.apt-root/.../script.js':
|
... | ... | |
395 |
501 |
monkeypatch.setitem(sha256_hashes, name, digest)
|
396 |
502 |
new_refs[PurePosixPath(name).name] = {'file': name, 'sha256': digest}
|
397 |
503 |
|
|
504 |
new_list = [*expected_source_copyright, new_refs['copyright']]
|
398 |
505 |
for obj in expected:
|
399 |
|
new_list = [*obj['source_copyright'], new_refs['copyright']]
|
400 |
506 |
monkeypatch.setitem(obj, 'source_copyright', new_list)
|
401 |
507 |
|
402 |
508 |
for obj in expected_resources:
|
403 |
509 |
new_list = [{'identifier': 'apt-common-licenses'}, *obj['dependencies']]
|
404 |
510 |
monkeypatch.setitem(obj, 'dependencies', new_list)
|
405 |
511 |
|
406 |
|
for obj in index_obj['definitions'][0], expected_resources[0]:
|
|
512 |
for obj in index_obj['definitions'][1], expected_resources[0]:
|
407 |
513 |
new_list = [new_refs['script.js'], *obj['scripts']]
|
408 |
514 |
monkeypatch.setitem(obj, 'scripts', new_list)
|
409 |
515 |
|
... | ... | |
484 |
590 |
|
485 |
591 |
return index_path
|
486 |
592 |
|
|
593 |
def try_validate(as_what, instance):
|
|
594 |
"""
|
|
595 |
Select the right JSON schema. Return without errors only if the instance
|
|
596 |
validates against it.
|
|
597 |
"""
|
|
598 |
major = _major_version_re.search(instance['$schema']).group('major')
|
|
599 |
exact_schema_version = {'1': '1.0.1', '2': '2'}[major]
|
|
600 |
schema_filename = f'{as_what}-{exact_schema_version}.schema.json'
|
|
601 |
hydrilla_util.validator_for(schema_filename).validate(instance)
|
|
602 |
|
487 |
603 |
@pytest.mark.subprocess_run(build, run_reuse)
|
488 |
604 |
@pytest.mark.usefixtures('mock_subprocess_run')
|
489 |
605 |
def test_build(sample_source, sample_source_make_variants, tmpdir):
|
... | ... | |
516 |
632 |
|
517 |
633 |
for resource_json in expected_resources:
|
518 |
634 |
subdir = resource_dir / resource_json['identifier']
|
519 |
|
assert ['2021.11.10'] == [path.name for path in subdir.iterdir()]
|
|
635 |
ver_str = hydrilla_util.version_string(resource_json['version'])
|
|
636 |
assert [ver_str] == [path.name for path in subdir.iterdir()]
|
520 |
637 |
|
521 |
|
assert json.loads((subdir / '2021.11.10').read_text()) == resource_json
|
|
638 |
assert json.loads((subdir / ver_str).read_text()) == resource_json
|
522 |
639 |
|
523 |
|
hydrilla_util.validator_for('api_resource_description-1.0.1.schema.json')\
|
524 |
|
.validate(resource_json)
|
|
640 |
try_validate('api_resource_description', resource_json)
|
525 |
641 |
|
526 |
642 |
# Verify files under 'mapping/'
|
527 |
643 |
mapping_dir = tmpdir / 'mapping'
|
528 |
644 |
assert ['helloapple'] == [path.name for path in mapping_dir.iterdir()]
|
529 |
645 |
|
530 |
646 |
subdir = mapping_dir / 'helloapple'
|
531 |
|
assert ['2021.11.10'] == [path.name for path in subdir.iterdir()]
|
532 |
647 |
|
533 |
|
assert json.loads((subdir / '2021.11.10').read_text()) == expected_mapping
|
|
648 |
ver_str = hydrilla_util.version_string(expected_mapping['version'])
|
|
649 |
assert [ver_str] == [path.name for path in subdir.iterdir()]
|
534 |
650 |
|
535 |
|
hydrilla_util.validator_for('api_mapping_description-1.0.1.schema.json')\
|
536 |
|
.validate(expected_mapping)
|
|
651 |
assert json.loads((subdir / ver_str).read_text()) == expected_mapping
|
|
652 |
|
|
653 |
try_validate('api_mapping_description', expected_mapping)
|
537 |
654 |
|
538 |
655 |
# Verify files under 'source/'
|
539 |
656 |
source_dir = tmpdir / 'source'
|
... | ... | |
558 |
675 |
assert json.loads((source_dir / 'hello.json').read_text()) == \
|
559 |
676 |
expected_source_description
|
560 |
677 |
|
561 |
|
hydrilla_util.validator_for('api_source_description-1.0.1.schema.json')\
|
562 |
|
.validate(expected_source_description)
|
|
678 |
try_validate('api_source_description', expected_source_description)
|
563 |
679 |
|
564 |
680 |
error_makers = []
|
565 |
681 |
def error_maker(function):
|
... | ... | |
642 |
758 |
monkeypatch.setitem(index_obj, 'copyright', new_list)
|
643 |
759 |
return FileReferenceError, '^report_spdx_not_in_copyright_list$'
|
644 |
760 |
|
|
761 |
@error_maker
|
|
762 |
def sample_source_error_combined_unsupported(monkeypatch, sample_source):
|
|
763 |
"""
|
|
764 |
Define mapping and resource together but leave source schema version at 1.x
|
|
765 |
where this is unsupported.
|
|
766 |
"""
|
|
767 |
mapping_def = index_obj['definitions'][0]
|
|
768 |
monkeypatch.setitem(mapping_def, 'type', 'mapping_and_resource')
|
|
769 |
|
|
770 |
return ValidationError,
|
|
771 |
|
645 |
772 |
@pytest.fixture(params=error_makers)
|
646 |
773 |
def sample_source_make_errors(request, monkeypatch, sample_source):
|
647 |
774 |
"""
|
add tests for features from version 2 of source package JSON schema