Skip to content

Commit

Permalink
WIP: Add new Rust-based tests
Browse files Browse the repository at this point in the history
There's a lot going on here.  First, this is intended to run
nicely as part of the new [cosa/kola ext-tests](coreos/coreos-assembler#1252).

With Rust we can get one big static binary that we can upload,
and include a webserver as part of the binary.  This way we don't
need to do the hack of running a container with Python or whatever.

Now, what's even better about Rust for this is that it has macros,
and specifically we are using [commandspec](https://github.com/tcr/commandspec/)
which allows us to "inline" shell script.  I think the macros
could be even better, but this shows how we can intermix
pure Rust code along with using shell safely enough.

We're using my fork of commandspec because the upstream hasn't
merged [a few PRs](https://github.com/tcr/commandspec/pulls?q=is%3Apr+author%3Acgwalters+).

This model is intended to replace *both* some of our
`make check` tests as well.

Oh, and this takes the obvious step of using the Rust OSTree bindings
as part of our tests.  Currently the "commandspec tests" and "API tests"
are separate, but nothing stops us from intermixing them if we wanted.

I haven't yet tried to write destructive tests with this but
I think it will go well.
  • Loading branch information
cgwalters committed Mar 30, 2020
1 parent 5b4c975 commit c7d9b58
Show file tree
Hide file tree
Showing 9 changed files with 466 additions and 31 deletions.
32 changes: 1 addition & 31 deletions tests/basic-test.sh
Expand Up @@ -21,7 +21,7 @@

set -euo pipefail

echo "1..$((88 + ${extra_basic_tests:-0}))"
echo "1..$((85 + ${extra_basic_tests:-0}))"

CHECKOUT_U_ARG=""
CHECKOUT_H_ARGS="-H"
Expand Down Expand Up @@ -1013,17 +1013,6 @@ stat '--format=%Y' test2-checkout/baz/deeper > deeper-mtime
assert_file_has_content deeper-mtime 0
echo "ok content mtime"

cd ${test_tmpdir}
rm -rf test2-checkout
mkdir -p test2-checkout
cd test2-checkout
mkfifo afifo
if $OSTREE commit ${COMMIT_ARGS} -b test2 -s "Attempt to commit a FIFO" 2>../errmsg; then
assert_not_reached "Committing a FIFO unexpetedly succeeded!"
assert_file_has_content ../errmsg "Unsupported file type"
fi
echo "ok commit of fifo was rejected"

cd ${test_tmpdir}
rm repo2 -rf
mkdir repo2
Expand Down Expand Up @@ -1162,22 +1151,3 @@ if test "$(id -u)" != "0"; then
else
echo "ok # SKIP not run when root"
fi

cd ${test_tmpdir}
rm -rf test2-checkout
mkdir -p test2-checkout
cd test2-checkout
touch blah
stat --printf="%.Y\n" ${test_tmpdir}/repo > ${test_tmpdir}/timestamp-orig.txt
$OSTREE commit ${COMMIT_ARGS} -b test2 -s "Should bump the mtime"
stat --printf="%.Y\n" ${test_tmpdir}/repo > ${test_tmpdir}/timestamp-new.txt
cd ..
if cmp timestamp-{orig,new}.txt; then
assert_not_reached "failed to update mtime on repo"
fi
echo "ok mtime updated"

cd ${test_tmpdir}
$OSTREE init --mode=bare --repo=repo-extensions
assert_has_dir repo-extensions/extensions
echo "ok extensions dir"
2 changes: 2 additions & 0 deletions tests/inst/.gitignore
@@ -0,0 +1,2 @@
target/
Cargo.lock
32 changes: 32 additions & 0 deletions tests/inst/Cargo.toml
@@ -0,0 +1,32 @@
[package]
name = "ostree-test"
version = "0.1.0"
authors = ["Colin Walters <walters@verbum.org>"]
edition = "2018"

[[bin]]
name = "ostree-test"
path = "src/insttest.rs"

[dependencies]
clap = "2.32.0"
structopt = "0.2"
commandspec = "0.12.2"
anyhow = "1.0"
tempfile = "3.1.0"
gio = "0.8"
ostree = { version = "0.7.1", features = ["v2020_1"] }
libtest-mimic = "0.2.0"
twoway = "0.2.1"
hyper = "0.13"
futures = "0.3.4"
http = "0.2.0"
hyper-staticfile = "0.5.1"
tokio = { version = "0.2", features = ["full"] }
futures-util = "0.3.1"
base64 = "0.12.0"

# See https://github.com/tcr/commandspec/pulls?q=is%3Apr+author%3Acgwalters+
[patch.crates-io]
commandspec = { git = "https://github.com/cgwalters/commandspec", branch = 'walters-master' }
#commandspec = { path = "/var/srv/walters/src/github/tcr/commandspec" }
39 changes: 39 additions & 0 deletions tests/inst/src/insttest.rs
@@ -0,0 +1,39 @@
use anyhow::Result;
// use structopt::StructOpt;
// // https://github.com/clap-rs/clap/pull/1397
// #[macro_use]
// extern crate clap;

mod repobin;
mod sysroot;
mod test;

fn nondestructive_tests() -> Vec<test::Test> {
repobin::tests()
.into_iter()
.chain(sysroot::tests().into_iter())
.collect()
}

fn run_test(test: &test::Test) -> libtest_mimic::Outcome {
if let Err(e) = (test.data)() {
libtest_mimic::Outcome::Failed {
msg: Some(e.to_string()),
}
} else {
libtest_mimic::Outcome::Passed
}
}

fn main() -> Result<()> {
// Ensure we're always in tempdir so we can rely on it globally
let tmp_dir = tempfile::Builder::new()
.prefix("ostree-insttest-top")
.tempdir()?;
std::env::set_current_dir(tmp_dir.path())?;

let args = libtest_mimic::Arguments::from_args();
let tests = nondestructive_tests();

libtest_mimic::run_tests(&args, tests, run_test).exit();
}
135 changes: 135 additions & 0 deletions tests/inst/src/repobin.rs
@@ -0,0 +1,135 @@
//! Tests that mostly use the CLI and operate on temporary
//! repositories.

use std::path::Path;

use anyhow::{Context, Result};
use commandspec::{sh_command, sh_execute};
use tokio::runtime::Runtime;

use crate::test::*;

pub(crate) fn tests() -> impl IntoIterator<Item = Test> {
crate::deftests_map!(
crate::test::with_tmpdir,
test_nofifo,
test_mtime,
test_extensions,
test_pull_basicauth
)
}

fn test_nofifo(tmp_dir: &Path) -> Result<()> {
sh_execute!(
r"cd {tmp_dir}
ostree --repo=repo init --mode=archive
mkdir tmproot
mkfifo tmproot/afile
",
tmp_dir = tmp_dir.to_str()
)?;
cmd_fails_with(
sh_command!(
r#"cd {tmp_dir}
ls -al
ostree --repo=repo commit -b fifotest -s "commit fifo" --tree=dir=./tmproot"#,
tmp_dir = tmp_dir.to_str()
)
.unwrap(),
"Not a regular file or symlink",
)?;
Ok(())
}

fn test_mtime(tmp_dir: &Path) -> Result<()> {
sh_execute!(
r"cd {tmp_dir}
ostree --repo=repo init --mode=archive
mkdir tmproot
echo afile > tmproot/afile
ostree --repo=repo commit -b test --tree=dir=tmproot >/dev/null
",
tmp_dir = tmp_dir.to_str()
)?;
let ts = tmp_dir.join("repo").metadata()?.modified().unwrap();
sh_execute!(
r#"cd {tmp_dir}
ostree --repo=repo commit -b test -s "bump mtime" --tree=dir=tmproot >/dev/null"#,
tmp_dir = tmp_dir.to_str()
)?;
assert_ne!(ts, tmp_dir.join("repo").metadata()?.modified().unwrap());
Ok(())
}

fn test_extensions(tmp_dir: &Path) -> Result<()> {
sh_execute!(
r"ostree --repo={tmp_dir}/repo init --mode=bare",
tmp_dir = tmp_dir.to_str()
)?;
assert!(tmp_dir.join("repo/extensions").exists());
Ok(())
}

async fn impl_test_pull_basicauth(tmp_dir: &Path) -> Result<()> {
let opts = TestHttpServerOpts {
basicauth: true,
..Default::default()
};
let serverrepo = tmp_dir.join("server/repo");
std::fs::create_dir_all(&serverrepo)?;
let addr = http_server(&serverrepo, opts).await?;
let tmp_dir = tmp_dir.to_path_buf();
tokio::task::spawn_blocking(move || -> Result<()> {
let baseuri = http::Uri::from_maybe_shared(format!("http://{}/", addr).into_bytes())?;
let unauthuri =
http::Uri::from_maybe_shared(format!("http://unknown:badpw@{}/", addr).into_bytes())?;
let authuri = http::Uri::from_maybe_shared(
format!("http://{}@{}/", TEST_HTTP_BASIC_AUTH, addr).into_bytes(),
)?;
let osroot = tmp_dir.join("osroot");
mkroot(&osroot)?;
sh_execute!(
r#"cd {tmp_dir}
ostree --repo={serverrepo} init --mode=archive
ostree --repo={serverrepo} commit -b os --tree=dir={osroot} >/dev/null
mkdir client
cd client
ostree --repo=repo init --mode=archive
ostree --repo=repo remote add --set=gpg-verify=false origin-unauth {baseuri}
ostree --repo=repo remote add --set=gpg-verify=false origin-badauth {unauthuri}
ostree --repo=repo remote add --set=gpg-verify=false origin-goodauth {authuri}
"#,
tmp_dir = tmp_dir.to_str(),
osroot = osroot.to_str(),
serverrepo = serverrepo.to_str(),
baseuri = baseuri.to_string(),
unauthuri = unauthuri.to_string(),
authuri = authuri.to_string()
)?;
for rem in &["unauth", "badauth"] {
cmd_fails_with(
sh_command!(
r#"ostree --repo={tmp_dir}/client/repo pull origin-{rem} os >/dev/null"#,
tmp_dir = tmp_dir.to_str(),
rem = *rem
)
.unwrap(),
"HTTP 403",
)
.context(rem)?;
}
sh_execute!(
r#"ostree --repo={tmp_dir}/client/repo pull origin-goodauth os >/dev/null"#,
tmp_dir = tmp_dir.to_str()
)?;
Ok(())
})
.await??;
Ok(())
}

fn test_pull_basicauth(tmp_dir: &Path) -> Result<()> {
let mut rt = Runtime::new()?;
rt.block_on(async move { impl_test_pull_basicauth(tmp_dir).await })?;
Ok(())
}
38 changes: 38 additions & 0 deletions tests/inst/src/sysroot.rs
@@ -0,0 +1,38 @@
//! Tests that mostly use the API and access the booted sysroot read-only.

use anyhow::Result;
use gio::prelude::*;
use ostree::prelude::*;

use crate::test::*;

pub(crate) fn tests() -> impl IntoIterator<Item = Test> {
let mut tests = crate::deftests!(test_sysroot_ro);
if !std::path::Path::new("/run/ostree-booted").exists() {
for t in &mut tests {
t.is_ignored = true
}
};
tests
}

fn test_sysroot_ro() -> Result<()> {
let cancellable = Some(gio::Cancellable::new());
let sysroot = ostree::Sysroot::new_default();
sysroot.load(cancellable.as_ref())?;
assert!(sysroot.is_booted());

let booted = sysroot.get_booted_deployment().expect("booted deployment");
assert!(!booted.is_staged());
let repo = sysroot.repo().expect("repo");

let csum = booted.get_csum().expect("booted csum");
let csum = csum.as_str();

let (root, rev) = repo.read_commit(csum, cancellable.as_ref())?;
assert_eq!(rev, csum);
let root = root.downcast::<ostree::RepoFile>().expect("downcast");
root.ensure_resolved()?;

Ok(())
}

0 comments on commit c7d9b58

Please sign in to comment.