Project

General

Profile

Download (12.4 KB) Statistics
| Branch: | Tag: | Revision:

hydrilla-builder / src / test / test_hydrilla_builder.py @ 8a036bc7

1 5ac7ec33 Wojtek Kosior
# SPDX-License-Identifier: CC0-1.0
2
3
# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
4
#
5
# Available under the terms of Creative Commons Zero v1.0 Universal.
6
7
import pytest
8 34072d8d Wojtek Kosior
import json
9 5ac7ec33 Wojtek Kosior
10
from tempfile import TemporaryDirectory
11
from pathlib import Path
12 34072d8d Wojtek Kosior
from hashlib import sha256, sha1
13
from zipfile import ZipFile
14 5ac7ec33 Wojtek Kosior
15
here = Path(__file__).resolve().parent
16
17 8a036bc7 Wojtek Kosior
default_srcdir = here / 'source-package-example'
18
19
default_js_filenames = ['bye.js', 'hello.js', 'message.js']
20
default_dist_filenames = [*default_js_filenames, 'LICENSES/CC0-1.0.txt']
21
default_src_filenames = [
22
    *default_dist_filenames,
23
    'README.txt', 'README.txt.license', '.reuse/dep5', 'index.json'
24
]
25
26
default_sha1_hashes   = {}
27
default_sha256_hashes = {}
28
default_contents      = {}
29
30
for fn in default_src_filenames:
31
    with open(default_srcdir / fn, 'rb') as file_handle:
32
        default_contents[fn]      = file_handle.read()
33
        default_sha256_hashes[fn] = sha256(default_contents[fn]).digest().hex()
34
        default_sha1_hashes[fn]   = sha1(default_contents[fn]).digest().hex()
35
36
class CaseSettings:
37
    """Gather parametrized values in a class."""
38
    def __init__(self):
39
        """Init CaseSettings with default values."""
40
        self.srcdir = default_srcdir
41
        self.index_json_path = Path('index.json')
42
        self.report_spdx_included = True
43
44
        self.js_filenames   = default_js_filenames.copy()
45
        self.dist_filenames = default_dist_filenames.copy()
46
        self.src_filenames  = default_src_filenames.copy()
47
48
        self.sha1_hashes   = default_sha1_hashes.copy()
49
        self.sha256_hashes = default_sha256_hashes.copy()
50
        self.contents      = default_contents.copy()
51
52
        self.expected_resources = [{
53
            'api_schema_version': [1, 0, 1],
54
            'source_name': 'hello',
55
            'source_copyright': [{
56
                'file': 'report.spdx',
57
                'sha256': '!!!!value to fill during test!!!!'
58
            }, {
59
                'file': 'LICENSES/CC0-1.0.txt',
60
                'sha256': self.sha256_hashes['LICENSES/CC0-1.0.txt']
61
            }],
62
            'type': 'resource',
63
            'identifier': 'helloapple',
64
            'long_name': 'Hello Apple',
65
            'uuid': 'a6754dcb-58d8-4b7a-a245-24fd7ad4cd68',
66
            'version': [2021, 11, 10],
67
            'revision': 1,
68
            'description': 'greets an apple',
69
            'dependencies': ['hello-message'],
70
            'scripts': [{
71
                'file': 'hello.js',
72
                'sha256': self.sha256_hashes['hello.js']
73
            }, {
74
                'file': 'bye.js',
75
                'sha256': self.sha256_hashes['bye.js']
76
            }]
77
        }, {
78
            'api_schema_version': [1, 0, 1],
79
            'source_name': 'hello',
80
            'source_copyright': [{
81
                'file': 'report.spdx',
82
                'sha256': '!!!!value to fill during test!!!!'
83
            }, {
84
                'file': 'LICENSES/CC0-1.0.txt',
85
                'sha256': self.sha256_hashes['LICENSES/CC0-1.0.txt']
86
            }],
87
            'type': 'resource',
88
            'identifier': 'hello-message',
89
            'long_name': 'Hello Message',
90
            'uuid': '1ec36229-298c-4b35-8105-c4f2e1b9811e',
91
            'version': [2021, 11, 10],
92
            'revision': 2,
93
            'description': 'define messages for saying hello and bye',
94
            'dependencies': [],
95
            'scripts': [{
96
                'file': 'message.js',
97
                'sha256': self.sha256_hashes['message.js']
98
            }]
99
        }]
