forked from arduino/arduino-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconftest.py
171 lines (147 loc) · 6.44 KB
/
conftest.py
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
# This file is part of arduino-cli.
#
# Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
#
# This software is released under the GNU General Public License version 3,
# which covers the main part of arduino-cli.
# The terms of this license can be found at:
# https://www.gnu.org/licenses/gpl-3.0.en.html
#
# You can be released from the requirements of the above licenses by purchasing
# a commercial license. Buying such a license is mandatory if you want to modify or
# otherwise use the software for commercial activities involving the Arduino
# software without disclosing the source code of your own applications. To purchase
# a commercial license, send an email to [email protected].
import os
import platform
import signal
import pytest
import simplejson as json
from invoke import Local
from invoke.context import Context
import tempfile
from .common import Board
@pytest.fixture(scope="function")
def data_dir(tmpdir_factory):
"""
A tmp folder will be created before running
each test and deleted at the end, this way all the
tests work in isolation.
"""
# it seems that paths generated by pytest's tmpdir_factory are too
# long and may lead to arduino-cli compile failures due to the
# combination of (some or all of) the following reasons:
# 1) Windows not liking path >260 chars in len
# 2) arm-gcc not fully optimizing long paths
# 3) libraries requiring headers deep down the include path
# for example:
#
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/mbed/rtos/Thread.h:29,
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/mbed/rtos/rtos.h:28,
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/mbed/mbed.h:23,
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/Arduino.h:32,
# from C:\Users\RUNNER~1\AppData\Local\Temp\arduino-sketch-739B2B6DD21EB014317DA2A46062811B\sketch\magic_wand.ino.cpp:1:
##[error]c:\users\runneradmin\appdata\local\temp\pytest-of-runneradmin\pytest-0\a7\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\new:39:10: fatal error: bits/c++config.h: No such file or directory
#
# due to the above on Windows we cut the tmp path straight to /tmp/xxxxxxxx
if platform.system() == "Windows":
with tempfile.TemporaryDirectory() as tmp:
yield tmp
else:
yield str(tmpdir_factory.mktemp("ArduinoTest"))
@pytest.fixture(scope="session")
def downloads_dir(tmpdir_factory):
"""
To save time and bandwidth, all the tests will access
the same download cache folder.
"""
return str(tmpdir_factory.mktemp("ArduinoTest"))
@pytest.fixture(scope="function")
def working_dir(tmpdir_factory):
"""
A tmp folder to work in
will be created before running each test and deleted
at the end, this way all the tests work in isolation.
"""
return str(tmpdir_factory.mktemp("ArduinoTestWork"))
@pytest.fixture(scope="function")
def run_command(pytestconfig, data_dir, downloads_dir, working_dir):
"""
Provide a wrapper around invoke's `run` API so that every test
will work in the same temporary folder.
Useful reference:
http://docs.pyinvoke.org/en/1.4/api/runners.html#invoke.runners.Result
"""
cli_path = os.path.join(str(pytestconfig.rootdir), "..", "arduino-cli")
env = {
"ARDUINO_DATA_DIR": data_dir,
"ARDUINO_DOWNLOADS_DIR": downloads_dir,
"ARDUINO_SKETCHBOOK_DIR": data_dir,
}
os.makedirs(os.path.join(data_dir, "packages"))
def _run(cmd_string):
cli_full_line = "{} {}".format(cli_path, cmd_string)
run_context = Context()
with run_context.cd(working_dir):
return run_context.run(
cli_full_line, echo=False, hide=True, warn=True, env=env
)
return _run
@pytest.fixture(scope="function")
def daemon_runner(pytestconfig, data_dir, downloads_dir, working_dir):
"""
Provide an invoke's `Local` object that has started the arduino-cli in daemon mode.
This way is simple to start and kill the daemon when the test is finished
via the kill() function
Useful reference:
http://docs.pyinvoke.org/en/1.4/api/runners.html#invoke.runners.Local
http://docs.pyinvoke.org/en/1.4/api/runners.html
"""
cli_full_line = os.path.join(str(pytestconfig.rootdir), "..", "arduino-cli daemon")
env = {
"ARDUINO_DATA_DIR": data_dir,
"ARDUINO_DOWNLOADS_DIR": downloads_dir,
"ARDUINO_SKETCHBOOK_DIR": data_dir,
}
os.makedirs(os.path.join(data_dir, "packages"))
run_context = Context()
run_context.cd(working_dir)
# Local Class is the implementation of a Runner abstract class
runner = Local(run_context)
runner.run(
cli_full_line, echo=False, hide=True, warn=True, env=env, asynchronous=True
)
# we block here until the test function using this fixture has returned
yield runner
# Kill the runner's process as we finished our test (platform dependent)
os_signal = signal.SIGTERM
if platform.system() != "Windows":
os_signal = signal.SIGKILL
os.kill(runner.process.pid, os_signal)
@pytest.fixture(scope="function")
def detected_boards(run_command):
"""
This fixture provides a list of all the boards attached to the host.
This fixture will parse the JSON output of `arduino-cli board list --format json`
to extract all the connected boards data.
:returns a list `Board` objects.
"""
assert run_command("core update-index")
result = run_command("board list --format json")
assert result.ok
detected_boards = []
for port in json.loads(result.stdout):
for board in port.get("boards", []):
fqbn = board.get("FQBN")
package, architecture, _id = fqbn.split(":")
detected_boards.append(
Board(
address=port.get("address"),
fqbn=fqbn,
package=package,
architecture=architecture,
id=_id,
core="{}:{}".format(package, architecture),
)
)
return detected_boards