This is an automated email from the ASF dual-hosted git repository.

damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 08ae6c7acff [Playground] Implement Java & Go multifile examples 
execution (#24874)
08ae6c7acff is described below

commit 08ae6c7acffe1a85571f44baa05db811ddea359b
Author: Timur Sultanov <[email protected]>
AuthorDate: Tue Mar 28 20:42:47 2023 +0400

    [Playground] Implement Java & Go multifile examples execution (#24874)
    
    * Find class which contains Java's main() method using javap
    
    * Support compiling multifile Java examples
    
    * Support compiling multifile Go examples
    
    * Improve error handling in java_fs_test.go
    
    * Remove redundant function
    
    * Add error handling to GetFilesFromFolder()
    
    * Improve error handling in java_fs_test.go
    
    * Treat inability to find class with main() method as an error
    
    * Fix failing tests in setup_builder_test.go
    
    * Revert changes to run_output_writer.go
    
    * Look up class with unit test for Java examples using javap
    
    * Remove unused functions
    
    * Pass context into methods for finding executable file names to support 
canceling search process due to timeout
    
    * Use more descriptive names for executable file searching functions
    
    * Improve error handling
    
    * Clarify purposes of tests
---
 .../internal/code_processing/code_processing.go    |  10 +-
 .../code_processing/code_processing_test.go        |   2 +-
 playground/backend/internal/executors/executor.go  |  10 +-
 .../backend/internal/executors/executor_builder.go |  30 +--
 .../internal/executors/executor_builder_test.go    |   2 +-
 .../backend/internal/executors/executor_test.go    |   8 +-
 playground/backend/internal/fs_tool/fs.go          |   6 +-
 playground/backend/internal/fs_tool/fs_test.go     |   4 +-
 playground/backend/internal/fs_tool/go_fs.go       |   4 +-
 playground/backend/internal/fs_tool/go_fs_test.go  |   4 +-
 playground/backend/internal/fs_tool/java_fs.go     | 130 +++++++++++--
 .../backend/internal/fs_tool/java_fs_test.go       | 215 ++++++++++++++++++---
 .../fs_tool/java_testdata/HasIncorrectMain.java    |  28 +++
 .../fs_tool/java_testdata/HasMainTest1.java        |  28 +++
 .../fs_tool/java_testdata/HasMainTest2.java        |  29 +++
 .../internal/fs_tool/java_testdata/HasNoMain.java  |  28 +++
 playground/backend/internal/fs_tool/scio_fs.go     |   2 +-
 .../internal/setup_tools/builder/setup_builder.go  |  51 +++--
 .../setup_tools/builder/setup_builder_test.go      | 149 +++++++++++---
 .../setup_tools/life_cycle/life_cycle_setuper.go   |   4 +-
 playground/backend/internal/utils/file_utils.go    |   4 +
 21 files changed, 609 insertions(+), 139 deletions(-)

diff --git a/playground/backend/internal/code_processing/code_processing.go 
b/playground/backend/internal/code_processing/code_processing.go
index 23e34240eaa..32cd9c38027 100644
--- a/playground/backend/internal/code_processing/code_processing.go
+++ b/playground/backend/internal/code_processing/code_processing.go
@@ -99,9 +99,9 @@ func runStep(ctx context.Context, cacheService cache.Cache, 
paths *fs_tool.LifeC
        var executorBuilder *executors.ExecutorBuilder
        err := error(nil)
        if isUnitTest {
-               executorBuilder, err = builder.TestRunner(paths, sdkEnv)
+               executorBuilder, err = builder.TestRunner(pipelineLifeCycleCtx, 
paths, sdkEnv)
        } else {
-               executorBuilder, err = builder.Runner(paths, 
utils.ReduceWhiteSpacesToSinge(pipelineOptions), sdkEnv)
+               executorBuilder, err = builder.Runner(pipelineLifeCycleCtx, 
paths, utils.ReduceWhiteSpacesToSinge(pipelineOptions), sdkEnv)
        }
        if err != nil {
                _ = processSetupError(err, pipelineId, cacheService, 
pipelineLifeCycleCtx)
@@ -171,7 +171,11 @@ func compileStep(ctx context.Context, cacheService 
cache.Cache, paths *fs_tool.L
                        return nil
                }
        } else { // in case of Java, Go (not unit test), Scala - need compile 
step
-               executorBuilder := builder.Compiler(paths, sdkEnv)
+               executorBuilder, err := builder.Compiler(paths, sdkEnv)
+               if err != nil {
+                       logger.Errorf("compileStep(): failed creating 
ExecutorBuilder = %v", executorBuilder)
+                       return nil
+               }
                executor := executorBuilder.Build()
                logger.Infof("%s: Compile() ...\n", pipelineId)
                compileCmd := executor.Compile(pipelineLifeCycleCtx)
diff --git 
a/playground/backend/internal/code_processing/code_processing_test.go 
b/playground/backend/internal/code_processing/code_processing_test.go
index f44cdb68d90..b9f4f7131c4 100644
--- a/playground/backend/internal/code_processing/code_processing_test.go
+++ b/playground/backend/internal/code_processing/code_processing_test.go
@@ -593,7 +593,7 @@ func Test_getRunOrTestCmd(t *testing.T) {
                Build()
 
        wantRunExec := exec.CommandContext(context.Background(), "runCommand", 
"arg1")
-       wantTestExec := exec.CommandContext(context.Background(), 
"testCommand", "arg1", "")
+       wantTestExec := exec.CommandContext(context.Background(), 
"testCommand", "arg1")
 
        type args struct {
                isUnitTest     bool
diff --git a/playground/backend/internal/executors/executor.go 
b/playground/backend/internal/executors/executor.go
index 4bba3b0d5eb..a2a6afec8e5 100644
--- a/playground/backend/internal/executors/executor.go
+++ b/playground/backend/internal/executors/executor.go
@@ -32,7 +32,7 @@ const (
 
 // CmdConfiguration for base cmd code execution
 type CmdConfiguration struct {
-       fileName        string
+       fileNames       []string
        workingDir      string
        commandName     string
        commandArgs     []string
@@ -94,7 +94,7 @@ func (ex *Executor) Prepare() func(chan bool, chan error, 
*sync.Map) {
 // Compile prepares the Cmd for code compilation
 // Returns Cmd instance
 func (ex *Executor) Compile(ctx context.Context) *exec.Cmd {
-       args := append(ex.compileArgs.commandArgs, ex.compileArgs.fileName)
+       args := append(ex.compileArgs.commandArgs, ex.compileArgs.fileNames...)
        cmd := exec.CommandContext(ctx, ex.compileArgs.commandName, args...)
        cmd.Dir = ex.compileArgs.workingDir
        return cmd
@@ -104,8 +104,8 @@ func (ex *Executor) Compile(ctx context.Context) *exec.Cmd {
 // Returns Cmd instance
 func (ex *Executor) Run(ctx context.Context) *exec.Cmd {
        args := ex.runArgs.commandArgs
-       if ex.runArgs.fileName != "" {
-               args = append(args, ex.runArgs.fileName)
+       if len(ex.runArgs.fileNames) > 0 {
+               args = append(args, ex.runArgs.fileNames...)
        }
        if ex.runArgs.pipelineOptions != nil && ex.runArgs.pipelineOptions[0] 
!= "" {
                args = append(args, ex.runArgs.pipelineOptions...)
@@ -118,7 +118,7 @@ func (ex *Executor) Run(ctx context.Context) *exec.Cmd {
 // RunTest prepares the Cmd for execution of the unit test
 // Returns Cmd instance
 func (ex *Executor) RunTest(ctx context.Context) *exec.Cmd {
-       args := append(ex.testArgs.commandArgs, ex.testArgs.fileName)
+       args := append(ex.testArgs.commandArgs, ex.testArgs.fileNames...)
        cmd := exec.CommandContext(ctx, ex.testArgs.commandName, args...)
        cmd.Dir = ex.testArgs.workingDir
        return cmd
diff --git a/playground/backend/internal/executors/executor_builder.go 
b/playground/backend/internal/executors/executor_builder.go
index c810f96e89c..371f02e3418 100644
--- a/playground/backend/internal/executors/executor_builder.go
+++ b/playground/backend/internal/executors/executor_builder.go
@@ -106,18 +106,18 @@ func (b *CompileBuilder) WithArgs(compileArgs []string) 
*CompileBuilder {
        return b
 }
 
-// WithFileName adds file name to executor
-func (b *CompileBuilder) WithFileName(fileName string) *CompileBuilder {
+// WithFileNames adds file names to executor
+func (b *CompileBuilder) WithFileNames(fileNames ...string) *CompileBuilder {
        b.actions = append(b.actions, func(e *Executor) {
-               e.compileArgs.fileName = fileName
+               e.compileArgs.fileNames = fileNames
        })
        return b
 }
 
-// WithExecutableFileName adds file name to executor
-func (b *RunBuilder) WithExecutableFileName(name string) *RunBuilder {
+// WithExecutableFileNames adds file name to executor
+func (b *RunBuilder) WithExecutableFileNames(names ...string) *RunBuilder {
        b.actions = append(b.actions, func(e *Executor) {
-               e.runArgs.fileName = name
+               e.runArgs.fileNames = names
        })
        return b
 }
@@ -146,14 +146,6 @@ func (b *RunBuilder) WithArgs(runArgs []string) 
*RunBuilder {
        return b
 }
 
-// WithGraphOutput adds the need of graph output to executor
-func (b *RunBuilder) WithGraphOutput() *RunBuilder {
-       b.actions = append(b.actions, func(e *Executor) {
-               //todo
-       })
-       return b
-}
-
 // WithCommand adds test command to executor
 func (b *UnitTestExecutorBuilder) WithCommand(testCmd string) 
*UnitTestExecutorBuilder {
        b.actions = append(b.actions, func(e *Executor) {
@@ -178,18 +170,10 @@ func (b *UnitTestExecutorBuilder) WithWorkingDir(dir 
string) *UnitTestExecutorBu
        return b
 }
 
-// WithGraphOutput adds the need of graph output to executor
-func (b *UnitTestExecutorBuilder) WithGraphOutput() *UnitTestExecutorBuilder {
-       b.actions = append(b.actions, func(e *Executor) {
-               //todo
-       })
-       return b
-}
-
 // WithExecutableFileName adds file name to executor
 func (b *UnitTestExecutorBuilder) WithExecutableFileName(name string) 
*UnitTestExecutorBuilder {
        b.actions = append(b.actions, func(e *Executor) {
-               e.testArgs.fileName = name
+               e.testArgs.fileNames = append(e.testArgs.fileNames, name)
        })
        return b
 }
diff --git a/playground/backend/internal/executors/executor_builder_test.go 
b/playground/backend/internal/executors/executor_builder_test.go
index 9c61a875369..8778b3b2f72 100644
--- a/playground/backend/internal/executors/executor_builder_test.go
+++ b/playground/backend/internal/executors/executor_builder_test.go
@@ -25,7 +25,7 @@ var handlers []handler
 func TestMain(m *testing.M) {
        handlers = []handler{
                func(e *Executor) {
-                       e.testArgs.fileName = "file name"
+                       e.testArgs.fileNames = append(e.testArgs.fileNames, 
"file name")
                },
                func(e *Executor) {
                        e.runArgs.pipelineOptions = []string{"--opt val"}
diff --git a/playground/backend/internal/executors/executor_test.go 
b/playground/backend/internal/executors/executor_test.go
index 603b748211f..947832d4fca 100644
--- a/playground/backend/internal/executors/executor_test.go
+++ b/playground/backend/internal/executors/executor_test.go
@@ -44,7 +44,7 @@ func TestExecutor_Compile(t *testing.T) {
                        name: "TestCompile",
                        fields: fields{
                                compileArgs: CmdConfiguration{
-                                       fileName:        "filePath",
+                                       fileNames:       []string{"filePath"},
                                        workingDir:      "./",
                                        commandName:     "testCommand",
                                        commandArgs:     []string{"-d", "bin", 
"-parameters", "-classpath", 
"/opt/apache/beam/jars/beam-sdks-java-harness.jar"},
@@ -97,7 +97,7 @@ func TestExecutor_Run(t *testing.T) {
                        name: "TestRun",
                        fields: fields{
                                runArgs: CmdConfiguration{
-                                       fileName:    "HelloWorld",
+                                       fileNames:   []string{"HelloWorld"},
                                        workingDir:  "./",
                                        commandName: "runCommand",
                                        commandArgs: []string{"-cp", 
"bin:/opt/apache/beam/jars/beam-sdks-java-harness.jar:" +
@@ -124,7 +124,7 @@ func TestExecutor_Run(t *testing.T) {
                        name: "TestRun with pipelineOptions",
                        fields: fields{
                                runArgs: CmdConfiguration{
-                                       fileName:    "HelloWorld",
+                                       fileNames:   []string{"HelloWorld"},
                                        workingDir:  "./",
                                        commandName: "runCommand",
                                        commandArgs: []string{"-cp", 
"bin:/opt/apache/beam/jars/beam-sdks-java-harness.jar:" +
@@ -185,7 +185,7 @@ func TestExecutor_RunTest(t *testing.T) {
                        name: "TestRunTest",
                        fields: fields{
                                testArgs: CmdConfiguration{
-                                       fileName:        "HelloWorld",
+                                       fileNames:       []string{"HelloWorld"},
                                        workingDir:      "./",
                                        commandName:     "testCommand",
                                        commandArgs:     []string{"-cp", 
"option1:option2"},
diff --git a/playground/backend/internal/fs_tool/fs.go 
b/playground/backend/internal/fs_tool/fs.go
index f132e71c21d..9c49fca53a0 100644
--- a/playground/backend/internal/fs_tool/fs.go
+++ b/playground/backend/internal/fs_tool/fs.go
@@ -16,7 +16,7 @@
 package fs_tool
 
 import (
-       "beam.apache.org/playground/backend/internal/logger"
+       "context"
        "fmt"
        "github.com/google/uuid"
        "io/fs"
@@ -25,6 +25,7 @@ import (
        pb "beam.apache.org/playground/backend/internal/api/v1"
        "beam.apache.org/playground/backend/internal/db/entity"
        "beam.apache.org/playground/backend/internal/emulators"
+       "beam.apache.org/playground/backend/internal/logger"
 )
 
 const (
@@ -44,7 +45,8 @@ type LifeCyclePaths struct {
        AbsoluteLogFilePath              string // 
/path/to/workingDir/pipelinesFolder/{pipelineId}/logs.log
        AbsoluteGraphFilePath            string // 
/path/to/workingDir/pipelinesFolder/{pipelineId}/graph.dot
        ProjectDir                       string // /path/to/workingDir/
-       ExecutableName                   func(string) (string, error)
+       FindExecutableName               func(context.Context, string) (string, 
error)
+       FindTestExecutableName           func(context.Context, string) (string, 
error)
 }
 
 // LifeCycle is used for preparing folders and files to process code for one 
code processing request.
diff --git a/playground/backend/internal/fs_tool/fs_test.go 
b/playground/backend/internal/fs_tool/fs_test.go
index 4241fd9a486..db029635961 100644
--- a/playground/backend/internal/fs_tool/fs_test.go
+++ b/playground/backend/internal/fs_tool/fs_test.go
@@ -327,9 +327,9 @@ func TestNewLifeCycle(t *testing.T) {
                        want: &LifeCycle{
                                folderGlobs: []string{baseFileFolder, 
srcFileFolder, execFileFolder},
                                Paths: LifeCyclePaths{
-                                       SourceFileName:                   
fmt.Sprintf("%s%s", pipelineId.String(), goSourceFileExtension),
+                                       SourceFileName:                   
fmt.Sprintf("%s%s", pipelineId.String(), GoSourceFileExtension),
                                        AbsoluteSourceFileFolderPath:     
srcFileFolder,
-                                       AbsoluteSourceFilePath:           
filepath.Join(srcFileFolder, fmt.Sprintf("%s%s", pipelineId.String(), 
goSourceFileExtension)),
+                                       AbsoluteSourceFilePath:           
filepath.Join(srcFileFolder, fmt.Sprintf("%s%s", pipelineId.String(), 
GoSourceFileExtension)),
                                        ExecutableFileName:               
fmt.Sprintf("%s%s", pipelineId.String(), goExecutableFileExtension),
                                        AbsoluteExecutableFileFolderPath: 
execFileFolder,
                                        AbsoluteExecutableFilePath:       
filepath.Join(execFileFolder, fmt.Sprintf("%s%s", pipelineId.String(), 
goExecutableFileExtension)),
diff --git a/playground/backend/internal/fs_tool/go_fs.go 
b/playground/backend/internal/fs_tool/go_fs.go
index 883f1803f68..f624152d3c9 100644
--- a/playground/backend/internal/fs_tool/go_fs.go
+++ b/playground/backend/internal/fs_tool/go_fs.go
@@ -20,11 +20,11 @@ import (
 )
 
 const (
-       goSourceFileExtension     = ".go"
+       GoSourceFileExtension     = ".go"
        goExecutableFileExtension = ""
 )
 
 // newGoLifeCycle creates LifeCycle with go SDK environment.
 func newGoLifeCycle(pipelineId uuid.UUID, pipelinesFolder string) *LifeCycle {
-       return newCompilingLifeCycle(pipelineId, pipelinesFolder, 
goSourceFileExtension, goExecutableFileExtension)
+       return newCompilingLifeCycle(pipelineId, pipelinesFolder, 
GoSourceFileExtension, goExecutableFileExtension)
 }
diff --git a/playground/backend/internal/fs_tool/go_fs_test.go 
b/playground/backend/internal/fs_tool/go_fs_test.go
index 8021e1c906b..e5e513fd253 100644
--- a/playground/backend/internal/fs_tool/go_fs_test.go
+++ b/playground/backend/internal/fs_tool/go_fs_test.go
@@ -50,9 +50,9 @@ func Test_newGoLifeCycle(t *testing.T) {
                        want: &LifeCycle{
                                folderGlobs: []string{baseFileFolder, 
srcFileFolder, binFileFolder},
                                Paths: LifeCyclePaths{
-                                       SourceFileName:                   
pipelineId.String() + goSourceFileExtension,
+                                       SourceFileName:                   
pipelineId.String() + GoSourceFileExtension,
                                        AbsoluteSourceFileFolderPath:     
srcFileFolder,
-                                       AbsoluteSourceFilePath:           
filepath.Join(srcFileFolder, pipelineId.String()+goSourceFileExtension),
+                                       AbsoluteSourceFilePath:           
filepath.Join(srcFileFolder, pipelineId.String()+GoSourceFileExtension),
                                        ExecutableFileName:               
pipelineId.String() + goExecutableFileExtension,
                                        AbsoluteExecutableFileFolderPath: 
binFileFolder,
                                        AbsoluteExecutableFilePath:       
filepath.Join(binFileFolder, pipelineId.String()+goExecutableFileExtension),
diff --git a/playground/backend/internal/fs_tool/java_fs.go 
b/playground/backend/internal/fs_tool/java_fs.go
index aa0ee6f1282..3666ee37244 100644
--- a/playground/backend/internal/fs_tool/java_fs.go
+++ b/playground/backend/internal/fs_tool/java_fs.go
@@ -16,10 +16,13 @@
 package fs_tool
 
 import (
+       "bytes"
+       "context"
        "errors"
        "fmt"
-       "io/ioutil"
        "os"
+       "os/exec"
+       "path/filepath"
        "strings"
 
        "github.com/google/uuid"
@@ -30,19 +33,23 @@ import (
 )
 
 const (
-       JavaSourceFileExtension   = ".java"
-       javaCompiledFileExtension = ".class"
+       JavaSourceFileExtension            = ".java"
+       javaCompiledFileExtension          = ".class"
+       javaEntryPointFullName             = "public static void 
main(java.lang.String[])"
+       javaDecompilerCommand              = "javap"
+       juintRunWithTestAnnotationConstant = "Lorg/junit/runner/RunWith;"
 )
 
 // newJavaLifeCycle creates LifeCycle with java SDK environment.
 func newJavaLifeCycle(pipelineId uuid.UUID, pipelinesFolder string) *LifeCycle 
{
        javaLifeCycle := newCompilingLifeCycle(pipelineId, pipelinesFolder, 
JavaSourceFileExtension, javaCompiledFileExtension)
-       javaLifeCycle.Paths.ExecutableName = executableName
+       javaLifeCycle.Paths.FindExecutableName = findExecutableName
+       javaLifeCycle.Paths.FindTestExecutableName = findTestExecutableName
        return javaLifeCycle
 }
 
-// executableName returns name that should be executed (HelloWorld for 
HelloWorld.class for java SDK)
-func executableName(executableFileFolderPath string) (string, error) {
+// findExecutableName returns name of the .class file which has main() method
+func findExecutableName(ctx context.Context, executableFileFolderPath string) 
(string, error) {
        dirEntries, err := os.ReadDir(executableFileFolderPath)
        if err != nil {
                return "", err
@@ -52,27 +59,110 @@ func executableName(executableFileFolderPath string) 
(string, error) {
        }
 
        if len(dirEntries) == 1 {
-               return strings.Split(dirEntries[0].Name(), ".")[0], nil
+               return utils.TrimExtension(dirEntries[0].Name()), nil
        }
 
        for _, entry := range dirEntries {
-               content, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", 
executableFileFolderPath, entry.Name()))
-               if err != nil {
-                       logger.Error(fmt.Sprintf("error during file reading: 
%s", err.Error()))
-                       break
-               }
-               ext := strings.Split(entry.Name(), ".")[1]
-               sdk := utils.ToSDKFromExt("." + ext)
+               select {
+               case <-ctx.Done():
+                       return "", ctx.Err()
+               default:
+                       filePath := fmt.Sprintf("%s/%s", 
executableFileFolderPath, entry.Name())
+                       content, err := os.ReadFile(filePath)
+                       if err != nil {
+                               logger.Errorf("findExecutableName(): error when 
reading file %s: %s", entry.Name(), err.Error())
+                               break
+                       }
+                       ext := filepath.Ext(entry.Name())
+                       filename := strings.TrimSuffix(entry.Name(), ext)
+                       sdk := utils.ToSDKFromExt(ext)
+
+                       if sdk == pb.Sdk_SDK_UNSPECIFIED {
+                               logger.Errorf("findExecutableName(): file %s: 
unknown file extension: %s, skipping", entry.Name(), ext)
+                               continue
+                       }
 
-               if sdk == pb.Sdk_SDK_UNSPECIFIED {
-                       logger.Error("invalid a file extension")
-                       break
+                       switch ext {
+                       case javaCompiledFileExtension:
+                               isMain, err := isMainClass(ctx, 
executableFileFolderPath, filename)
+                               if err != nil {
+                                       logger.Errorf("findExecutableName(): 
file %s: error during checking main class: %s", entry.Name(), err.Error())
+                                       break
+                               }
+                               if isMain {
+                                       logger.Infof("findExecutableName(): 
main file is %s", filename)
+                                       return filename, nil
+                               }
+                       default:
+                               if utils.IsFileMain(string(content), sdk) {
+                                       return filename, nil
+                               }
+                       }
                }
+       }
+
+       return "", errors.New("cannot find file with main() method")
+}
+
+// findTestExecutableName returns name of the .class file which has JUnit tests
+func findTestExecutableName(ctx context.Context, executableFileFolderPath 
string) (string, error) {
+       dirEntries, err := os.ReadDir(executableFileFolderPath)
+       if err != nil {
+               return "", err
+       }
+       if len(dirEntries) < 1 {
+               return "", errors.New("number of executable files should be at 
least one")
+       }
+
+       if len(dirEntries) == 1 {
+               return utils.TrimExtension(dirEntries[0].Name()), nil
+       }
+
+       for _, entry := range dirEntries {
+               select {
+               case <-ctx.Done():
+                       return "", ctx.Err()
+               default:
+                       ext := filepath.Ext(entry.Name())
+                       filename := strings.TrimSuffix(entry.Name(), ext)
 
-               if utils.IsFileMain(string(content), sdk) {
-                       return strings.Split(entry.Name(), ".")[0], nil
+                       if ext == javaCompiledFileExtension {
+                               isTest, err := isTestClass(ctx, 
executableFileFolderPath, filename)
+                               if err != nil {
+                                       
logger.Errorf("findTestExecutableName(): file %s: error during checking main 
class: %s", entry.Name(), err.Error())
+                                       break
+                               }
+                               if isTest {
+                                       logger.Infof("findTestExecutableName(): 
main file is %s", filename)
+                                       return filename, nil
+                               }
+                       }
                }
        }
 
-       return strings.Split(dirEntries[len(dirEntries)-1].Name(), ".")[0], nil
+       return "", errors.New("cannot find file with unit tests")
+}
+
+func isMainClass(ctx context.Context, classPath string, className string) 
(bool, error) {
+       cmd := exec.CommandContext(ctx, javaDecompilerCommand, "-public", 
"-classpath", classPath, className)
+       var out bytes.Buffer
+       cmd.Stdout = &out
+       err := cmd.Run()
+       if err != nil {
+               return false, err
+       }
+
+       return strings.Contains(out.String(), javaEntryPointFullName), nil
+}
+
+func isTestClass(ctx context.Context, classPath string, className string) 
(bool, error) {
+       cmd := exec.CommandContext(ctx, javaDecompilerCommand, "-verbose", 
"-classpath", classPath, className)
+       var out bytes.Buffer
+       cmd.Stdout = &out
+       err := cmd.Run()
+       if err != nil {
+               return false, err
+       }
+
+       return strings.Contains(out.String(), 
juintRunWithTestAnnotationConstant), nil
 }
diff --git a/playground/backend/internal/fs_tool/java_fs_test.go 
b/playground/backend/internal/fs_tool/java_fs_test.go
index b8144f816a4..9ac452b91e1 100644
--- a/playground/backend/internal/fs_tool/java_fs_test.go
+++ b/playground/backend/internal/fs_tool/java_fs_test.go
@@ -16,14 +16,15 @@
 package fs_tool
 
 import (
+       "beam.apache.org/playground/backend/internal/utils"
+       "context"
        "os"
+       "os/exec"
        "path/filepath"
        "reflect"
        "testing"
 
        "github.com/google/uuid"
-
-       "beam.apache.org/playground/backend/internal/utils"
 )
 
 func Test_newJavaLifeCycle(t *testing.T) {
@@ -62,7 +63,7 @@ func Test_newJavaLifeCycle(t *testing.T) {
                                        AbsoluteBaseFolderPath:           
baseFileFolder,
                                        AbsoluteLogFilePath:              
filepath.Join(baseFileFolder, logFileName),
                                        AbsoluteGraphFilePath:            
filepath.Join(baseFileFolder, utils.GraphFileName),
-                                       ExecutableName:                   
executableName,
+                                       FindExecutableName:               
findExecutableName,
                                },
                        },
                },
@@ -85,24 +86,62 @@ func Test_executableName(t *testing.T) {
        workDir := "workingDir"
        preparedPipelinesFolder := filepath.Join(workDir, pipelinesFolder)
        lc := newJavaLifeCycle(pipelineId, preparedPipelinesFolder)
-       lc.CreateFolders()
-       defer os.RemoveAll(workDir)
+       err := lc.CreateFolders()
+       if err != nil {
+               t.Errorf("Failed to create folders %s, error = %v", workDir, 
err)
+       }
+       defer func() {
+               err := os.RemoveAll(workDir)
+               if err != nil {
+                       t.Errorf("Failed to cleanup %s, error = %v", workDir, 
err)
+               }
+       }()
+
+       compileJavaFiles := func(sourceFiles ...string) error {
+               compiledDir := filepath.Join(workDir, pipelinesFolder, 
pipelineId.String(), compiledFolderName)
+
+               args := append([]string{"-d", compiledDir}, sourceFiles...)
+               err := exec.Command("javac", args...).Run()
+               if err != nil {
+                       return err
+               }
+               return nil
+       }
+
+       cleanupFunc := func() error {
+               compiled := filepath.Join(workDir, pipelinesFolder, 
pipelineId.String(), compiledFolderName)
+               dirEntries, err := os.ReadDir(compiled)
+               if err != nil {
+                       return err
+               }
+
+               for _, entry := range dirEntries {
+                       err := os.Remove(filepath.Join(compiled, entry.Name()))
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               return nil
+       }
 
        type args struct {
                executableFolder string
        }
        tests := []struct {
                name    string
-               prepare func()
+               prepare func() error
+               cleanup func() error
                args    args
                want    string
                wantErr bool
        }{
                {
-                       // Test case with calling sourceFileName method with 
empty directory.
+                       // Test case with calling findExecutableName() function 
with empty directory.
                        // As a result, want to receive an error.
                        name:    "Directory is empty",
-                       prepare: func() {},
+                       prepare: func() error { return nil },
+                       cleanup: func() error { return nil },
                        args: args{
                                executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
                        },
@@ -110,17 +149,19 @@ func Test_executableName(t *testing.T) {
                        wantErr: true,
                },
                {
-                       // Test case with calling sourceFileName method with 
correct pipelineId and workingDir.
+                       // Test case with calling findExecutableName() function 
with correct pipelineId and workingDir.
                        // As a result, want to receive a name that should be 
executed
                        name: "Get executable name",
-                       prepare: func() {
+                       prepare: func() error {
                                compiled := filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), compiledFolderName)
                                filePath := filepath.Join(compiled, 
"temp.class")
                                err := os.WriteFile(filePath, 
[]byte("TEMP_DATA"), 0600)
                                if err != nil {
-                                       panic(err)
+                                       return err
                                }
+                               return nil
                        },
+                       cleanup: cleanupFunc,
                        args: args{
                                executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
                        },
@@ -128,10 +169,11 @@ func Test_executableName(t *testing.T) {
                        wantErr: false,
                },
                {
-                       // Test case with calling sourceFileName method with 
wrong directory.
+                       // Test case with calling findExecutableName() function 
with wrong directory.
                        // As a result, want to receive an error.
                        name:    "Directory doesn't exist",
-                       prepare: func() {},
+                       prepare: func() error { return nil },
+                       cleanup: func() error { return nil },
                        args: args{
                                executableFolder: filepath.Join(workDir, 
pipelineId.String()),
                        },
@@ -139,39 +181,162 @@ func Test_executableName(t *testing.T) {
                        wantErr: true,
                },
                {
-                       // Test case with calling sourceFileName method with 
multiple files where one of them is main
+                       // Test case with calling findExecutableName() function 
with multiple files where one of them is main
                        // As a result, want to receive a name that should be 
executed
                        name: "Multiple files where one of them is main",
-                       prepare: func() {
+                       prepare: func() error {
                                compiled := filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), compiledFolderName)
-                               secondaryFilePath := filepath.Join(compiled, 
"temp.scala")
-                               err := os.WriteFile(secondaryFilePath, 
[]byte("TEMP_DATA"), 0600)
+                               primaryFilePath := filepath.Join(compiled, 
"main.scala")
+                               err := os.WriteFile(primaryFilePath, 
[]byte("object MinimalWordCount {def main(cmdlineArgs: Array[String]): Unit = 
{}}"), 0600)
                                if err != nil {
-                                       panic(err)
+                                       return err
                                }
-                               primaryFilePath := filepath.Join(compiled, 
"main.scala")
-                               err = os.WriteFile(primaryFilePath, 
[]byte("object MinimalWordCount {def main(cmdlineArgs: Array[String]): Unit = 
{}}"), 0600)
+                               secondaryFilePath := filepath.Join(compiled, 
"temp.scala")
+                               err = os.WriteFile(secondaryFilePath, 
[]byte("TEMP_DATA"), 0600)
                                if err != nil {
-                                       panic(err)
+                                       return err
                                }
+                               return nil
                        },
+                       cleanup: cleanupFunc,
                        args: args{
                                executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
                        },
                        want:    "main",
                        wantErr: false,
                },
+               {
+                       // Test case with calling findExecutableName() function 
with multiple files where one of them is a .class file
+                       // with main() method. The executable class name is the 
same as the source file name.
+                       // As a result, want to receive a name that should be 
executed
+                       name: "Multiple Java class files where one of them 
contains main",
+                       prepare: func() error {
+                               testdataPath := "java_testdata"
+                               sourceFile := filepath.Join(testdataPath, 
"HasMainTest1.java")
+
+                               err := compileJavaFiles(sourceFile)
+                               if err != nil {
+                                       return err
+                               }
+
+                               return nil
+                       },
+                       cleanup: cleanupFunc,
+                       args: args{
+                               executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
+                       },
+                       want:    "HasMainTest1",
+                       wantErr: false,
+               },
+               {
+                       // Test case with calling findExecutableName() function 
with multiple files where one of them is a .class file
+                       // with main() method. The executable class name is 
different from the source file name.
+                       // As a result, want to receive a name that should be 
executed
+                       name: "Multiple Java class files where one of them 
contains main",
+                       prepare: func() error {
+                               testdataPath := "java_testdata"
+                               // The source file contains two classes, one of 
them has main() method (class name is different from the source file name)
+                               sourceFile := filepath.Join(testdataPath, 
"HasMainTest2.java")
+
+                               err := compileJavaFiles(sourceFile)
+                               if err != nil {
+                                       return err
+                               }
+
+                               return nil
+                       },
+                       cleanup: cleanupFunc,
+                       args: args{
+                               executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
+                       },
+                       want:    "Bar",
+                       wantErr: false,
+               },
+               {
+                       // Test case with calling findExecutableName() function 
with multiple files where one of them is a .class file
+                       // with main() method with incorrect signature.
+                       // As a result, want to receive a name that should be 
executed
+                       name: "Multiple Java class files where one of them 
contains main() with incorrect signature",
+                       prepare: func() error {
+                               testdataPath := "java_testdata"
+                               sourceFile := filepath.Join(testdataPath, 
"HasIncorrectMain.java")
+
+                               err := compileJavaFiles(sourceFile)
+                               if err != nil {
+                                       return err
+                               }
+
+                               return nil
+                       },
+                       cleanup: cleanupFunc,
+                       args: args{
+                               executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
+                       },
+                       want:    "",
+                       wantErr: true,
+               },
+               {
+                       // Test case with calling findExecutableName() function 
with multiple files where none of them has main().
+                       // As a result, want to receive a name that should be 
executed
+                       name: "Multiple Java class files where none of them 
contain main()",
+                       prepare: func() error {
+                               testdataPath := "java_testdata"
+                               sourceFile := filepath.Join(testdataPath, 
"HasNoMain.java")
+
+                               err := compileJavaFiles(sourceFile)
+                               if err != nil {
+                                       return err
+                               }
+
+                               return nil
+                       },
+                       cleanup: cleanupFunc,
+                       args: args{
+                               executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
+                       },
+                       want:    "",
+                       wantErr: true,
+               },
+               {
+                       // Test case with calling findExecutableName() function 
with file which has multiple dots in its name
+                       // As a result, want to receive a name that should be 
executed
+                       name: "File with multiple dots in the name",
+                       prepare: func() error {
+                               compiled := filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), compiledFolderName)
+                               primaryFilePath := filepath.Join(compiled, 
"main.function.scala")
+                               err := os.WriteFile(primaryFilePath, 
[]byte("object MinimalWordCount {def main(cmdlineArgs: Array[String]): Unit = 
{}}"), 0600)
+                               if err != nil {
+                                       return err
+                               }
+                               return nil
+                       },
+                       cleanup: cleanupFunc,
+                       args: args{
+                               executableFolder: filepath.Join(workDir, 
pipelinesFolder, pipelineId.String(), "bin"),
+                       },
+                       want:    "main.function",
+                       wantErr: false,
+               },
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       tt.prepare()
-                       got, err := executableName(tt.args.executableFolder)
+                       err := tt.prepare()
+                       if err != nil {
+                               t.Errorf("java_fs_test cleanup error = %v", err)
+                       }
+                       defer func() {
+                               err = tt.cleanup()
+                               if err != nil {
+                                       t.Errorf("java_fs_test cleanup error = 
%v", err)
+                               }
+                       }()
+                       got, err := findExecutableName(context.Background(), 
tt.args.executableFolder)
                        if (err != nil) != tt.wantErr {
-                               t.Errorf("sourceFileName() error = %v, wantErr 
%v", err, tt.wantErr)
+                               t.Errorf("java_fs_test error = %v, wantErr %v", 
err, tt.wantErr)
                                return
                        }
                        if got != tt.want {
-                               t.Errorf("sourceFileName() got = %v, want %v", 
got, tt.want)
+                               t.Errorf("java_fs_test got = %v, want %v", got, 
tt.want)
                        }
                })
        }
diff --git 
a/playground/backend/internal/fs_tool/java_testdata/HasIncorrectMain.java 
b/playground/backend/internal/fs_tool/java_testdata/HasIncorrectMain.java
new file mode 100644
index 00000000000..c9309e9e89f
--- /dev/null
+++ b/playground/backend/internal/fs_tool/java_testdata/HasIncorrectMain.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+class Bar {
+    public static void bar() {
+        System.out.println("Hello");
+    }
+}
+
+class HasIncorrectMain {
+    public static void main() {
+        Bar.bar();
+    }
+}
diff --git 
a/playground/backend/internal/fs_tool/java_testdata/HasMainTest1.java 
b/playground/backend/internal/fs_tool/java_testdata/HasMainTest1.java
new file mode 100644
index 00000000000..718ea320581
--- /dev/null
+++ b/playground/backend/internal/fs_tool/java_testdata/HasMainTest1.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+class Bar {
+    public static void bar() {
+        System.out.println("Hello");
+    }
+}
+
+class HasMainTest1 {
+    public static void main(java.lang.String[] args) {
+        Bar.bar();
+    }
+}
diff --git 
a/playground/backend/internal/fs_tool/java_testdata/HasMainTest2.java 
b/playground/backend/internal/fs_tool/java_testdata/HasMainTest2.java
new file mode 100644
index 00000000000..299d88a18c9
--- /dev/null
+++ b/playground/backend/internal/fs_tool/java_testdata/HasMainTest2.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+class Bar {
+    public static void bar() {
+        System.out.println("Hello");
+    }
+    public static void main(java.lang.String[] args) {
+        Bar.bar();
+    }
+}
+
+class HasMainTest1 {
+
+}
diff --git a/playground/backend/internal/fs_tool/java_testdata/HasNoMain.java 
b/playground/backend/internal/fs_tool/java_testdata/HasNoMain.java
new file mode 100644
index 00000000000..c9e8c0e3d1b
--- /dev/null
+++ b/playground/backend/internal/fs_tool/java_testdata/HasNoMain.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+class Bar {
+    public static void bar() {
+        System.out.println("Hello");
+    }
+}
+
+class HasIncorrectMain {
+    public static void foo() {
+        Bar.bar();
+    }
+}
diff --git a/playground/backend/internal/fs_tool/scio_fs.go 
b/playground/backend/internal/fs_tool/scio_fs.go
index ea8d834ca8b..491c158d2b7 100644
--- a/playground/backend/internal/fs_tool/scio_fs.go
+++ b/playground/backend/internal/fs_tool/scio_fs.go
@@ -26,6 +26,6 @@ const (
 // newScioLifeCycle creates LifeCycle with scala SDK environment.
 func newScioLifeCycle(pipelineId uuid.UUID, pipelinesFolder string) *LifeCycle 
{
        lc := newInterpretedLifeCycle(pipelineId, pipelinesFolder, 
scioExecutableFileExtension)
-       lc.Paths.ExecutableName = executableName
+       lc.Paths.FindExecutableName = findExecutableName
        return lc
 }
diff --git a/playground/backend/internal/setup_tools/builder/setup_builder.go 
b/playground/backend/internal/setup_tools/builder/setup_builder.go
index 772cb6aebcf..f7d68582b06 100644
--- a/playground/backend/internal/setup_tools/builder/setup_builder.go
+++ b/playground/backend/internal/setup_tools/builder/setup_builder.go
@@ -16,6 +16,7 @@
 package builder
 
 import (
+       "context"
        "fmt"
        "path/filepath"
        "strings"
@@ -64,7 +65,7 @@ func Preparer(paths *fs_tool.LifeCyclePaths, sdkEnv 
*environment.BeamEnvs, valRe
 }
 
 // Compiler return executor with set args for compiler
-func Compiler(paths *fs_tool.LifeCyclePaths, sdkEnv *environment.BeamEnvs) 
*executors.ExecutorBuilder {
+func Compiler(paths *fs_tool.LifeCyclePaths, sdkEnv *environment.BeamEnvs) 
(*executors.ExecutorBuilder, error) {
        sdk := sdkEnv.ApacheBeamSdk
        executorConfig := sdkEnv.ExecutorConfig
        builder := executors.NewExecutorBuilder().
@@ -72,21 +73,38 @@ func Compiler(paths *fs_tool.LifeCyclePaths, sdkEnv 
*environment.BeamEnvs) *exec
                WithCommand(executorConfig.CompileCmd).
                WithWorkingDir(paths.AbsoluteBaseFolderPath).
                WithArgs(executorConfig.CompileArgs).
-               WithFileName(paths.AbsoluteSourceFilePath).
                ExecutorBuilder
 
        switch sdk {
        case pb.Sdk_SDK_JAVA:
+               javaSources, err := 
GetFilesFromFolder(paths.AbsoluteSourceFileFolderPath, 
fs_tool.JavaSourceFileExtension)
+               if err != nil {
+                       return nil, err
+               }
                builder = builder.
                        WithCompiler().
-                       
WithFileName(GetFirstFileFromFolder(paths.AbsoluteSourceFileFolderPath)).
+                       WithFileNames(javaSources...).
+                       ExecutorBuilder
+       case pb.Sdk_SDK_GO:
+               goSources, err := 
GetFilesFromFolder(paths.AbsoluteSourceFileFolderPath, 
fs_tool.GoSourceFileExtension)
+               if err != nil {
+                       return nil, err
+               }
+               builder = builder.
+                       WithCompiler().
+                       WithFileNames(goSources...).
+                       ExecutorBuilder
+       default:
+               builder = builder.
+                       WithCompiler().
+                       WithFileNames(paths.AbsoluteSourceFilePath).
                        ExecutorBuilder
        }
-       return &builder
+       return &builder, nil
 }
 
 // Runner return executor with set args for runner
-func Runner(paths *fs_tool.LifeCyclePaths, pipelineOptions string, sdkEnv 
*environment.BeamEnvs) (*executors.ExecutorBuilder, error) {
+func Runner(ctx context.Context, paths *fs_tool.LifeCyclePaths, 
pipelineOptions string, sdkEnv *environment.BeamEnvs) 
(*executors.ExecutorBuilder, error) {
        sdk := sdkEnv.ApacheBeamSdk
 
        if sdk == pb.Sdk_SDK_JAVA || sdk == pb.Sdk_SDK_SCIO {
@@ -103,31 +121,30 @@ func Runner(paths *fs_tool.LifeCyclePaths, 
pipelineOptions string, sdkEnv *envir
        switch sdk {
        case pb.Sdk_SDK_JAVA: // Executable name for java class is known after 
compilation
                args := replaceLogPlaceholder(paths, executorConfig)
-               className, err := 
paths.ExecutableName(paths.AbsoluteExecutableFileFolderPath)
+               className, err := paths.FindExecutableName(ctx, 
paths.AbsoluteExecutableFileFolderPath)
                if err != nil {
                        return nil, fmt.Errorf("no executable file name found 
for JAVA pipeline at %s", paths.AbsoluteExecutableFileFolderPath)
                }
                builder = builder.
                        WithRunner().
                        WithArgs(args).
-                       WithExecutableFileName(className).
+                       WithExecutableFileNames(className).
                        WithPipelineOptions(strings.Split(pipelineOptions, " 
")).
                        ExecutorBuilder
        case pb.Sdk_SDK_GO: //go run command is executable file itself
                builder = builder.
                        WithRunner().
-                       WithExecutableFileName("").
                        WithCommand(paths.AbsoluteExecutableFilePath).
                        WithPipelineOptions(strings.Split(pipelineOptions, " 
")).
                        ExecutorBuilder
        case pb.Sdk_SDK_PYTHON:
                builder = builder.
                        WithRunner().
-                       
WithExecutableFileName(paths.AbsoluteExecutableFilePath).
+                       
WithExecutableFileNames(paths.AbsoluteExecutableFilePath).
                        WithPipelineOptions(strings.Split(pipelineOptions, " 
")).
                        ExecutorBuilder
        case pb.Sdk_SDK_SCIO:
-               className, err := 
paths.ExecutableName(paths.AbsoluteBaseFolderPath)
+               className, err := paths.FindExecutableName(ctx, 
paths.AbsoluteBaseFolderPath)
                if err != nil {
                        return nil, fmt.Errorf("no executable file name found 
for SCIO pipeline at %s", paths.AbsoluteBaseFolderPath)
                }
@@ -142,12 +159,11 @@ func Runner(paths *fs_tool.LifeCyclePaths, 
pipelineOptions string, sdkEnv *envir
 }
 
 // TestRunner return executor with set args for runner
-func TestRunner(paths *fs_tool.LifeCyclePaths, sdkEnv *environment.BeamEnvs) 
(*executors.ExecutorBuilder, error) {
+func TestRunner(ctx context.Context, paths *fs_tool.LifeCyclePaths, sdkEnv 
*environment.BeamEnvs) (*executors.ExecutorBuilder, error) {
        sdk := sdkEnv.ApacheBeamSdk
        executorConfig := sdkEnv.ExecutorConfig
        builder := executors.NewExecutorBuilder().
                WithTestRunner().
-               WithExecutableFileName(paths.AbsoluteExecutableFilePath).
                WithCommand(executorConfig.TestCmd).
                WithArgs(executorConfig.TestArgs).
                WithWorkingDir(paths.AbsoluteSourceFileFolderPath).
@@ -155,7 +171,7 @@ func TestRunner(paths *fs_tool.LifeCyclePaths, sdkEnv 
*environment.BeamEnvs) (*e
 
        switch sdk {
        case pb.Sdk_SDK_JAVA: // Executable name for java class is known after 
compilation
-               className, err := 
paths.ExecutableName(paths.AbsoluteExecutableFileFolderPath)
+               className, err := paths.FindTestExecutableName(ctx, 
paths.AbsoluteExecutableFileFolderPath)
                if err != nil {
                        return nil, fmt.Errorf("no executable file name found 
for JAVA pipeline at %s", paths.AbsoluteExecutableFileFolderPath)
                }
@@ -167,6 +183,10 @@ func TestRunner(paths *fs_tool.LifeCyclePaths, sdkEnv 
*environment.BeamEnvs) (*e
                builder = builder.WithTestRunner().
                        
WithExecutableFileName(paths.AbsoluteSourceFileFolderPath). // run all tests in 
folder
                        ExecutorBuilder
+       default:
+               builder = builder.WithTestRunner().
+                       
WithExecutableFileName(paths.AbsoluteExecutableFilePath).
+                       ExecutorBuilder
        }
        return &builder, nil
 }
@@ -185,7 +205,6 @@ func replaceLogPlaceholder(paths *fs_tool.LifeCyclePaths, 
executorConfig *enviro
 }
 
 // GetFirstFileFromFolder return a name of the first file in a specified folder
-func GetFirstFileFromFolder(folderAbsolutePath string) string {
-       files, _ := filepath.Glob(fmt.Sprintf("%s/*%s", folderAbsolutePath, 
fs_tool.JavaSourceFileExtension))
-       return files[0]
+func GetFilesFromFolder(folderAbsolutePath string, extension string) 
([]string, error) {
+       return filepath.Glob(fmt.Sprintf("%s/*%s", folderAbsolutePath, 
extension))
 }
diff --git 
a/playground/backend/internal/setup_tools/builder/setup_builder_test.go 
b/playground/backend/internal/setup_tools/builder/setup_builder_test.go
index 08eaab3d542..2387a0c9164 100644
--- a/playground/backend/internal/setup_tools/builder/setup_builder_test.go
+++ b/playground/backend/internal/setup_tools/builder/setup_builder_test.go
@@ -16,7 +16,9 @@
 package builder
 
 import (
+       "context"
        "fmt"
+       "log"
        "os"
        "path/filepath"
        "reflect"
@@ -38,7 +40,10 @@ const emptyFolder = "emptyFolder"
 
 var pythonPaths *fs_tool.LifeCyclePaths
 var pythonSdkEnv *environment.BeamEnvs
+var pythonLC *fs_tool.LifeCycle
 var javaLC *fs_tool.LifeCycle
+var goLC *fs_tool.LifeCycle
+var scioLC *fs_tool.LifeCycle
 var javaPaths *fs_tool.LifeCyclePaths
 var javaSdkEnv *environment.BeamEnvs
 var goPaths *fs_tool.LifeCyclePaths
@@ -47,30 +52,80 @@ var scioPaths *fs_tool.LifeCyclePaths
 var scioSdkEnv *environment.BeamEnvs
 
 func TestMain(m *testing.M) {
-       setup()
-       defer teardown()
+       err := setup()
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer func() {
+               err = teardown()
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }()
        m.Run()
 }
 
-func setup() {
-       os.Mkdir(emptyFolder, 0666)
-
-       pipelineId := uuid.New()
+func setup() error {
+       err := os.Mkdir(emptyFolder, 0666)
+       if err != nil {
+               return err
+       }
 
-       pythonLC, _ := fs_tool.NewLifeCycle(pb.Sdk_SDK_PYTHON, pipelineId, "")
+       pythonPipelineId := uuid.New()
+       pythonLC, err = fs_tool.NewLifeCycle(pb.Sdk_SDK_PYTHON, 
pythonPipelineId, "")
+       if err != nil {
+               return err
+       }
+       err = pythonLC.CreateFolders()
+       if err != nil {
+               return err
+       }
        pythonPaths = &pythonLC.Paths
 
-       javaLC, _ = fs_tool.NewLifeCycle(pb.Sdk_SDK_JAVA, pipelineId, "")
+       javaPipelineId := uuid.New()
+       javaLC, err = fs_tool.NewLifeCycle(pb.Sdk_SDK_JAVA, javaPipelineId, "")
+       if err != nil {
+               return err
+       }
        javaPaths = &javaLC.Paths
-       javaLC.CreateFolders()
-       os.Create(filepath.Join(javaPaths.AbsoluteExecutableFilePath))
-       os.Create(filepath.Join(javaPaths.AbsoluteSourceFilePath))
+       err = javaLC.CreateFolders()
+       if err != nil {
+               return err
+       }
+       _, err = os.Create(filepath.Join(javaPaths.AbsoluteExecutableFilePath))
+       if err != nil {
+               return err
+       }
+       _, err = os.Create(filepath.Join(javaPaths.AbsoluteSourceFilePath))
+       if err != nil {
+               return err
+       }
 
-       goLC, _ := fs_tool.NewLifeCycle(pb.Sdk_SDK_GO, pipelineId, "")
+       goPipelineId := uuid.New()
+       goLC, err = fs_tool.NewLifeCycle(pb.Sdk_SDK_GO, goPipelineId, "")
+       if err != nil {
+               return err
+       }
+       err = goLC.CreateFolders()
+       if err != nil {
+               return err
+       }
        goPaths = &goLC.Paths
 
-       scioLC, _ := fs_tool.NewLifeCycle(pb.Sdk_SDK_SCIO, pipelineId, "")
+       scioPipelineId := uuid.New()
+       scioLC, err = fs_tool.NewLifeCycle(pb.Sdk_SDK_SCIO, scioPipelineId, "")
+       if err != nil {
+               return err
+       }
+       err = scioLC.CreateFolders()
+       if err != nil {
+               return err
+       }
        scioPaths = &scioLC.Paths
+       _, err = os.Create(filepath.Join(scioPaths.AbsoluteSourceFilePath))
+       if err != nil {
+               return err
+       }
 
        executorConfig := &environment.ExecutorConfig{
                CompileCmd:  "MOCK_COMPILE_CMD",
@@ -81,11 +136,32 @@ func setup() {
        javaSdkEnv = environment.NewBeamEnvs(pb.Sdk_SDK_JAVA, "", 
executorConfig, "", 0)
        goSdkEnv = environment.NewBeamEnvs(pb.Sdk_SDK_GO, "", executorConfig, 
"", 0)
        scioSdkEnv = environment.NewBeamEnvs(pb.Sdk_SDK_SCIO, "", 
executorConfig, "", 0)
+
+       return nil
 }
 
-func teardown() {
-       os.Remove(emptyFolder)
-       javaLC.DeleteFolders()
+func teardown() error {
+       err := os.Remove(emptyFolder)
+       if err != nil {
+               return err
+       }
+       err = pythonLC.DeleteFolders()
+       if err != nil {
+               return err
+       }
+       err = javaLC.DeleteFolders()
+       if err != nil {
+               return err
+       }
+       err = goLC.DeleteFolders()
+       if err != nil {
+               return err
+       }
+       err = scioLC.DeleteFolders()
+       if err != nil {
+               return err
+       }
+       return nil
 }
 
 func TestValidator(t *testing.T) {
@@ -307,26 +383,36 @@ func TestPreparer(t *testing.T) {
 }
 
 func TestCompiler(t *testing.T) {
+       javaSources, err := 
GetFilesFromFolder(javaPaths.AbsoluteSourceFileFolderPath, 
fs_tool.JavaSourceFileExtension)
+       if err != nil {
+               t.Errorf("Failed to get Java source files, error = %v", err)
+       }
+
        wantJavaExecutor := executors.NewExecutorBuilder().
                WithCompiler().
                WithCommand(javaSdkEnv.ExecutorConfig.CompileCmd).
                WithWorkingDir(javaPaths.AbsoluteBaseFolderPath).
                WithArgs(javaSdkEnv.ExecutorConfig.CompileArgs).
-               
WithFileName(GetFirstFileFromFolder(javaPaths.AbsoluteSourceFileFolderPath))
+               WithFileNames(javaSources...)
+
+       goSources, err := 
GetFilesFromFolder(goPaths.AbsoluteSourceFileFolderPath, 
fs_tool.GoSourceFileExtension)
+       if err != nil {
+               t.Errorf("Failed to get Go source files, error = %v", err)
+       }
 
        wantGoExecutor := executors.NewExecutorBuilder().
                WithCompiler().
                WithCommand(goSdkEnv.ExecutorConfig.CompileCmd).
                WithWorkingDir(goPaths.AbsoluteBaseFolderPath).
                WithArgs(goSdkEnv.ExecutorConfig.CompileArgs).
-               WithFileName(goPaths.AbsoluteSourceFilePath)
+               WithFileNames(goSources...)
 
        wantScioExecutor := executors.NewExecutorBuilder().
                WithCompiler().
                WithCommand(scioSdkEnv.ExecutorConfig.CompileCmd).
                WithWorkingDir(scioPaths.AbsoluteBaseFolderPath).
                WithArgs(scioSdkEnv.ExecutorConfig.CompileArgs).
-               WithFileName(scioPaths.AbsoluteSourceFilePath)
+               WithFileNames(scioPaths.AbsoluteSourceFilePath)
 
        type args struct {
                paths  *fs_tool.LifeCyclePaths
@@ -366,7 +452,10 @@ func TestCompiler(t *testing.T) {
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       got := Compiler(tt.args.paths, tt.args.sdkEnv)
+                       got, err := Compiler(tt.args.paths, tt.args.sdkEnv)
+                       if err != nil {
+                               t.Errorf("Compiler() error = %v", err)
+                       }
                        if !reflect.DeepEqual(fmt.Sprint(got.Build()), 
fmt.Sprint(tt.want.Build())) {
                                t.Errorf("Compiler() = %v, want %v", 
got.Build(), tt.want.Build())
                        }
@@ -381,20 +470,20 @@ func TestRunnerBuilder(t *testing.T) {
 
        wantPythonExecutor := executors.NewExecutorBuilder().
                WithRunner().
-               WithExecutableFileName(pythonPaths.AbsoluteExecutableFilePath).
+               WithExecutableFileNames(pythonPaths.AbsoluteExecutableFilePath).
                WithWorkingDir(pythonPaths.AbsoluteBaseFolderPath).
                WithCommand(pythonSdkEnv.ExecutorConfig.RunCmd).
                WithArgs(pythonSdkEnv.ExecutorConfig.RunArgs).
                WithPipelineOptions(strings.Split("", " "))
 
        arg := replaceLogPlaceholder(javaPaths, javaSdkEnv.ExecutorConfig)
-       javaClassName, err := 
javaPaths.ExecutableName(javaPaths.AbsoluteExecutableFileFolderPath)
+       javaClassName, err := 
javaPaths.FindExecutableName(context.Background(), 
javaPaths.AbsoluteExecutableFileFolderPath)
        if err != nil {
-               panic(err)
+               t.Errorf("Cannot get executable name for Java, error = %v", err)
        }
        wantJavaExecutor := executors.NewExecutorBuilder().
                WithRunner().
-               WithExecutableFileName(javaClassName).
+               WithExecutableFileNames(javaClassName).
                WithWorkingDir(javaPaths.AbsoluteBaseFolderPath).
                WithCommand(javaSdkEnv.ExecutorConfig.RunCmd).
                WithArgs(arg).
@@ -404,13 +493,13 @@ func TestRunnerBuilder(t *testing.T) {
                WithRunner().
                WithWorkingDir(goPaths.AbsoluteBaseFolderPath).
                WithCommand(goPaths.AbsoluteExecutableFilePath).
-               WithExecutableFileName("").
+               WithExecutableFileNames("").
                WithArgs(goSdkEnv.ExecutorConfig.RunArgs).
                WithPipelineOptions(strings.Split("", " "))
 
-       scioClassName, err := 
scioPaths.ExecutableName(scioPaths.AbsoluteBaseFolderPath)
+       scioClassName, err := 
scioPaths.FindExecutableName(context.Background(), 
scioPaths.AbsoluteSourceFileFolderPath)
        if err != nil {
-               panic(err)
+               t.Errorf("Cannot get executable name for SCIO, error = %v", err)
        }
        stringArg := fmt.Sprintf("%s %s %s", 
scioSdkEnv.ExecutorConfig.RunArgs[0], scioClassName, "")
        wantScioExecutor := executors.NewExecutorBuilder().
@@ -482,7 +571,7 @@ func TestRunnerBuilder(t *testing.T) {
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       got, _ := Runner(tt.args.paths, 
tt.args.pipelineOptions, tt.args.sdkEnv)
+                       got, _ := Runner(context.Background(), tt.args.paths, 
tt.args.pipelineOptions, tt.args.sdkEnv)
                        if tt.want != nil {
                                if !reflect.DeepEqual(fmt.Sprint(got.Build()), 
fmt.Sprint(tt.want.Build())) {
                                        t.Errorf("Runner() got = %v, want %v", 
got.Build(), tt.want.Build())
@@ -500,7 +589,7 @@ func TestTestRunner(t *testing.T) {
        incorrectJavaPaths := *javaPaths
        incorrectJavaPaths.AbsoluteExecutableFileFolderPath = emptyFolder
 
-       className, err := 
javaPaths.ExecutableName(javaPaths.AbsoluteExecutableFileFolderPath)
+       className, err := javaPaths.FindExecutableName(context.Background(), 
javaPaths.AbsoluteExecutableFileFolderPath)
        if err != nil {
                panic(err)
        }
@@ -569,7 +658,7 @@ func TestTestRunner(t *testing.T) {
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       got, _ := TestRunner(tt.args.paths, tt.args.sdkEnv)
+                       got, _ := TestRunner(context.Background(), 
tt.args.paths, tt.args.sdkEnv)
                        if tt.want != nil {
                                if !reflect.DeepEqual(fmt.Sprint(got.Build()), 
fmt.Sprint(tt.want.Build())) {
                                        t.Errorf("TestRunner() got = %v, want 
%v", got.Build(), tt.want.Build())
diff --git 
a/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go 
b/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go
index 878d73efb1c..ee1d92dcca6 100644
--- a/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go
+++ b/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go
@@ -206,7 +206,7 @@ func prepareSbtFiles(lc *fs_tool.LifeCycle, pipelineFolder 
string, workingDir st
        absLogFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, 
logFileName))
        absGraphFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, 
utils.GraphFileName))
        projectFolder, _ := filepath.Abs(filepath.Join(pipelineFolder, 
scioProjectName))
-       executableName := lc.Paths.ExecutableName
+       executableName := lc.Paths.FindExecutableName
 
        err = os.Remove(filepath.Join(absFileFolderPath, defaultExampleInSbt))
        if err != nil {
@@ -229,7 +229,7 @@ func prepareSbtFiles(lc *fs_tool.LifeCycle, pipelineFolder 
string, workingDir st
                AbsoluteLogFilePath:              absLogFilePath,
                AbsoluteGraphFilePath:            absGraphFilePath,
                ProjectDir:                       projectFolder,
-               ExecutableName:                   executableName,
+               FindExecutableName:               executableName,
        }
 
        return lc, nil
diff --git a/playground/backend/internal/utils/file_utils.go 
b/playground/backend/internal/utils/file_utils.go
index d303fe75649..5e0d524363c 100644
--- a/playground/backend/internal/utils/file_utils.go
+++ b/playground/backend/internal/utils/file_utils.go
@@ -171,3 +171,7 @@ func ToSDKFromExt(ext string) pb.Sdk {
                return pb.Sdk_SDK_UNSPECIFIED
        }
 }
+
+func TrimExtension(filename string) string {
+       return strings.TrimSuffix(filename, filepath.Ext(filename))
+}

Reply via email to