Skip to content

Commit

Permalink
Add scripted tests for the nest/finest binaries (#98)
Browse files Browse the repository at this point in the history
* Add scripted tests for the nest/finest binaries

* Use $NEST_CHROOT as default chroot for nest/finest tests

* Fix travis.yml

* Fix travis.yml

* Remove global sudo to tighten its scope to invoking nest/finest
  • Loading branch information
doom authored and Arignir committed Aug 3, 2019
1 parent 0193b23 commit 1f2b3f3
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 13 deletions.
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
dist: bionic
language: rust
cache: cargo
rust: nightly
sudo: true

before_script:
- sudo add-apt-repository -y ppa:deadsnakes/ppa
- sudo apt-get update
- sudo apt-get install python3.7 python3.7-distutils
- curl https://bootstrap.pypa.io/get-pip.py | sudo python3.7
- rustup toolchain install nightly-2019-07-25
- rustup default nightly-2019-07-25
- rustup component add rustfmt-preview
- rustup update
- cargo update
- sudo pip3.7 install -r ./tests/requirements.txt

script:
- cargo fmt --all -- --check
Expand Down
224 changes: 224 additions & 0 deletions tests/nesttests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import json
import os
import shutil
import subprocess
import tarfile
import tempfile
import toml
from typing import Any, Dict, List
from contextlib import contextmanager
from time import sleep


class Package:
def __init__(
self,
name: str,
category: str,
version: str,
kind: str,
description: str = "A package",
tags: List[str] = None,
maintainer: str = "nest-tests@raven-os.org",
licenses: List[str] = None,
upstream_url: str = None,
):
self.name = name
self.category = category
self.version = version
self.kind = kind
self.description = description
self.tags = tags or []
self.maintainer = maintainer
self.licenses = licenses or ["gpl_v3"]
self.upstream_url = upstream_url or "https://google.com"
self.dependencies = {}
self.files = {}

def full_name(self) -> str:
return f"tests::{self.category}/{self.name}"

def package_id(self) -> str:
return f"tests::{self.category}/{self.name}#{self.version}"

def add_dependency(self, dependency: 'Package', version_requirement: str) -> 'Package':
self.dependencies[dependency.full_name()] = version_requirement
return self

def add_file(self, path, with_content=None, from_reader=None) -> 'Package':
if not (with_content ^ from_reader):
raise ValueError("Invalid arguments: exactly one of 'with_content' and 'from_reader' must be used")
# self.files[path] =
return self

def add_symlink(self, path: str, target: str) -> 'Package':
return self

def add_directory(self, path: str) -> 'Package':
return self

def _create_in(self, directory: str):
directory = f"{directory}/{self.category}/{self.name}"
os.makedirs(directory, exist_ok=True)
manifest = {
"name": self.name,
"category": self.category,
"version": self.version,
"kind": self.kind,
"wrap_date": "2019-05-27T16:34:15Z",
"metadata": {
"description": self.description,
"tags": self.tags,
"maintainer": self.maintainer,
"licenses": self.licenses,
"upstream_url": self.upstream_url
},
"dependencies": self.dependencies
}
manifest_path = f"{directory}/manifest.toml"
with open(manifest_path, 'x') as f:
toml.dump(manifest, f)

files = [(manifest_path, "manifest.toml")]

if self.kind == "effective":
with tarfile.open(f"{directory}/data.tar.gz", "w:gz") as tar:
pass
files.append((f"{directory}/data.tar.gz", "data.tar.gz"))

with tarfile.open(f"{directory}/{self.name}-{self.version}.nest", "x") as tar:
for name, arcname in files:
tar.add(name, arcname=arcname)
os.remove(name)


def _create_packages(packages: List[Package]):
for package in packages:
package._create_in("/tmp/nest-server/packages")


def _create_configuration_file():
configuration = {
'name': 'tests',
'pretty_name': 'Tests',
'package_dir': './packages/',
'cache_dir': './cache/',
'auth_token': 'a_very_strong_password', 'links': [
{'name': 'Tests', 'url': '/', 'active': True},
{'name': 'Stable',
'url': 'https://stable.raven-os.org'},
{'name': 'Beta',
'url': 'https://beta.raven-os.org'},
{'name': 'Unstable',
'url': 'https://unstable.raven-os.org'}
]
}
with open("/tmp/nest-server/Repository.toml", "w") as f:
toml.dump(configuration, f)


@contextmanager
def nest_server(packages: List[Package] = None):
_create_packages(packages or [])
_create_configuration_file()
nest_server_path = os.getenv("NEST_SERVER")
p = subprocess.Popen(["cargo", "run", "-q"], cwd=nest_server_path, stdout=subprocess.DEVNULL)
sleep(0.5) # Wait a bit so the server initializes properly
try:
yield
finally:
p.kill()
if os.path.exists(f"{nest_server_path}/packages"):
shutil.rmtree(f"{nest_server_path}/packages")
if os.path.exists(f"{nest_server_path}/cache"):
shutil.rmtree(f"{nest_server_path}/cache")


@contextmanager
def create_config(entries: Dict[str, Dict[str, Any]] = None):
entries = entries or {"repositories": {"tests": {"mirrors": ["http://localhost:8000"]}}}
path = tempfile.NamedTemporaryFile().name
with open(path, 'w') as f:
toml.dump(entries, f)
try:
yield path
finally:
os.remove(path)


class _Depgraph:
def __init__(self, path: str):
if os.path.exists(path):
self.data = json.load(open(path, 'r'))
else:
self.data = {"node_names": {}}

def installed_packages(self):
return filter(lambda name: name[0] != '@', self.data["node_names"])

def groups(self):
return filter(lambda name: name[0] == '@', self.data["node_names"])


class _Nest:
def __init__(self, config: str = None, chroot: str = None):
self.config = config
self.chroot = chroot

def _run(self, *args: str, input_str: str = None):
cmd = ["sudo", f"PATH={os.getenv('PATH')}", "env", "cargo", "run", "-q", "--bin", "nest", "--"]
if self.config:
cmd += ("--config", self.config)
if self.chroot:
cmd += ("--chroot", self.chroot)
cmd += args
return subprocess.run(cmd, capture_output=True, input=input_str and input_str.encode())

def pull(self, confirm=True):
return self._run("pull", input_str="yes" if confirm else "no")

def install(self, *packages: str, confirm=True):
return self._run("install", *packages, input_str="yes" if confirm else "no")

def uninstall(self, *packages: str, confirm=True):
return self._run("uninstall", *packages, input_str="yes" if confirm else "no")

def list(self):
pass

def depgraph(self) -> _Depgraph:
return _Depgraph(f"{self.chroot}/var/nest/depgraph")

def help(self):
return self._run("help")


def nest(config: str = None, chroot: str = None) -> _Nest:
chroot = chroot or os.getenv("NEST_CHROOT")
return _Nest(config, chroot)


class _Finest:
def __init__(self, config: str = None, chroot: str = None):
self.config = config
self.chroot = chroot

def _run(self, *args: str, input_str: str = None):
cmd = ["sudo", f"PATH={os.getenv('PATH')}", "env", "cargo", "run", "-q", "--bin", "finest", "--"]
if self.config:
cmd += ("--config", self.config)
if self.chroot:
cmd += ("--chroot", self.chroot)
cmd += args
return subprocess.run(cmd, capture_output=True, input=input_str and input_str.encode())

def pull(self):
return self._run("pull", input_str="yes")

def help(self):
return self._run("help")


def finest(config: str = None, chroot: str = None) -> _Finest:
chroot = chroot or os.getenv("NEST_CHROOT")
return _Finest(config, chroot)
1 change: 1 addition & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
toml==0.10.0
48 changes: 35 additions & 13 deletions tests/run.sh
Original file line number Diff line number Diff line change
@@ -1,34 +1,56 @@
#!/usr/bin/env bash

# Common variables shared among all tests
export NEST="cargo run --bin=nest"
export CARGO="env cargo"
export NEST_SERVER="/tmp/nest-server"
export PYTHONPATH=$(dirname "$0")
export NEST_CHROOT="/tmp/chroot/"

# Colors
export RED="\033[1;31m"
export GREEN="\033[1;32m"
export RESET="\033[0m"

declare -i nb_tests=1
if [ ! -d $NEST_SERVER ]; then
echo "Cloning latest nest-server..."
git clone https://github.com/raven-os/nest-server $NEST_SERVER
elif [ ! -e $NEST_SERVER ]; then
echo "$NEST_SERVER already exists and is not a directory, aborting."
exit 1
fi

pushd $NEST_SERVER
$CARGO build
popd

if [ -e $NEST_CHROOT ]; then
echo "$NEST_CHROOT already exists, aborting."
exit 1
fi

declare -i nb_tests=0
declare -i success=0
declare tests_dir=$(dirname "$0")

# Run all tests
for ((i=1; i <= $nb_tests; i++)) do
$tests_dir/test_$i/run.sh > /dev/null 2> /dev/null
declare -i out_code=$?

if [[ $out_code -eq 0 ]]; then
printf "[%02i] ${GREEN}OK${RESET}\n" $i
success=$(($success + 1))
else
printf "[%02i] ${RED}KO${RESET}\n" $i
fi
for test in $tests_dir/test_*; do
$test/run.py
declare -i out_code=$?

if [[ $out_code -eq 0 ]]; then
printf "[%02i] ${GREEN}OK${RESET}\n" $nb_tests
success=$(($success + 1))
else
printf "[%02i] ${RED}KO${RESET}\n" $nb_tests
fi
nb_tests=$(($nb_tests + 1))
sudo rm -rf $NEST_CHROOT
done

echo
echo "$success/$nb_tests tests passed"

# Exit 1 if any test failed to ensure the build fails on Travis
if [[ $success -ne $nb_tests ]]; then
exit 1
exit 1
fi
10 changes: 10 additions & 0 deletions tests/test_0/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python3.7

"""
Launching nest and finest with a valid configuration file should succeed
"""

from nesttests import *

assert nest().help().returncode == 0
assert finest().help().returncode == 0
22 changes: 22 additions & 0 deletions tests/test_1/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python3.7

"""
Launching nest and finest with the --help option should succeed, even with an invalid configuration file
"""

from nesttests import *
import os

assert nest(config="/non_existent/not_existing_either.toml").help().returncode == 0
assert finest(config="/non_existent/not_existing_either.toml").help().returncode == 0

with create_config(entries={}) as config_path:
os.chmod(path=config_path, mode=0o222) # Make the configuration file write-only
assert nest(config=config_path).help().returncode == 0
assert finest(config=config_path).help().returncode == 0

with create_config(entries={}) as config_path:
with open(config_path, 'w+') as f:
f.write("<(^v^)>") # Write invalid data
assert nest(config=config_path).help().returncode == 0
assert finest(config=config_path).help().returncode == 0
10 changes: 10 additions & 0 deletions tests/test_2/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python3.7

"""
Pulling available repositories with a valid configuration file should succeed
"""

from nesttests import *

with nest_server(), create_config() as config_path:
assert nest(config=config_path).pull().returncode == 0
22 changes: 22 additions & 0 deletions tests/test_3/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python3.7

"""
Pulling available repositories with an invalid configuration file should fail
"""

from nesttests import *
import os

assert nest(config="/non_existent/not_existing_either.toml").pull().returncode == 1
assert finest(config="/non_existent/not_existing_either.toml").pull().returncode == 1

with create_config(entries={}) as config_path:
os.chmod(path=config_path, mode=0o222) # Make the configuration file write-only
assert nest(config=config_path).pull().returncode == 1
assert finest(config=config_path).pull().returncode == 1

with create_config(entries={}) as config_path:
with open(config_path, 'w+') as f:
f.write("<(^v^)>") # Write invalid data
assert nest(config=config_path).pull().returncode == 1
assert finest(config=config_path).pull().returncode == 1
12 changes: 12 additions & 0 deletions tests/test_4/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python3.7

"""
Installation of an unavailable packages should trigger an error
"""

from nesttests import *

with nest_server(), create_config() as config_path:
nest = nest(config=config_path)
assert nest.pull().returncode == 0
assert nest.install("unavailable-package").returncode == 1
Loading

0 comments on commit 1f2b3f3

Please sign in to comment.