Merge pull request #727 from tamird/build-everywhere

integration-test: avoid reliance on kernel headers
reviewable/pr638/r35
Tamir Duberstein 1 year ago committed by GitHub
commit cb42d028d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,7 +5,7 @@ edition = "2021"
publish = false
[dependencies]
anyhow = { workspace = true, default-features = true }
anyhow = { workspace = true, features = ["std"] }
assert_matches = { workspace = true }
aya = { workspace = true }
aya-log = { workspace = true }
@ -16,10 +16,7 @@ netns-rs = { workspace = true }
object = { workspace = true }
rbpf = { workspace = true }
test-case = { workspace = true }
tokio = { workspace = true, default-features = false, features = [
"macros",
"time",
] }
tokio = { workspace = true, features = ["macros", "time"] }
[build-dependencies]
cargo_metadata = { workspace = true }

@ -1,10 +1,9 @@
#include <linux/bpf.h>
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
// clang-format on
SEC("xdp")
int xdp_drop(struct xdp_md *ctx)
{
return XDP_DROP;
}
int xdp_drop(struct xdp_md *ctx) { return XDP_DROP; }
char _license[] SEC("license") = "GPL";

@ -1,10 +1,9 @@
#include <linux/bpf.h>
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
// clang-format on
SEC("xdp")
int xdp_pass(struct xdp_md *ctx)
{
return XDP_PASS;
}
int xdp_pass(struct xdp_md *ctx) { return XDP_PASS; }
char _license[] SEC("license") = "GPL";

