Skip to content

Commit

Permalink
example test
Browse files Browse the repository at this point in the history
  • Loading branch information
SoloJacobs committed Nov 4, 2024
1 parent e14df6f commit 2097d8d
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 69 deletions.
78 changes: 9 additions & 69 deletions .github/workflows/system_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,7 @@ on:
workflow_call: {}

jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: x86_64-pc-windows-gnu
# By default, setup-rust-toolchain sets "-D warnings". As a side effect, the settings in
# .cargo/config.toml are ignored:
# https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
# "There are four mutually exclusive sources of extra flags"
rustflags: ""
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -r tests/minimal_suite/requirements.txt
- uses: actions/download-artifact@v4
with:
name: rcc
path: C:\

# MSVC uses vctip.exe for telemetry. vctip.exe is started as a child of termination.exe. This
# can cause CI failures, if "vctip.exe" does not terminate before `get_children` is called.
# It is unclear why MSVC is running, despite target=x86_64-pc-windows-gnu. The following
# command is intended turn off telemetry via vctip.exe.
- shell: pwsh
run: Get-ChildItem -Filter vctip.exe -Recurse "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC" | rm
- run: cargo test --target=x86_64-pc-windows-gnu --test test_plan_run --test test_agent_plugin -- --ignored
- run: cargo run --example termination --target=x86_64-pc-windows-gnu
- run: cargo run --example termination --target=x86_64-pc-windows-gnu -- C:\windows64\rcc.exe

- run: mkdir C:\managed_robots
- run: tar --create -z --directory tests\minimal_suite\ --file C:\managed_robots\minimal_suite.tar.gz *
- run: net user "test_user" "uCjV*NRE#XH2a" /add
- run: cargo test --target=x86_64-pc-windows-gnu --test test_scheduler -- --nocapture --ignored
env:
TEST_DIR: C:\test_scheduler
RCC_BINARY_PATH: C:\windows64\rcc.exe
MANAGED_ROBOT_ARCHIVE_PATH: C:\managed_robots\minimal_suite.tar.gz
N_SECONDS_RUN_MAX: 300
TEST_USER: test_user
- uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: system_test_debug_information_windows
path: C:\test_scheduler

linux:
test_import_hololib:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -65,33 +17,21 @@ jobs:
# https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
# "There are four mutually exclusive sources of extra flags"
rustflags: ""
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -r tests/minimal_suite/requirements.txt
- uses: actions/download-artifact@v4
with:
name: rcc
path: /tmp/
# file permissions are not retained during upload:
# https://github.com/actions/upload-artifact?tab=readme-ov-file#permission-loss
- run: chmod +x /tmp/linux64/rcc

- run: cargo test --target=x86_64-unknown-linux-gnu --test test_plan_run --test test_agent_plugin -- --ignored
- run: cargo run --example termination --target=x86_64-unknown-linux-gnu
- run: cargo run --example termination --target=x86_64-unknown-linux-gnu -- /tmp/linux64/rcc

- run: mkdir /tmp/managed_robots
- run: tar --create --gzip --directory tests/minimal_suite/ --file /tmp/managed_robots/minimal_suite.tar.gz .
- run: cargo test --target=x86_64-unknown-linux-gnu --test test_scheduler -- --nocapture --ignored
- run: echo "CARGO_MANIFEST_DIR=$(pwd)" >> $GITHUB_ENV
- run: cargo test --target=x86_64-unknown-linux-gnu --test test_ht_import_scheduler -- --nocapture --ignored
env:
TEST_DIR: /tmp/test_scheduler
RCC_BINARY_PATH: /tmp/linux64/rcc
MANAGED_ROBOT_ARCHIVE_PATH: /tmp/managed_robots/minimal_suite.tar.gz
N_SECONDS_RUN_MAX: 300
- uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: system_test_debug_information_linux
path: /tmp/test_scheduler
N_SECONDS_RUN_MAX: 120
- uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: system_test_debug_information_linux
path: /tmp/test_scheduler
1 change: 1 addition & 0 deletions src/bin/scheduler/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn main() -> AnyhowResult<()> {
}

