Mayo commited on
fix: pipeline stop when no text to render
Browse files
koharu-app/src/pipeline/engines/renderer.rs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
| 7 |
//!
|
| 8 |
//! Requires an `Image { role: Inpainted }` node on the page.
|
| 9 |
|
| 10 |
-
use anyhow::
|
| 11 |
use async_trait::async_trait;
|
| 12 |
use koharu_core::{
|
| 13 |
ImageRole, MaskRole, NodeDataPatch, NodePatch, Op, TextDataPatch, TextStyle, Transform,
|
|
@@ -69,7 +69,7 @@ impl Engine for Model {
|
|
| 69 |
.collect();
|
| 70 |
|
| 71 |
if inputs.is_empty() {
|
| 72 |
-
return
|
| 73 |
}
|
| 74 |
|
| 75 |
let page_opts = PageRenderOptions {
|
|
|
|
| 7 |
//!
|
| 8 |
//! Requires an `Image { role: Inpainted }` node on the page.
|
| 9 |
|
| 10 |
+
use anyhow::Result;
|
| 11 |
use async_trait::async_trait;
|
| 12 |
use koharu_core::{
|
| 13 |
ImageRole, MaskRole, NodeDataPatch, NodePatch, Op, TextDataPatch, TextStyle, Transform,
|
|
|
|
| 69 |
.collect();
|
| 70 |
|
| 71 |
if inputs.is_empty() {
|
| 72 |
+
return Ok(Vec::new());
|
| 73 |
}
|
| 74 |
|
| 75 |
let page_opts = PageRenderOptions {
|
tests/integration-tests/tests/pipelines.rs
CHANGED
|
@@ -3,7 +3,10 @@
|
|
| 3 |
|
| 4 |
use koharu_client::apis::default_api as api;
|
| 5 |
use koharu_client::models;
|
|
|
|
| 6 |
use koharu_integration_tests::TestApp;
|
|
|
|
|
|
|
| 7 |
|
| 8 |
fn empty_pipeline_request(steps: Vec<String>) -> models::StartPipelineRequest {
|
| 9 |
models::StartPipelineRequest {
|
|
@@ -16,6 +19,53 @@ fn empty_pipeline_request(steps: Vec<String>) -> models::StartPipelineRequest {
|
|
| 16 |
}
|
| 17 |
}
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
#[tokio::test]
|
| 20 |
async fn pipeline_without_project_errors() -> anyhow::Result<()> {
|
| 21 |
let app = TestApp::spawn().await?;
|
|
@@ -71,3 +121,48 @@ async fn cancel_operation_via_download_id_is_204() -> anyhow::Result<()> {
|
|
| 71 |
api::cancel_operation(&app.client_config, "anything").await?;
|
| 72 |
Ok(())
|
| 73 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
use koharu_client::apis::default_api as api;
|
| 5 |
use koharu_client::models;
|
| 6 |
+
use koharu_core::{ImageRole, JobStatus, NodeKind, PageId};
|
| 7 |
use koharu_integration_tests::TestApp;
|
| 8 |
+
use reqwest::multipart::{Form, Part};
|
| 9 |
+
use tokio::time::{Duration, Instant, sleep};
|
| 10 |
|
| 11 |
fn empty_pipeline_request(steps: Vec<String>) -> models::StartPipelineRequest {
|
| 12 |
models::StartPipelineRequest {
|
|
|
|
| 19 |
}
|
| 20 |
}
|
| 21 |
|
| 22 |
+
async fn import_pages(app: &TestApp, files: Vec<(&str, Vec<u8>)>) -> anyhow::Result<Vec<String>> {
|
| 23 |
+
let mut form = Form::new();
|
| 24 |
+
for (name, bytes) in files {
|
| 25 |
+
form = form.part(
|
| 26 |
+
"file",
|
| 27 |
+
Part::bytes(bytes)
|
| 28 |
+
.file_name(name.to_string())
|
| 29 |
+
.mime_str("image/png")?,
|
| 30 |
+
);
|
| 31 |
+
}
|
| 32 |
+
let resp = app
|
| 33 |
+
.client_config
|
| 34 |
+
.client
|
| 35 |
+
.post(format!("{}/pages", app.base_url))
|
| 36 |
+
.multipart(form)
|
| 37 |
+
.send()
|
| 38 |
+
.await?
|
| 39 |
+
.error_for_status()?;
|
| 40 |
+
let body: serde_json::Value = resp.json().await?;
|
| 41 |
+
Ok(body["pages"]
|
| 42 |
+
.as_array()
|
| 43 |
+
.expect("pages array")
|
| 44 |
+
.iter()
|
| 45 |
+
.map(|id| id.as_str().expect("uuid string").to_string())
|
| 46 |
+
.collect())
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
async fn wait_for_job(
|
| 50 |
+
app: &TestApp,
|
| 51 |
+
operation_id: &str,
|
| 52 |
+
) -> anyhow::Result<koharu_core::JobSummary> {
|
| 53 |
+
let deadline = Instant::now() + Duration::from_secs(10);
|
| 54 |
+
loop {
|
| 55 |
+
if let Some(job) = app.app.jobs.get(operation_id) {
|
| 56 |
+
let snapshot = job.value().clone();
|
| 57 |
+
if snapshot.status != JobStatus::Running {
|
| 58 |
+
return Ok(snapshot);
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
anyhow::ensure!(
|
| 62 |
+
Instant::now() < deadline,
|
| 63 |
+
"timed out waiting for job {operation_id}"
|
| 64 |
+
);
|
| 65 |
+
sleep(Duration::from_millis(50)).await;
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
#[tokio::test]
|
| 70 |
async fn pipeline_without_project_errors() -> anyhow::Result<()> {
|
| 71 |
let app = TestApp::spawn().await?;
|
|
|
|
| 121 |
api::cancel_operation(&app.client_config, "anything").await?;
|
| 122 |
Ok(())
|
| 123 |
}
|
| 124 |
+
|
| 125 |
+
#[tokio::test]
|
| 126 |
+
async fn renderer_pipeline_noops_on_pages_without_text_blocks() -> anyhow::Result<()> {
|
| 127 |
+
let app = TestApp::spawn().await?;
|
| 128 |
+
app.open_fresh_project("render-noop").await?;
|
| 129 |
+
|
| 130 |
+
let png = TestApp::tiny_png(24, 24, [255, 255, 255, 255]);
|
| 131 |
+
let page_ids = import_pages(&app, vec![("a.png", png.clone()), ("b.png", png)]).await?;
|
| 132 |
+
let pages = page_ids
|
| 133 |
+
.iter()
|
| 134 |
+
.map(|id| id.parse::<uuid::Uuid>())
|
| 135 |
+
.collect::<Result<Vec<_>, _>>()?;
|
| 136 |
+
|
| 137 |
+
let resp = api::start_pipeline(
|
| 138 |
+
&app.client_config,
|
| 139 |
+
models::StartPipelineRequest {
|
| 140 |
+
steps: vec!["koharu-renderer".into()],
|
| 141 |
+
pages: Some(Some(pages)),
|
| 142 |
+
region: None,
|
| 143 |
+
target_language: None,
|
| 144 |
+
system_prompt: None,
|
| 145 |
+
default_font: None,
|
| 146 |
+
},
|
| 147 |
+
)
|
| 148 |
+
.await?;
|
| 149 |
+
|
| 150 |
+
let job = wait_for_job(&app, &resp.operation_id).await?;
|
| 151 |
+
assert_eq!(job.status, JobStatus::Completed);
|
| 152 |
+
assert_eq!(job.error, None);
|
| 153 |
+
|
| 154 |
+
let session = app.app.current_session().expect("session");
|
| 155 |
+
let scene = session.scene.read();
|
| 156 |
+
for page_id in page_ids {
|
| 157 |
+
let page_id = page_id.parse::<uuid::Uuid>().map(PageId)?;
|
| 158 |
+
let page = scene.page(page_id).expect("page exists");
|
| 159 |
+
let rendered = page
|
| 160 |
+
.nodes
|
| 161 |
+
.values()
|
| 162 |
+
.filter(|node| matches!(&node.kind, NodeKind::Image(img) if img.role == ImageRole::Rendered))
|
| 163 |
+
.count();
|
| 164 |
+
assert_eq!(rendered, 0, "renderer should no-op on textless page");
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
Ok(())
|
| 168 |
+
}
|