File: test_task.py

package info (click to toggle)
ros2-colcon-core 0.20.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,156 kB
  • sloc: python: 10,333; makefile: 7
file content (258 lines) | stat: -rw-r--r-- 9,177 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# Copyright 2016-2018 Dirk Thomas
# Licensed under the Apache License, Version 2.0

import os
from pathlib import Path
import sys
from tempfile import TemporaryDirectory
from unittest.mock import Mock
from unittest.mock import patch

from colcon_core.event.command import Command
from colcon_core.event.job import JobProgress
from colcon_core.event.output import StderrLine
from colcon_core.event.output import StdoutLine
from colcon_core.plugin_system import instantiate_extensions
from colcon_core.task import add_task_arguments
from colcon_core.task import create_file
from colcon_core.task import get_task_extension
from colcon_core.task import get_task_extensions
from colcon_core.task import install
from colcon_core.task import run
from colcon_core.task import TaskContext
from colcon_core.task import TaskExtensionPoint
import pytest

from .extension_point_context import ExtensionPointContext
from .run_until_complete import run_until_complete


def test_context_interface():
    context = TaskContext(pkg=None, args=None, dependencies=None)
    with pytest.raises(NotImplementedError):
        context.put_event_into_queue(None)


class Extension(TaskExtensionPoint):
    TASK_NAME = 'do'

    async def do(self, *args, **kwargs):
        self.progress('progress')
        self.print('hello')
        self.print('hello', file=sys.stdout)
        self.print('world', file=sys.stderr)
        with pytest.raises(AssertionError):
            self.print('invalid file handle', file=False)
        return 1


def test_extension_interface():
    context = Mock()

    # capture events
    events = []

    def put_event_into_queue(event):
        events.append(event)

    context.put_event_into_queue = put_event_into_queue

    extension = Extension()
    extension.set_context(context=context)
    rc = run_until_complete(extension())
    assert rc == 1

    assert len(events) == 4
    assert isinstance(events[0], JobProgress)
    assert events[0].progress == 'progress'
    assert isinstance(events[1], StdoutLine)
    assert events[1].line == 'hello\n'
    assert isinstance(events[2], StdoutLine)
    assert events[2].line == 'hello\n'
    assert isinstance(events[3], StderrLine)
    assert events[3].line == 'world\n'


# TODO figure out how to avoid the stderr output
@pytest.mark.skip(
    reason='Results in stderr output due to a UnicodeDecodeError for the '
           'generated coverage files')
def test_run():
    context = Mock()
    events = []

    def put_event_into_queue(event):
        events.append(event)

    context.put_event_into_queue = put_event_into_queue
    cmd = [
        sys.executable, '-c',
        "import sys; print('hello'); print('world', file=sys.stderr)"]
    coroutine = run(context, cmd)
    completed_process = run_until_complete(coroutine)
    assert completed_process.returncode == 0
    assert len(events) == 3
    assert isinstance(events[0], Command)
    assert events[0].cmd == cmd
    assert isinstance(events[1], StdoutLine)
    assert events[1].line == b'hello\n'
    assert isinstance(events[2], StderrLine)
    assert events[2].line == b'world\n'


class Extension1(TaskExtensionPoint):

    def build(self, *args, **kwargs):
        pass  # pragma: no cover


class Extension2(TaskExtensionPoint):

    def build(self, *args, **kwargs):
        pass  # pragma: no cover


def instantiate_extensions_without_cache(
    group_name, *, exclude_names=None, unique_instance=False
):
    return instantiate_extensions(group_name)


