Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dockerfiles/dynamicconfig/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ frontend.enableUpdateWorkflowExecutionAsyncAccepted:
frontend.workerVersioningWorkflowAPIs:
- value: true
constraints: {}
frontend.activityAPIsEnabled:
- value: true
constraints: {}
11 changes: 11 additions & 0 deletions features/activity/standalone/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Standalone activity

A client executes an activity directly, without a workflow.

Each feature:

* Starts the activity with the client on the feature task queue
* Waits for the activity result
* Describes the activity execution and confirms the `Completed` status

Requires a server with the dynamic config `frontend.activityAPIsEnabled=true`, enabled in `dockerfiles/dynamicconfig/docker.yaml`.
3 changes: 3 additions & 0 deletions features/activity/standalone/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"noWorkflow": true
}
35 changes: 35 additions & 0 deletions features/activity/standalone/feature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace activity.standalone;

using Temporalio.Activities;
using Temporalio.Api.Enums.V1;
using Temporalio.Client;
using Temporalio.Features.Harness;
using Temporalio.Worker;

class Feature : IFeature
{
public void ConfigureWorker(Runner runner, TemporalWorkerOptions options) =>
options.AddAllActivities<MyActivities>(null);

public async Task<WorkflowHandle?> ExecuteAsync(Runner runner)
{
var handle = await runner.Client.StartActivityAsync(
() => MyActivities.Echo("hello"),
new(Guid.NewGuid().ToString(), runner.WorkerOptions.TaskQueue!)
{
StartToCloseTimeout = TimeSpan.FromMinutes(1),
});

Assert.Equal("hello", await handle.GetResultAsync());

var desc = await handle.DescribeAsync();
Assert.Equal(ActivityExecutionStatus.Completed, desc.Status);
return null;
}

class MyActivities
{
[Activity]
public static Task<string> Echo(string value) => Task.FromResult(value);
}
}
42 changes: 42 additions & 0 deletions features/activity/standalone/feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package standalone

import (
"context"
"time"

"github.com/google/uuid"
"github.com/temporalio/features/harness/go/harness"
"go.temporal.io/api/enums/v1"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/workflow"
)

var Feature = harness.Feature{
Workflows: Workflow,
Activities: Echo,
Execute: Execute,
}

func Workflow(workflow.Context) error { return nil }

func Echo(_ context.Context, value string) (string, error) {
return value, nil
}

func Execute(ctx context.Context, r *harness.Runner) (client.WorkflowRun, error) {
handle, err := r.Client.ExecuteActivity(ctx, client.StartActivityOptions{
ID: uuid.NewString(),
TaskQueue: r.TaskQueue,
StartToCloseTimeout: 1 * time.Minute,
}, Echo, "hello")
r.Require.NoError(err)

var result string
r.Require.NoError(handle.Get(ctx, &result))
r.Require.Equal("hello", result)

desc, err := handle.Describe(ctx, client.DescribeActivityOptions{})
r.Require.NoError(err)
r.Require.Equal(enums.ACTIVITY_EXECUTION_STATUS_COMPLETED, desc.Status)
return nil, nil
}
56 changes: 56 additions & 0 deletions features/activity/standalone/feature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package activity.standalone;

import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
import io.temporal.api.enums.v1.ActivityExecutionStatus;
import io.temporal.client.ActivityClient;
import io.temporal.client.ActivityClientOptions;
import io.temporal.client.StartActivityOptions;
import io.temporal.sdkfeatures.Feature;
import io.temporal.sdkfeatures.Run;
import io.temporal.sdkfeatures.Runner;
import io.temporal.sdkfeatures.SimpleWorkflow;
import java.time.Duration;
import java.util.UUID;
import org.junit.jupiter.api.Assertions;

@ActivityInterface
public interface feature extends Feature, SimpleWorkflow {
@ActivityMethod
String echo(String value);

class Impl implements feature {
@Override
public void workflow() {}

@Override
public String echo(String value) {
return value;
}

@Override
public Run execute(Runner runner) {
var client =
ActivityClient.newInstance(
runner.service,
ActivityClientOptions.newBuilder().setNamespace(runner.config.namespace).build());

var handle =
client.start(
feature.class,
feature::echo,
StartActivityOptions.newBuilder()
.setId(UUID.randomUUID().toString())
.setTaskQueue(runner.config.taskQueue)
.setStartToCloseTimeout(Duration.ofMinutes(1))
.build(),
"hello");

Assertions.assertEquals("hello", handle.getResult());
Assertions.assertEquals(
ActivityExecutionStatus.ACTIVITY_EXECUTION_STATUS_COMPLETED,
handle.describe().getStatus());
return null;
}
}
}
42 changes: 42 additions & 0 deletions features/activity/standalone/feature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import uuid
from datetime import timedelta

from temporalio import activity, workflow
from temporalio.client import ActivityExecutionStatus, WorkflowHandle

from harness.python.feature import Runner, register_feature


@workflow.defn
class Workflow:
@workflow.run
async def run(self) -> None:
pass


@activity.defn
async def echo(value: str) -> str:
return value


async def start(runner: Runner) -> WorkflowHandle:
handle = await runner.client.start_activity(
echo,
"hello",
id=str(uuid.uuid4()),
task_queue=runner.task_queue,
start_to_close_timeout=timedelta(minutes=1),
)
assert await handle.result() == "hello"

