run.rs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. use std::{
  2. fmt::Write as _,
  3. io::BufReader,
  4. path::PathBuf,
  5. process::{Command, Stdio},
  6. };
  7. use anyhow::{Context as _, Result};
  8. use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
  9. use clap::Parser;
  10. use crate::build_ebpf::{build_ebpf, Architecture, BuildEbpfOptions as BuildOptions};
  11. #[derive(Debug, Parser)]
  12. pub struct Options {
  13. /// Set the endianness of the BPF target
  14. #[clap(default_value = "bpfel-unknown-none", long)]
  15. pub bpf_target: Architecture,
  16. /// Build and run the release target
  17. #[clap(long)]
  18. pub release: bool,
  19. /// The command used to wrap your application
  20. #[clap(short, long, default_value = "sudo -E")]
  21. pub runner: String,
  22. /// libbpf directory
  23. #[clap(long, action)]
  24. pub libbpf_dir: PathBuf,
  25. /// Arguments to pass to your application
  26. #[clap(name = "args", last = true)]
  27. pub run_args: Vec<String>,
  28. }
  29. /// Build the project
  30. fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>> {
  31. let mut cmd = Command::new("cargo");
  32. cmd.args([
  33. "build",
  34. "--tests",
  35. "--message-format=json",
  36. "--package=integration-test",
  37. ]);
  38. if release {
  39. cmd.arg("--release");
  40. }
  41. let mut cmd = cmd
  42. .stdout(Stdio::piped())
  43. .spawn()
  44. .with_context(|| format!("failed to spawn {cmd:?}"))?;
  45. let reader = BufReader::new(cmd.stdout.take().unwrap());
  46. let mut executables = Vec::new();
  47. let mut compiler_messages = String::new();
  48. for message in Message::parse_stream(reader) {
  49. #[allow(clippy::collapsible_match)]
  50. match message.context("valid JSON")? {
  51. Message::CompilerArtifact(Artifact {
  52. executable,
  53. target: Target { src_path, .. },
  54. ..
  55. }) => {
  56. if let Some(executable) = executable {
  57. executables.push((src_path.into(), executable.into()));
  58. }
  59. }
  60. Message::CompilerMessage(CompilerMessage { message, .. }) => {
  61. assert_eq!(writeln!(&mut compiler_messages, "{message}"), Ok(()));
  62. }
  63. _ => {}
  64. }
  65. }
  66. let status = cmd
  67. .wait()
  68. .with_context(|| format!("failed to wait for {cmd:?}"))?;
  69. match status.code() {
  70. Some(code) => match code {
  71. 0 => Ok(executables),
  72. code => Err(anyhow::anyhow!(
  73. "{cmd:?} exited with status code {code}:\n{compiler_messages}"
  74. )),
  75. },
  76. None => Err(anyhow::anyhow!("{cmd:?} terminated by signal")),
  77. }
  78. }
  79. /// Build and run the project
  80. pub fn run(opts: Options) -> Result<()> {
  81. let Options {
  82. bpf_target,
  83. release,
  84. runner,
  85. libbpf_dir,
  86. run_args,
  87. } = opts;
  88. // build our ebpf program followed by our application
  89. build_ebpf(BuildOptions {
  90. target: bpf_target,
  91. libbpf_dir,
  92. })
  93. .context("error while building eBPF program")?;
  94. let binaries = build(release).context("error while building userspace application")?;
  95. let mut args = runner.trim().split_terminator(' ');
  96. let runner = args.next().ok_or(anyhow::anyhow!("no first argument"))?;
  97. let args = args.collect::<Vec<_>>();
  98. let mut failures = String::new();
  99. for (src_path, binary) in binaries {
  100. let mut cmd = Command::new(runner);
  101. let cmd = cmd
  102. .args(args.iter())
  103. .arg(binary)
  104. .args(run_args.iter())
  105. .arg("--test-threads=1");
  106. println!("{} running {cmd:?}", src_path.display());
  107. let status = cmd
  108. .status()
  109. .with_context(|| format!("failed to run {cmd:?}"))?;
  110. match status.code() {
  111. Some(code) => match code {
  112. 0 => {}
  113. code => assert_eq!(
  114. writeln!(
  115. &mut failures,
  116. "{} exited with status code {code}",
  117. src_path.display()
  118. ),
  119. Ok(())
  120. ),
  121. },
  122. None => assert_eq!(
  123. writeln!(&mut failures, "{} terminated by signal", src_path.display()),
  124. Ok(())
  125. ),
  126. }
  127. }
  128. if failures.is_empty() {
  129. Ok(())
  130. } else {
  131. Err(anyhow::anyhow!("failures:\n{}", failures))
  132. }
  133. }