#![cfg(windows)]
use runner_manager_platform::service::TaskPrincipal;
use std::ffi::OsString;
use runner_manager_platform::wsl::exec::{CommandRequest, CommandRunner, HostCommandRunner};
use runner_manager_platform::wsl::probe::{WslExecutable, WslInvoker, probe_readiness};
use runner_manager_platform::wsl::task::{
LIFECYCLE_TASK_PREFIX, LifecycleTask, LifecycleTaskControl, LifecycleTaskIdentity,
PRODUCT_MARKER,
};
const FIXTURE_PREFIX: &str = "runner-manager-selftest-wsl-";
const LINUX_BINARY: &str = "/usr/local/bin/runner-manager";
fn fixture_distribution(tag: &str) -> String {
let tag: String = tag
.chars()
.map(|character| {
if character.is_ascii_alphanumeric() {
character.to_ascii_lowercase()
} else {
'-'
}
})
.collect();
format!("{FIXTURE_PREFIX}{tag}")
}
struct TaskFixture {
distribution: String,
identity: LifecycleTaskIdentity,
}
impl TaskFixture {
fn new(tag: &str) -> Self {
let distribution = fixture_distribution(tag);
let identity =
LifecycleTaskIdentity::for_distribution(&distribution).expect("a usable fixture name");
assert!(
identity.name().starts_with(LIFECYCLE_TASK_PREFIX),
"the derived task name must be the product's own shape: {}",
identity.name()
);
assert!(
identity.name().contains("runner-manager-selftest"),
"the derived task name must carry the fixture marker ci.yml greps for, or a \
leaked registration would be invisible to the leak check: {}",
identity.name()
);
assert!(
!task_exists(identity.name()),
"a task named {} already exists; an earlier run leaked it",
identity.name()
);
Self {
distribution,
identity,
}
}
fn identity(&self) -> &LifecycleTaskIdentity {
&self.identity
}
fn task(&self) -> LifecycleTask {
LifecycleTask::new(
self.identity.clone(),
TaskPrincipal::current().expect("an interactive session has a principal"),
&WslExecutable::locate(),
LINUX_BINARY,
)
}
}
impl Drop for TaskFixture {
fn drop(&mut self) {
assert!(
self.distribution.starts_with(FIXTURE_PREFIX),
"refusing to remove a task that is not this file's fixture"
);
schtasks(&["/Delete", "/TN", self.identity.name(), "/F"]);
}
}
fn schtasks(arguments: &[&str]) -> bool {
let request =
CommandRequest::new("schtasks.exe").args(arguments.iter().copied().map(OsString::from));
HostCommandRunner
.run(&request)
.map(|output| output.success())
.unwrap_or(false)
}
fn task_exists(name: &str) -> bool {
schtasks(&["/Query", "/TN", name, "/FO", "CSV", "/NH"])
}
#[test]
#[ignore = "registers a real Windows scheduled task; run explicitly"]
fn the_rendered_task_is_one_task_scheduler_accepts_reads_back_and_removes() {
let fixture = TaskFixture::new("round-trip");
let control = LifecycleTaskControl::new(&HostCommandRunner);
control
.register(&fixture.task())
.expect("Task Scheduler accepts the document this build renders");
assert!(
task_exists(fixture.identity().name()),
"the registration must be visible to `schtasks` itself, not only to the library \
that made it"
);
let registered = control
.query(fixture.identity())
.expect("Task Scheduler answers")
.expect("the task this test just registered");
assert!(
registered.is_product_owned(),
"the exported description must carry {PRODUCT_MARKER}, or `detach` would refuse \
to remove a task this product created"
);
assert!(registered.enabled(), "a freshly registered task is enabled");
assert!(
registered.command().to_lowercase().ends_with("wsl.exe"),
"the action starts wsl.exe and nothing else: {}",
registered.command()
);
for expected in ["--distribution", "--user", "root", "--exec", "wsl-host"] {
assert!(
registered.arguments().contains(expected),
"the argument string Task Scheduler round-tripped is missing {expected:?}: {}",
registered.arguments()
);
}
for shell in ["cmd", "&&", "|", ";", "powershell"] {
assert!(
!registered.arguments().contains(shell),
"no shell text may survive into the registered action, and {shell:?} did: {}",
registered.arguments()
);
}
control
.register(&fixture.task())
.expect("re-registration replaces the task in place");
assert!(task_exists(fixture.identity().name()));
let detached = control
.detach(fixture.identity())
.expect("the product's own task is removed");
assert!(detached.removed);
assert_eq!(detached.name, fixture.identity().name());
assert!(
!task_exists(fixture.identity().name()),
"after `detach` the registration is gone from Task Scheduler itself"
);
let again = control
.detach(fixture.identity())
.expect("a second detach is convergent");
assert!(!again.removed);
}
#[test]
#[ignore = "creates a real Windows scheduled task; run explicitly"]
fn a_task_this_product_did_not_create_is_neither_replaced_nor_removed() {
let fixture = TaskFixture::new("foreign");
let control = LifecycleTaskControl::new(&HostCommandRunner);
assert!(
schtasks(&[
"/Create",
"/TN",
fixture.identity().name(),
"/TR",
"cmd.exe /c exit",
"/SC",
"ONCE",
"/ST",
"23:59",
"/F",
]),
"the arrangement itself must succeed, or this test proves nothing"
);
let registered = control
.query(fixture.identity())
.expect("Task Scheduler answers")
.expect("the hand-made task");
assert!(
!registered.is_product_owned(),
"a task with no {PRODUCT_MARKER} in its description is not this product's"
);
let refused = control
.register(&fixture.task())
.expect_err("registering over a foreign task must refuse");
assert_eq!(refused.kind(), "foreign_task", "{refused}");
let refused = control
.detach(fixture.identity())
.expect_err("detaching a foreign task must refuse");
assert_eq!(refused.kind(), "foreign_task", "{refused}");
assert!(
task_exists(fixture.identity().name()),
"and after both refusals the operator's own task is still there"
);
}
#[test]
#[ignore = "runs the real wsl.exe; run explicitly"]
fn a_distribution_that_is_not_installed_is_refused_before_anything_is_started() {
let distribution = fixture_distribution("absent");
let executable = WslExecutable::locate();
let invoker = WslInvoker::new(&HostCommandRunner, &executable);
let refused = probe_readiness(&invoker, &distribution)
.expect_err("a distribution nobody installed can never be ready");
let message = refused.to_string();
assert!(
message.contains(&distribution) || message.contains("wsl.exe"),
"the refusal must name what was asked for or the program that could not answer: \
{message}"
);
assert!(
!task_exists(
LifecycleTaskIdentity::for_distribution(&distribution)
.expect("a usable fixture name")
.name()
),
"a refused preflight registers nothing"
);
}