documenting action spec

This commit is contained in:
2026-02-09 00:30:48 -06:00
parent a74e13fa0b
commit 588b319fec
38 changed files with 3050 additions and 286 deletions

View File

@@ -217,6 +217,8 @@ mod tests {
is_workflow: false,
workflow_def: None,
is_adhoc: false,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
created: chrono::Utc::now(),
updated: chrono::Utc::now(),
};
@@ -249,6 +251,8 @@ mod tests {
is_workflow: false,
workflow_def: None,
is_adhoc: false,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
created: chrono::Utc::now(),
updated: chrono::Utc::now(),
};

View File

@@ -13,7 +13,6 @@ path = "src/main.rs"
[dependencies]
# Internal dependencies
attune-common = { path = "../common" }
attune-worker = { path = "../worker" }
# Async runtime
tokio = { workspace = true }

View File

@@ -663,7 +663,7 @@ async fn handle_test(
detailed: bool,
output_format: OutputFormat,
) -> Result<()> {
use attune_worker::{TestConfig, TestExecutor};
use attune_common::test_executor::{TestConfig, TestExecutor};
use std::path::{Path, PathBuf};
// Determine if pack is a path or a pack name

View File

@@ -96,7 +96,10 @@ pub mod enums {
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)?)
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
self.to_string(),
buf,
)?)
}
}
@@ -159,7 +162,80 @@ pub mod enums {
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)?)
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
self.to_string(),
buf,
)?)
}
}
/// Format for action output parsing
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum OutputFormat {
/// Plain text (no parsing)
Text,
/// Parse as JSON
Json,
/// Parse as YAML
Yaml,
/// Parse as JSON Lines (each line is a separate JSON object/value)
Jsonl,
}
impl Default for OutputFormat {
fn default() -> Self {
Self::Text
}
}
impl fmt::Display for OutputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Text => write!(f, "text"),
Self::Json => write!(f, "json"),
Self::Yaml => write!(f, "yaml"),
Self::Jsonl => write!(f, "jsonl"),
}
}
}
impl FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"text" => Ok(Self::Text),
"json" => Ok(Self::Json),
"yaml" => Ok(Self::Yaml),
"jsonl" => Ok(Self::Jsonl),
_ => Err(format!("Invalid output format: {}", s)),
}
}
}
impl sqlx::Type<sqlx::Postgres> for OutputFormat {
fn type_info() -> sqlx::postgres::PgTypeInfo {
<String as sqlx::Type<sqlx::Postgres>>::type_info()
}
}
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for OutputFormat {
fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
let s = <String as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
s.parse().map_err(|e: String| e.into())
}
}
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for OutputFormat {
fn encode_by_ref(
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
self.to_string(),
buf,
)?)
}
}
@@ -438,6 +514,8 @@ pub mod action {
pub parameter_delivery: ParameterDelivery,
#[sqlx(default)]
pub parameter_format: ParameterFormat,
#[sqlx(default)]
pub output_format: OutputFormat,
pub created: DateTime<Utc>,
pub updated: DateTime<Utc>,
}
@@ -644,7 +722,7 @@ pub mod execution {
/// Provides direct access to workflow orchestration state without JOINs.
/// The `workflow_execution` field within this metadata is separate from
/// the `parent` field above, as they serve different query patterns.
#[sqlx(json)]
#[sqlx(json, default)]
pub workflow_task: Option<WorkflowTaskMetadata>,
pub created: DateTime<Utc>,

View File

@@ -106,10 +106,7 @@ impl ActionExecutor {
let is_success = result.is_success();
debug!(
"Execution {} result: exit_code={}, error={:?}, is_success={}",
execution_id,
result.exit_code,
result.error,
is_success
execution_id, result.exit_code, result.error, is_success
);
if is_success {
@@ -232,7 +229,11 @@ impl ActionExecutor {
info!("No execution config present");
}
info!("Extracted {} parameters: {:?}", parameters.len(), parameters);
info!(
"Extracted {} parameters: {:?}",
parameters.len(),
parameters
);
// Prepare standard environment variables
let mut env = HashMap::new();
@@ -383,6 +384,7 @@ impl ActionExecutor {
max_stderr_bytes: self.max_stderr_bytes,
parameter_delivery: action.parameter_delivery,
parameter_format: action.parameter_format,
output_format: action.output_format,
};
Ok(context)
@@ -538,7 +540,8 @@ impl ActionExecutor {
if stderr_path.exists() {
if let Ok(contents) = tokio::fs::read_to_string(&stderr_path).await {
if !contents.trim().is_empty() {
result_data["stderr_log"] = serde_json::json!(stderr_path.to_string_lossy());
result_data["stderr_log"] =
serde_json::json!(stderr_path.to_string_lossy());
}
}
}

View File

@@ -6,7 +6,9 @@
use super::native::NativeRuntime;
use super::python::PythonRuntime;
use super::shell::ShellRuntime;
use super::{ExecutionContext, ExecutionResult, Runtime, RuntimeError, RuntimeResult};
use super::{
ExecutionContext, ExecutionResult, OutputFormat, Runtime, RuntimeError, RuntimeResult,
};
use async_trait::async_trait;
use tracing::{debug, info};
@@ -123,6 +125,7 @@ impl Runtime for LocalRuntime {
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::{ParameterDelivery, ParameterFormat};
use std::collections::HashMap;
#[tokio::test]
@@ -149,6 +152,9 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: ParameterDelivery::default(),
parameter_format: ParameterFormat::default(),
output_format: OutputFormat::default(),
};
assert!(runtime.can_execute(&context));
@@ -168,12 +174,15 @@ def run():
secrets: HashMap::new(),
timeout: Some(10),
working_dir: None,
entry_point: "shell".to_string(),
code: Some("echo 'hello from shell'".to_string()),
entry_point: "run.sh".to_string(),
code: Some("#!/bin/bash\necho 'hello from shell'".to_string()),
code_path: None,
runtime_name: Some("shell".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: ParameterDelivery::default(),
parameter_format: ParameterFormat::default(),
output_format: OutputFormat::default(),
};
assert!(runtime.can_execute(&context));
@@ -194,12 +203,15 @@ def run():
secrets: HashMap::new(),
timeout: Some(10),
working_dir: None,
entry_point: "unknown".to_string(),
entry_point: "run".to_string(),
code: Some("some code".to_string()),
code_path: None,
runtime_name: None,
runtime_name: Some("unknown".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: ParameterDelivery::default(),
parameter_format: ParameterFormat::default(),
output_format: OutputFormat::default(),
};
assert!(!runtime.can_execute(&context));

View File

@@ -19,7 +19,6 @@ pub use python::PythonRuntime;
pub use shell::ShellRuntime;
use async_trait::async_trait;
use attune_common::models::{ParameterDelivery, ParameterFormat};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
@@ -34,6 +33,9 @@ pub use log_writer::{BoundedLogResult, BoundedLogWriter};
pub use parameter_passing::{ParameterDeliveryConfig, PreparedParameters};
pub use python_venv::PythonVenvManager;
// Re-export parameter types from common
pub use attune_common::models::{OutputFormat, ParameterDelivery, ParameterFormat};
/// Runtime execution result
pub type RuntimeResult<T> = std::result::Result<T, RuntimeError>;
@@ -119,6 +121,10 @@ pub struct ExecutionContext {
/// Format for parameter serialization
#[serde(default)]
pub parameter_format: ParameterFormat,
/// Format for output parsing
#[serde(default)]
pub output_format: OutputFormat,
}
fn default_max_log_bytes() -> usize {
@@ -146,6 +152,7 @@ impl ExecutionContext {
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: ParameterDelivery::default(),
parameter_format: ParameterFormat::default(),
output_format: OutputFormat::default(),
}
}
}

View File

@@ -46,20 +46,14 @@ fn format_dotenv(parameters: &HashMap<String, JsonValue>) -> Result<String, Runt
/// Format parameters as JSON
fn format_json(parameters: &HashMap<String, JsonValue>) -> Result<String, RuntimeError> {
serde_json::to_string_pretty(parameters).map_err(|e| {
RuntimeError::ExecutionFailed(format!(
"Failed to serialize parameters to JSON: {}",
e
))
RuntimeError::ExecutionFailed(format!("Failed to serialize parameters to JSON: {}", e))
})
}
/// Format parameters as YAML
fn format_yaml(parameters: &HashMap<String, JsonValue>) -> Result<String, RuntimeError> {
serde_yaml_ng::to_string(parameters).map_err(|e| {
RuntimeError::ExecutionFailed(format!(
"Failed to serialize parameters to YAML: {}",
e
))
RuntimeError::ExecutionFailed(format!("Failed to serialize parameters to YAML: {}", e))
})
}
@@ -81,18 +75,21 @@ pub fn create_parameter_file(
) -> Result<NamedTempFile, RuntimeError> {
let formatted = format_parameters(parameters, format)?;
let mut temp_file = NamedTempFile::new()
.map_err(|e| RuntimeError::IoError(e))?;
let mut temp_file = NamedTempFile::new().map_err(|e| RuntimeError::IoError(e))?;
// Set restrictive permissions (owner read-only)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = temp_file.as_file().metadata()
let mut perms = temp_file
.as_file()
.metadata()
.map_err(|e| RuntimeError::IoError(e))?
.permissions();
perms.set_mode(0o400); // Read-only for owner
temp_file.as_file().set_permissions(perms)
temp_file
.as_file()
.set_permissions(perms)
.map_err(|e| RuntimeError::IoError(e))?;
}
@@ -100,9 +97,7 @@ pub fn create_parameter_file(
.write_all(formatted.as_bytes())
.map_err(|e| RuntimeError::IoError(e))?;
temp_file
.flush()
.map_err(|e| RuntimeError::IoError(e))?;
temp_file.flush().map_err(|e| RuntimeError::IoError(e))?;
debug!(
"Created parameter file at {:?} with format {:?}",
@@ -165,10 +160,7 @@ pub fn prepare_parameters(
let formatted = format_parameters(parameters, config.format)?;
// Add environment variables to indicate delivery method
env.insert(
"ATTUNE_PARAMETER_DELIVERY".to_string(),
"stdin".to_string(),
);
env.insert("ATTUNE_PARAMETER_DELIVERY".to_string(), "stdin".to_string());
env.insert(
"ATTUNE_PARAMETER_FORMAT".to_string(),
config.format.to_string(),
@@ -182,10 +174,7 @@ pub fn prepare_parameters(
let path = temp_file.path().to_path_buf();
// Add environment variables to indicate delivery method and file location
env.insert(
"ATTUNE_PARAMETER_DELIVERY".to_string(),
"file".to_string(),
);
env.insert("ATTUNE_PARAMETER_DELIVERY".to_string(), "file".to_string());
env.insert(
"ATTUNE_PARAMETER_FORMAT".to_string(),
config.format.to_string(),
@@ -256,7 +245,6 @@ mod tests {
assert!(result.contains("42"));
}
#[test]
#[test]
fn test_create_parameter_file() {
let mut params = HashMap::new();

View File

@@ -4,7 +4,7 @@
use super::{
BoundedLogWriter, DependencyManagerRegistry, DependencySpec, ExecutionContext, ExecutionResult,
Runtime, RuntimeError, RuntimeResult,
OutputFormat, Runtime, RuntimeError, RuntimeResult,
};
use async_trait::async_trait;
use std::path::PathBuf;
@@ -214,6 +214,7 @@ if __name__ == '__main__':
timeout_secs: Option<u64>,
max_stdout_bytes: usize,
max_stderr_bytes: usize,
output_format: OutputFormat,
) -> RuntimeResult<ExecutionResult> {
let start = Instant::now();
@@ -330,13 +331,41 @@ if __name__ == '__main__':
exit_code, duration_ms, stdout_result.truncated, stderr_result.truncated
);
// Try to parse result from stdout
let result = if exit_code == 0 {
stdout_result
.content
.lines()
.last()
.and_then(|line| serde_json::from_str(line).ok())
// Parse result from stdout based on output_format
let result = if exit_code == 0 && !stdout_result.content.trim().is_empty() {
match output_format {
OutputFormat::Text => {
// No parsing - text output is captured in stdout field
None
}
OutputFormat::Json => {
// Try to parse last line of stdout as JSON
stdout_result
.content
.trim()
.lines()
.last()
.and_then(|line| serde_json::from_str(line).ok())
}
OutputFormat::Yaml => {
// Try to parse stdout as YAML
serde_yaml_ng::from_str(stdout_result.content.trim()).ok()
}
OutputFormat::Jsonl => {
// Parse each line as JSON and collect into array
let mut items = Vec::new();
for line in stdout_result.content.trim().lines() {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(line) {
items.push(value);
}
}
if items.is_empty() {
None
} else {
Some(serde_json::Value::Array(items))
}
}
}
} else {
None
};
@@ -368,6 +397,7 @@ if __name__ == '__main__':
python_path: PathBuf,
max_stdout_bytes: usize,
max_stderr_bytes: usize,
output_format: OutputFormat,
) -> RuntimeResult<ExecutionResult> {
debug!(
"Executing Python script with {} secrets (passed via stdin)",
@@ -389,6 +419,7 @@ if __name__ == '__main__':
timeout_secs,
max_stdout_bytes,
max_stderr_bytes,
output_format,
)
.await
}
@@ -403,6 +434,7 @@ if __name__ == '__main__':
python_path: PathBuf,
max_stdout_bytes: usize,
max_stderr_bytes: usize,
output_format: OutputFormat,
) -> RuntimeResult<ExecutionResult> {
debug!(
"Executing Python file: {:?} with {} secrets",
@@ -425,6 +457,7 @@ if __name__ == '__main__':
timeout_secs,
max_stdout_bytes,
max_stderr_bytes,
output_format,
)
.await
}
@@ -515,6 +548,7 @@ impl Runtime for PythonRuntime {
python_path,
context.max_stdout_bytes,
context.max_stderr_bytes,
context.output_format,
)
.await;
}
@@ -529,6 +563,7 @@ impl Runtime for PythonRuntime {
python_path,
context.max_stdout_bytes,
context.max_stderr_bytes,
context.output_format,
)
.await
}
@@ -625,6 +660,9 @@ def run(x, y):
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -658,6 +696,9 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -691,6 +732,9 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -736,6 +780,9 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();

View File

@@ -4,7 +4,8 @@
use super::{
parameter_passing::{self, ParameterDeliveryConfig},
BoundedLogWriter, ExecutionContext, ExecutionResult, Runtime, RuntimeError, RuntimeResult,
BoundedLogWriter, ExecutionContext, ExecutionResult, OutputFormat, Runtime, RuntimeError,
RuntimeResult,
};
use async_trait::async_trait;
use std::path::PathBuf;
@@ -58,6 +59,7 @@ impl ShellRuntime {
timeout_secs: Option<u64>,
max_stdout_bytes: usize,
max_stderr_bytes: usize,
output_format: OutputFormat,
) -> RuntimeResult<ExecutionResult> {
let start = Instant::now();
@@ -198,14 +200,41 @@ impl ShellRuntime {
exit_code, duration_ms, stdout_result.truncated, stderr_result.truncated
);
// Try to parse result from stdout as JSON
// Parse result from stdout based on output_format
let result = if exit_code == 0 && !stdout_result.content.trim().is_empty() {
stdout_result
.content
.trim()
.lines()
.last()
.and_then(|line| serde_json::from_str(line).ok())
match output_format {
OutputFormat::Text => {
// No parsing - text output is captured in stdout field
None
}
OutputFormat::Json => {
// Try to parse last line of stdout as JSON
stdout_result
.content
.trim()
.lines()
.last()
.and_then(|line| serde_json::from_str(line).ok())
}
OutputFormat::Yaml => {
// Try to parse stdout as YAML
serde_yaml_ng::from_str(stdout_result.content.trim()).ok()
}
OutputFormat::Jsonl => {
// Parse each line as JSON and collect into array
let mut items = Vec::new();
for line in stdout_result.content.trim().lines() {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(line) {
items.push(value);
}
}
if items.is_empty() {
None
} else {
Some(serde_json::Value::Array(items))
}
}
}
} else {
None
};
@@ -338,6 +367,7 @@ impl ShellRuntime {
timeout_secs: Option<u64>,
max_stdout_bytes: usize,
max_stderr_bytes: usize,
output_format: OutputFormat,
) -> RuntimeResult<ExecutionResult> {
debug!(
"Executing shell script with {} secrets (passed via stdin)",
@@ -360,6 +390,7 @@ impl ShellRuntime {
timeout_secs,
max_stdout_bytes,
max_stderr_bytes,
output_format,
)
.await
}
@@ -374,6 +405,7 @@ impl ShellRuntime {
timeout_secs: Option<u64>,
max_stdout_bytes: usize,
max_stderr_bytes: usize,
output_format: OutputFormat,
) -> RuntimeResult<ExecutionResult> {
debug!(
"Executing shell file: {:?} with {} secrets",
@@ -397,6 +429,7 @@ impl ShellRuntime {
timeout_secs,
max_stdout_bytes,
max_stderr_bytes,
output_format,
)
.await
}
@@ -448,11 +481,8 @@ impl Runtime for ShellRuntime {
format: context.parameter_format,
};
let prepared_params = parameter_passing::prepare_parameters(
&context.parameters,
&mut env,
config,
)?;
let prepared_params =
parameter_passing::prepare_parameters(&context.parameters, &mut env, config)?;
// Get stdin content if parameters are delivered via stdin
let parameters_stdin = prepared_params.stdin_content();
@@ -478,6 +508,7 @@ impl Runtime for ShellRuntime {
context.timeout,
context.max_stdout_bytes,
context.max_stderr_bytes,
context.output_format,
)
.await;
}
@@ -492,6 +523,7 @@ impl Runtime for ShellRuntime {
context.timeout,
context.max_stdout_bytes,
context.max_stderr_bytes,
context.output_format,
)
.await
}
@@ -577,6 +609,7 @@ mod tests {
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -609,6 +642,7 @@ mod tests {
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -636,6 +670,7 @@ mod tests {
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -665,6 +700,7 @@ mod tests {
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -709,6 +745,7 @@ echo "missing=$missing"
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -720,4 +757,58 @@ echo "missing=$missing"
assert!(result.stdout.contains("db_pass=super_secret_pass"));
assert!(result.stdout.contains("missing="));
}
#[tokio::test]
async fn test_shell_runtime_jsonl_output() {
let runtime = ShellRuntime::new();
let context = ExecutionContext {
execution_id: 6,
action_ref: "test.jsonl".to_string(),
parameters: HashMap::new(),
env: HashMap::new(),
secrets: HashMap::new(),
timeout: Some(10),
working_dir: None,
entry_point: "shell".to_string(),
code: Some(
r#"
echo '{"id": 1, "name": "Alice"}'
echo '{"id": 2, "name": "Bob"}'
echo '{"id": 3, "name": "Charlie"}'
"#
.to_string(),
),
code_path: None,
runtime_name: Some("shell".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_common::models::ParameterDelivery::default(),
parameter_format: attune_common::models::ParameterFormat::default(),
output_format: attune_common::models::OutputFormat::Jsonl,
};
let result = runtime.execute(context).await.unwrap();
assert!(result.is_success());
assert_eq!(result.exit_code, 0);
// Verify result is parsed as an array of JSON objects
let parsed_result = result.result.expect("Should have parsed result");
assert!(parsed_result.is_array());
let items = parsed_result.as_array().unwrap();
assert_eq!(items.len(), 3);
// Verify first item
assert_eq!(items[0]["id"], 1);
assert_eq!(items[0]["name"], "Alice");
// Verify second item
assert_eq!(items[1]["id"], 2);
assert_eq!(items[1]["name"], "Bob");
// Verify third item
assert_eq!(items[2]["id"], 3);
assert_eq!(items[2]["name"], "Charlie");
}
}

View File

@@ -32,6 +32,8 @@ for i in range(100):
runtime_name: Some("python".to_string()),
max_stdout_bytes: 500, // Small limit to trigger truncation
max_stderr_bytes: 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -70,6 +72,8 @@ for i in range(100):
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 300, // Small limit for stderr
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -109,6 +113,8 @@ done
runtime_name: Some("shell".to_string()),
max_stdout_bytes: 400, // Small limit
max_stderr_bytes: 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -144,6 +150,8 @@ print("Hello, World!")
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024, // Large limit
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -184,6 +192,8 @@ for i in range(50):
runtime_name: Some("python".to_string()),
max_stdout_bytes: 300, // Both limits are small
max_stderr_bytes: 300,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -226,6 +236,8 @@ time.sleep(30) # Will timeout before this
runtime_name: Some("python".to_string()),
max_stdout_bytes: 500,
max_stderr_bytes: 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -262,6 +274,8 @@ sys.stdout.write("Small output")
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024, // Large limit to avoid truncation
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();

View File

@@ -59,6 +59,8 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -155,6 +157,8 @@ echo "SECURITY_PASS: Secrets not in environment but accessible via get_secret"
runtime_name: Some("shell".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -203,6 +207,8 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result1 = runtime.execute(context1).await.unwrap();
@@ -239,6 +245,8 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result2 = runtime.execute(context2).await.unwrap();
@@ -286,6 +294,8 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -329,6 +339,8 @@ fi
runtime_name: Some("shell".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();
@@ -380,6 +392,8 @@ def run():
runtime_name: Some("python".to_string()),
max_stdout_bytes: 10 * 1024 * 1024,
max_stderr_bytes: 10 * 1024 * 1024,
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
parameter_format: attune_worker::runtime::ParameterFormat::default(),
};
let result = runtime.execute(context).await.unwrap();