Mayo commited on
Commit
2c81087
·
unverified ·
1 Parent(s): 7c976af

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::{Result, 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 Err(anyhow!("no text translations to render"));
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
+ }