@ -1,30 +1,30 @@
#include <linux/bpf.h>
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
// clang-format on
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 1);
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 1);
} map_1 SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 1);
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 1);
} map_2 SEC(".maps");
SEC("tracepoint")
int bpf_prog(void *ctx)
{
__u32 key = 0;
__u64 twenty_four = 24;
__u64 forty_two = 42;
bpf_map_update_elem(&map_1, &key, &twenty_four, BPF_ANY);
bpf_map_update_elem(&map_2, &key, &forty_two, BPF_ANY);
return 0;
int bpf_prog(void *ctx) {
__u32 key = 0;
__u64 twenty_four = 24;
__u64 forty_two = 42;
bpf_map_update_elem(&map_1, &key, &twenty_four, BPF_ANY);
bpf_map_update_elem(&map_2, &key, &forty_two, BPF_ANY);
return 0;
}
char _license[] SEC("license") = "GPL";

@ -1,10 +1,10 @@
// clang-format off
#include <linux/bpf.h>
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
// clang-format on
char _license[] __attribute__((section("license"), used)) = "GPL";
char _license[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
@ -19,12 +19,17 @@ long set_output(__u64 value) {
}
struct relocated_struct_with_scalars {
#ifndef TARGET
__u8 a;
#endif
__u8 b;
__u8 c;
#ifdef TARGET
__u8 d;
#endif
};
__attribute__((noinline)) int field_global() {
__noinline int field_global() {
struct relocated_struct_with_scalars s = {1, 2, 3};
return set_output(__builtin_preserve_access_index(s.b));
}
@ -32,11 +37,16 @@ __attribute__((noinline)) int field_global() {
SEC("uprobe") int field(void *ctx) { return field_global(); }
struct relocated_struct_with_pointer {
#ifndef TARGET
struct relocated_struct_with_pointer *first;
#endif
struct relocated_struct_with_pointer *second;
#ifdef TARGET
struct relocated_struct_with_pointer *first;
#endif
};
__attribute__((noinline)) int pointer_global() {
__noinline int pointer_global() {
struct relocated_struct_with_pointer s = {
(struct relocated_struct_with_pointer *)42,
(struct relocated_struct_with_pointer *)21,
@ -46,49 +56,86 @@ __attribute__((noinline)) int pointer_global() {
SEC("uprobe") int pointer(void *ctx) { return pointer_global(); }
__attribute__((noinline)) int struct_flavors_global() {
__noinline int struct_flavors_global() {
struct relocated_struct_with_scalars s = {1, 2, 3};
#ifndef TARGET
if (bpf_core_field_exists(s.a)) {
return set_output(__builtin_preserve_access_index(s.a));
#else
if (bpf_core_field_exists(s.d)) {
return set_output(__builtin_preserve_access_index(s.d));
#endif
} else {
return set_output(__builtin_preserve_access_index(s.b));
return set_output(__builtin_preserve_access_index(s.c));
}
}
SEC("uprobe") int struct_flavors(void *ctx) { return struct_flavors_global(); }
enum relocated_enum_unsigned_32 { U32 = 0xAAAAAAAA };
enum relocated_enum_unsigned_32 {
U32_VAL =
#ifndef TARGET
0xAAAAAAAA
#else
0xBBBBBBBB
#endif
};
__attribute__((noinline)) int enum_unsigned_32_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_32, U32));
__noinline int enum_unsigned_32_global() {
return set_output(
bpf_core_enum_value(enum relocated_enum_unsigned_32, U32_VAL));
}
SEC("uprobe") int enum_unsigned_32(void *ctx) {
return enum_unsigned_32_global();
}
enum relocated_enum_signed_32 { S32 = -0x7AAAAAAA };
enum relocated_enum_signed_32 {
S32_VAL =
#ifndef TARGET
-0x7AAAAAAA
#else
-0x7BBBBBBB
#endif
};
__attribute__((noinline)) int enum_signed_32_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_signed_32, S32));
__noinline int enum_signed_32_global() {
return set_output(
bpf_core_enum_value(enum relocated_enum_signed_32, S32_VAL));
}
SEC("uprobe") int enum_signed_32(void *ctx) { return enum_signed_32_global(); }
enum relocated_enum_unsigned_64 { U64 = 0xAAAAAAAABBBBBBBB };
enum relocated_enum_unsigned_64 {
U64_VAL =
#ifndef TARGET
0xAAAAAAAABBBBBBBB
#else
0xCCCCCCCCDDDDDDDD
#endif
};
__attribute__((noinline)) int enum_unsigned_64_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_64, U64));
__noinline int enum_unsigned_64_global() {
return set_output(
bpf_core_enum_value(enum relocated_enum_unsigned_64, U64_VAL));
}
SEC("uprobe") int enum_unsigned_64(void *ctx) {
return enum_unsigned_64_global();
}
enum relocated_enum_signed_64 { u64 = -0xAAAAAAABBBBBBBB };
enum relocated_enum_signed_64 {
S64_VAL =
#ifndef TARGET
-0xAAAAAAABBBBBBBB
#else
-0xCCCCCCCDDDDDDDD
#endif
};
__attribute__((noinline)) int enum_signed_64_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_signed_64, u64));
__noinline int enum_signed_64_global() {
return set_output(
bpf_core_enum_value(enum relocated_enum_signed_64, S64_VAL));
}
SEC("uprobe") int enum_signed_64(void *ctx) { return enum_signed_64_global(); }

@ -1,77 +0,0 @@
// clang-format off
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
// clang-format on
#include <stdlib.h>
long set_output(__u64 value) { exit((int)value); }
struct relocated_struct_with_scalars {
__u8 b;
__u8 c;
__u8 d;
};
__attribute__((noinline)) int field_global() {
struct relocated_struct_with_scalars s = {1, 2, 3};
return set_output(__builtin_preserve_access_index(s.b));
}
struct relocated_struct_with_pointer {
struct relocated_struct_with_pointer *second;
struct relocated_struct_with_pointer *first;
};
__attribute__((noinline)) int pointer_global() {
struct relocated_struct_with_pointer s = {
(struct relocated_struct_with_pointer *)42,
(struct relocated_struct_with_pointer *)21,
};
return set_output((__u64)__builtin_preserve_access_index(s.first));
}
__attribute__((noinline)) int struct_flavors_global() {
struct relocated_struct_with_scalars s = {1, 2, 3};
if (bpf_core_field_exists(s.b)) {
return set_output(__builtin_preserve_access_index(s.b));
} else {
return set_output(__builtin_preserve_access_index(s.c));
}
}
enum relocated_enum_unsigned_32 { U32 = 0xBBBBBBBB };
__attribute__((noinline)) int enum_unsigned_32_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_32, U32));
}
enum relocated_enum_signed_32 { S32 = -0x7BBBBBBB };
__attribute__((noinline)) int enum_signed_32_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_signed_32, S32));
}
enum relocated_enum_unsigned_64 { U64 = 0xCCCCCCCCDDDDDDDD };
__attribute__((noinline)) int enum_unsigned_64_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_64, U64));
}
enum relocated_enum_signed_64 { u64 = -0xCCCCCCCDDDDDDDD };
__attribute__((noinline)) int enum_signed_64_global() {
return set_output(bpf_core_enum_value(enum relocated_enum_signed_64, u64));
}
// Avoids dead code elimination by the compiler.
int main() {
field_global();
pointer_global();
struct_flavors_global();
enum_unsigned_32_global();
enum_signed_32_global();
enum_unsigned_64_global();
enum_signed_64_global();
}