fn run() -> Result<(), Terminate> {
println!("{:?}", std::env::args());
let args = cli::Args::parse();
logging::init(args.log_specification(), args.log_path).context("Logging setup failed.")?;
info!("Program started and logging set up");
Expand Down
254 changes: 254 additions & 0 deletions tests/test_ht_import_scheduler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#![cfg(unix)]
pub mod rcc;
use anyhow::{bail, Result as AnyhowResult};
use assert_cmd::cargo::cargo_bin;
use camino::{Utf8Path, Utf8PathBuf};
use robotmk::config::{
Config, EnvironmentConfig, ExecutionConfig, PlanConfig, PlanMetadata, RCCConfig,
RCCEnvironmentConfig, RCCProfileConfig, RetryStrategy, RobotConfig, SequentialPlanGroup,
SessionConfig, Source, WorkingDirectoryCleanupConfig,
};
use robotmk::section::Host;
use serde_json::to_string;
use std::env::var;
use std::fs::{create_dir_all, remove_file, write};
use std::path::Path;
use std::time::Duration;
use tokio::{
process::Command,
select,
time::{sleep, timeout},
};
use walkdir::WalkDir;

#[tokio::test]
#[ignore]
async fn test_ht_import_scheduler() -> AnyhowResult<()> {
let test_dir = Utf8PathBuf::from(var("TEST_DIR")?);
let rcc_binary = Utf8PathBuf::from(var("RCC_BINARY_PATH")?);
let suite_dir = Utf8PathBuf::from(var("CARGO_MANIFEST_DIR")?)
.join("tests")
.join("minimal_suite");
create_dir_all(&test_dir)?;

let mut rcc_task_script = Command::new(rcc_binary.clone());
rcc_task_script
.arg("task")
.arg("script")
.arg("--robot")
.arg(suite_dir.join("robot.yaml"))
.arg("--")
.arg("true");
rcc_task_script.status().await?;

let mut rcc_ht_export = Command::new(rcc_binary.clone());
rcc_ht_export
.arg("holotree")
.arg("export")
.arg("--robot")
.arg(suite_dir.join("robot.yaml"))
.arg("--zipfile")
.arg(test_dir.join("hololib.zip"));
rcc_ht_export.status().await?;

let mut rcc_cleanup = Command::new(rcc_binary.clone());
rcc_cleanup.arg("configuration").arg("cleanup").arg("--all");
rcc_cleanup.status().await?;

let config = create_config(
&test_dir,
&suite_dir,
RCCConfig {
binary_path: rcc_binary,
profile_config: RCCProfileConfig::Default,
},
);

run_scheduler(
&test_dir,
&config,
var("N_SECONDS_RUN_MAX")?.parse::<u64>()?,
)
.await?;

assert_working_directory(&config.working_directory).await?;
assert_results_directory(&config.results_directory);
Ok(())
}

fn create_config(test_dir: &Utf8Path, suite_dir: &Utf8Path, rcc_config: RCCConfig) -> Config {
Config {
working_directory: test_dir.join("working"),
results_directory: test_dir.join("results"),
managed_directory: test_dir.join("managed_robots"),
rcc_config,
plan_groups: vec![SequentialPlanGroup {
plans: vec![PlanConfig {
id: "rcc_headless".into(),
source: Source::Manual {
base_dir: suite_dir.into(),
},
robot_config: RobotConfig {
robot_target: "tasks.robot".into(),
top_level_suite_name: None,
suites: vec![],
tests: vec![],
test_tags_include: vec![],
test_tags_exclude: vec![],
variables: vec![],
variable_files: vec![],
argument_files: vec![],
exit_on_failure: false,
},
execution_config: ExecutionConfig {
n_attempts_max: 1,
retry_strategy: RetryStrategy::Complete,
timeout: 10,
},
environment_config: EnvironmentConfig::Rcc(RCCEnvironmentConfig {
robot_yaml_path: "robot.yaml".into(),
build_timeout: 1200,
remote_origin: None,
catalog_zip: Some(test_dir.join("hololib.zip")),
}),
session_config: SessionConfig::Current,
working_directory_cleanup_config: WorkingDirectoryCleanupConfig::MaxExecutions(4),
host: Host::Source,
metadata: PlanMetadata {
application: "app".into(),
suite_name: "minimal_suite".into(),
variant: "".into(),
},
}],
execution_interval: 30,
}],
}
}

async fn run_scheduler(
test_dir: &Utf8Path,
config: &Config,
n_seconds_run_max: u64,
) -> AnyhowResult<()> {
let config_path = test_dir.join("config.json");
write(&config_path, to_string(&config)?)?;
let run_flag_path = test_dir.join("run_flag");
write(&run_flag_path, "")?;

let mut robotmk_no_env_cmd = Command::new("sudo");
robotmk_no_env_cmd
.arg("unshare")
.arg("--net")
.arg("--")
.arg(cargo_bin("robotmk_scheduler"))
.arg("-vv")
.arg("--run-flag")
.arg(&run_flag_path)
.arg(config_path);
let mut robotmk_child_proc = robotmk_no_env_cmd.spawn()?;

select! {
_ = await_plan_results(config) => {},
_ = robotmk_child_proc.wait() => {
bail!("Scheduler terminated unexpectedly")
},
_ = sleep(Duration::from_secs(n_seconds_run_max)) => {
if let Err(e) = remove_file(&run_flag_path) {
eprintln!("Removing run file failed: {e}");
}
bail!(format!("No plan result files appeared with {n_seconds_run_max} seconds"))
},
};
remove_file(&run_flag_path)?;
assert!(timeout(Duration::from_secs(3), robotmk_child_proc.wait())
.await
.is_ok());

Ok(())
}

async fn await_plan_results(config: &Config) {
let expected_result_files: Vec<Utf8PathBuf> = config
.plan_groups
.iter()
.flat_map(|plan_group| {
plan_group.plans.iter().map(|plan_config| {
config
.results_directory
.join("plans")
.join(format!("{}.json", &plan_config.id))
})
})
.collect();
loop {
if expected_result_files
.iter()
.all(|expected_result_file| expected_result_file.is_file())
{
break;
}
sleep(Duration::from_secs(5)).await;
}
}

async fn assert_working_directory(working_directory: &Utf8Path) -> AnyhowResult<()> {
assert!(working_directory.is_dir());
assert_eq!(
directory_entries(working_directory, 1),
["environment_building", "plans", "rcc_setup"]
);
assert_eq!(
directory_entries(working_directory.join("environment_building"), 2),
[
"current_user",
"current_user/rcc_headless.stderr",
"current_user/rcc_headless.stdout",
]
);
assert_eq!(
directory_entries(working_directory.join("plans"), 1),
["rcc_headless"]
);

let entries_rcc_headless =
directory_entries(working_directory.join("plans").join("rcc_headless"), 2).join("");
assert!(entries_rcc_headless.contains("rebot.xml"));
assert!(!entries_rcc_headless.contains("1.bat"));

Ok(())
}

fn assert_results_directory(results_directory: &Utf8Path) {
assert!(results_directory.is_dir());
assert_eq!(
directory_entries(results_directory, 2),
[
"environment_build_states.json",
"plans",
"plans/rcc_headless.json",
"scheduler_phase.json",
"setup_failures.json"
]
);
}

fn directory_entries(directory: impl AsRef<Path>, max_depth: usize) -> Vec<String> {
WalkDir::new(&directory)
.max_depth(max_depth)
.sort_by_file_name()
.into_iter()
.map(|dir_entry_result| {
dir_entry_result
.unwrap()
.path()
.strip_prefix(&directory)
.unwrap()
.to_str()
.unwrap()
.into()
})
.filter(|entry: &String| !entry.is_empty())
// align unix and windows
.map(|s| s.replace("\\", "/"))
.collect()
}

0 comments on commit 2097d8d

Please sign in to comment.