1616import platform
1717import signal
1818import shutil
19+ import time
1920from pathlib import Path
2021
2122import pytest
2223import simplejson as json
2324from invoke import Local
2425from invoke .context import Context
2526import tempfile
27+ from filelock import FileLock
2628
2729from .common import Board
2830
@@ -54,17 +56,32 @@ def data_dir(tmpdir_factory):
5456 if platform .system () == "Windows" :
5557 with tempfile .TemporaryDirectory () as tmp :
5658 yield tmp
59+ # shutil.rmtree(tmp, ignore_errors=True)
5760 else :
58- yield str (tmpdir_factory .mktemp ("ArduinoTest" ))
61+ data = tmpdir_factory .mktemp ("ArduinoTest" )
62+ yield str (data )
63+ # shutil.rmtree(data, ignore_errors=True)
5964
6065
6166@pytest .fixture (scope = "session" )
62- def downloads_dir (tmpdir_factory ):
67+ def downloads_dir (tmpdir_factory , worker_id ):
6368 """
6469 To save time and bandwidth, all the tests will access
6570 the same download cache folder.
6671 """
67- return str (tmpdir_factory .mktemp ("ArduinoTest" ))
72+ download_dir = tmpdir_factory .mktemp ("ArduinoTest" , numbered = False )
73+
74+ # This folders should be created only once per session, if we're running
75+ # tests in parallel using multiple processes we need to make sure this
76+ # this fixture is executed only once, thus the use of the lockfile
77+ if not worker_id == "master" :
78+ lock = Path (download_dir / "lock" )
79+ with FileLock (lock ):
80+ if not lock .is_file ():
81+ lock .touch ()
82+
83+ yield str (download_dir )
84+ # shutil.rmtree(download_dir, ignore_errors=True)
6885
6986
7087@pytest .fixture (scope = "function" )
@@ -74,7 +91,9 @@ def working_dir(tmpdir_factory):
7491 will be created before running each test and deleted
7592 at the end, this way all the tests work in isolation.
7693 """
77- return str (tmpdir_factory .mktemp ("ArduinoTestWork" ))
94+ work_dir = tmpdir_factory .mktemp ("ArduinoTestWork" )
95+ yield str (work_dir )
96+ # shutil.rmtree(work_dir, ignore_errors=True)
7897
7998
8099@pytest .fixture (scope = "function" )
@@ -95,9 +114,12 @@ def run_command(pytestconfig, data_dir, downloads_dir, working_dir):
95114 }
96115 (Path (data_dir ) / "packages" ).mkdir ()
97116
98- def _run (cmd_string , custom_working_dir = None ):
117+ def _run (cmd_string , custom_working_dir = None , custom_env = None ):
118+
99119 if not custom_working_dir :
100120 custom_working_dir = working_dir
121+ if not custom_env :
122+ custom_env = env
101123 cli_full_line = '"{}" {}' .format (cli_path , cmd_string )
102124 run_context = Context ()
103125 # It might happen that we need to change directories between drives on Windows,
@@ -109,7 +131,7 @@ def _run(cmd_string, custom_working_dir=None):
109131 # It escapes spaces in the path using "\ " but it doesn't always work,
110132 # wrapping the path in quotation marks is the safest approach
111133 with run_context .prefix (f'{ cd_command } "{ custom_working_dir } "' ):
112- return run_context .run (cli_full_line , echo = False , hide = True , warn = True , env = env )
134+ return run_context .run (cli_full_line , echo = False , hide = True , warn = True , env = custom_env )
113135
114136 return _run
115137
@@ -195,3 +217,19 @@ def copy_sketch(working_dir):
195217 test_sketch_path = Path (working_dir ) / "sketch_simple"
196218 shutil .copytree (sketch_path , test_sketch_path )
197219 yield str (test_sketch_path )
220+
221+
222+ @pytest .fixture (scope = "function" )
223+ def wait_for_board (run_command ):
224+ def _waiter (seconds = 10 ):
225+ # Waits for the specified amount of second for a board to be visible.
226+ # This is necessary since it might happen that a board is not immediately
227+ # available after a test upload and subsequent tests might consequently fail.
228+ time_end = time .time () + seconds
229+ while time .time () < time_end :
230+ result = run_command ("board list --format json" )
231+ ports = json .loads (result .stdout )
232+ if len ([p .get ("boards" , []) for p in ports ]) > 0 :
233+ break
234+
235+ return _waiter
0 commit comments