use std::path::{Path, PathBuf};
use crate::engine::{EngineKind, detect};
use crate::profile::harness_live::build;
use crate::profile::{AccountKind, HarnessKind, MountAccess, NetworkMode, Profile, ProviderChoice, SafeName, Template};
use crate::provider::{Key, ProviderEntry, ProviderKind, Providers, Tag};
use crate::store::{Session, SessionTab, SessionTabKind, SessionWorkspace, Store, add_profile};
const WORKSPACE: &str = "dayaniklilik";
const PROFILES: [(&str, HarnessKind, &str, &str); 5] = [
("oc-mimo", HarnessKind::OpenCode, "mimo", "mimo-v2.6-flash"),
("oc-kimi", HarnessKind::OpenCode, "kimi", "kimi-for-coding"),
("cc-mimo", HarnessKind::ClaudeCode, "mimo", "mimo-v2.6-flash"),
("cc-kimi", HarnessKind::ClaudeCode, "kimi", "kimi-for-coding"),
("cx-mimo", HarnessKind::Codex, "mimo", "mimo-v2.6-flash"),
];
fn profile(name: &str, harness: HarnessKind, tag: &str, model: &str) -> Profile {
Profile {
name: SafeName::parse(name).expect("the name is safe"),
harness,
template: Template::Recommended,
account: AccountKind::Provider,
provider: Some(ProviderChoice::model(tag, model)),
assets: MountAccess::ReadOnly,
network: NetworkMode::None,
without: Vec::new(),
os: crate::base::Os::Debian,
}
}
fn owners_key(file: &str) -> Key {
let home = std::env::var_os("HOME").expect("a home folder");
let text = std::fs::read_to_string(PathBuf::from(home).join(".config/quvyta").join(file))
.unwrap_or_else(|_| panic!("the owner's {file} is there"));
Key::new(&text).expect("the key reads as one")
}
fn write(path: &Path, text: &str) {
std::fs::create_dir_all(path.parent().expect("a folder")).expect("the folder is made");
std::fs::write(path, text).expect("the file is written");
}
struct Filling {
home: Option<PathBuf>,
}
impl Filling {
fn start(home: &Path) -> Self {
let used = std::fs::read_dir(home).is_ok_and(|mut entries| entries.next().is_some());
assert!(!used, "{} is not empty; the trial fills a home of its own", home.display());
std::fs::create_dir_all(home).expect("the home is made");
Self { home: Some(home.to_owned()) }
}
fn keep(mut self) {
self.home = None;
}
}
impl Drop for Filling {
fn drop(&mut self) {
if let Some(home) = &self.home {
let _ = std::fs::remove_dir_all(home);
}
}
}
fn is_trial_home(home: &Path) -> bool {
home.join("Documents/Quvyta/Code/Workspaces").join(WORKSPACE).is_dir()
&& home.join(".local/share/quvyta/code/providers.toml").is_file()
}
fn clear(home: &Path) {
assert!(is_trial_home(home), "{} is not an endurance trial home; nothing is removed", home.display());
std::fs::remove_dir_all(home).expect("the trial home is removed");
}
#[test]
#[ignore = "fills a home folder for the endurance trial and builds its images; run with QCODE_CONTAINER_TESTS=1 and QCODE_ENDURANCE_HOME"]
fn endurance_home_is_prepared() {
if std::env::var("QCODE_CONTAINER_TESTS").as_deref() != Ok("1") {
return;
}
let home = PathBuf::from(std::env::var("QCODE_ENDURANCE_HOME").expect("QCODE_ENDURANCE_HOME names the home"));
let filling = Filling::start(&home);
let config = home.join(".config");
let data = home.join(".local/share");
let root = home.join("Documents/Quvyta/Code");
write(
&config.join("quvyta/code.conf"),
&format!(
"language = \"en\"\n\n[setup]\ncompleted = true\n\n[engine]\nkind = \"podman\"\n\n[folder]\npath = \"{}\"\n",
root.display()
),
);
write(&config.join("quvyta/quvyta.conf"), "update-notice = false\n");
let store = Store::new(&root);
store.prepare().expect("the store is made");
let today = qframe::date::Date::today_utc();
let file = store.create_workspace("Dayaniklilik", today).expect("the workspace is made");
assert_eq!(file.id.as_str(), WORKSPACE);
let paths = store.workspace_paths(&file.id);
let mut tabs = Vec::new();
for (name, harness, tag, model) in PROFILES {
store.write_profile(&profile(name, harness, tag, model)).expect("the profile is written");
add_profile(&paths, name, today).expect("the workspace carries the profile");
tabs.push(SessionTab {
kind: SessionTabKind::Profile(name.to_owned()),
conversation: None,
opened: 0,
number: None,
name: None,
});
}
std::fs::create_dir_all(paths.code.join("ledger")).expect("the ledger folder");
let mut providers = Providers::in_memory().at(data.join("quvyta/code/providers.toml"));
for (tag, kind, file) in
[("mimo", ProviderKind::MimoTokenPlan, "mimo-key"), ("kimi", ProviderKind::KimiCode, "kimi-key")]
{
let mut entry = ProviderEntry::new(Tag::parse(tag).expect("a tag"), kind, kind.suggested_base());
entry.key = Some(owners_key(file));
providers.add(entry).expect("the provider is added");
}
providers.save().expect("providers.toml is written");
let session = Session {
active: Some(file.id.clone()),
workspaces: vec![SessionWorkspace { id: file.id, active_tab: 0, tabs }],
};
session.save(&data.join("quvyta/code/session.toml")).expect("the session is written");
let engine = detect(EngineKind::Podman).expect("podman answers");
for (name, harness, tag, model) in PROFILES {
let started = std::time::Instant::now();
build(&engine, &profile(name, harness, tag, model));
println!("{name}: image built in {} s", started.elapsed().as_secs());
}
let _ = crate::engine::run::capture(&engine.remove_image("qcode/harnesstest-base"));
filling.keep();
println!("{} is ready; clear it with endurance_home_is_cleared when the trial is over", home.display());
}
#[test]
#[ignore = "removes the home of the endurance trial, keys and all; run with QCODE_CONTAINER_TESTS=1 and QCODE_ENDURANCE_HOME"]
fn endurance_home_is_cleared() {
if std::env::var("QCODE_CONTAINER_TESTS").as_deref() != Ok("1") {
return;
}
clear(Path::new(&std::env::var("QCODE_ENDURANCE_HOME").expect("QCODE_ENDURANCE_HOME names the home")));
}
mod tests {
use super::{Filling, WORKSPACE, clear, is_trial_home};
use std::path::{Path, PathBuf};
fn parent(name: &str) -> PathBuf {
let stamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos();
let dir = std::env::temp_dir().join(format!("qcode-endurance-{name}-{}-{stamp}", std::process::id()));
std::fs::create_dir_all(&dir).expect("a folder");
dir
}
fn fill(home: &Path) {
super::write(&home.join(".local/share/quvyta/code/providers.toml"), "key = \"stand-in\"\n");
std::fs::create_dir_all(home.join("Documents/Quvyta/Code/Workspaces").join(WORKSPACE)).expect("a workspace");
}
#[test]
fn a_preparation_that_stops_halfway_leaves_no_key_behind() {
let dir = parent("halfway");
let home = dir.join("home");
let stopped = std::panic::catch_unwind(|| {
let _filling = Filling::start(&home);
fill(&home);
panic!("a step of the preparation fails");
});
let gone = !home.exists();
let _ = std::fs::remove_dir_all(&dir);
assert!(stopped.is_err());
assert!(gone, "the half-made home and the key in it are gone");
}
#[test]
fn a_finished_home_stays_for_the_trial_and_is_cleared_after_it() {
let dir = parent("finished");
let home = dir.join("home");
let filling = Filling::start(&home);
fill(&home);
filling.keep();
let kept = is_trial_home(&home);
clear(&home);
let gone = !home.exists();
let _ = std::fs::remove_dir_all(&dir);
assert!(kept, "the trial finds its home");
assert!(gone, "and the end of the trial takes it away");
}
#[test]
fn a_folder_in_use_is_neither_filled_nor_cleared() {
let dir = parent("in-use");
std::fs::write(dir.join("notes.txt"), "the person's own").expect("a file of theirs");
let filled = std::panic::catch_unwind(|| Filling::start(&dir).keep());
let cleared = std::panic::catch_unwind(|| clear(&dir));
let kept = dir.join("notes.txt").is_file();
let _ = std::fs::remove_dir_all(&dir);
assert!(filled.is_err() && cleared.is_err());
assert!(kept, "the folder named by mistake is untouched");
}
}