100
        self.expected_mapping = {
101
            'api_schema_version': [1, 0, 1],
102
            'source_name': 'hello',
103
            'source_copyright': [{
104
                'file': 'report.spdx',
105
                'sha256': '!!!!value to fill during test!!!!'
106
            }, {
107
                'file': 'LICENSES/CC0-1.0.txt',
108
                'sha256': self.sha256_hashes['LICENSES/CC0-1.0.txt']
109
            }],
110
            'type': 'mapping',
111
	    'identifier': 'helloapple',
112
	    'long_name': 'Hello Apple',
113
	    'uuid': '54d23bba-472e-42f5-9194-eaa24c0e3ee7',
114
	    'version': [2021, 11, 10],
115
	    'description': 'causes apple to get greeted on Hydrillabugs issue tracker',
116
	    'payloads': {
117
	        'https://hydrillabugs.koszko.org/***': {
118
		    'identifier': 'helloapple'
119
	        },
120
	        'https://hachettebugs.koszko.org/***': {
121
		    'identifier': 'helloapple'
122
                }
123
            }
124
        }
125
        self.expected_source_description = {
126
            'api_schema_version': [1, 0, 1],
127
            'source_name': 'hello',
128
            'source_copyright': [{
129
                'file': 'report.spdx',
130
                'sha256': '!!!!value to fill during test!!!!'
131
            }, {
132
                'file': 'LICENSES/CC0-1.0.txt',
133
                'sha256': self.sha256_hashes['LICENSES/CC0-1.0.txt']
134
            }],
135
            'source_archives': {
136
                'zip': {
137
                    'sha256': '!!!!value to fill during test!!!!',
138
                }
139
            },
140
            'upstream_url': 'https://git.koszko.org/hydrilla-source-package-example',
141
            'definitions': [{
142
                'type': 'resource',
143
                'identifier': 'helloapple',
144
                'version': [2021, 11, 10],
145
            }, {
146
                'type':       'resource',
147
                'identifier': 'hello-message',
148
                'version':     [2021, 11, 10],
149
            }, {
150
                'type': 'mapping',
151
                'identifier': 'helloapple',
152
                'version': [2021, 11, 10],
153
            }]
154
        }
155
156
    def expected(self) -> list[dict]:
157
        """
158
        Convenience method to get a list of expected jsons of 2 resources,
159
        1 mapping and 1 source description we have.
160
        """
161
        return [
162
            *self.expected_resources,
163
            self.expected_mapping,
164
            self.expected_source_description
165
        ]
166
167 5ac7ec33 Wojtek Kosior
@pytest.fixture()
168 8a036bc7 Wojtek Kosior
def tmpdir() -> str:
169 5ac7ec33 Wojtek Kosior
    with TemporaryDirectory() as tmpdir:
170
        yield tmpdir
171
172 8a036bc7 Wojtek Kosior
def prepare_default(tmpdir: Path) -> CaseSettings:
173
    """Use sample source package directory as exists in VCS."""
174
    return CaseSettings()
175
176
def prepare_external_index_json(tmpdir: Path) -> dict:
177
    """
178
    Use sample source package directory with an alternative, modified
179
    index.json.
180
    """
181
    settings = CaseSettings()
182
183
    from hydrilla_builder.build import strip_json_comments
184
185
    with open(settings.srcdir / 'index.json', 'rt') as file_handle:
186
        obj = json.loads(strip_json_comments(file_handle.read()))
187
188
    # Add comments that should be preserved.
189
    for dictionary in (obj, settings.expected_source_description):
190
        dictionary['comment'] = 'index_json comment'
191
192
    for i, dicts in enumerate(zip(obj['definitions'], settings.expected())):
193
        for dictionary in dicts:
