-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Docker driver for tests in default configuration (#1358)
- Loading branch information
Showing
14 changed files
with
324 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Python: Current File", | ||
"type": "python", | ||
"request": "launch", | ||
"program": "${file}", | ||
"console": "integratedTerminal", | ||
"justMyCode": true | ||
}, | ||
{ | ||
"name": "Python: run.py", | ||
"type": "python", | ||
"request": "launch", | ||
"cwd": "${workspaceFolder}/testsuite", | ||
"program": "run.py", | ||
"args": [ | ||
"default-cache" // Replace with the test you want to debug | ||
], | ||
"console": "integratedTerminal", | ||
"justMyCode": true | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# This docker image is used in tests with the `docker_wrapper` driver. | ||
|
||
FROM alire/gnat:ubuntu-lts | ||
|
||
RUN useradd -m -s /bin/bash user && \ | ||
chown user:user /home/user | ||
|
||
RUN pip3 install e3-testsuite | ||
|
||
WORKDIR /testsuite | ||
USER user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
""" | ||
This is run inside a docker container, so we aren't in the context of e3. We | ||
prepare the environment and run the test script directly. | ||
""" | ||
|
||
import json | ||
import os | ||
import subprocess | ||
import sys | ||
|
||
from drivers import alr | ||
from drivers.alr import run_alr | ||
|
||
|
||
def main(): | ||
testsuite_root = "/testsuite" # Must match docker volume mount | ||
home = os.environ["HOME"] | ||
work_dir = "/tmp/test" | ||
|
||
# We receive the test environment prepared by e3 as JSON via environment. | ||
# With this test_env dictionary we are mostly in the same situation as in | ||
# the Python driver. | ||
test_env = json.loads(os.environ['ALIRE_TEST_ENV']) | ||
|
||
# Find the test sources inside docker. The test_env we got still has the | ||
# host paths. | ||
|
||
test_dir = test_env['test_dir'] # The test source dir to find test.py | ||
# Strip the prefix that comes from the host filesystem | ||
test_dir = test_dir[test_dir.find(testsuite_root):] | ||
|
||
# Create a pristine folder for the test to run in | ||
os.mkdir(work_dir) | ||
|
||
# Set up the environment | ||
|
||
# alr path | ||
os.environ["ALR_PATH"] = "/usr/bin/alr" # Must match docker volume mount | ||
|
||
# Disable autoconfig of the community index, to prevent unintended use | ||
run_alr("config", "--global", "--set", "index.auto_community", "false") | ||
|
||
# Disable selection of toolchain. Tests that | ||
# require a configured compiler will have to set it up explicitly. | ||
run_alr("toolchain", "--disable-assistant") | ||
|
||
# Disable warning on old index, to avoid having to update index versions | ||
# when they're still compatible. | ||
run_alr("config", "--global", "--set", "warning.old_index", "false") | ||
|
||
# indexes to use | ||
if 'indexes' in test_env: | ||
alr.prepare_indexes( | ||
config_dir=home + "/.config/alire", | ||
working_dir=work_dir, | ||
index_descriptions=test_env.get('indexes', {})) | ||
|
||
# Give access to our Python helpers | ||
env = os.environ.copy() | ||
python_path = env.get('PYTHONPATH', '') | ||
env['PYTHONPATH'] = python_path # Ensure it exists, even if empty | ||
env['PYTHONPATH'] += os.path.pathsep + testsuite_root # And add ours | ||
|
||
# Run the test | ||
try: | ||
subprocess.run(["python3", test_dir + "/test.py"], | ||
cwd=work_dir, env=env, check=True) | ||
except: | ||
# No need to let the exception to muddle the reporting, the output of | ||
# the test with any exception is already in the log that the | ||
# docker_wrapper will print. | ||
sys.exit(1) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import hashlib | ||
import json | ||
import os | ||
import subprocess | ||
from importlib import import_module | ||
from typing import Tuple | ||
|
||
from drivers.helpers import FileLock, on_linux | ||
from e3.testsuite.driver.classic import (ClassicTestDriver, | ||
TestAbortWithFailure, | ||
TestSkip) | ||
|
||
DOCKERFILE = "Dockerfile" | ||
PLACEHOLDER = "TO_BE_COMPUTED" | ||
TAG = "alire_testsuite" | ||
ENV_FLAG = "ALIRE_DOCKER_ENABLED" # Set to "False" to disable docker tests | ||
LABEL_HASH = "hash" | ||
|
||
|
||
def is_docker_available() -> bool: | ||
# Restrict docker testing only to Linux | ||
if not on_linux(): | ||
return False | ||
|
||
# Detect explicitly disabled | ||
if os.environ.get(ENV_FLAG, "True").lower() in ["0", "false"]: | ||
return False | ||
|
||
# Detect python library | ||
try: | ||
import_module("docker") | ||
except ImportError: | ||
return False | ||
|
||
# Detect executable | ||
try: | ||
subprocess.run(["docker", "--version"], check=True, | ||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | ||
except (subprocess.CalledProcessError, FileNotFoundError): | ||
return False | ||
|
||
return True | ||
|
||
|
||
def get_client(): | ||
if is_docker_available(): | ||
import docker | ||
return docker.from_env() | ||
else: | ||
return None | ||
|
||
|
||
def compute_dockerfile_hash() -> str: | ||
with open(DOCKERFILE, 'r') as file: | ||
content = file.read() | ||
return hashlib.sha256(content.encode('utf-8')).hexdigest() | ||
|
||
|
||
def already_built() -> Tuple[str, str]: | ||
file_hash = compute_dockerfile_hash() | ||
|
||
# Check existing images | ||
client = get_client() | ||
for image in client.images.list(): | ||
if LABEL_HASH in image.labels and image.labels[LABEL_HASH] == file_hash: | ||
return image, file_hash | ||
|
||
return None, file_hash | ||
|
||
|
||
def build_image() -> None: | ||
# We need to lock here as multiple docker tests might attempt to create the | ||
# image at the same time | ||
with FileLock("/tmp/alire_testsuite_docker.lock"): | ||
image, file_hash = already_built() | ||
|
||
if image: | ||
return | ||
|
||
# Do the actual build | ||
get_client().images.build( | ||
path="..", dockerfile=f"testsuite/{DOCKERFILE}", rm=True, tag=TAG, | ||
labels={LABEL_HASH : compute_dockerfile_hash()}) | ||
|
||
|
||
class DockerWrapperDriver(ClassicTestDriver): | ||
|
||
# This is a workaround for Windows, where attempting to use rlimit by e3-core | ||
# causes permission errors. TODO: remove once e3-core has a proper solution. | ||
@property | ||
def default_process_timeout(self): | ||
return None | ||
|
||
def run(self): | ||
if not is_docker_available(): | ||
raise TestSkip('Docker testing is disabled or not available') | ||
|
||
build_image() | ||
|
||
# Run our things | ||
try: | ||
container = get_client().containers.run( | ||
# Regular image launching | ||
image=TAG, tty=True, stdin_open=True, detach=True, | ||
|
||
# Pass the test environment to the container as JSON | ||
environment={"ALIRE_TEST_ENV": json.dumps(self.test_env)}, | ||
|
||
# Bind the testsuite directory as read-only and the alr executable | ||
volumes={ os.path.abspath(".") : { "bind": "/testsuite", "mode": "ro" } | ||
, os.path.abspath("..") + "/bin/alr" : { "bind": "/usr/bin/alr", "mode": "ro" } | ||
}, | ||
|
||
# In the container, launch the script that will setup the test | ||
command= | ||
"/bin/python3 -c 'from drivers.driver import docker_nested;" | ||
"docker_nested.main()'") | ||
|
||
# Wait for the container to finish and retrieve its output | ||
result = container.wait() | ||
output = container.logs().decode() | ||
|
||
if (code := result["StatusCode"]) != 0: | ||
self.result.log += f'Docker command failed with exit code {code} and output:\n{output}' | ||
raise TestAbortWithFailure( | ||
f"Docker command failed with exit code {code}") | ||
|
||
finally: | ||
# Don't leave dead containers around | ||
if 'container' in locals() and container: | ||
container.remove() | ||
|
||
# Check that the test succeeded inside the docker container | ||
out_lines = output.splitlines() | ||
if not out_lines or out_lines[-1] != 'SUCCESS': | ||
self.result.log += f'missing SUCCESS output line:\n{output}' | ||
raise TestAbortWithFailure('missing SUCCESS output line') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
docker | ||
e3-testsuite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
""" | ||
Check inside a pristine environment that the default cache is located where | ||
it should. | ||
""" | ||
|
||
import os | ||
|
||
from drivers.alr import alr_with, init_local_crate | ||
|
||
# Forcing the deployment of a binary crate triggers the use of the global | ||
# cache, which should be created at the expected location. | ||
init_local_crate() | ||
alr_with("gnat_native") | ||
|
||
home = os.environ["HOME"] | ||
|
||
assert \ | ||
os.path.isdir(f"{home}/.cache/alire/dependencies/gnat_native_8888.0.0_99fa3a55"), \ | ||
"Default cache not found at the expected location" | ||
|
||
|
||
print('SUCCESS') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
driver: docker-wrapper | ||
indexes: | ||
toolchain_index: | ||
copy_crates_src: True |