use crate::meta_storage::psql::PsqlClient;
use crate::meta_storage::tests::utils::psql_backend;
use crate::meta_storage::{MetaBackend, MetaConnApi, MetaResolutionApi, MetaTicketApi};
use crate::schema::{
DimensionMetadata, Job, Resolution, TableShape, TaskMetadata, Ticket, TicketStatus,
};
const TICKET_SCHEMA: &str = "operon_rebuild_ticket";
const DIMENSION_SCHEMA: &str = "operon_rebuild_dimension";
const UNRECORDED_SCHEMA: &str = "operon_rebuild_unrecorded";
const SUMMARY_SCHEMA: &str = "operon_rebuild_summary";
fn delta_over_i() -> TaskMetadata<1> {
TaskMetadata {
id: "delta",
dims: ["i"],
spawn_dim: None,
priority: &[],
}
}
fn delta_over_i_j() -> TaskMetadata<2> {
TaskMetadata {
id: "delta",
dims: ["i", "j"],
spawn_dim: None,
priority: &[],
}
}
fn dim_j() -> DimensionMetadata<0> {
DimensionMetadata { id: "j", deps: [] }
}
fn dim_j_over_i() -> DimensionMetadata<1> {
DimensionMetadata {
id: "j",
deps: ["i"],
}
}
async fn drop_schema(client: PsqlClient<'_>, schema: &str) {
let stmt = format!("DROP SCHEMA IF EXISTS {schema} CASCADE;");
let _num_rows = client.execute(&stmt, &[]).await.expect("drop the schema");
}
async fn columns(client: PsqlClient<'_>, schema: &'static str, table: &'static str) -> Vec<String> {
let stmt = "SELECT column_name
FROM information_schema.columns
WHERE table_schema = $1 AND table_name = $2
ORDER BY ordinal_position;";
client
.query(stmt, &[&schema, &table])
.await
.expect("read the columns")
.iter()
.map(|row| row.get(0))
.collect()
}
#[tokio::test]
async fn ticket_table_is_rebuilt_when_a_task_gains_a_dimension() {
let Some(psql) = psql_backend(TICKET_SCHEMA) else {
eprintln!("skipping: POSTGRES_URI is not set, so there is no Postgres to build tables in");
return;
};
let conn = psql.scheduler_conn().await.expect("scheduler conn");
let client = conn.as_client();
drop_schema(client, TICKET_SCHEMA).await;
client.init_schema().await.expect("init_schema");
client.init_ticket_hash().await.expect("init_ticket_hash");
client
.init_ticket_status_type()
.await
.expect("init_ticket_status_type");
client
.init_ticket_summary()
.await
.expect("init_ticket_summary");
let _ = client
.ticket(delta_over_i())
.init()
.await
.expect("narrow init");
client
.ticket(delta_over_i())
.put(Ticket::new(0).with_coordinate::<0>(0))
.await
.expect("narrow put");
client
.ticket(delta_over_i())
.mark_done(Job { coordinate: [0] })
.await
.expect("narrow mark_done");
assert_eq!(
client
.ticket(delta_over_i())
.get_status()
.await
.expect("narrow status"),
(1, 0, 0)
);
assert_eq!(
client
.ticket(delta_over_i_j())
.init()
.await
.expect("wide init"),
TableShape::STALE
);
assert_eq!(
client
.ticket(delta_over_i_j())
.init()
.await
.expect("wide init again"),
TableShape::CURRENT
);
assert_eq!(
columns(client, TICKET_SCHEMA, "ticket_delta").await,
["i", "j", "deps_done", "deps_quota", "status"]
);
assert!(
client
.ticket(delta_over_i_j())
.get_all(TicketStatus::Done)
.await
.expect("wide get_all")
.is_empty()
);
assert_eq!(
client
.ticket(delta_over_i_j())
.get_status()
.await
.expect("wide status"),
(0, 0, 0)
);
client
.ticket(delta_over_i_j())
.put(
Ticket::new(0)
.with_coordinate::<0>(0)
.with_coordinate::<1>(1),
)
.await
.expect("wide put");
assert_eq!(
client
.ticket(delta_over_i_j())
.get_status()
.await
.expect("wide status after put"),
(0, 1, 0)
);
}
#[tokio::test]
async fn resolution_table_is_rebuilt_when_a_dimension_gains_a_dependency() {
let Some(psql) = psql_backend(DIMENSION_SCHEMA) else {
eprintln!("skipping: POSTGRES_URI is not set, so there is no Postgres to build tables in");
return;
};
let conn = psql.scheduler_conn().await.expect("scheduler conn");
let client = conn.as_client();
drop_schema(client, DIMENSION_SCHEMA).await;
client.init_schema().await.expect("init_schema");
client
.init_dimension_hash()
.await
.expect("init_dimension_hash");
let _ = client
.resolution(dim_j())
.init()
.await
.expect("narrow init");
client
.resolution(dim_j())
.put(Resolution {
coordinate: [],
ub: 2,
})
.await
.expect("narrow put");
assert_eq!(
client
.resolution(dim_j_over_i())
.init()
.await
.expect("wide init"),
TableShape::STALE
);
assert_eq!(
client
.resolution(dim_j_over_i())
.init()
.await
.expect("wide init again"),
TableShape::CURRENT
);
assert_eq!(
columns(client, DIMENSION_SCHEMA, "dimension_j").await,
["i", "ub"]
);
assert!(
client
.resolution(dim_j_over_i())
.get([0])
.await
.expect("wide get")
.is_none()
);
client
.resolution(dim_j_over_i())
.put(Resolution {
coordinate: [0],
ub: 3,
})
.await
.expect("wide put");
let resolution = client
.resolution(dim_j_over_i())
.get([0])
.await
.expect("wide get after put")
.expect("a resolution at i = 0");
assert_eq!((resolution.coordinate, resolution.ub), ([0], 3));
}
#[tokio::test]
async fn ticket_summary_is_rebuilt_when_its_shape_changes() {
let Some(psql) = psql_backend(SUMMARY_SCHEMA) else {
eprintln!("skipping: POSTGRES_URI is not set, so there is no Postgres to build tables in");
return;
};
let conn = psql.scheduler_conn().await.expect("scheduler conn");
let client = conn.as_client();
drop_schema(client, SUMMARY_SCHEMA).await;
client.init_schema().await.expect("init_schema");
client.init_ticket_hash().await.expect("init_ticket_hash");
client
.init_ticket_status_type()
.await
.expect("init_ticket_status_type");
client
.init_ticket_summary()
.await
.expect("first init_ticket_summary");
let _ = client
.ticket(delta_over_i())
.init()
.await
.expect("first init");
for coordinate in [0, 1] {
client
.ticket(delta_over_i())
.put(Ticket::new(0).with_coordinate::<0>(coordinate))
.await
.expect("put");
}
client
.ticket(delta_over_i())
.mark_done(Job { coordinate: [0] })
.await
.expect("mark_done");
assert_eq!(
client
.ticket(delta_over_i())
.get_status()
.await
.expect("status before the rebuild"),
(1, 1, 0)
);
let stmt = format!(
"ALTER TABLE {SUMMARY_SCHEMA}.ticket_summary RENAME COLUMN task_id TO job_id;
DELETE FROM {SUMMARY_SCHEMA}._ticket_summary_hash;"
);
client
.batch_execute(&stmt)
.await
.expect("spell the summary as the earlier release did");
client
.init_ticket_summary()
.await
.expect("second init_ticket_summary");
assert_eq!(
columns(client, SUMMARY_SCHEMA, "ticket_summary").await,
["task_id", "waiting", "queued", "done"]
);
assert_eq!(
client
.ticket(delta_over_i())
.init()
.await
.expect("second init"),
TableShape::CURRENT
);
assert_eq!(
client
.ticket(delta_over_i())
.get_status()
.await
.expect("status after the rebuild"),
(1, 1, 0)
);
client
.ticket(delta_over_i())
.mark_done(Job { coordinate: [1] })
.await
.expect("mark_done after the rebuild");
assert_eq!(
client
.ticket(delta_over_i())
.get_status()
.await
.expect("status after the second mark_done"),
(2, 0, 0)
);
}
#[tokio::test]
async fn ticket_table_is_rebuilt_when_its_shape_is_unrecorded() {
let Some(psql) = psql_backend(UNRECORDED_SCHEMA) else {
eprintln!("skipping: POSTGRES_URI is not set, so there is no Postgres to build tables in");
return;
};
let conn = psql.scheduler_conn().await.expect("scheduler conn");
let client = conn.as_client();
drop_schema(client, UNRECORDED_SCHEMA).await;
client.init_schema().await.expect("init_schema");
client.init_ticket_hash().await.expect("init_ticket_hash");
client
.init_ticket_status_type()
.await
.expect("init_ticket_status_type");
client
.init_ticket_summary()
.await
.expect("init_ticket_summary");
let _ = client
.ticket(delta_over_i())
.init()
.await
.expect("recorded init");
client
.ticket(delta_over_i())
.put(Ticket::new(0).with_coordinate::<0>(0))
.await
.expect("recorded put");
let stmt = format!("DELETE FROM {UNRECORDED_SCHEMA}._ticket_hash WHERE id = 'delta';");
let _ = client
.execute(&stmt, &[])
.await
.expect("drop the shape record");
assert_eq!(
client
.ticket(delta_over_i())
.init()
.await
.expect("unrecorded init"),
TableShape::STALE
);
assert!(
client
.ticket(delta_over_i())
.get_all(TicketStatus::Waiting)
.await
.expect("unrecorded get_all")
.is_empty()
);
assert_eq!(
client
.ticket(delta_over_i())
.init()
.await
.expect("unrecorded init again"),
TableShape::CURRENT
);
}