def test_add_task_arguments():
    parser = Mock()
    task_name = 'colcon_core.task.build'
    with ExtensionPointContext(extension1=Extension1, extension2=Extension2):
        with patch(
            'colcon_core.task.instantiate_extensions',
            side_effect=instantiate_extensions_without_cache
        ):
            extensions = get_task_extensions(task_name)
            # one exception, one success
            extensions['extension1'].add_arguments = Mock(
                side_effect=RuntimeError('custom exception'))
            with patch('colcon_core.task.logger.error') as error:
                add_task_arguments(parser, task_name)
            assert error.call_count == 1
            assert len(error.call_args[0]) == 1
            assert error.call_args[0][0].startswith(
                "Exception in task extension 'build.extension1': custom "
                'exception\n')

            # invalid return value
            extensions['extension1'].add_arguments = Mock()
            extensions['extension2'].add_arguments = Mock(return_value=None)
            with patch('colcon_core.task.logger.error') as error:
                add_task_arguments(parser, task_name)
            assert error.call_count == 1
            assert len(error.call_args[0]) == 1
            assert error.call_args[0][0].startswith(
                "Exception in task extension 'build.extension1': "
                'add_arguments() should return None\n')
            assert extensions['extension2'].add_arguments.call_count == 1


def test_get_task_extension():
    task_name = 'colcon_core.task.build'
    with ExtensionPointContext(extension1=Extension1, extension2=Extension2):
        # request invalid extension
        extension = get_task_extension(task_name, 'package_type')
        assert extension is None

        # request valid extension
        extension = get_task_extension(task_name, 'extension2')
        assert isinstance(extension, Extension2)


def test_create_file():
    with TemporaryDirectory(prefix='test_colcon_') as base_path:
        args = Mock()
        args.install_base = base_path

        create_file(args, 'file.txt')
        path = Path(base_path) / 'file.txt'
        assert path.is_file()
        assert path.read_text() == ''

        create_file(args, 'path/file.txt', content='content')
        path = Path(base_path) / 'path' / 'file.txt'
        assert path.is_file()
        assert path.read_text() == 'content'


def test_install():
    with TemporaryDirectory(prefix='test_colcon_') as base_path:
        args = Mock()
        args.path = os.path.join(base_path, 'path')
        args.install_base = os.path.join(base_path, 'install')
        args.symlink_install = False

        # create source files
        os.makedirs(args.path)
        with open(os.path.join(args.path, 'source.txt'), 'w') as h:
            h.write('content')
        with open(os.path.join(args.path, 'source2.txt'), 'w') as h:
            h.write('content2')

        # copy file
        install(args, 'source.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert not path.is_symlink()
        assert path.read_text() == 'content'

        # skip all symlink tests on Windows for now
        if sys.platform == 'win32':  # pragma: no cover
            return

        # symlink file, removing existing file
        args.symlink_install = True
        install(args, 'source.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert path.is_symlink()
        assert path.samefile(os.path.join(args.path, 'source.txt'))
        assert path.read_text() == 'content'

        # symlink other file, removing existing directory
        os.remove(os.path.join(args.install_base, 'destination.txt'))
        os.makedirs(os.path.join(args.install_base, 'destination.txt'))
        install(args, 'source2.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert path.is_symlink()
        assert path.samefile(os.path.join(args.path, 'source2.txt'))
        assert path.read_text() == 'content2'

        # copy file, removing existing symlink
        args.symlink_install = False
        install(args, 'source.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert not path.is_symlink()
        assert path.read_text() == 'content'

        # symlink file
        os.remove(os.path.join(args.install_base, 'destination.txt'))
        args.symlink_install = True
        install(args, 'source.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert path.is_symlink()
        assert path.samefile(os.path.join(args.path, 'source.txt'))
        assert path.read_text() == 'content'

        # symlink file, same already existing
        install(args, 'source.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert path.is_symlink()
        assert path.samefile(os.path.join(args.path, 'source.txt'))
        assert path.read_text() == 'content'

        # symlink exists, but to a not existing location
        os.remove(os.path.join(args.path, 'source.txt'))
        install(args, 'source2.txt', 'destination.txt')
        path = Path(base_path) / 'install' / 'destination.txt'
        assert path.is_file()
        assert path.is_symlink()
        assert path.samefile(os.path.join(args.path, 'source2.txt'))