194
            dictionary['comment'] = f'item {i}'
195
196
    # Remove spdx report generation
197
    del obj['reuse_generate_spdx_report']
198
    obj['copyright'].remove({'file': 'report.spdx'})
199
200
    settings.report_spdx_included = False
201
202
    for json_description in settings.expected():
203
        json_description['source_copyright'] = \
204
            [fr for fr in json_description['source_copyright']
205
             if fr['file'] != 'report.spdx']
206
207
    # Use default value ([]) for 'additionall_files' property
208
    del obj['additional_files']
209
210
    settings.src_filenames = [*settings.dist_filenames, 'index.json']
211
212
    # Use default value ([]) for 'scripts' property in one of the resources
213
    del obj['definitions'][1]['scripts']
214
215
    settings.expected_resources[1]['scripts'] = []
216
217
    for prefix in ('js', 'dist', 'src'):
218
        getattr(settings, f'{prefix}_filenames').remove('message.js')
219
220
    # Use default value ({}) for 'pyloads' property in mapping
221
    del obj['definitions'][2]['payloads']
222
223
    settings.expected_mapping['payloads'] = {}
224
225
    # Add some unrecognized properties that should be stripped
226
    to_process = [obj]
227
    while to_process:
228
        processed = to_process.pop()
229
230
        if type(processed) is list:
231
            to_process.extend(processed)
232
        elif type(processed) is dict and 'spurious_property' not in processed:
233
            to_process.extend(processed.values())
234
            processed['spurious_property'] = 'some value'
235
236
    # Replace the other index.json with new one
237
    settings.index_json_path = tmpdir / 'replacement.json'
238
239
    contents = json.dumps(obj).encode()
240
241
    settings.contents['index.json'] = contents
242
243
    settings.sha256_hashes['index.json'] = sha256(contents).digest().hex()
244
    settings.sha1_hashes['index.json']   = sha1(contents).digest().hex()
245
246
    with open(settings.index_json_path, 'wb') as file_handle:
247
        file_handle.write(contents)
248
249
    return settings
250
251
@pytest.mark.parametrize('prepare_source_example', [
252
    prepare_default, prepare_external_index_json
253
])
254
def test_build(tmpdir, prepare_source_example):
255 5ac7ec33 Wojtek Kosior
    """Build the sample source package and verify the produced files."""
256
    from hydrilla_builder.build import Build
257
258 34072d8d Wojtek Kosior
    # First, build the package
259 8a036bc7 Wojtek Kosior
    dstdir = Path(tmpdir) / 'dstdir'
260
    tmpdir = Path(tmpdir) / 'example'
261
262
    dstdir.mkdir(exist_ok=True)
263
    tmpdir.mkdir(exist_ok=True)
264 5ac7ec33 Wojtek Kosior
265 8a036bc7 Wojtek Kosior
    settings = prepare_source_example(tmpdir)
266
267
    build = Build(settings.srcdir, settings.index_json_path)
268 34072d8d Wojtek Kosior
    build.write_package_files(dstdir)
269
270
    # Verify directories under destination directory
271
    assert {'file', 'resource', 'mapping', 'source'} == \
272
        set([path.name for path in dstdir.iterdir()])
273
274
    # Verify files under 'file/'
275
    file_dir = dstdir / 'file'
276
277 8a036bc7 Wojtek Kosior
    for fn in settings.dist_filenames:
278
        dist_file_path = file_dir / f'sha256-{settings.sha256_hashes[fn]}'
279 34072d8d Wojtek Kosior
        assert dist_file_path.is_file()
280
281
        with open(dist_file_path, 'rb') as file_handle:
282 8a036bc7 Wojtek Kosior
            assert file_handle.read() == settings.contents[fn]
283
284
    sha256_hashes_set = set([settings.sha256_hashes[fn]
285
                             for fn in settings.dist_filenames])
286 34072d8d Wojtek Kosior
287
    spdx_report_sha256 = None
288
289
    for path in file_dir.iterdir():
290
        assert path.name.startswith('sha256-')
