Skip to content

Commit 01859dd

Browse files
t-perssonTobias Persson
andauthored
Compress workspace after execution (#16)
Change to pathlib in most places. Create a new workspace interface for handling directories. Add more logs about what is happening Compress workspace after execution. Upload compressed workspace to log area Added a scenario test for a full ETR execution Co-authored-by: Tobias Persson <tobias.persson@axis.com>
1 parent 1a82827 commit 01859dd

File tree

11 files changed

+742
-73
lines changed

11 files changed

+742
-73
lines changed

src/etos_test_runner/etr.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,8 @@ def run_etr(self):
131131
activity_name = self.etos.config.get("test_config").get("name")
132132
triggered = self.etos.events.send_activity_triggered(activity_name)
133133
self._clear_artifacts()
134-
top_dir = os.getcwd()
135-
136-
with self.etos.utils.chdir(top_dir):
137-
self.etos.events.send_activity_started(triggered)
138-
result = self._run_tests()
134+
self.etos.events.send_activity_started(triggered)
135+
result = self._run_tests()
139136
except Exception as exc: # pylint:disable=broad-except
140137
self.etos.events.send_activity_finished(
141138
triggered, {"conclusion": "FAILED", "description": str(exc)}

src/etos_test_runner/lib/executor.py

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import json
2222
import re
2323
from pathlib import Path
24+
from shutil import copy
2425
from pprint import pprint
25-
from tempfile import mkdtemp
2626

27-
BASE = os.path.dirname(os.path.abspath(__file__))
27+
BASE = Path(__file__).parent.absolute()
2828

2929

3030
class TestCheckoutTimeout(TimeoutError):
@@ -36,24 +36,24 @@ def _test_checkout_signal_handler(signum, frame): # pylint:disable=unused-argum
3636
raise TestCheckoutTimeout("Took too long to checkout test cases.")
3737

3838

39-
class Executor: # pylint: disable=too-many-instance-attributes
39+
class Executor: # pylint:disable=too-many-instance-attributes
4040
"""Execute a single test-case, -class, -module, -folder etc."""
4141

4242
report_path = "test_output.log"
4343
test_name = ""
4444
current_test = None
4545
test_regex = {}
46-
test_checkouts = {}
4746
logger = logging.getLogger("Executor")
4847

49-
# pylint: disable=too-many-arguments
5048
def __init__(self, test, iut, etos):
5149
"""Initialize.
5250
5351
:param test: Test to execute.
5452
:type test: str
5553
:param iut: IUT to execute test on.
5654
:type iut: :obj:`etr.lib.iut.Iut`
55+
:param etos: ETOS library instance.
56+
:type etos: :obj:`etos_lib.etos.Etos`
5757
"""
5858
self.load_regex()
5959
self.test = test
@@ -109,7 +109,7 @@ def load_regex(self):
109109
except json.decoder.JSONDecodeError as exception:
110110
self.logger.error("%r", exception)
111111
self.logger.error("Failed to load JSON %r", path)
112-
except Exception as exception:
112+
except Exception as exception: # pylint:disable=broad-except
113113
self.logger.error("%r", exception)
114114
self.logger.error("Unknown error when loading regex JSON file.")
115115

@@ -119,48 +119,35 @@ def _checkout_tests(self, test_checkout):
119119
:param test_checkout: Test checkout parameters from test suite.
120120
:type test_checkout: list
121121
"""
122-
with open(os.path.join(BASE, "checkout.sh"), "w") as checkout_file:
122+
checkout = Path().joinpath("checkout.sh")
123+
with checkout.open(mode="w") as checkout_file:
123124
checkout_file.write('eval "$(pyenv init -)"\n')
124125
checkout_file.write("pyenv shell --unset\n")
125126
for command in test_checkout:
126127
checkout_file.write("{} || exit 1\n".format(command))
127128

129+
self.logger.info("Checkout script:\n" "%s", checkout.read_text())
130+
128131
signal.signal(signal.SIGALRM, _test_checkout_signal_handler)
129132
signal.alarm(60)
130133
try:
131-
checkout = os.path.join(BASE, "checkout.sh")
132134
success, output = self.etos.utils.call(
133-
["/bin/bash", checkout], shell=True, wait_output=False
135+
["/bin/bash", str(checkout)], shell=True, wait_output=False
134136
)
135137
finally:
136138
signal.alarm(0)
137139
if not success:
138140
pprint(output)
139141
raise Exception("Could not checkout tests using '{}'".format(test_checkout))
140142

141-
def test_directory(self, test_checkout):
142-
"""Test directory for the test checkout.
143-
144-
If a test directory does not already exist, generate it by calling the
145-
supplied command from test suite.
146-
If it does exist, just return that directory.
147-
148-
:param test_checkout: Test checkout parameters from test suite.
149-
:type test_checkout: list
150-
:return: Folder where to execute a testcase
151-
:rtype: str
152-
"""
153-
string_checkout = " ".join(test_checkout)
154-
if self.test_checkouts.get(string_checkout) is None:
155-
test_folder = mkdtemp(dir=os.getcwd())
156-
with self.etos.utils.chdir(test_folder):
157-
self._checkout_tests(test_checkout)
158-
self.test_checkouts[string_checkout] = test_folder
159-
return self.test_checkouts.get(string_checkout)
160-
161143
def _build_test_command(self):
162144
"""Build up the actual test command based on data from event."""
163-
executor = os.path.join(BASE, "executor.sh")
145+
base_executor = Path(BASE).joinpath("executor.sh")
146+
executor = Path().joinpath("executor.sh")
147+
copy(base_executor, executor)
148+
149+
self.logger.info("Executor script:\n" "%s", executor.read_text())
150+
164151
test_command = ""
165152
parameters = []
166153

@@ -170,8 +157,8 @@ def _build_test_command(self):
170157
else:
171158
parameters.append("{}={}".format(parameter, value))
172159

173-
test_command = "{} {} {} 2>&1".format(
174-
executor, self.test_command, " ".join(parameters)
160+
test_command = "./{} {} {} 2>&1".format(
161+
str(executor), self.test_command, " ".join(parameters)
175162
)
176163
return test_command
177164

@@ -182,16 +169,20 @@ def __enter__(self):
182169
def __exit__(self, _type, value, traceback):
183170
"""Exit context."""
184171

185-
@staticmethod
186-
def _pre_execution(command):
172+
def _pre_execution(self, command):
187173
"""Write pre execution command to a shell script.
188174
189175
:param command: Environment and pre execution shell command to write to shell script.
190176
:type command: str
191177
"""
192-
with open(os.path.join(BASE, "environ.sh"), "w") as environ_file:
178+
environ = Path().joinpath("environ.sh")
179+
with environ.open(mode="w") as environ_file:
193180
for arg in command:
194181
environ_file.write("{} || exit 1\n".format(arg))
182+
self.logger.info(
183+
"Pre-execution script (includes ENVIRONMENT):\n" "%s",
184+
environ.read_text(),
185+
)
195186

196187
def _build_environment_command(self):
197188
"""Build command for setting environment variables prior to execution.
@@ -297,26 +288,33 @@ def parse(self, line):
297288
self.current_test, "SKIPPED"
298289
)
299290

300-
def execute(self):
301-
"""Execute a test case."""
302-
self.logger.info("Figure out test directory.")
303-
test_directory = self.test_directory(self.checkout_command)
291+
def execute(self, workspace):
292+
"""Execute a test case.
293+
294+
:param workspace: Workspace instance for creating test directories.
295+
:type workspace: :obj:`etos_test_runner.lib.workspace.Workspace`
296+
"""
304297
line = False
305-
self.logger.info("Change directory to test directory '%s'.", test_directory)
306-
with self.etos.utils.chdir(test_directory):
307-
self.report_path = os.path.join(test_directory, self.report_path)
308-
self.logger.info("Report path: %s", self.report_path)
309-
self.logger.info("Executing pre-execution script.")
298+
with workspace.test_directory(
299+
" ".join(self.checkout_command), self._checkout_tests, self.checkout_command
300+
) as test_directory:
301+
self.report_path = test_directory.joinpath(self.report_path)
302+
self.logger.info("Report path: %r", self.report_path)
303+
304+
self.logger.info("Build pre-execution script.")
310305
self._pre_execution(self._build_environment_command())
306+
311307
self.logger.info("Build test command")
312308
command = self._build_test_command()
313-
self.logger.info("Run test command: %s", command)
309+
310+
self.logger.info("Run test command: %r", command)
314311
iterator = self.etos.utils.iterable_call(
315312
[command], shell=True, executable="/bin/bash", output=self.report_path
316313
)
317-
self.logger.info("Start test.")
314+
315+
self.logger.info("Wait for test to finish.")
318316
for _, line in iterator:
319317
if self.test_regex:
320318
self.parse(line)
321-
self.logger.info("Finished.")
322319
self.result = line
320+
self.logger.info("Finished with result %r.", self.result)

src/etos_test_runner/lib/iut.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
"""IUT data structure module."""
1717
import os
1818
import logging
19-
from packageurl import PackageURL
2019
from collections import OrderedDict
20+
from packageurl import PackageURL
2121
from jsontas.jsontas import JsonTas
2222

2323

2424
class Iut: # pylint: disable=too-few-public-methods
2525
"""Data object for IUTs."""
26+
2627
logger = logging.getLogger(__name__)
2728

2829
def __init__(self, product):
@@ -49,7 +50,9 @@ def prepare(self):
4950
for step, definition in self.test_runner.get("steps", {}).items():
5051
step_method = self.steps.get(step)
5152
if step_method is None:
52-
self.logger.error("Step %r does not exist. Available %r", step, self.steps)
53+
self.logger.error(
54+
"Step %r does not exist. Available %r", step, self.steps
55+
)
5356
continue
5457
self.logger.info("Executing step %r", step)
5558
definition = OrderedDict(**definition)

src/etos_test_runner/lib/logs.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,21 @@ def _upload_log(self, context, log, name, folder):
236236
self.logger.error("Attempted upload of %r", log)
237237
return upload["url"]
238238

239+
def upload_workspace(self, workspace):
240+
"""Upload compressed workspace to log area.
241+
242+
:param workspace: Workspace to upload.
243+
:type workspace: :obj:`etos_test_runner.lib.workspace.Workspace`
244+
"""
245+
filename, filepath = self._log_name_and_path(
246+
workspace.compressed_workspace.name, str(workspace.compressed_workspace)
247+
)
248+
log = {"file": filepath, "name": filename, "folder": self.log_storage_path}
249+
log["uri"] = self._upload_log(
250+
self.context, log["file"], log["name"], log["folder"]
251+
)
252+
self.logs.append(log)
253+
239254
def _rename_log_file_if_already_exists(self, target_path, log):
240255
"""Rename a log file if it already exists.
241256

src/etos_test_runner/lib/testrunner.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
import time
1818
import os
1919
import logging
20+
from pathlib import Path
2021
from pprint import pprint
2122

2223
from etos_test_runner.lib.graphql import request_test_suite_started
2324
from etos_test_runner.lib.iut_monitoring import IutMonitoring
2425
from etos_test_runner.lib.logs import LogHandler
2526
from etos_test_runner.lib.executor import Executor
26-
27-
BASE = os.path.dirname(os.path.abspath(__file__))
27+
from etos_test_runner.lib.workspace import Workspace
2828

2929

3030
class TestRunner:
@@ -48,12 +48,10 @@ def __init__(self, iut, etos):
4848
self.issuer = {"name": "ETOS Test Runner"}
4949

5050
self.env = os.environ
51-
if not os.path.isdir(os.getenv("TEST_ARTIFACT_PATH")):
52-
os.makedirs(os.getenv("TEST_ARTIFACT_PATH"))
53-
if not os.path.isdir(os.getenv("TEST_LOCAL_PATH")):
54-
os.makedirs(os.getenv("TEST_LOCAL_PATH"))
55-
if not os.path.isdir(os.getenv("GLOBAL_ARTIFACT_PATH")):
56-
os.makedirs(os.getenv("GLOBAL_ARTIFACT_PATH"))
51+
52+
Path(os.getenv("TEST_ARTIFACT_PATH")).mkdir(exist_ok=True)
53+
Path(os.getenv("TEST_LOCAL_PATH")).mkdir(exist_ok=True)
54+
Path(os.getenv("GLOBAL_ARTIFACT_PATH")).mkdir(exist_ok=True)
5755

5856
def test_suite_started(self):
5957
"""Publish a test suite started event.
@@ -131,17 +129,19 @@ def run_tests(self, log_handler):
131129
"""
132130
recipes = self.config.get("recipes")
133131
result = True
134-
for num, test in enumerate(recipes):
135-
self.logger.info("Executing test %s/%s", num + 1, len(recipes))
136-
with Executor(test, self.iut, self.etos) as executor:
137-
self.logger.info("Starting test '%s'", executor.test_name)
138-
executor.execute()
139-
self.logger.info("Gather logs.")
140-
log_handler.gather_logs_for_executor(executor)
132+
with Workspace() as workspace:
133+
for num, test in enumerate(recipes):
134+
self.logger.info("Executing test %s/%s", num + 1, len(recipes))
135+
with Executor(test, self.iut, self.etos) as executor:
136+
self.logger.info("Starting test '%s'", executor.test_name)
137+
executor.execute(workspace)
138+
self.logger.info("Gather logs.")
139+
log_handler.gather_logs_for_executor(executor)
141140

142-
if not executor.result:
143-
result = executor.result
144-
self.logger.info("Test finished. Result: %s.", executor.result)
141+
if not executor.result:
142+
result = executor.result
143+
self.logger.info("Test finished. Result: %s.", executor.result)
144+
log_handler.upload_workspace(workspace)
145145
return result
146146

147147
def outcome(self, result, executed, description):

0 commit comments

Comments
 (0)