desc = await handle.describe()
assert desc.status == ActivityExecutionStatus.COMPLETED

return await runner.start_single_parameterless_workflow()


register_feature(
workflows=[Workflow],
activities=[echo],
start=start,
)
40 changes: 40 additions & 0 deletions features/activity/standalone/feature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require 'securerandom'

require 'temporalio/activity'
require 'temporalio/client'

require 'harness'

class StandaloneEchoActivity < Temporalio::Activity::Definition
def execute(value)
value
end
end

start = proc do |client, task_queue, _feature|
client.start_activity(
StandaloneEchoActivity,
'hello',
id: SecureRandom.uuid,
task_queue: task_queue,
start_to_close_timeout: 60
)
end

check_result = proc do |handle, _feature|
result = handle.result
raise "Expected result \"hello\", got #{result.inspect}" unless result == 'hello'

status = handle.describe.status
completed = Temporalio::Client::ActivityExecutionStatus::COMPLETED
raise "Expected status #{completed}, got #{status}" unless status == completed
end

Harness.register_feature(
workflows: [],
activities: [StandaloneEchoActivity],
start: start,
check_result: check_result
)
63 changes: 63 additions & 0 deletions features/activity/standalone/feature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::harness::{Feature, FeatureContext, FeatureFuture, FeatureOutcome};
use anyhow::{Result, ensure};
use std::time::Duration;
use temporalio_client::{
ActivityDescribeOptions, ActivityExecutionInfoLike, ActivityExecutionStatus,
ActivityStartOptions,
};
use temporalio_macros::activities;
use temporalio_sdk::{
WorkerOptions,
activities::{ActivityContext, ActivityError},
};

pub struct Activities;

#[activities]
impl Activities {
#[activity]
pub async fn echo(_ctx: ActivityContext, value: String) -> Result<String, ActivityError> {
Ok(value)
}
}

struct StandaloneActivity;

impl Feature for StandaloneActivity {
fn worker_options(&self, ctx: &FeatureContext) -> Result<Option<WorkerOptions>> {
Ok(Some(
WorkerOptions::new(ctx.task_queue.clone())
.register_activities(Activities)
.build(),
))
}

fn execute<'a>(&'a self, ctx: &'a FeatureContext) -> FeatureFuture<'a> {
Box::pin(async move {
let options = ActivityStartOptions::with_start_to_close_timeout(
ctx.task_queue.clone(),
ctx.task_queue.clone(),
Duration::from_secs(60),
)
.build();
let handle = ctx
.client
.start_activity(Activities::echo, "hello".to_owned(), options)
.await?;
ensure!(
handle.result().await? == "hello",
"unexpected activity result"
);
let description = handle.describe(ActivityDescribeOptions::default()).await?;
ensure!(
description.status() == ActivityExecutionStatus::Completed,
"unexpected activity status"
);
Ok(FeatureOutcome::Passed)
})
}
}

pub fn feature() -> Box<dyn Feature> {
Box::new(StandaloneActivity)
}
29 changes: 29 additions & 0 deletions features/activity/standalone/feature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as assert from 'assert';
import { randomUUID } from 'crypto';
import { Feature } from '@temporalio/harness';

export async function workflow(): Promise<void> {}

const activities = {
async echo(value: string): Promise<string> {
return value;
},
};

export const feature = new Feature({
workflow,
activities,
alternateRun: async (runner) => {
const handle = await runner.client.activity.start('echo', {
id: randomUUID(),
taskQueue: runner.options.taskQueue,
startToCloseTimeout: '1 minute',
args: ['hello'],
});

assert.equal(await handle.result(), 'hello');

const desc = await handle.describe();
assert.equal(desc.status, 'COMPLETED');
},
});
2 changes: 2 additions & 0 deletions features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
activity_cancel_try_cancel "github.com/temporalio/features/features/activity/cancel_try_cancel"
activity_retry_on_error "github.com/temporalio/features/features/activity/retry_on_error"
activity_shutdown "github.com/temporalio/features/features/activity/shutdown"
activity_standalone "github.com/temporalio/features/features/activity/standalone"
bugs_go_activity_start_race "github.com/temporalio/features/features/bugs/go/activity_start_race"
bugs_go_child_workflow_cancel_panic "github.com/temporalio/features/features/bugs/go/child_workflow_cancel_panic"
build_id_versioning_activity_and_child_on_correct_version "github.com/temporalio/features/features/build_id_versioning/activity_and_child_on_correct_version"
Expand Down Expand Up @@ -81,6 +82,7 @@ func init() {
activity_cancel_try_cancel.Feature,
activity_retry_on_error.Feature,
activity_shutdown.Feature,
activity_standalone.Feature,
bugs_go_activity_start_race.Feature,
bugs_go_child_workflow_cancel_panic.Feature,
build_id_versioning_activity_and_child_on_correct_version.Feature,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class PreparedFeature {
activity.retry_on_error.feature.Impl.class,
activity.cancel_try_cancel.feature.Impl.class,
activity.shutdown.feature.Impl.class,
activity.standalone.feature.Impl.class,
child_workflow.result.feature.Impl.class,
child_workflow.signal.feature.Impl.class,
client.http_proxy.feature.Impl.class,
Expand Down
Loading