Integration tests (#313)

* Integration tests

* Set "HOMEBREW_NO_AUTO_UPDATE=1" for virtualenv installation step

* Use CIRRUS_WORKING_DIR as temporary directory if present
This commit is contained in:
Nikolay Edigaryev 2022-11-14 22:24:51 +04:00 committed by GitHub
parent 833c162187
commit 8524d93741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 207 additions and 3 deletions

View File

@ -1,13 +1,30 @@
task: task:
name: Test on Ventura name: Test on Ventura
alias: test
persistent_worker: persistent_worker:
labels: labels:
name: Mac-Mini-M1 name: scaleway-m1
build_script: swift test test_script:
test_script: swift test - swift test
integration_test_script:
# Build Tart
- swift build
- codesign --sign - --entitlements Resources/tart.entitlements --force .build/debug/tart
- export PATH=$(pwd)/.build/arm64-apple-macosx/debug:$PATH
# Run integration tests
- cd integration-tests
- HOMEBREW_NO_AUTO_UPDATE=1 brew install virtualenv
- virtualenv venv
- source venv/bin/activate
- pip install -r requirements.txt
- pytest --verbose --junit-xml=pytest-junit.xml
pytest_junit_result_artifacts:
path: "integration-tests/pytest-junit.xml"
format: junit
task: task:
name: Build name: Build
alias: build
only_if: $CIRRUS_TAG == '' only_if: $CIRRUS_TAG == ''
macos_instance: macos_instance:
image: ghcr.io/cirruslabs/macos-ventura-xcode:latest image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
@ -19,6 +36,9 @@ task:
task: task:
name: Release name: Release
only_if: $CIRRUS_TAG != '' only_if: $CIRRUS_TAG != ''
depends_on:
- test
- build
macos_instance: macos_instance:
image: ghcr.io/cirruslabs/macos-ventura-xcode:latest image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
env: env:

View File

@ -138,6 +138,11 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
ipswURL = try await VM.retrieveIPSW(remoteURL: ipswURL) ipswURL = try await VM.retrieveIPSW(remoteURL: ipswURL)
} }
// We create a temporary TART_HOME directory in tests, which has its "cache" folder symlinked
// to the users Tart cache directory (~/.tart/cache). However, the Virtualization.Framework
// cannot deal with paths that contain symlinks, so expand them here first.
ipswURL.resolveSymlinksInPath()
// Load the restore image and try to get the requirements // Load the restore image and try to get the requirements
// that match both the image and our platform // that match both the image and our platform
let image = try await withCheckedThrowingContinuation { continuation in let image = try await withCheckedThrowingContinuation { continuation in

View File

@ -0,0 +1,16 @@
import pytest
from tart import Tart
from docker_registry import DockerRegistry
@pytest.fixture(scope="class")
def tart():
with Tart() as tart:
yield tart
@pytest.fixture(scope="class")
def docker_registry():
with DockerRegistry() as docker_registry:
yield docker_registry

View File

@ -0,0 +1,20 @@
import requests
from testcontainers.core.waiting_utils import wait_container_is_ready
from testcontainers.general import DockerContainer
class DockerRegistry(DockerContainer):
_default_exposed_port = 5000
def __init__(self):
super().__init__("registry:2")
self.with_exposed_ports(self._default_exposed_port)
@wait_container_is_ready(requests.exceptions.ConnectionError)
def remote_name(self, for_vm: str):
exposed_port = self.get_exposed_port(self._default_exposed_port)
requests.get(f"http://127.0.0.1:{exposed_port}/v2/")
return f"127.0.0.1:{exposed_port}/tart/{for_vm}:latest"

View File

@ -0,0 +1,5 @@
pytest
testcontainers
requests
bitmath
pytest-dependency

33
integration-tests/tart.py Normal file
View File

@ -0,0 +1,33 @@
import tempfile
import os
import subprocess
class Tart:
def __init__(self):
self.tmp_dir = tempfile.TemporaryDirectory(dir=os.environ.get("CIRRUS_WORKING_DIR"))
# Link to the users IPSW cache to make things faster
src = os.path.join(os.path.expanduser("~"), ".tart", "cache", "IPSWs")
dst = os.path.join(self.tmp_dir.name, "cache", "IPSWs")
os.makedirs(os.path.join(self.tmp_dir.name, "cache"))
os.symlink(src, dst)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.tmp_dir.cleanup()
def home(self) -> str:
return self.tmp_dir.name
def run(self, args):
env = os.environ.copy()
env.update({"TART_HOME": self.tmp_dir.name})
completed_process = subprocess.run(["tart"] + args, env=env, capture_output=True)
completed_process.check_returncode()
return completed_process.stdout.decode("utf-8"), completed_process.stderr.decode("utf-8")

View File

@ -0,0 +1,10 @@
def test_clone(tart):
# Create a Linux VM (because we can create it really fast)
tart.run(["create", "--linux", "debian"])
# Clone the VM
tart.run(["clone", "debian", "ubuntu"])
# Ensure that we have now 2 VMs
stdout, _, = tart.run(["list", "--quiet"])
assert stdout == "debian\nubuntu\n"

View File

@ -0,0 +1,16 @@
def test_create_macos(tart):
# Create a macOS VM
tart.run(["create", "--from-ipsw", "latest", "macos-vm"])
# Ensure that the VM was created
stdout, _ = tart.run(["list", "--quiet"])
assert stdout == "macos-vm\n"
def test_create_linux(tart):
# Create a Linux VM
tart.run(["create", "--linux", "linux-vm"])
# Ensure that the VM was created
stdout, _ = tart.run(["list", "--quiet"])
assert stdout == "linux-vm\n"

View File

@ -0,0 +1,14 @@
def test_delete(tart):
# Create a Linux VM (because we can create it really fast)
tart.run(["create", "--linux", "debian"])
# Ensure that the VM exists
stdout, _, = tart.run(["list", "--quiet"])
assert stdout == "debian\n"
# Delete the VM
tart.run(["delete", "debian"])
# Ensure that the VM was removed
stdout, _, = tart.run(["list", "--quiet"])
assert stdout == ""

View File

@ -0,0 +1,55 @@
import os
import tempfile
import timeit
import uuid
import bitmath
import pytest
amount_to_transfer = bitmath.GB(1)
minimal_speed_per_second = bitmath.Mb(100)
class TestOCI:
@pytest.mark.dependency()
def test_push_speed(self, tart, vm_with_random_disk, docker_registry):
start = timeit.default_timer()
tart.run(["push", "--insecure", vm_with_random_disk, docker_registry.remote_name(vm_with_random_disk)])
stop = timeit.default_timer()
actual_speed_per_second = self._calculate_speed_per_second(amount_to_transfer, stop - start)
assert actual_speed_per_second > minimal_speed_per_second
@pytest.mark.dependency(depends=["TestOCI::test_push_speed"])
def test_pull_speed(self, tart, vm_with_random_disk, docker_registry):
start = timeit.default_timer()
tart.run(["pull", "--insecure", docker_registry.remote_name(vm_with_random_disk)])
stop = timeit.default_timer()
actual_speed_per_second = self._calculate_speed_per_second(amount_to_transfer, stop - start)
assert actual_speed_per_second > minimal_speed_per_second
@staticmethod
def _calculate_speed_per_second(amount_transferred, time_taken):
return (amount_transferred / time_taken).best_prefix(bitmath.SI)
@pytest.fixture(scope="class")
def vm_with_random_disk(tart):
vm_name = str(uuid.uuid4())
# Create a VM (Linux for speed's sake)
tart.run(["create", "--linux", vm_name])
# Populate VM's disk with "amount_to_transfer" of random bytes
# to effectively disable Tart's OCI blob compression
disk_path = os.path.join(tart.home(), "vms", vm_name, "disk.img")
with tempfile.NamedTemporaryFile(delete=False) as tf:
tf.write(os.urandom(amount_to_transfer.bytes))
tf.close()
os.rename(tf.name, disk_path)
yield vm_name
tart.run(["delete", vm_name])

View File

@ -0,0 +1,10 @@
def test_rename(tart):
# Create a Linux VM (because we can create it really fast)
tart.run(["create", "--linux", "debian"])
# Rename that VM
tart.run(["rename", "debian", "ubuntu"])
# Ensure that the VM is now named "ubuntu"
stdout, _, = tart.run(["list", "--quiet"])
assert stdout == "ubuntu\n"