You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by pa...@apache.org on 2021/11/23 02:12:26 UTC

[beam] branch master updated: Merge pull request #16031 from [BEAM-13296][Playground][Bugfix] remove redundant code from controller

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

pabloem 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 8ad47d8  Merge pull request #16031 from [BEAM-13296][Playground][Bugfix] remove redundant code from controller
8ad47d8 is described below

commit 8ad47d8a8d4ba59a05013dda8b0fe427a546bab2
Author: daria.malkova <da...@akvelon.com>
AuthorDate: Tue Nov 23 05:11:31 2021 +0300

    Merge pull request #16031 from [BEAM-13296][Playground][Bugfix] remove redundant code from controller
    
    * [Playground][BEAM-12941][Bugfix] Fix workflows for playground applications (#83)
    
    * Update workflows for playground
    
    * Attempt to fix tests
    
    * Remove continue on error to catch errors
    
    * Fix linter problem for backend dockerfile
    
    * Update folder to run backend go linter
    
    * Moved flutter test to execution via gradle tasks
    
    * Revert "[Playground][BEAM-12941][Bugfix] Fix workflows for playground applications (#83)" (#88)
    
    This reverts commit b73f5f70ac4184b56a0a03922731c5f2f69b9566.
    
    * Remove redundant methods from controller, move precompiled objects method to separate package
    
    * fix RAT
    
    Co-authored-by: Sergey Kalinin <91...@users.noreply.github.com>
    Co-authored-by: Ilya <il...@akvelon.com>
    Co-authored-by: Aydar Zainutdinov <ay...@akvelon.com>
    Co-authored-by: Pavel Avilov <pa...@akvelon.com>
---
 playground/backend/cmd/server/controller.go        | 335 +--------------------
 .../internal/utils/precompiled_objects_utils.go    |  38 +++
 .../utils/precompiled_objects_utils_test.go        |  75 +++++
 3 files changed, 117 insertions(+), 331 deletions(-)

diff --git a/playground/backend/cmd/server/controller.go b/playground/backend/cmd/server/controller.go
index 6c90474..f42266c 100644
--- a/playground/backend/cmd/server/controller.go
+++ b/playground/backend/cmd/server/controller.go
@@ -160,14 +160,14 @@ func (controller *playgroundController) GetPrecompiledObjects(ctx context.Contex
 	bucket := cloud_bucket.New()
 	sdkToCategories, err := bucket.GetPrecompiledObjects(ctx, info.Sdk, info.Category)
 	if err != nil {
-		logger.Errorf("%s: GetPrecompiledObjects(): cloud storage error: %s", err.Error())
+		logger.Errorf("GetPrecompiledObjects(): cloud storage error: %s", err.Error())
 		return nil, errors.InternalError("GetPrecompiledObjects(): ", err.Error())
 	}
 	response := pb.GetPrecompiledObjectsResponse{SdkCategories: make([]*pb.Categories, 0)}
 	for sdkName, categories := range *sdkToCategories {
 		sdkCategory := pb.Categories{Sdk: pb.Sdk(pb.Sdk_value[sdkName]), Categories: make([]*pb.Categories_Category, 0)}
 		for categoryName, precompiledObjects := range categories {
-			PutPrecompiledObjectsToCategory(categoryName, &precompiledObjects, &sdkCategory)
+			utils.PutPrecompiledObjectsToCategory(categoryName, &precompiledObjects, &sdkCategory)
 		}
 		response.SdkCategories = append(response.SdkCategories, &sdkCategory)
 	}
@@ -179,7 +179,7 @@ func (controller *playgroundController) GetPrecompiledObjectCode(ctx context.Con
 	cd := cloud_bucket.New()
 	codeString, err := cd.GetPrecompiledObject(ctx, info.GetCloudPath())
 	if err != nil {
-		logger.Errorf("%s: GetPrecompiledObject(): cloud storage error: %s", err.Error())
+		logger.Errorf("GetPrecompiledObject(): cloud storage error: %s", err.Error())
 		return nil, errors.InternalError("GetPrecompiledObjects(): ", err.Error())
 	}
 	response := pb.GetPrecompiledObjectCodeResponse{Code: *codeString}
@@ -191,336 +191,9 @@ func (controller *playgroundController) GetPrecompiledObjectOutput(ctx context.C
 	cd := cloud_bucket.New()
 	output, err := cd.GetPrecompiledObjectOutput(ctx, info.GetCloudPath())
 	if err != nil {
-		logger.Errorf("%s: GetPrecompiledObjectOutput(): cloud storage error: %s", err.Error())
+		logger.Errorf("GetPrecompiledObjectOutput(): cloud storage error: %s", err.Error())
 		return nil, errors.InternalError("GetPrecompiledObjectOutput(): ", err.Error())
 	}
 	response := pb.GetRunOutputResponse{Output: *output}
 	return &response, nil
 }
-
-// PutPrecompiledObjectsToCategory adds categories with precompiled objects to protobuf object
-func PutPrecompiledObjectsToCategory(categoryName string, precompiledObjects *cloud_bucket.PrecompiledObjects, sdkCategory *pb.Categories) {
-	category := pb.Categories_Category{
-		CategoryName:       categoryName,
-		PrecompiledObjects: make([]*pb.PrecompiledObject, 0),
-	}
-	for _, object := range *precompiledObjects {
-		category.PrecompiledObjects = append(category.PrecompiledObjects, &pb.PrecompiledObject{
-			CloudPath:   object.CloudPath,
-			Name:        object.Name,
-			Description: object.Description,
-			Type:        object.Type,
-		})
-	}
-	sdkCategory.Categories = append(sdkCategory.Categories, &category)
-}
-
-// setupLifeCycle creates fs_tool.LifeCycle and prepares files and folders needed to code processing
-func setupLifeCycle(sdk pb.Sdk, code string, pipelineId uuid.UUID, workingDir string) (*fs_tool.LifeCycle, error) {
-	// create file system service
-	lc, err := fs_tool.NewLifeCycle(sdk, pipelineId, workingDir)
-	if err != nil {
-		logger.Errorf("%s: RunCode(): NewLifeCycle(): %s\n", pipelineId, err.Error())
-		return nil, err
-	}
-
-	// create folders
-	err = lc.CreateFolders()
-	if err != nil {
-		logger.Errorf("%s: RunCode(): CreateFolders(): %s\n", pipelineId, err.Error())
-		return nil, err
-	}
-
-	// create file with code
-	_, err = lc.CreateExecutableFile(code)
-	if err != nil {
-		logger.Errorf("%s: RunCode(): CreateExecutableFile(): %s\n", pipelineId, err.Error())
-		return nil, err
-	}
-	return lc, nil
-}
-
-// setupCompileBuilder returns executors.CompileBuilder with validators and compiler based on sdk
-func setupCompileBuilder(lc *fs_tool.LifeCycle, sdk pb.Sdk, executorConfig *environment.ExecutorConfig) *executors.CompileBuilder {
-	filePath := lc.GetAbsoluteExecutableFilePath()
-	val := setupValidators(sdk, filePath)
-
-	compileBuilder := executors.NewExecutorBuilder().
-		WithValidator().
-		WithSdkValidators(val).
-		WithCompiler()
-
-	switch sdk {
-	case pb.Sdk_SDK_JAVA:
-		workingDir := lc.GetAbsoluteExecutableFilesFolderPath()
-
-		compileBuilder = compileBuilder.
-			WithCommand(executorConfig.CompileCmd).
-			WithArgs(executorConfig.CompileArgs).
-			WithFileName(filePath).
-			WithWorkingDir(workingDir)
-	}
-	return compileBuilder
-}
-
-// setupRunBuilder returns executors.RunBuilder based on sdk
-func setupRunBuilder(pipelineId uuid.UUID, lc *fs_tool.LifeCycle, sdk pb.Sdk, env *environment.Environment, compileBuilder *executors.CompileBuilder) (*executors.RunBuilder, error) {
-	runBuilder := compileBuilder.
-		WithRunner().
-		WithCommand(env.BeamSdkEnvs.ExecutorConfig.RunCmd).
-		WithArgs(env.BeamSdkEnvs.ExecutorConfig.RunArgs).
-		WithWorkingDir(lc.GetAbsoluteExecutableFilesFolderPath())
-
-	switch sdk {
-	case pb.Sdk_SDK_JAVA:
-		className, err := lc.ExecutableName(pipelineId, env.ApplicationEnvs.WorkingDir())
-		if err != nil {
-			logger.Errorf("%s: get executable file name: %s\n", pipelineId, err.Error())
-			return nil, err
-		}
-
-		runBuilder = runBuilder.
-			WithClassName(className)
-	}
-	return runBuilder, nil
-}
-
-// setupValidators returns slice of validators.Validator based on sdk
-func setupValidators(sdk pb.Sdk, filepath string) *[]validators.Validator {
-	var val *[]validators.Validator
-	switch sdk {
-	case pb.Sdk_SDK_JAVA:
-		val = validators.GetJavaValidators(filepath)
-	}
-	return val
-}
-
-// processCode validates, compiles and runs code by pipelineId.
-// During each operation updates status of execution and saves it into cache:
-// - In case of processing works more that timeout duration saves playground.Status_STATUS_RUN_TIMEOUT as cache.Status into cache.
-// - In case of code processing has been canceled saves playground.Status_STATUS_CANCELED as cache.Status into cache.
-// - In case of validation step is failed saves playground.Status_STATUS_VALIDATION_ERROR as cache.Status into cache.
-// - In case of compile step is failed saves playground.Status_STATUS_COMPILE_ERROR as cache.Status and compile logs as cache.CompileOutput into cache.
-// - In case of compile step is completed with no errors saves compile output as cache.CompileOutput into cache.
-// - In case of run step is failed saves playground.Status_STATUS_RUN_ERROR as cache.Status and run logs as cache.RunError into cache.
-// - In case of run step is completed with no errors saves playground.Status_STATUS_FINISHED as cache.Status and run output as cache.RunOutput into cache.
-// At the end of this method deletes all created folders.
-func processCode(ctx context.Context, cacheService cache.Cache, lc *fs_tool.LifeCycle, compileBuilder *executors.CompileBuilder, pipelineId uuid.UUID, env *environment.Environment, sdk pb.Sdk) {
-	ctxWithTimeout, finishCtxFunc := context.WithTimeout(ctx, env.ApplicationEnvs.PipelineExecuteTimeout())
-	defer func(lc *fs_tool.LifeCycle) {
-		finishCtxFunc()
-		cleanUp(pipelineId, lc)
-	}(lc)
-
-	errorChannel := make(chan error, 1)
-	dataChannel := make(chan interface{}, 1)
-	successChannel := make(chan bool, 1)
-	cancelChannel := make(chan bool, 1)
-
-	go cancelCheck(ctxWithTimeout, pipelineId, cancelChannel, cacheService)
-
-	// build executor for validate and compile steps
-	exec := compileBuilder.Build()
-
-	// validate
-	logger.Infof("%s: Validate() ...\n", pipelineId)
-	validateFunc := exec.Validate()
-	go validateFunc(successChannel, errorChannel)
-
-	if !processStep(ctxWithTimeout, pipelineId, cacheService, cancelChannel, successChannel, nil, errorChannel, pb.Status_STATUS_VALIDATION_ERROR, pb.Status_STATUS_PREPARING) {
-		return
-	}
-
-	// prepare
-	logger.Infof("%s: Prepare() ...\n", pipelineId)
-	prepareFunc := exec.Prepare()
-	go prepareFunc(successChannel, errorChannel)
-
-	if !processStep(ctxWithTimeout, pipelineId, cacheService, cancelChannel, successChannel, nil, errorChannel, pb.Status_STATUS_PREPARATION_ERROR, pb.Status_STATUS_COMPILING) {
-		return
-	}
-
-	// compile
-	logger.Infof("%s: Compile() ...\n", pipelineId)
-	compileCmd := exec.Compile(ctxWithTimeout)
-	go func(successCh chan bool, errCh chan error, dataCh chan interface{}) {
-		// TODO separate stderr from stdout
-		data, err := compileCmd.CombinedOutput()
-		dataCh <- data
-		if err != nil {
-			errCh <- err
-			successCh <- false
-		} else {
-			successCh <- true
-		}
-	}(successChannel, errorChannel, dataChannel)
-
-	if !processStep(ctxWithTimeout, pipelineId, cacheService, cancelChannel, successChannel, dataChannel, errorChannel, pb.Status_STATUS_COMPILE_ERROR, pb.Status_STATUS_EXECUTING) {
-		return
-	}
-
-	runBuilder, err := setupRunBuilder(pipelineId, lc, sdk, env, compileBuilder)
-	if err != nil {
-		logger.Errorf("%s: error during setup runBuilder: %s\n", pipelineId, err.Error())
-		setToCache(ctxWithTimeout, cacheService, pipelineId, cache.Status, pb.Status_STATUS_ERROR)
-		return
-	}
-
-	// build executor for run step
-	exec = runBuilder.Build()
-
-	// run
-	logger.Infof("%s: Run() ...\n", pipelineId)
-	runCmd := exec.Run(ctxWithTimeout)
-	go func(successCh chan bool, errCh chan error, dataCh chan interface{}) {
-		// TODO separate stderr from stdout
-		data, err := runCmd.CombinedOutput()
-		dataCh <- data
-		if err != nil {
-			errCh <- err
-			successChannel <- false
-		} else {
-			successChannel <- true
-		}
-	}(successChannel, errorChannel, dataChannel)
-
-	processStep(ctxWithTimeout, pipelineId, cacheService, cancelChannel, successChannel, dataChannel, errorChannel, pb.Status_STATUS_RUN_ERROR, pb.Status_STATUS_FINISHED)
-}
-
-// processStep processes each executor's step with cancel and timeout checks.
-// If finishes by canceling, timeout or error - returns false.
-// If finishes successfully returns true.
-func processStep(ctx context.Context, pipelineId uuid.UUID, cacheService cache.Cache, cancelChannel, successChannel chan bool, dataChannel chan interface{}, errorChannel chan error, errorCaseStatus, successCaseStatus pb.Status) bool {
-	select {
-	case <-ctx.Done():
-		finishByTimeout(ctx, pipelineId, cacheService)
-		return false
-	case <-cancelChannel:
-		processCancel(ctx, cacheService, pipelineId)
-		return false
-	case ok := <-successChannel:
-		var data []byte = nil
-		if dataChannel != nil {
-			temp := <-dataChannel
-			data = temp.([]byte)
-		}
-		if !ok {
-			err := <-errorChannel
-			processError(ctx, err, data, pipelineId, cacheService, errorCaseStatus)
-			return false
-		}
-		processSuccess(ctx, data, pipelineId, cacheService, successCaseStatus)
-	}
-	return true
-}
-
-// finishByTimeout is used in case of runCode method finished by timeout
-func finishByTimeout(ctx context.Context, pipelineId uuid.UUID, cacheService cache.Cache) {
-	logger.Errorf("%s: processCode finish because of timeout\n", pipelineId)
-
-	// set to cache pipelineId: cache.SubKey_Status: Status_STATUS_RUN_TIMEOUT
-	setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_RUN_TIMEOUT)
-}
-
-// cancelCheck checks cancel flag for code processing.
-// If cancel flag doesn't exist in cache continue working.
-// If context is done it means that code processing was finished (successfully/with error/timeout). Return.
-// If cancel flag exists, and it is true it means that code processing was canceled. Set true to cancelChannel and return.
-func cancelCheck(ctx context.Context, pipelineId uuid.UUID, cancelChannel chan bool, cacheService cache.Cache) {
-	ticker := time.NewTicker(500 * time.Millisecond)
-	for {
-		select {
-		case <-ctx.Done():
-			ticker.Stop()
-			return
-		case _ = <-ticker.C:
-			cancel, err := cacheService.GetValue(ctx, pipelineId, cache.Canceled)
-			if err != nil {
-				continue
-			}
-			if cancel.(bool) {
-				cancelChannel <- true
-			}
-			return
-		}
-	}
-}
-
-// cleanUp removes all prepared folders for received LifeCycle
-func cleanUp(pipelineId uuid.UUID, lc *fs_tool.LifeCycle) {
-	logger.Infof("%s: DeleteFolders() ...\n", pipelineId)
-	if err := lc.DeleteFolders(); err != nil {
-		logger.Error("%s: DeleteFolders(): %s\n", pipelineId, err.Error())
-	}
-	logger.Infof("%s: DeleteFolders() complete\n", pipelineId)
-	logger.Infof("%s: complete\n", pipelineId)
-}
-
-// processError processes error received during processing code via setting a corresponding status and output to cache
-func processError(ctx context.Context, err error, data []byte, pipelineId uuid.UUID, cacheService cache.Cache, status pb.Status) {
-	switch status {
-	case pb.Status_STATUS_VALIDATION_ERROR:
-		logger.Errorf("%s: Validate(): %s\n", pipelineId, err.Error())
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_VALIDATION_ERROR)
-	case pb.Status_STATUS_PREPARATION_ERROR:
-		logger.Errorf("%s: Prepare(): %s\n", pipelineId, err.Error())
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_PREPARATION_ERROR)
-	case pb.Status_STATUS_COMPILE_ERROR:
-		logger.Errorf("%s: Compile(): err: %s, output: %s\n", pipelineId, err.Error(), data)
-
-		setToCache(ctx, cacheService, pipelineId, cache.CompileOutput, "error: "+err.Error()+", output: "+string(data))
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_COMPILE_ERROR)
-	case pb.Status_STATUS_RUN_ERROR:
-		logger.Errorf("%s: Run(): err: %s, output: %s\n", pipelineId, err.Error(), data)
-
-		setToCache(ctx, cacheService, pipelineId, cache.RunError, "error: "+err.Error()+", output: "+string(data))
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_RUN_ERROR)
-	}
-}
-
-// processSuccess processes case after successful code processing via setting a corresponding status and output to cache
-func processSuccess(ctx context.Context, output []byte, pipelineId uuid.UUID, cacheService cache.Cache, status pb.Status) {
-	switch status {
-	case pb.Status_STATUS_PREPARING:
-		logger.Infof("%s: Validate() finish\n", pipelineId)
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_PREPARING)
-	case pb.Status_STATUS_COMPILING:
-		logger.Infof("%s: Prepare() finish\n", pipelineId)
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_COMPILING)
-	case pb.Status_STATUS_EXECUTING:
-		logger.Infof("%s: Compile() finish\n", pipelineId)
-
-		setToCache(ctx, cacheService, pipelineId, cache.CompileOutput, string(output))
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_EXECUTING)
-	case pb.Status_STATUS_FINISHED:
-		logger.Infof("%s: Run() finish\n", pipelineId)
-
-		setToCache(ctx, cacheService, pipelineId, cache.RunOutput, string(output))
-
-		setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_FINISHED)
-	}
-}
-
-// processCancel process case when code processing was canceled
-func processCancel(ctx context.Context, cacheService cache.Cache, pipelineId uuid.UUID) {
-	logger.Infof("%s: was canceled\n", pipelineId)
-
-	// set to cache pipelineId: cache.SubKey_Status: pb.Status_STATUS_CANCELED
-	setToCache(ctx, cacheService, pipelineId, cache.Status, pb.Status_STATUS_CANCELED)
-}
-
-// setToCache puts value to cache by key and subKey
-func setToCache(ctx context.Context, cacheService cache.Cache, key uuid.UUID, subKey cache.SubKey, value interface{}) error {
-	err := cacheService.SetValue(ctx, key, subKey, value)
-	if err != nil {
-		logger.Errorf("%s: cache.SetValue: %s\n", key, err.Error())
-	}
-	return err
-}
diff --git a/playground/backend/internal/utils/precompiled_objects_utils.go b/playground/backend/internal/utils/precompiled_objects_utils.go
new file mode 100644
index 0000000..3abb468
--- /dev/null
+++ b/playground/backend/internal/utils/precompiled_objects_utils.go
@@ -0,0 +1,38 @@
+// 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.
+
+package utils
+
+import (
+	pb "beam.apache.org/playground/backend/internal/api/v1"
+	"beam.apache.org/playground/backend/internal/cloud_bucket"
+)
+
+// PutPrecompiledObjectsToCategory adds categories with precompiled objects to protobuf object
+func PutPrecompiledObjectsToCategory(categoryName string, precompiledObjects *cloud_bucket.PrecompiledObjects, sdkCategory *pb.Categories) {
+	category := pb.Categories_Category{
+		CategoryName:       categoryName,
+		PrecompiledObjects: make([]*pb.PrecompiledObject, 0),
+	}
+	for _, object := range *precompiledObjects {
+		category.PrecompiledObjects = append(category.PrecompiledObjects, &pb.PrecompiledObject{
+			CloudPath:   object.CloudPath,
+			Name:        object.Name,
+			Description: object.Description,
+			Type:        object.Type,
+		})
+	}
+	sdkCategory.Categories = append(sdkCategory.Categories, &category)
+}
diff --git a/playground/backend/internal/utils/precompiled_objects_utils_test.go b/playground/backend/internal/utils/precompiled_objects_utils_test.go
new file mode 100644
index 0000000..07a7cbc
--- /dev/null
+++ b/playground/backend/internal/utils/precompiled_objects_utils_test.go
@@ -0,0 +1,75 @@
+// 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.
+
+package utils
+
+import (
+	pb "beam.apache.org/playground/backend/internal/api/v1"
+	"beam.apache.org/playground/backend/internal/cloud_bucket"
+	"reflect"
+	"testing"
+)
+
+func TestPutPrecompiledObjectsToCategory(t *testing.T) {
+	precompiledObjectToAdd := &cloud_bucket.PrecompiledObjects{
+		{"TestName", "SDK_JAVA/TestCategory/TestName.java", "TestDescription", pb.PrecompiledObjectType_PRECOMPILED_OBJECT_TYPE_EXAMPLE, []string{""}},
+	}
+	type args struct {
+		categoryName       string
+		precompiledObjects *cloud_bucket.PrecompiledObjects
+		sdkCategory        *pb.Categories
+	}
+	tests := []struct {
+		name string
+		args args
+		want *pb.Categories
+	}{
+		{
+			name: "Test PutPrecompiledObjectsToCategory",
+			args: args{
+				categoryName:       "TestCategory",
+				precompiledObjects: precompiledObjectToAdd,
+				sdkCategory: &pb.Categories{
+					Sdk:        pb.Sdk_SDK_JAVA,
+					Categories: []*pb.Categories_Category{},
+				},
+			},
+			want: &pb.Categories{
+				Sdk: pb.Sdk_SDK_JAVA,
+				Categories: []*pb.Categories_Category{
+					{
+						CategoryName: "TestCategory", PrecompiledObjects: []*pb.PrecompiledObject{
+							{
+								CloudPath:   "SDK_JAVA/TestCategory/TestName.java",
+								Name:        "TestName",
+								Description: "TestDescription",
+								Type:        pb.PrecompiledObjectType_PRECOMPILED_OBJECT_TYPE_EXAMPLE,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			PutPrecompiledObjectsToCategory(tt.args.categoryName, tt.args.precompiledObjects, tt.args.sdkCategory)
+			got := tt.args.sdkCategory
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PutPrecompiledObjectsToCategory() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}