@ -1,28 +1,25 @@
#include <linux/bpf.h>
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
// clang-format on
char _license[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 2);
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 2);
} RESULTS SEC(".maps");
static __u64
inc_cb(void *map, __u32 *key, void *val,
void *data)
{
__u64 *value = val;
*value += 1;
return 0;
static __u64 inc_cb(void *map, __u32 *key, void *val, void *data) {
__u64 *value = val;
*value += 1;
return 0;
}
SEC("uprobe/test_text_64_64_reloc")
int test_text_64_64_reloc(struct pt_regs *ctx)
{
bpf_for_each_map_elem(&RESULTS, inc_cb, NULL, 0);
return 0;
int test_text_64_64_reloc(struct pt_regs *ctx) {
bpf_for_each_map_elem(&RESULTS, inc_cb, NULL, 0);
return 0;
}

@ -4,7 +4,7 @@ use std::{
fs,
io::{BufRead as _, BufReader},
path::PathBuf,
process::{Child, Command, Stdio},
process::{Child, Command, Output, Stdio},
};
use cargo_metadata::{
@ -64,20 +64,14 @@ fn main() {
panic!("unsupported endian={:?}", endian)
};
const C_BPF: &[(&str, &str)] = &[
("ext.bpf.c", "ext.bpf.o"),
("main.bpf.c", "main.bpf.o"),
("multimap-btf.bpf.c", "multimap-btf.bpf.o"),
("reloc.bpf.c", "reloc.bpf.o"),
("text_64_64_reloc.c", "text_64_64_reloc.o"),
const C_BPF: &[(&str, bool)] = &[
("ext.bpf.c", false),
("main.bpf.c", false),
("multimap-btf.bpf.c", false),
("reloc.bpf.c", true),
("text_64_64_reloc.c", false),
];
let c_bpf = C_BPF.iter().map(|(src, dst)| (src, out_dir.join(dst)));
const C_BTF: &[(&str, &str)] = &[("reloc.btf.c", "reloc.btf.o")];
let c_btf = C_BTF.iter().map(|(src, dst)| (src, out_dir.join(dst)));
if build_integration_bpf {
let libbpf_dir = manifest_dir
.parent()
@ -116,65 +110,67 @@ fn main() {
target_arch.push(arch);
};
for (src, dst) in c_bpf {
let src = bpf_dir.join(src);
println!("cargo:rerun-if-changed={}", src.to_str().unwrap());
// NB: libbpf's documentation suggests that vmlinux.h be generated by running `bpftool btf
// dump file /sys/kernel/btf/vmlinux format c`; this allows CO-RE to work.
//
// However in our tests we do not make use of kernel data structures, and so any vmlinux.h
// which defines the constants we need (e.g. `__u8`, `__u64`, `BPF_MAP_TYPE_ARRAY`,
// `BPF_ANY`, `XDP_PASS`, `XDP_DROP`, etc.) will suffice. Since we already have a libbpf
// submodule which happens to include such a file, we use it.
let libbpf_vmlinux_dir = libbpf_dir.join(".github/actions/build-selftests");
exec(
Command::new("clang")
.arg("-I")
.arg(&libbpf_headers_dir)
.args(["-g", "-O2", "-target", target, "-c"])
.arg(&target_arch)
.arg(src)
.arg("-o")
.arg(dst),
)
.unwrap();
}
let clang = || {
let mut cmd = Command::new("clang");
cmd.arg("-nostdlibinc")
.arg("-I")
.arg(&libbpf_headers_dir)
.arg("-I")
.arg(&libbpf_vmlinux_dir)
.args(["-g", "-O2", "-target", target, "-c"])
.arg(&target_arch);
cmd
};
for (src, dst) in c_btf {
for (src, build_btf) in C_BPF {
let dst = out_dir.join(src).with_extension("o");
let src = bpf_dir.join(src);
println!("cargo:rerun-if-changed={}", src.to_str().unwrap());
let mut cmd = Command::new("clang");
cmd.arg("-I")
.arg(&libbpf_headers_dir)
.args(["-g", "-target", target, "-c"])
.arg(&target_arch)
.arg(src)
.args(["-o", "-"]);
let mut child = cmd
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
let Child { stdout, .. } = &mut child;
let stdout = stdout.take().unwrap();
let mut output = OsString::new();
output.push(".BTF=");
output.push(dst);
exec(
// NB: objcopy doesn't support reading from stdin, so we have to use llvm-objcopy.
Command::new("llvm-objcopy")
.arg("--dump-section")
.arg(output)
.arg("-")
.stdin(stdout),
)
.unwrap();
let status = child
.wait()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
match status.code() {
Some(code) => match code {
0 => {}
code => panic!("{cmd:?} exited with status code {code}"),
},
None => panic!("{cmd:?} terminated by signal"),
exec(clang().arg(&src).arg("-o").arg(&dst)).unwrap();
if *build_btf {
let mut cmd = clang();
let mut child = cmd
.arg("-DTARGET")
.arg(&src)
.args(["-o", "-"])
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
let Child { stdout, .. } = &mut child;
let stdout = stdout.take().unwrap();
let dst = dst.with_extension("target.o");
let mut output = OsString::new();
output.push(".BTF=");
output.push(dst);
exec(
// NB: objcopy doesn't support reading from stdin, so we have to use llvm-objcopy.
Command::new("llvm-objcopy")
.arg("--dump-section")
.arg(output)
.arg("-")
.stdin(stdout),
)
.unwrap();
let output = child
.wait_with_output()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
let Output { status, .. } = &output;
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {output:?}");
}
}
@ -257,13 +253,7 @@ fn main() {
let status = child
.wait()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
match status.code() {
Some(code) => match code {
0 => {}
code => panic!("{cmd:?} exited with status code {code}"),
},
None => panic!("{cmd:?} terminated by signal"),
}
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
stderr.join().map_err(std::panic::resume_unwind).unwrap();
@ -273,8 +263,13 @@ fn main() {
.unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
}
} else {
for (_src, dst) in c_bpf.chain(c_btf) {
for (src, build_btf) in C_BPF {
let dst = out_dir.join(src).with_extension("o");
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
if *build_btf {
let dst = dst.with_extension("target.o");
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
}
}
let Package { targets, .. } = integration_ebpf_package;

@ -5,7 +5,8 @@ pub const MAIN: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/main.b
pub const MULTIMAP_BTF: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.o"));
pub const RELOC_BPF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.o"));
pub const RELOC_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.btf.o"));
pub const RELOC_BTF: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o"));
pub const TEXT_64_64_RELOC: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o"));

@ -2,20 +2,20 @@ use test_case::test_case;
use aya::{maps::Array, programs::UProbe, util::KernelVersion, BpfLoader, Btf, Endianness};
#[test_case("field", false, None, 2)]
#[test_case("field", true, None, 1)]
#[test_case("enum_signed_32", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7AAAAAAAi32 as u64)]
#[test_case("enum_signed_32", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7BBBBBBBi32 as u64)]
#[test_case("enum_signed_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xAAAAAAABBBBBBBBi64 as u64)]
#[test_case("enum_signed_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xCCCCCCCDDDDDDDDi64 as u64)]
#[test_case("enum_unsigned_32", false, None, 0xAAAAAAAA)]
#[test_case("enum_unsigned_32", true, None, 0xBBBBBBBB)]
#[test_case("enum_unsigned_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xAAAAAAAABBBBBBBB)]
#[test_case("enum_unsigned_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xCCCCCCCCDDDDDDDD)]
#[test_case("field", false, None, 2)]
#[test_case("field", true, None, 1)]
#[test_case("pointer", false, None, 42)]
#[test_case("pointer", true, None, 21)]
#[test_case("struct_flavors", false, None, 1)]
#[test_case("struct_flavors", true, None, 1)]
#[test_case("enum_signed_32", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7AAAAAAAi32 as u64)]
#[test_case("enum_signed_32", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7BBBBBBBi32 as u64)]
#[test_case("enum_unsigned_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xAAAAAAAABBBBBBBB)]
#[test_case("enum_unsigned_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xCCCCCCCCDDDDDDDD)]
#[test_case("enum_signed_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xAAAAAAABBBBBBBBi64 as u64)]
#[test_case("enum_signed_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xCCCCCCCDDDDDDDDi64 as u64)]
#[test_case("struct_flavors", true, None, 2)]
fn relocation_tests(
program: &str,
with_relocations: bool,

@ -6,10 +6,10 @@ edition = "2021"
publish = false
[dependencies]
anyhow = { workspace = true, default-features = true }
anyhow = { workspace = true, features = ["std"] }
aya-tool = { workspace = true }
cargo_metadata = { workspace = true }
clap = { workspace = true, default-features = true, features = ["derive"] }
clap = { workspace = true, features = ["derive"] }
dialoguer = { workspace = true }
diff = { workspace = true }
indoc = { workspace = true }

@ -1,4 +1,4 @@
use anyhow::{anyhow, Context as _, Result};
use anyhow::{bail, Context as _, Result};
use std::{
fs,
path::{Path, PathBuf},
@ -13,13 +13,10 @@ pub fn exec(cmd: &mut Command) -> Result<()> {
let status = cmd
.status()
.with_context(|| format!("failed to run {cmd:?}"))?;
match status.code() {
Some(code) => match code {
0 => Ok(()),
code => Err(anyhow!("{cmd:?} exited with code {code}")),
},
None => Err(anyhow!("{cmd:?} terminated by signal")),
if status.code() != Some(0) {
bail!("{cmd:?} failed: {status:?}")
}
Ok(())
}
// Create a symlink in the out directory to work around the fact that cargo ignores anything

@ -62,7 +62,7 @@ pub fn public_api(options: Options, metadata: Metadata) -> Result<()> {
});
if !errors.is_empty() {
bail!("public API errors:\n{errors}");
bail!("public API errors:\n{errors}")
}
Ok(())
}

@ -6,7 +6,7 @@ use std::{
process::{Child, Command, Stdio},
};
use anyhow::{bail, Context as _, Result};
use anyhow::{anyhow, bail, Context as _, Result};
use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
use clap::Parser;
use xtask::AYA_BUILD_INTEGRATION_BPF;
@ -77,14 +77,9 @@ pub fn build(opts: BuildOptions) -> Result<Vec<(String, PathBuf)>> {
let status = child
.wait()
.with_context(|| format!("failed to wait for {cmd:?}"))?;
match status.code() {
Some(code) => match code {
0 => {}
code => bail!("{cmd:?} exited with status code {code}"),
},
None => bail!("{cmd:?} terminated by signal"),
if status.code() != Some(0) {
bail!("{cmd:?} failed: {status:?}")
}
Ok(executables)
}
@ -98,7 +93,7 @@ pub fn run(opts: Options) -> Result<()> {
let binaries = build(build_options).context("error while building userspace application")?;
let mut args = runner.trim().split_terminator(' ');
let runner = args.next().ok_or(anyhow::anyhow!("no first argument"))?;
let runner = args.next().ok_or(anyhow!("no first argument"))?;
let args = args.collect::<Vec<_>>();
let mut failures = String::new();
@ -110,24 +105,18 @@ pub fn run(opts: Options) -> Result<()> {
.args(run_args.iter())
.arg("--test-threads=1");
println!("{} running {cmd:?}", name);
println!("{name} running {cmd:?}");
let status = cmd
.status()
.with_context(|| format!("failed to run {cmd:?}"))?;
match status.code() {
Some(code) => match code {
0 => {}
code => writeln!(&mut failures, "{} exited with status code {code}", name)
.context("String write failed")?,
},
None => writeln!(&mut failures, "{} terminated by signal", name)
.context("String write failed")?,
if status.code() != Some(0) {
writeln!(&mut failures, "{name} failed: {status:?}").context("String write failed")?
}
}
if failures.is_empty() {
Ok(())
} else {
Err(anyhow::anyhow!("failures:\n{}", failures))
Err(anyhow!("failures:\n{}", failures))
}
}

Loading…
Cancel
Save