291 8a036bc7 Wojtek Kosior
        if path.name[7:] in sha256_hashes_set:
292 34072d8d Wojtek Kosior
            continue
293
294 8a036bc7 Wojtek Kosior
        assert spdx_report_sha256 is None and settings.report_spdx_included
295 34072d8d Wojtek Kosior
296
        with open(path, 'rt') as file_handle:
297
            spdx_contents = file_handle.read()
298
299
        spdx_report_sha256 = sha256(spdx_contents.encode()).digest().hex()
300
        assert spdx_report_sha256 == path.name[7:]
301
302 8a036bc7 Wojtek Kosior
        for fn in settings.src_filenames:
303
            if not any([n in fn.lower() for n in ('license', 'reuse')]):
304
                assert settings.sha1_hashes[fn]
305
306
    if settings.report_spdx_included:
307
        assert spdx_report_sha256
308
        for obj in settings.expected():
309
            for file_ref in obj['source_copyright']:
310
                if file_ref['file'] == 'report.spdx':
311
                    file_ref['sha256'] = spdx_report_sha256
312 34072d8d Wojtek Kosior
313
    # Verify files under 'resource/'
314
    resource_dir = dstdir / 'resource'
315
316 8a036bc7 Wojtek Kosior
    assert set([rj['identifier'] for rj in settings.expected_resources]) == \
317 34072d8d Wojtek Kosior
        set([path.name for path in resource_dir.iterdir()])
318
319 8a036bc7 Wojtek Kosior
    for resource_json in settings.expected_resources:
320 34072d8d Wojtek Kosior
        subdir = resource_dir / resource_json['identifier']
321
        assert ['2021.11.10'] == [path.name for path in subdir.iterdir()]
322
323
        with open(subdir / '2021.11.10', 'rt') as file_handle:
324
            assert json.load(file_handle) == resource_json
325
326
    # Verify files under 'mapping/'
327
    mapping_dir = dstdir / 'mapping'
328
    assert ['helloapple'] == [path.name for path in mapping_dir.iterdir()]
329
330
    subdir = mapping_dir / 'helloapple'
331
    assert ['2021.11.10'] == [path.name for path in subdir.iterdir()]
332
333
    with open(subdir / '2021.11.10', 'rt') as file_handle:
334 8a036bc7 Wojtek Kosior
        assert json.load(file_handle) == settings.expected_mapping
335 34072d8d Wojtek Kosior
336
    # Verify files under 'source/'
337
    source_dir = dstdir / 'source'
338
    assert {'hello.json', 'hello.zip'} == \
339
        set([path.name for path in source_dir.iterdir()])
340
341 8a036bc7 Wojtek Kosior
    zip_filenames = [f'hello/{fn}' for fn in settings.src_filenames]
342 34072d8d Wojtek Kosior
343
    with ZipFile(source_dir / 'hello.zip', 'r') as archive:
344
        assert set([f.filename for f in archive.filelist]) == set(zip_filenames)
345
346 8a036bc7 Wojtek Kosior
        for zip_fn, src_fn in zip(zip_filenames, settings.src_filenames):
347 34072d8d Wojtek Kosior
            with archive.open(zip_fn, 'r') as zip_file_handle:
348 8a036bc7 Wojtek Kosior
                assert zip_file_handle.read() == settings.contents[src_fn]
349 34072d8d Wojtek Kosior
350 8a036bc7 Wojtek Kosior
    zip_ref = settings.expected_source_description['source_archives']['zip']
351 34072d8d Wojtek Kosior
    with open(source_dir / 'hello.zip', 'rb') as file_handle:
352 8a036bc7 Wojtek Kosior
        zip_ref['sha256'] = sha256(file_handle.read()).digest().hex()
353 34072d8d Wojtek Kosior
354
    with open(source_dir / 'hello.json', 'rt') as file_handle:
355 8a036bc7 Wojtek Kosior
        assert json.load(file_handle) == settings.expected_source_description
356 34072d8d Wojtek Kosior
357 8a036bc7 Wojtek Kosior
# TODO: also check error handling