Skip to content

Commit

Permalink
add test
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas committed Nov 19, 2024
1 parent fec02c6 commit cb025eb
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 46 deletions.
1 change: 0 additions & 1 deletion examples/java/echo/src/main/java/ftl/echo/Echo.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public class Echo {

@Export
@Verb
@VerbName("bro.kens")
public EchoResponse echo(EchoRequest req, TimeClient time) {
var response = time.time();
return new EchoResponse("Hello, " + req.name().orElse("anonymous") + "! The time is " + response.toString() + ".");
Expand Down
3 changes: 1 addition & 2 deletions frontend/cli/cmd_serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,12 @@ func (s *serveCmd) Run(
controllerClient ftlv1connect.ControllerServiceClient,
provisionerClient provisionerconnect.ProvisionerServiceClient,
schemaClient ftlv1connect.SchemaServiceClient,
devModeEndpoints <-chan scaling.DevModeEndpoints,
) error {
bindAllocator, err := bind.NewBindAllocator(s.Bind, 2)
if err != nil {
return fmt.Errorf("could not create bind allocator: %w", err)
}
return s.run(ctx, projConfig, cm, sm, optional.None[chan bool](), false, bindAllocator, controllerClient, provisionerClient, schemaClient, devModeEndpoints)
return s.run(ctx, projConfig, cm, sm, optional.None[chan bool](), false, bindAllocator, controllerClient, provisionerClient, schemaClient, nil)
}

//nolint:maintidx
Expand Down
37 changes: 37 additions & 0 deletions internal/integration/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,31 @@ func Wait(module string) Action {
}
}

// WaitWithTimeout for the given module to deploy.
func WaitWithTimeout(module string, timeout time.Duration) Action {
return func(t testing.TB, ic TestContext) {
Infof("Waiting for %s to become ready", module)
deadline := time.After(timeout)
tick := time.NewTicker(time.Millisecond * 100)
defer tick.Stop()
for {
select {
case <-deadline:
t.Fatalf("deployment of module %q not found", module)
return
case <-tick.C:
status, err := ic.Controller.Status(ic, connect.NewRequest(&ftlv1.StatusRequest{}))
assert.NoError(t, err)
for _, deployment := range status.Msg.Deployments {
if deployment.Name == module {
return
}
}
}
}
}
}

func Sleep(duration time.Duration) Action {
return func(t testing.TB, ic TestContext) {
Infof("Sleeping for %s", duration)
Expand Down Expand Up @@ -414,6 +439,18 @@ func VerifySchema(check func(ctx context.Context, t testing.TB, sch *schemapb.Sc
}
}

// VerifyControllerStatus lets you test the current controller status
func VerifyControllerStatus(check func(ctx context.Context, t testing.TB, status *ftlv1.StatusResponse)) Action {
return func(t testing.TB, ic TestContext) {
sch, err := ic.Controller.Status(ic, connect.NewRequest(&ftlv1.StatusRequest{}))
if err != nil {
t.Errorf("failed to get schema: %v", err)
return
}
check(ic.Context, t, sch.Msg)
}
}

// VerifySchemaVerb lets you test the current schema for a specific verb
func VerifySchemaVerb(module string, verb string, check func(ctx context.Context, t testing.TB, schema *schemapb.Schema, verb *schemapb.Verb)) Action {
return func(t testing.TB, ic TestContext) {
Expand Down
24 changes: 21 additions & 3 deletions internal/integration/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,19 @@ func WithProvisionerConfig(config string) Option {
}
}

// WithDevMode starts the server using FTL dev, so modules are deployed automatically on change
func WithDevMode() Option {
return func(o *options) {
o.devMode = true
}
}

type options struct {
languages []string
testDataDir string
ftlConfigPath string
startController bool
devMode bool
startProvisioner bool
provisionerConfig string
requireJava bool
Expand Down Expand Up @@ -296,7 +304,12 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
if opts.startController {
Infof("Starting ftl cluster")

args := []string{filepath.Join(binDir, "ftl"), "serve", "--recreate"}
command := "serve"
if opts.devMode {
command = "dev"
}

args := []string{filepath.Join(binDir, "ftl"), command, "--recreate"}
if !opts.console {
args = append(args, "--no-console")
}
Expand All @@ -309,7 +322,7 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
args = append(args, "--provisioner-plugin-config="+configFile)
}
}
ctx = startProcess(ctx, t, args...)
ctx = startProcess(ctx, t, tmpDir, opts.devMode, args...)
}
if opts.startController || opts.kube {
controller = rpc.Dial(ftlv1connect.NewControllerServiceClient, "http://localhost:8892", log.Debug)
Expand All @@ -335,6 +348,7 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
realT: t,
Language: language,
kubeNamespace: kubeNamespace,
devMode: opts.devMode,
kubeClient: optional.Ptr(kubeClient),
}
defer dumpKubePods(ctx, ic.kubeClient, ic.kubeNamespace)
Expand Down Expand Up @@ -415,6 +429,7 @@ type TestContext struct {
// Set if the test is running on kubernetes
kubeClient optional.Option[kubernetes.Clientset]
kubeNamespace string
devMode bool

Controller ftlv1connect.ControllerServiceClient
Provisioner provisionerconnect.ProvisionerServiceClient
Expand Down Expand Up @@ -501,10 +516,13 @@ func (l *logWriter) Write(p []byte) (n int, err error) {
}

// startProcess runs a binary in the background and terminates it when the test completes.
func startProcess(ctx context.Context, t testing.TB, args ...string) context.Context {
func startProcess(ctx context.Context, t testing.TB, tempDir string, devMode bool, args ...string) context.Context {
t.Helper()
ctx, cancel := context.WithCancel(ctx)
cmd := ftlexec.Command(ctx, log.Debug, "..", args[0], args[1:]...)
if devMode {
cmd.Dir = tempDir
}
err := cmd.Start()
assert.NoError(t, err)
terminated := make(chan bool)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.tomlj.TomlParseResult;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
Expand All @@ -30,6 +31,7 @@
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.grpc.deployment.BindableServiceBuildItem;
Expand Down Expand Up @@ -199,4 +201,10 @@ void openSocket(BuildProducer<RequireVirtualHttpBuildItem> virtual,
socket.produce(RequireSocketHttpBuildItem.MARKER);
virtual.produce(RequireVirtualHttpBuildItem.MARKER);
}

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep(onlyIf = IsDevelopment.class)
void hotReload(ShutdownContextBuildItem shutdownContextBuildItem, FTLRecorder recorder) {
recorder.startReloadTimer(shutdownContextBuildItem);
}
}
4 changes: 4 additions & 0 deletions jvm-runtime/ftl-runtime/common/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-development-mode-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-grpc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.BiFunction;

import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
Expand All @@ -11,6 +13,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.arc.Arc;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import xyz.block.ftl.runtime.http.FTLHttpHandler;
import xyz.block.ftl.runtime.http.HTTPVerbInvoker;
Expand Down Expand Up @@ -152,4 +155,20 @@ public Object extractParameter(ResteasyReactiveRequestContext context) {
throw new RuntimeException(e);
}
}

public void startReloadTimer(ShutdownContext shutdownContext) {
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
HotReloadSetup.doScan();
}
}, 1000, 1000);
shutdownContext.addShutdownTask(new Runnable() {
@Override
public void run() {
t.cancel();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package xyz.block.ftl.runtime;

import io.quarkus.dev.spi.HotReplacementContext;
import io.quarkus.dev.spi.HotReplacementSetup;

public class HotReloadSetup implements HotReplacementSetup {

static volatile HotReplacementContext context;

@Override
public void setupHotDeployment(HotReplacementContext hrc) {
context = hrc;
}

static void doScan() {
if (context != null) {
try {
context.doScan(false);
} catch (Exception e) {
// ignore
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xyz.block.ftl.runtime.HotReloadSetup
41 changes: 40 additions & 1 deletion jvm-runtime/jvm_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,63 @@ import (

"github.com/alecthomas/repr"

ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1"
schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema"
"github.com/TBD54566975/ftl/go-runtime/ftl"
in "github.com/TBD54566975/ftl/internal/integration"
"github.com/TBD54566975/ftl/internal/schema"
)

func TestLifecycleJVM(t *testing.T) {
deployment := ""
in.Run(t,
in.WithLanguages("java", "kotlin"),
in.WithDevMode(),
in.GitInit(),
in.Exec("rm", "ftl-project.toml"),
in.Exec("ftl", "init", "test", "."),
in.IfLanguage("java", in.Exec("ftl", "new", "java", ".", "echo")),
in.IfLanguage("kotlin", in.Exec("ftl", "new", "kotlin", ".", "echo")),
in.Deploy("echo"),
in.WaitWithTimeout("echo", time.Minute),
in.VerifyControllerStatus(func(ctx context.Context, t testing.TB, status *ftlv1.StatusResponse) {
assert.Equal(t, 1, len(status.Deployments))
deployment = status.Deployments[0].Key
}),
in.Call("echo", "echo", "Bob", func(t testing.TB, response string) {
assert.Equal(t, "Hello, Bob!", response)
}),
// Now test hot reload
in.IfLanguage("java", in.EditFile("echo", func(content []byte) []byte {
return []byte(strings.ReplaceAll(string(content), "Hello", "Bye"))
}, "src/main/java/com/example/EchoVerb.java")),
in.IfLanguage("kotlin", in.EditFile("echo", func(content []byte) []byte {
return []byte(strings.ReplaceAll(string(content), "Hello", "Bye"))
}, "src/main/kotlin/com/example/EchoVerb.kt")),
in.Sleep(time.Second*2), // Annoyingly quarkus limits to one restart check every 2s, which is fine for normal dev, but a pain for these tests
in.Call("echo", "echo", "Bob", func(t testing.TB, response string) {
assert.Equal(t, "Bye, Bob!", response)
}),
in.VerifyControllerStatus(func(ctx context.Context, t testing.TB, status *ftlv1.StatusResponse) {
// Non structurally changing edits should not trigger a new deployment.
t.Logf("status %v", status)
assert.Equal(t, 1, len(status.Deployments))
assert.Equal(t, deployment, status.Deployments[0].Key)
}),
// Structural change should result in a new deployment
in.IfLanguage("java", in.EditFile("echo", func(content []byte) []byte {
return []byte(strings.ReplaceAll(string(content), "@Export", ""))
}, "src/main/java/com/example/EchoVerb.java")),
in.IfLanguage("kotlin", in.EditFile("echo", func(content []byte) []byte {
return []byte(strings.ReplaceAll(string(content), "@Export", ""))
}, "src/main/kotlin/com/example/EchoVerb.kt")),
in.Call("echo", "echo", "Bob", func(t testing.TB, response string) {
assert.Equal(t, "Bye, Bob!", response)
}),
in.VerifyControllerStatus(func(ctx context.Context, t testing.TB, status *ftlv1.StatusResponse) {
// Non structurally changing edits should not trigger a new deployment.
assert.Equal(t, 1, len(status.Deployments))
assert.NotEqual(t, deployment, status.Deployments[0].Key)
}),
)
}

Expand Down
Loading

0 comments on commit cb025eb

Please sign in to comment.