From c34e534547f88e7a26445ffa19f81a89dc5952db Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Thu, 17 Feb 2022 20:02:36 +0300 Subject: [PATCH 1/6] Increase test coverage for the code_processing package --- .../code_processing/code_processing_test.go | 171 +++++++++++++++--- 1 file changed, 150 insertions(+), 21 deletions(-) diff --git a/playground/backend/internal/code_processing/code_processing_test.go b/playground/backend/internal/code_processing/code_processing_test.go index cae46b653d06..70a74ca82a52 100644 --- a/playground/backend/internal/code_processing/code_processing_test.go +++ b/playground/backend/internal/code_processing/code_processing_test.go @@ -43,7 +43,6 @@ const ( javaConfig = "{\n \"compile_cmd\": \"javac\",\n \"run_cmd\": \"java\",\n \"test_cmd\": \"java\",\n \"compile_args\": [\n \"-d\",\n \"bin\",\n \"-classpath\"\n ],\n \"run_args\": [\n \"-cp\",\n \"bin:\"\n ],\n \"test_args\": [\n \"-cp\",\n \"bin:\",\n \"JUnit\"\n ]\n}" pythonConfig = "{\n \"compile_cmd\": \"\",\n \"run_cmd\": \"python3\",\n \"compile_args\": [],\n \"run_args\": []\n}" goConfig = "{\n \"compile_cmd\": \"go\",\n \"run_cmd\": \"\",\n \"compile_args\": [\n \"build\",\n \"-o\",\n \"bin\"\n ],\n \"run_args\": [\n ]\n}" - fileName = "fakeFileName" pipelinesFolder = "executable_files" configFolder = "configs" ) @@ -99,11 +98,12 @@ func Test_Process(t *testing.T) { if err != nil { panic(err) } - sdkEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) + sdkJavaEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) if err != nil { panic(err) } - + sdkGoEnv := *sdkJavaEnv + sdkGoEnv.ApacheBeamSdk = pb.Sdk_SDK_GO type args struct { ctx context.Context appEnv *environment.ApplicationEnvs @@ -136,7 +136,7 @@ func Test_Process(t *testing.T) { args: args{ ctx: context.Background(), appEnv: &environment.ApplicationEnvs{}, - sdkEnv: sdkEnv, + sdkEnv: sdkJavaEnv, pipelineId: uuid.New(), pipelineOptions: "", }, @@ -155,7 +155,7 @@ func Test_Process(t *testing.T) { args: args{ ctx: context.Background(), appEnv: appEnvs, - sdkEnv: sdkEnv, + sdkEnv: sdkJavaEnv, pipelineId: uuid.New(), pipelineOptions: "", }, @@ -174,7 +174,7 @@ func Test_Process(t *testing.T) { args: args{ ctx: context.Background(), appEnv: appEnvs, - sdkEnv: sdkEnv, + sdkEnv: sdkJavaEnv, pipelineId: uuid.New(), pipelineOptions: "", }, @@ -193,7 +193,7 @@ func Test_Process(t *testing.T) { args: args{ ctx: context.Background(), appEnv: appEnvs, - sdkEnv: sdkEnv, + sdkEnv: sdkJavaEnv, pipelineId: uuid.New(), pipelineOptions: "", }, @@ -212,7 +212,7 @@ func Test_Process(t *testing.T) { args: args{ ctx: context.Background(), appEnv: appEnvs, - sdkEnv: sdkEnv, + sdkEnv: sdkJavaEnv, pipelineId: uuid.New(), pipelineOptions: "", }, @@ -220,7 +220,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode without any error cases. // As a result status into cache should be set as Status_STATUS_FINISHED. - name: "processing complete successfully", + name: "processing complete successfully on java sdk", createExecFile: true, cancelFunc: false, code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", @@ -231,7 +231,26 @@ func Test_Process(t *testing.T) { args: args{ ctx: context.Background(), appEnv: appEnvs, - sdkEnv: sdkEnv, + sdkEnv: sdkJavaEnv, + pipelineId: uuid.New(), + pipelineOptions: "", + }, + }, + { + // Test case with calling processCode method with incorrect go code. + // As a result status into cache should be set as Status_STATUS_PREPARATION_ERROR. + name: "prepare step failed", + createExecFile: true, + code: "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\").\n}\n", + cancelFunc: false, + expectedStatus: pb.Status_STATUS_PREPARATION_ERROR, + expectedCompileOutput: nil, + expectedRunOutput: nil, + expectedRunError: nil, + args: args{ + ctx: context.Background(), + appEnv: appEnvs, + sdkEnv: &sdkGoEnv, pipelineId: uuid.New(), pipelineOptions: "", }, @@ -683,7 +702,7 @@ func Benchmark_ProcessPython(b *testing.B) { } ctx := context.Background() - code := "if __name__ == \"__main__\":\n print(\"Hello world!\")\n" + code := "import argparse\nimport logging\nimport re\n\nimport apache_beam as beam\nfrom apache_beam.io import ReadFromText\nfrom apache_beam.io import WriteToText\nfrom apache_beam.options.pipeline_options import PipelineOptions\nfrom apache_beam.options.pipeline_options import SetupOptions\n\n\nclass WordExtractingDoFn(beam.DoFn):\n \"\"\"Parse each line of input text into words.\"\"\"\n def process(self, element):\n \"\"\"Returns an iterator over the words of this element.\n\n The element is a line of text. If the line is blank, note that, too.\n\n Args:\n element: the element being processed\n\n Returns:\n The processed element.\n \"\"\"\n return re.findall(r'[\\w\\']+', element, re.UNICODE)\n\n\ndef run(argv=None, save_main_session=True):\n \"\"\"Main entry point; defines and runs the wordcount pipeline.\"\"\"\n parser = argparse.ArgumentParser()\n parser.add_argument(\n '--input',\n dest='input',\n default='gs://dataflow-samples/shakespeare/kinglear.txt',\n help='Input file to process.')\n parser.add_argument(\n '--output',\n dest='output',\n required=True,\n help='Output file to write results to.')\n known_args, pipeline_args = parser.parse_known_args(argv)\n\n # We use the save_main_session option because one or more DoFn's in this\n # workflow rely on global context (e.g., a module imported at module level).\n pipeline_options = PipelineOptions(pipeline_args)\n pipeline_options.view_as(SetupOptions).save_main_session = save_main_session\n\n # The pipeline will be run on exiting the with block.\n with beam.Pipeline(options=pipeline_options) as p:\n\n # Read the text file[pattern] into a PCollection.\n lines = p | 'Read' >> ReadFromText(known_args.input)\n\n counts = (\n lines\n | 'Split' >> (beam.ParDo(WordExtractingDoFn()).with_output_types(str))\n | 'PairWithOne' >> beam.Map(lambda x: (x, 1))\n | 'GroupAndSum' >> beam.CombinePerKey(sum))\n\n # Format the counts into a PCollection of strings.\n def format_result(word, count):\n return '%s: %d' % (word, count)\n\n output = counts | 'Format' >> beam.MapTuple(format_result)\n\n # Write the output using a \"Write\" transform that has side effects.\n # pylint: disable=expression-not-assigned\n output | 'Write' >> WriteToText(known_args.output)\n\n\nif __name__ == '__main__':\n logging.getLogger().setLevel(logging.INFO)\n run()" b.ResetTimer() for i := 0; i < b.N; i++ { @@ -695,7 +714,7 @@ func Benchmark_ProcessPython(b *testing.B) { } b.StartTimer() - Process(ctx, cacheService, lc, pipelineId, appEnv, sdkEnv, "") + Process(ctx, cacheService, lc, pipelineId, appEnv, sdkEnv, "--output t.txt") } } @@ -948,11 +967,14 @@ func Test_runStep(t *testing.T) { if err != nil { panic(err) } - sdkEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) + sdkJavaEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) if err != nil { panic(err) } - sdkEnv.ApacheBeamSdk = pb.Sdk_SDK_PYTHON + sdkPythonEnv := *sdkJavaEnv + sdkPythonEnv.ApacheBeamSdk = pb.Sdk_SDK_PYTHON + sdkGoEnv := *sdkJavaEnv + sdkGoEnv.ApacheBeamSdk = pb.Sdk_SDK_GO type args struct { ctx context.Context cacheService cache.Cache @@ -962,6 +984,7 @@ func Test_runStep(t *testing.T) { pipelineOptions string pipelineLifeCycleCtx context.Context cancelChannel chan bool + createExecFile bool } tests := []struct { name string @@ -969,28 +992,61 @@ func Test_runStep(t *testing.T) { code string }{ { - name: "Test run step working without an error", + name: "Test run step working on python sdk without an error", args: args{ ctx: context.Background(), cacheService: cacheService, pipelineId: uuid.UUID{}, isUnitTest: false, - sdkEnv: sdkEnv, + sdkEnv: &sdkPythonEnv, pipelineOptions: "", pipelineLifeCycleCtx: context.Background(), cancelChannel: make(chan bool, 1), + createExecFile: true, }, code: "if __name__ == \"__main__\":\n print(\"Hello world!\")\n", }, + { + name: "Test run step working on go sdk", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.New(), + isUnitTest: true, + sdkEnv: &sdkGoEnv, + pipelineOptions: "", + pipelineLifeCycleCtx: context.Background(), + cancelChannel: make(chan bool, 1), + createExecFile: true, + }, + code: "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n", + }, + { + name: "Test run step without preparing files with code", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.UUID{}, + isUnitTest: true, + sdkEnv: sdkJavaEnv, + pipelineOptions: "", + pipelineLifeCycleCtx: context.Background(), + cancelChannel: make(chan bool, 1), + createExecFile: false, + }, + code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - lc, _ := fs_tool.NewLifeCycle(pb.Sdk_SDK_PYTHON, tt.args.pipelineId, filepath.Join(os.Getenv("APP_WORK_DIR"), pipelinesFolder)) - err := lc.CreateFolders() - if err != nil { - t.Fatalf("error during prepare folders: %s", err.Error()) + lc, _ := fs_tool.NewLifeCycle(tt.args.sdkEnv.ApacheBeamSdk, tt.args.pipelineId, filepath.Join(os.Getenv("APP_WORK_DIR"), pipelinesFolder)) + if tt.args.createExecFile { + err := lc.CreateFolders() + if err != nil { + t.Fatalf("error during prepare folders: %s", err.Error()) + } + _ = lc.CreateSourceCodeFile(tt.code) } - _ = lc.CreateSourceCodeFile(tt.code) runStep(tt.args.ctx, tt.args.cacheService, &lc.Paths, tt.args.pipelineId, tt.args.isUnitTest, tt.args.sdkEnv, tt.args.pipelineOptions, tt.args.pipelineLifeCycleCtx, tt.args.cancelChannel) }) } @@ -1004,3 +1060,76 @@ func syncMapLen(syncMap *sync.Map) int { }) return length } + +func TestGetGraph(t *testing.T) { + ctx := context.Background() + pipelineId1 := uuid.New() + graph := "GRAPH" + err := cacheService.SetValue(ctx, pipelineId1, cache.Graph, graph) + if err != nil { + return + } + pipelineId2 := uuid.New() + err = cacheService.SetValue(ctx, pipelineId2, cache.Graph, 1) + if err != nil { + return + } + type args struct { + ctx context.Context + cacheService cache.Cache + key uuid.UUID + errorTitle string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "get graph when key exist in cache", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + key: pipelineId1, + errorTitle: "error", + }, + want: graph, + wantErr: false, + }, + { + name: "get graph when key doesn't exist in cache", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + key: uuid.New(), + errorTitle: "error", + }, + want: "", + wantErr: true, + }, + { + name: "get graph when value from cache by key couldn't be converted to a string", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + key: pipelineId2, + errorTitle: "error", + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetGraph(tt.args.ctx, tt.args.cacheService, tt.args.key, tt.args.errorTitle) + if (err != nil) != tt.wantErr { + t.Errorf("GetGraph() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetGraph() got = %v, want %v", got, tt.want) + } + }) + } +} From 1d670262b2ce0ee41f1819a7452dda97349f1fb2 Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Fri, 18 Feb 2022 17:25:43 +0300 Subject: [PATCH 2/6] Refactoring code --- .../code_processing/code_processing_test.go | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/playground/backend/internal/code_processing/code_processing_test.go b/playground/backend/internal/code_processing/code_processing_test.go index 70a74ca82a52..6b6172ac1ae1 100644 --- a/playground/backend/internal/code_processing/code_processing_test.go +++ b/playground/backend/internal/code_processing/code_processing_test.go @@ -125,7 +125,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode method with small timeout. // As a result status into cache should be set as Status_STATUS_RUN_TIMEOUT. - name: "small pipeline execution timeout", + name: "Small pipeline execution timeout", createExecFile: false, code: "", cancelFunc: false, @@ -144,7 +144,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode method without preparing files with code. // As a result status into cache should be set as Status_STATUS_VALIDATION_ERROR. - name: "validation failed", + name: "Validation failed", createExecFile: false, code: "", cancelFunc: false, @@ -163,7 +163,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode method with incorrect code. // As a result status into cache should be set as Status_STATUS_COMPILE_ERROR. - name: "compilation failed", + name: "Compilation failed", createExecFile: true, code: "MOCK_CODE", cancelFunc: false, @@ -182,7 +182,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode method with incorrect logic into code. // As a result status into cache should be set as Status_STATUS_RUN_ERROR. - name: "run failed", + name: "Run failed", createExecFile: true, code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(1/0);\n }\n}", cancelFunc: false, @@ -201,7 +201,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode with canceling code processing. // As a result status into cache should be set as Status_STATUS_CANCELED. - name: "cancel", + name: "Cancel", createExecFile: true, code: "class HelloWorld {\n public static void main(String[] args) {\n while(true){}\n }\n}", cancelFunc: true, @@ -220,7 +220,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode without any error cases. // As a result status into cache should be set as Status_STATUS_FINISHED. - name: "processing complete successfully on java sdk", + name: "Processing complete successfully on java sdk", createExecFile: true, cancelFunc: false, code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", @@ -239,7 +239,7 @@ func Test_Process(t *testing.T) { { // Test case with calling processCode method with incorrect go code. // As a result status into cache should be set as Status_STATUS_PREPARATION_ERROR. - name: "prepare step failed", + name: "Prepare step failed", createExecFile: true, code: "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\").\n}\n", cancelFunc: false, @@ -336,7 +336,7 @@ func TestGetProcessingOutput(t *testing.T) { { // Test case with calling GetProcessingOutput with pipelineId which doesn't contain run output. // As a result, want to receive an error. - name: "get run output with incorrect pipelineId", + name: "Get run output with incorrect pipelineId", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -350,7 +350,7 @@ func TestGetProcessingOutput(t *testing.T) { { // Test case with calling GetProcessingOutput with pipelineId which contains incorrect run output. // As a result, want to receive an error. - name: "get run output with incorrect run output", + name: "Get run output with incorrect run output", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -364,7 +364,7 @@ func TestGetProcessingOutput(t *testing.T) { { // Test case with calling GetProcessingOutput with pipelineId which contains run output. // As a result, want to receive an expected string. - name: "get run output with correct pipelineId", + name: "Get run output with correct pipelineId", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -418,7 +418,7 @@ func TestGetProcessingStatus(t *testing.T) { { // Test case with calling GetProcessingStatus with pipelineId which doesn't contain status. // As a result, want to receive an error. - name: "get status with incorrect pipelineId", + name: "Get status with incorrect pipelineId", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -431,7 +431,7 @@ func TestGetProcessingStatus(t *testing.T) { { // Test case with calling GetProcessingStatus with pipelineId which contains incorrect status value in cache. // As a result, want to receive an error. - name: "get status with incorrect cache value", + name: "Get status with incorrect cache value", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -444,7 +444,7 @@ func TestGetProcessingStatus(t *testing.T) { { // Test case with calling GetProcessingStatus with pipelineId which contains status. // As a result, want to receive an expected status. - name: "get status with correct pipelineId", + name: "Get status with correct pipelineId", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -494,7 +494,7 @@ func TestGetLastIndex(t *testing.T) { { // Test case with calling GetLastIndex with pipelineId which doesn't contain last index. // As a result, want to receive an error. - name: "get last index with incorrect pipelineId", + name: "Get last index with incorrect pipelineId", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -508,7 +508,7 @@ func TestGetLastIndex(t *testing.T) { { // Test case with calling GetLastIndex with pipelineId which contains incorrect status value in cache. // As a result, want to receive an error. - name: "get last index with incorrect cache value", + name: "Get last index with incorrect cache value", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -522,7 +522,7 @@ func TestGetLastIndex(t *testing.T) { { // Test case with calling GetLastIndex with pipelineId which contains last index. // As a result, want to receive an expected last index. - name: "get last index with correct pipelineId", + name: "Get last index with correct pipelineId", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -578,7 +578,7 @@ func Test_getRunOrTestCmd(t *testing.T) { }{ { //Get cmd objects with set run executor - name: "get run cmd", + name: "Get run cmd", args: args{ isUnitTest: false, executor: &runEx, @@ -588,7 +588,7 @@ func Test_getRunOrTestCmd(t *testing.T) { }, { //Get cmd objects with set test executor - name: "get test cmd", + name: "Get test cmd", args: args{ isUnitTest: true, executor: &testEx, @@ -702,19 +702,19 @@ func Benchmark_ProcessPython(b *testing.B) { } ctx := context.Background() - code := "import argparse\nimport logging\nimport re\n\nimport apache_beam as beam\nfrom apache_beam.io import ReadFromText\nfrom apache_beam.io import WriteToText\nfrom apache_beam.options.pipeline_options import PipelineOptions\nfrom apache_beam.options.pipeline_options import SetupOptions\n\n\nclass WordExtractingDoFn(beam.DoFn):\n \"\"\"Parse each line of input text into words.\"\"\"\n def process(self, element):\n \"\"\"Returns an iterator over the words of this element.\n\n The element is a line of text. If the line is blank, note that, too.\n\n Args:\n element: the element being processed\n\n Returns:\n The processed element.\n \"\"\"\n return re.findall(r'[\\w\\']+', element, re.UNICODE)\n\n\ndef run(argv=None, save_main_session=True):\n \"\"\"Main entry point; defines and runs the wordcount pipeline.\"\"\"\n parser = argparse.ArgumentParser()\n parser.add_argument(\n '--input',\n dest='input',\n default='gs://dataflow-samples/shakespeare/kinglear.txt',\n help='Input file to process.')\n parser.add_argument(\n '--output',\n dest='output',\n required=True,\n help='Output file to write results to.')\n known_args, pipeline_args = parser.parse_known_args(argv)\n\n # We use the save_main_session option because one or more DoFn's in this\n # workflow rely on global context (e.g., a module imported at module level).\n pipeline_options = PipelineOptions(pipeline_args)\n pipeline_options.view_as(SetupOptions).save_main_session = save_main_session\n\n # The pipeline will be run on exiting the with block.\n with beam.Pipeline(options=pipeline_options) as p:\n\n # Read the text file[pattern] into a PCollection.\n lines = p | 'Read' >> ReadFromText(known_args.input)\n\n counts = (\n lines\n | 'Split' >> (beam.ParDo(WordExtractingDoFn()).with_output_types(str))\n | 'PairWithOne' >> beam.Map(lambda x: (x, 1))\n | 'GroupAndSum' >> beam.CombinePerKey(sum))\n\n # Format the counts into a PCollection of strings.\n def format_result(word, count):\n return '%s: %d' % (word, count)\n\n output = counts | 'Format' >> beam.MapTuple(format_result)\n\n # Write the output using a \"Write\" transform that has side effects.\n # pylint: disable=expression-not-assigned\n output | 'Write' >> WriteToText(known_args.output)\n\n\nif __name__ == '__main__':\n logging.getLogger().setLevel(logging.INFO)\n run()" - + wordCountCode := "import argparse\nimport logging\nimport re\n\nimport apache_beam as beam\nfrom apache_beam.io import ReadFromText\nfrom apache_beam.io import WriteToText\nfrom apache_beam.options.pipeline_options import PipelineOptions\nfrom apache_beam.options.pipeline_options import SetupOptions\n\n\nclass WordExtractingDoFn(beam.DoFn):\n \"\"\"Parse each line of input text into words.\"\"\"\n def process(self, element):\n \"\"\"Returns an iterator over the words of this element.\n\n The element is a line of text. If the line is blank, note that, too.\n\n Args:\n element: the element being processed\n\n Returns:\n The processed element.\n \"\"\"\n return re.findall(r'[\\w\\']+', element, re.UNICODE)\n\n\ndef run(argv=None, save_main_session=True):\n \"\"\"Main entry point; defines and runs the wordcount pipeline.\"\"\"\n parser = argparse.ArgumentParser()\n parser.add_argument(\n '--input',\n dest='input',\n default='gs://dataflow-samples/shakespeare/kinglear.txt',\n help='Input file to process.')\n parser.add_argument(\n '--output',\n dest='output',\n required=True,\n help='Output file to write results to.')\n known_args, pipeline_args = parser.parse_known_args(argv)\n\n # We use the save_main_session option because one or more DoFn's in this\n # workflow rely on global context (e.g., a module imported at module level).\n pipeline_options = PipelineOptions(pipeline_args)\n pipeline_options.view_as(SetupOptions).save_main_session = save_main_session\n\n # The pipeline will be run on exiting the with block.\n with beam.Pipeline(options=pipeline_options) as p:\n\n # Read the text file[pattern] into a PCollection.\n lines = p | 'Read' >> ReadFromText(known_args.input)\n\n counts = (\n lines\n | 'Split' >> (beam.ParDo(WordExtractingDoFn()).with_output_types(str))\n | 'PairWithOne' >> beam.Map(lambda x: (x, 1))\n | 'GroupAndSum' >> beam.CombinePerKey(sum))\n\n # Format the counts into a PCollection of strings.\n def format_result(word, count):\n return '%s: %d' % (word, count)\n\n output = counts | 'Format' >> beam.MapTuple(format_result)\n\n # Write the output using a \"Write\" transform that has side effects.\n # pylint: disable=expression-not-assigned\n output | 'Write' >> WriteToText(known_args.output)\n\n\nif __name__ == '__main__':\n logging.getLogger().setLevel(logging.INFO)\n run()" + pipelineOptions := "--output t.txt" b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() pipelineId := uuid.New() - lc := prepareFiles(b, pipelineId, code, pb.Sdk_SDK_PYTHON) + lc := prepareFiles(b, pipelineId, wordCountCode, pb.Sdk_SDK_PYTHON) if err = utils.SetToCache(ctx, cacheService, pipelineId, cache.Canceled, false); err != nil { b.Fatal("error during set cancel flag to cache") } b.StartTimer() - Process(ctx, cacheService, lc, pipelineId, appEnv, sdkEnv, "--output t.txt") + Process(ctx, cacheService, lc, pipelineId, appEnv, sdkEnv, pipelineOptions) } } @@ -975,6 +975,9 @@ func Test_runStep(t *testing.T) { sdkPythonEnv.ApacheBeamSdk = pb.Sdk_SDK_PYTHON sdkGoEnv := *sdkJavaEnv sdkGoEnv.ApacheBeamSdk = pb.Sdk_SDK_GO + helloWordPython := "if __name__ == \"__main__\":\n print(\"Hello world!\")\n" + helloWordGo := "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n" + helloWordJava := "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}" type args struct { ctx context.Context cacheService cache.Cache @@ -987,12 +990,13 @@ func Test_runStep(t *testing.T) { createExecFile bool } tests := []struct { - name string - args args - code string + name string + args args + code string + expectedStatus pb.Status }{ { - name: "Test run step working on python sdk without an error", + name: "Test run step working on python sdk", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -1004,7 +1008,8 @@ func Test_runStep(t *testing.T) { cancelChannel: make(chan bool, 1), createExecFile: true, }, - code: "if __name__ == \"__main__\":\n print(\"Hello world!\")\n", + code: helloWordPython, + expectedStatus: pb.Status_STATUS_RUN_ERROR, }, { name: "Test run step working on go sdk", @@ -1019,7 +1024,8 @@ func Test_runStep(t *testing.T) { cancelChannel: make(chan bool, 1), createExecFile: true, }, - code: "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n", + code: helloWordGo, + expectedStatus: pb.Status_STATUS_RUN_ERROR, }, { name: "Test run step without preparing files with code", @@ -1034,7 +1040,8 @@ func Test_runStep(t *testing.T) { cancelChannel: make(chan bool, 1), createExecFile: false, }, - code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", + code: helloWordJava, + expectedStatus: pb.Status_STATUS_ERROR, }, } for _, tt := range tests { @@ -1048,6 +1055,10 @@ func Test_runStep(t *testing.T) { _ = lc.CreateSourceCodeFile(tt.code) } runStep(tt.args.ctx, tt.args.cacheService, &lc.Paths, tt.args.pipelineId, tt.args.isUnitTest, tt.args.sdkEnv, tt.args.pipelineOptions, tt.args.pipelineLifeCycleCtx, tt.args.cancelChannel) + status, _ := cacheService.GetValue(tt.args.ctx, tt.args.pipelineId, cache.Status) + if status != tt.expectedStatus { + t.Errorf("runStep() got status = %v, want %v", status, tt.expectedStatus) + } }) } } @@ -1087,7 +1098,7 @@ func TestGetGraph(t *testing.T) { wantErr bool }{ { - name: "get graph when key exist in cache", + name: "Get graph when key exist in cache", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -1098,7 +1109,7 @@ func TestGetGraph(t *testing.T) { wantErr: false, }, { - name: "get graph when key doesn't exist in cache", + name: "Get graph when key doesn't exist in cache", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -1109,7 +1120,7 @@ func TestGetGraph(t *testing.T) { wantErr: true, }, { - name: "get graph when value from cache by key couldn't be converted to a string", + name: "Get graph when value from cache by key couldn't be converted to a string", args: args{ ctx: context.Background(), cacheService: cacheService, From 90aa08b928571a8d2303146861c3b65b6f87f0c7 Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Tue, 22 Feb 2022 07:48:26 +0300 Subject: [PATCH 3/6] Add test cases with mock cache --- .../code_processing/code_processing_test.go | 332 +++++++++++++++--- .../setup_tools/builder/setup_builder.go | 2 +- 2 files changed, 283 insertions(+), 51 deletions(-) diff --git a/playground/backend/internal/code_processing/code_processing_test.go b/playground/backend/internal/code_processing/code_processing_test.go index 6b6172ac1ae1..966eab26197a 100644 --- a/playground/backend/internal/code_processing/code_processing_test.go +++ b/playground/backend/internal/code_processing/code_processing_test.go @@ -19,6 +19,7 @@ import ( pb "beam.apache.org/playground/backend/internal/api/v1" "beam.apache.org/playground/backend/internal/cache" "beam.apache.org/playground/backend/internal/cache/local" + "beam.apache.org/playground/backend/internal/cache/redis" "beam.apache.org/playground/backend/internal/environment" "beam.apache.org/playground/backend/internal/executors" "beam.apache.org/playground/backend/internal/fs_tool" @@ -26,6 +27,7 @@ import ( "beam.apache.org/playground/backend/internal/validators" "context" "fmt" + "github.com/go-redis/redismock/v8" "github.com/google/uuid" "go.uber.org/goleak" "io/fs" @@ -45,6 +47,9 @@ const ( goConfig = "{\n \"compile_cmd\": \"go\",\n \"run_cmd\": \"\",\n \"compile_args\": [\n \"build\",\n \"-o\",\n \"bin\"\n ],\n \"run_args\": [\n ]\n}" pipelinesFolder = "executable_files" configFolder = "configs" + helloWordPython = "if __name__ == \"__main__\":\n print(\"Hello world!\")\n" + helloWordGo = "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n" + helloWordJava = "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}" ) var opt goleak.Option @@ -223,7 +228,7 @@ func Test_Process(t *testing.T) { name: "Processing complete successfully on java sdk", createExecFile: true, cancelFunc: false, - code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", + code: helloWordJava, expectedStatus: pb.Status_STATUS_FINISHED, expectedCompileOutput: "", expectedRunOutput: "Hello world!\n", @@ -606,7 +611,20 @@ func Test_getRunOrTestCmd(t *testing.T) { } } -func setupBenchmarks(sdk pb.Sdk) { +func getSdkEnv(sdk pb.Sdk) (*environment.BeamEnvs, error) { + setupSDK(sdk) + appEnvs, err := environment.GetApplicationEnvsFromOsEnvs() + if err != nil { + return nil, err + } + sdkEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) + if err != nil { + return nil, err + } + return sdkEnv, nil +} + +func setupSDK(sdk pb.Sdk) { err := os.MkdirAll(configFolder, fs.ModePerm) if err != nil { panic(err) @@ -659,7 +677,7 @@ func prepareFiles(b *testing.B, pipelineId uuid.UUID, code string, sdk pb.Sdk) * } func Benchmark_ProcessJava(b *testing.B) { - setupBenchmarks(pb.Sdk_SDK_JAVA) + setupSDK(pb.Sdk_SDK_JAVA) defer teardownBenchmarks() appEnv, err := environment.GetApplicationEnvsFromOsEnvs() @@ -689,7 +707,7 @@ func Benchmark_ProcessJava(b *testing.B) { } func Benchmark_ProcessPython(b *testing.B) { - setupBenchmarks(pb.Sdk_SDK_PYTHON) + setupSDK(pb.Sdk_SDK_PYTHON) defer teardownBenchmarks() appEnv, err := environment.GetApplicationEnvsFromOsEnvs() @@ -719,7 +737,7 @@ func Benchmark_ProcessPython(b *testing.B) { } func Benchmark_ProcessGo(b *testing.B) { - setupBenchmarks(pb.Sdk_SDK_GO) + setupSDK(pb.Sdk_SDK_GO) defer teardownBenchmarks() appEnv, err := environment.GetApplicationEnvsFromOsEnvs() @@ -797,13 +815,13 @@ func Benchmark_GetLastIndex(b *testing.B) { } func Test_validateStep(t *testing.T) { - appEnvs, err := environment.GetApplicationEnvsFromOsEnvs() + javaSdkEnv, err := getSdkEnv(pb.Sdk_SDK_JAVA) if err != nil { panic(err) } - sdkEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) - if err != nil { - panic(err) + incorrectSdkEnv := &environment.BeamEnvs{ + ApacheBeamSdk: pb.Sdk_SDK_UNSPECIFIED, + ExecutorConfig: nil, } type args struct { ctx context.Context @@ -826,13 +844,27 @@ func Test_validateStep(t *testing.T) { ctx: context.Background(), cacheService: cacheService, pipelineId: uuid.New(), - sdkEnv: sdkEnv, + sdkEnv: javaSdkEnv, pipelineLifeCycleCtx: context.Background(), validationResults: &sync.Map{}, cancelChannel: make(chan bool, 1), }, want: 3, - code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", + code: helloWordJava, + }, + { + name: "Test validation step with incorrect sdkEnv", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.New(), + sdkEnv: incorrectSdkEnv, + pipelineLifeCycleCtx: context.Background(), + validationResults: &sync.Map{}, + cancelChannel: make(chan bool, 1), + }, + want: 0, + code: helloWordJava, }, } for _, tt := range tests { @@ -853,17 +885,18 @@ func Test_validateStep(t *testing.T) { } func Test_prepareStep(t *testing.T) { - appEnvs, err := environment.GetApplicationEnvsFromOsEnvs() + javaSdkEnv, err := getSdkEnv(pb.Sdk_SDK_JAVA) if err != nil { panic(err) } - sdkEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) - if err != nil { - panic(err) + incorrectSdkEnv := &environment.BeamEnvs{ + ApacheBeamSdk: pb.Sdk_SDK_UNSPECIFIED, + ExecutorConfig: nil, } validationResults := sync.Map{} validationResults.Store(validators.UnitTestValidatorName, false) validationResults.Store(validators.KatasValidatorName, false) + pipelineLifeCycleCtx, _ := context.WithTimeout(context.Background(), 1) type args struct { ctx context.Context cacheService cache.Cache @@ -874,10 +907,10 @@ func Test_prepareStep(t *testing.T) { cancelChannel chan bool } tests := []struct { - name string - args args - want *executors.Executor - code string + name string + args args + code string + expectedStatus pb.Status }{ { name: "Test preparer step working without an error", @@ -885,12 +918,41 @@ func Test_prepareStep(t *testing.T) { ctx: context.Background(), cacheService: cacheService, pipelineId: uuid.New(), - sdkEnv: sdkEnv, + sdkEnv: javaSdkEnv, + pipelineLifeCycleCtx: context.Background(), + validationResults: &validationResults, + cancelChannel: make(chan bool, 1), + }, + code: helloWordJava, + expectedStatus: pb.Status_STATUS_COMPILING, + }, + { + name: "Test preparer step working with incorrect sdkEnv", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.New(), + sdkEnv: incorrectSdkEnv, pipelineLifeCycleCtx: context.Background(), validationResults: &validationResults, cancelChannel: make(chan bool, 1), }, - code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", + code: "", + expectedStatus: pb.Status_STATUS_ERROR, + }, + { + name: "Error during expired context of the example", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.New(), + sdkEnv: javaSdkEnv, + pipelineLifeCycleCtx: pipelineLifeCycleCtx, + validationResults: &validationResults, + cancelChannel: make(chan bool, 1), + }, + code: "", + expectedStatus: pb.Status_STATUS_RUN_TIMEOUT, }, } for _, tt := range tests { @@ -901,22 +963,25 @@ func Test_prepareStep(t *testing.T) { t.Fatalf("error during prepare folders: %s", err.Error()) } _ = lc.CreateSourceCodeFile(tt.code) - if got := prepareStep(tt.args.ctx, tt.args.cacheService, &lc.Paths, tt.args.pipelineId, tt.args.sdkEnv, tt.args.pipelineLifeCycleCtx, tt.args.validationResults, tt.args.cancelChannel); got == nil { - t.Errorf("prepareStep(): got nil instead of preparer executor") + prepareStep(tt.args.ctx, tt.args.cacheService, &lc.Paths, tt.args.pipelineId, tt.args.sdkEnv, tt.args.pipelineLifeCycleCtx, tt.args.validationResults, tt.args.cancelChannel) + status, _ := cacheService.GetValue(tt.args.ctx, tt.args.pipelineId, cache.Status) + if status != tt.expectedStatus { + t.Errorf("prepareStep: got status = %v, want %v", status, tt.expectedStatus) } }) } } func Test_compileStep(t *testing.T) { - appEnvs, err := environment.GetApplicationEnvsFromOsEnvs() + sdkJavaEnv, err := getSdkEnv(pb.Sdk_SDK_JAVA) if err != nil { panic(err) } - sdkEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) + sdkPythonEnv, err := getSdkEnv(pb.Sdk_SDK_PYTHON) if err != nil { panic(err) } + pipelineLifeCycleCtx, _ := context.WithTimeout(context.Background(), 1) type args struct { ctx context.Context cacheService cache.Cache @@ -927,57 +992,84 @@ func Test_compileStep(t *testing.T) { cancelChannel chan bool } tests := []struct { - name string - args args - want *executors.Executor - code string + name string + args args + code string + expectedStatus pb.Status }{ { - name: "Test compilation step working without an error", + name: "Test compilation step working on java sdk", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.New(), + sdkEnv: sdkJavaEnv, + isUnitTest: false, + pipelineLifeCycleCtx: context.Background(), + cancelChannel: make(chan bool, 1), + }, + code: helloWordJava, + expectedStatus: pb.Status_STATUS_EXECUTING, + }, + { + name: "Test compilation step working on python sdk", args: args{ ctx: context.Background(), cacheService: cacheService, pipelineId: uuid.New(), - sdkEnv: sdkEnv, + sdkEnv: sdkPythonEnv, isUnitTest: false, pipelineLifeCycleCtx: context.Background(), cancelChannel: make(chan bool, 1), }, - want: nil, - code: "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}", + code: helloWordPython, + expectedStatus: pb.Status_STATUS_EXECUTING, + }, + { + name: "Error during expired context of the example", + args: args{ + ctx: context.Background(), + cacheService: cacheService, + pipelineId: uuid.New(), + sdkEnv: sdkJavaEnv, + isUnitTest: false, + pipelineLifeCycleCtx: pipelineLifeCycleCtx, + cancelChannel: make(chan bool, 1), + }, + code: helloWordJava, + expectedStatus: pb.Status_STATUS_RUN_TIMEOUT, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - lc, _ := fs_tool.NewLifeCycle(pb.Sdk_SDK_JAVA, tt.args.pipelineId, filepath.Join(os.Getenv("APP_WORK_DIR"), pipelinesFolder)) + lc, _ := fs_tool.NewLifeCycle(tt.args.sdkEnv.ApacheBeamSdk, tt.args.pipelineId, filepath.Join(os.Getenv("APP_WORK_DIR"), pipelinesFolder)) err := lc.CreateFolders() if err != nil { t.Fatalf("error during prepare folders: %s", err.Error()) } _ = lc.CreateSourceCodeFile(tt.code) - if got := compileStep(tt.args.ctx, tt.args.cacheService, &lc.Paths, tt.args.pipelineId, tt.args.sdkEnv, tt.args.isUnitTest, tt.args.pipelineLifeCycleCtx, tt.args.cancelChannel); got == nil { - t.Errorf("compileStep: got nil instead of compiler executor") + compileStep(tt.args.ctx, tt.args.cacheService, &lc.Paths, tt.args.pipelineId, tt.args.sdkEnv, tt.args.isUnitTest, tt.args.pipelineLifeCycleCtx, tt.args.cancelChannel) + status, _ := cacheService.GetValue(tt.args.ctx, tt.args.pipelineId, cache.Status) + if status != tt.expectedStatus { + t.Errorf("compileStep: got status = %v, want %v", status, tt.expectedStatus) } }) } } func Test_runStep(t *testing.T) { - appEnvs, err := environment.GetApplicationEnvsFromOsEnvs() + sdkJavaEnv, err := getSdkEnv(pb.Sdk_SDK_JAVA) if err != nil { panic(err) } - sdkJavaEnv, err := environment.ConfigureBeamEnvs(appEnvs.WorkingDir()) + sdkPythonEnv, err := getSdkEnv(pb.Sdk_SDK_PYTHON) + if err != nil { + panic(err) + } + sdkGoEnv, err := getSdkEnv(pb.Sdk_SDK_GO) if err != nil { panic(err) } - sdkPythonEnv := *sdkJavaEnv - sdkPythonEnv.ApacheBeamSdk = pb.Sdk_SDK_PYTHON - sdkGoEnv := *sdkJavaEnv - sdkGoEnv.ApacheBeamSdk = pb.Sdk_SDK_GO - helloWordPython := "if __name__ == \"__main__\":\n print(\"Hello world!\")\n" - helloWordGo := "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n" - helloWordJava := "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}" type args struct { ctx context.Context cacheService cache.Cache @@ -1000,9 +1092,9 @@ func Test_runStep(t *testing.T) { args: args{ ctx: context.Background(), cacheService: cacheService, - pipelineId: uuid.UUID{}, + pipelineId: uuid.New(), isUnitTest: false, - sdkEnv: &sdkPythonEnv, + sdkEnv: sdkPythonEnv, pipelineOptions: "", pipelineLifeCycleCtx: context.Background(), cancelChannel: make(chan bool, 1), @@ -1018,7 +1110,7 @@ func Test_runStep(t *testing.T) { cacheService: cacheService, pipelineId: uuid.New(), isUnitTest: true, - sdkEnv: &sdkGoEnv, + sdkEnv: sdkGoEnv, pipelineOptions: "", pipelineLifeCycleCtx: context.Background(), cancelChannel: make(chan bool, 1), @@ -1135,11 +1227,151 @@ func TestGetGraph(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := GetGraph(tt.args.ctx, tt.args.cacheService, tt.args.key, tt.args.errorTitle) if (err != nil) != tt.wantErr { - t.Errorf("GetGraph() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("GetGraph error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { - t.Errorf("GetGraph() got = %v, want %v", got, tt.want) + t.Errorf("GetGraph got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_processSetupError(t *testing.T) { + client, mock := redismock.NewClientMock() + pipelineId := uuid.New() + errorMessage := "MOCK_ERROR" + type args struct { + err error + pipelineId uuid.UUID + cacheService cache.Cache + ctxWithTimeout context.Context + } + tests := []struct { + name string + mocks func() + args args + wantErr bool + }{ + { + name: "Error during HSet operation", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), "MOCK_VALUE").SetErr(fmt.Errorf(errorMessage)) + }, + args: args{ + err: fmt.Errorf(errorMessage), + pipelineId: pipelineId, + cacheService: &redis.Cache{ + Client: client, + }, + ctxWithTimeout: nil, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mocks() + if err := processSetupError(tt.args.err, tt.args.pipelineId, tt.args.cacheService, tt.args.ctxWithTimeout); (err != nil) != tt.wantErr { + t.Errorf("processSetupError() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_processErrorWithSavingOutput(t *testing.T) { + client, mock := redismock.NewClientMock() + pipelineId := uuid.New() + errorMessage := "MOCK_ERROR" + subKey := cache.RunOutput + type args struct { + ctx context.Context + err error + errorOutput []byte + pipelineId uuid.UUID + subKey cache.SubKey + cacheService cache.Cache + errorTitle string + newStatus pb.Status + } + tests := []struct { + name string + mocks func() + args args + wantErr bool + }{ + { + name: "Error during HSet operation", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), subKey).SetErr(fmt.Errorf(errorMessage)) + }, + args: args{ + ctx: context.Background(), + err: fmt.Errorf(errorMessage), + errorOutput: nil, + pipelineId: pipelineId, + subKey: subKey, + cacheService: &redis.Cache{Client: client}, + errorTitle: "", + newStatus: 0, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mocks() + if err := processErrorWithSavingOutput(tt.args.ctx, tt.args.err, tt.args.errorOutput, tt.args.pipelineId, tt.args.subKey, tt.args.cacheService, tt.args.errorTitle, tt.args.newStatus); (err != nil) != tt.wantErr { + t.Errorf("processErrorWithSavingOutput() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_processRunError(t *testing.T) { + client, mock := redismock.NewClientMock() + pipelineId := uuid.New() + errorMessage := "MOCK_ERROR" + subKey := cache.RunError + errorChannel := make(chan error, 1) + errorChannel <- fmt.Errorf(errorMessage) + type args struct { + ctx context.Context + errorChannel chan error + errorOutput []byte + pipelineId uuid.UUID + cacheService cache.Cache + stopReadLogsChannel chan bool + finishReadLogsChannel chan bool + } + tests := []struct { + name string + mocks func() + args args + wantErr bool + }{ + { + name: "Error during HSet operation", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), subKey).SetErr(fmt.Errorf(errorMessage)) + }, + args: args{ + ctx: context.Background(), + errorChannel: errorChannel, + errorOutput: nil, + pipelineId: pipelineId, + cacheService: &redis.Cache{Client: client}, + stopReadLogsChannel: nil, + finishReadLogsChannel: nil, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mocks() + if err := processRunError(tt.args.ctx, tt.args.errorChannel, tt.args.errorOutput, tt.args.pipelineId, tt.args.cacheService, tt.args.stopReadLogsChannel, tt.args.finishReadLogsChannel); (err != nil) != tt.wantErr { + t.Errorf("processRunError() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/playground/backend/internal/setup_tools/builder/setup_builder.go b/playground/backend/internal/setup_tools/builder/setup_builder.go index c4d05b1bc410..9d4ecaa8dc92 100644 --- a/playground/backend/internal/setup_tools/builder/setup_builder.go +++ b/playground/backend/internal/setup_tools/builder/setup_builder.go @@ -58,7 +58,7 @@ func Preparer(paths *fs_tool.LifeCyclePaths, sdkEnv *environment.BeamEnvs, valRe WithPreparer(). WithSdkPreparers(prep). ExecutorBuilder - return &builder, err + return &builder, nil } // Compiler return executor with set args for compiler From db272d46044bb5ef92957e05a7646950d45140eb Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Thu, 24 Feb 2022 09:54:53 +0300 Subject: [PATCH 4/6] Add test for processCompileSuccess method --- .../code_processing/code_processing_test.go | 159 +++++++++++++++++- 1 file changed, 157 insertions(+), 2 deletions(-) diff --git a/playground/backend/internal/code_processing/code_processing_test.go b/playground/backend/internal/code_processing/code_processing_test.go index 966eab26197a..edab4619223a 100644 --- a/playground/backend/internal/code_processing/code_processing_test.go +++ b/playground/backend/internal/code_processing/code_processing_test.go @@ -26,6 +26,7 @@ import ( "beam.apache.org/playground/backend/internal/utils" "beam.apache.org/playground/backend/internal/validators" "context" + "encoding/json" "fmt" "github.com/go-redis/redismock/v8" "github.com/google/uuid" @@ -47,9 +48,11 @@ const ( goConfig = "{\n \"compile_cmd\": \"go\",\n \"run_cmd\": \"\",\n \"compile_args\": [\n \"build\",\n \"-o\",\n \"bin\"\n ],\n \"run_args\": [\n ]\n}" pipelinesFolder = "executable_files" configFolder = "configs" + resourcesFolder = "resources" helloWordPython = "if __name__ == \"__main__\":\n print(\"Hello world!\")\n" helloWordGo = "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n" helloWordJava = "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}" + graphFilePath = "resources/graph.dot" ) var opt goleak.Option @@ -65,16 +68,26 @@ func TestMain(m *testing.M) { func setup() { // create configs for java - err := os.MkdirAll("configs", fs.ModePerm) + err := os.MkdirAll(configFolder, fs.ModePerm) if err != nil { panic(err) } - filePath := filepath.Join("configs", pb.Sdk_SDK_JAVA.String()+".json") + filePath := filepath.Join(configFolder, pb.Sdk_SDK_JAVA.String()+".json") err = os.WriteFile(filePath, []byte(javaConfig), 0600) if err != nil { panic(err) } + // create dir with graph file + err = os.MkdirAll(resourcesFolder, fs.ModePerm) + if err != nil { + panic(err) + } + err = os.WriteFile(graphFilePath, []byte("graph"), 0600) + if err != nil { + panic(err) + } + path, err := os.Getwd() if err != nil { panic(err) @@ -94,6 +107,10 @@ func teardown() { if err != nil { panic(fmt.Errorf("error during test teardown: %s", err.Error())) } + err = os.RemoveAll(resourcesFolder) + if err != nil { + panic(fmt.Errorf("error during test teardown: %s", err.Error())) + } os.Clearenv() } @@ -1376,3 +1393,141 @@ func Test_processRunError(t *testing.T) { }) } } + +func Test_processCompileSuccess(t *testing.T) { + client, mock := redismock.NewClientMock() + pipelineId := uuid.New() + output := "output" + cacheMock := &redis.Cache{Client: client} + marshalLogs, _ := json.Marshal(cache.Logs) + marshalCompileOutput, _ := json.Marshal(cache.CompileOutput) + marshalRunOutput, _ := json.Marshal(cache.RunOutput) + marshalRunError, _ := json.Marshal(cache.RunError) + outputMarshal, _ := json.Marshal(output) + marshalEmptyString, _ := json.Marshal("") + type args struct { + ctx context.Context + output []byte + pipelineId uuid.UUID + cacheService cache.Cache + } + tests := []struct { + name string + mocks func() + args args + wantErr bool + }{ + { + name: "Error during set value to CompileOutput subKey", + mocks: func() { + }, + args: args{ + ctx: context.Background(), + output: []byte(output), + pipelineId: pipelineId, + cacheService: cacheMock, + }, + wantErr: true, + }, + { + name: "Error during set value to RunOutput subKey", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), marshalCompileOutput, outputMarshal).SetVal(1) + }, + args: args{ + ctx: context.Background(), + output: []byte(output), + pipelineId: pipelineId, + cacheService: cacheMock, + }, + wantErr: true, + }, + { + name: "Error during set value to RunError subKey", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), marshalCompileOutput, outputMarshal).SetVal(1) + mock.ExpectHSet(pipelineId.String(), marshalRunOutput, marshalEmptyString).SetVal(1) + }, + args: args{ + ctx: context.Background(), + output: []byte(output), + pipelineId: pipelineId, + cacheService: cacheMock, + }, + wantErr: true, + }, + { + name: "Error during set value to Logs subKey", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), marshalCompileOutput, outputMarshal).SetVal(1) + mock.ExpectHSet(pipelineId.String(), marshalRunOutput, marshalEmptyString).SetVal(1) + mock.ExpectHSet(pipelineId.String(), marshalRunError, marshalEmptyString).SetVal(1) + }, + args: args{ + ctx: context.Background(), + output: []byte(output), + pipelineId: pipelineId, + cacheService: cacheMock, + }, + wantErr: true, + }, + { + name: "Error during set value to Graph subKey", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), marshalCompileOutput, outputMarshal).SetVal(1) + mock.ExpectHSet(pipelineId.String(), marshalRunOutput, marshalEmptyString).SetVal(1) + mock.ExpectHSet(pipelineId.String(), marshalRunError, marshalEmptyString).SetVal(1) + mock.ExpectHSet(pipelineId.String(), marshalLogs, marshalEmptyString).SetVal(1) + }, + args: args{ + ctx: context.Background(), + output: []byte(output), + pipelineId: pipelineId, + cacheService: cacheMock, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mocks() + if err := processCompileSuccess(tt.args.ctx, tt.args.output, tt.args.pipelineId, tt.args.cacheService); (err != nil) != tt.wantErr { + t.Errorf("processCompileSuccess() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_readGraphFile(t *testing.T) { + pipelineLifeCycleCtx, _ := context.WithTimeout(context.Background(), 1*time.Second) + type args struct { + pipelineLifeCycleCtx context.Context + backgroundCtx context.Context + cacheService cache.Cache + graphFilePath string + pipelineId uuid.UUID + } + tests := []struct { + name string + args args + }{ + { + name: "All success", + args: args{ + pipelineLifeCycleCtx: pipelineLifeCycleCtx, + backgroundCtx: context.Background(), + cacheService: cacheService, + graphFilePath: graphFilePath, + pipelineId: uuid.New(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + readGraphFile(tt.args.pipelineLifeCycleCtx, tt.args.backgroundCtx, tt.args.cacheService, tt.args.graphFilePath, tt.args.pipelineId) + if v, _ := cacheService.GetValue(tt.args.backgroundCtx, tt.args.pipelineId, cache.Graph); v == nil { + t.Errorf("readGraphFile() error: the graph was not cached") + } + }) + } +} From 0c60360904f013695733122b1a37f19c8bf137d9 Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Mon, 28 Feb 2022 11:04:25 +0300 Subject: [PATCH 5/6] Update test names --- .../code_processing/code_processing_test.go | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/playground/backend/internal/code_processing/code_processing_test.go b/playground/backend/internal/code_processing/code_processing_test.go index edab4619223a..4921a0bd9233 100644 --- a/playground/backend/internal/code_processing/code_processing_test.go +++ b/playground/backend/internal/code_processing/code_processing_test.go @@ -53,6 +53,7 @@ const ( helloWordGo = "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\")\n}\n" helloWordJava = "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello world!\");\n }\n}" graphFilePath = "resources/graph.dot" + jsonExtension = ".json" ) var opt goleak.Option @@ -72,7 +73,7 @@ func setup() { if err != nil { panic(err) } - filePath := filepath.Join(configFolder, pb.Sdk_SDK_JAVA.String()+".json") + filePath := filepath.Join(configFolder, pb.Sdk_SDK_JAVA.String()+jsonExtension) err = os.WriteFile(filePath, []byte(javaConfig), 0600) if err != nil { panic(err) @@ -126,6 +127,7 @@ func Test_Process(t *testing.T) { } sdkGoEnv := *sdkJavaEnv sdkGoEnv.ApacheBeamSdk = pb.Sdk_SDK_GO + incorrectGoHelloWord := "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\").\n}\n" type args struct { ctx context.Context appEnv *environment.ApplicationEnvs @@ -263,7 +265,7 @@ func Test_Process(t *testing.T) { // As a result status into cache should be set as Status_STATUS_PREPARATION_ERROR. name: "Prepare step failed", createExecFile: true, - code: "package main\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"hello world\").\n}\n", + code: incorrectGoHelloWord, cancelFunc: false, expectedStatus: pb.Status_STATUS_PREPARATION_ERROR, expectedCompileOutput: nil, @@ -646,7 +648,7 @@ func setupSDK(sdk pb.Sdk) { if err != nil { panic(err) } - filePath := filepath.Join(configFolder, sdk.String()+".json") + filePath := filepath.Join(configFolder, sdk.String()+jsonExtension) switch sdk { case pb.Sdk_SDK_JAVA: err = os.WriteFile(filePath, []byte(javaConfig), 0600) @@ -1015,7 +1017,7 @@ func Test_compileStep(t *testing.T) { expectedStatus pb.Status }{ { - name: "Test compilation step working on java sdk", + name: "Test compilation step finishes successfully on java sdk", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -1029,7 +1031,7 @@ func Test_compileStep(t *testing.T) { expectedStatus: pb.Status_STATUS_EXECUTING, }, { - name: "Test compilation step working on python sdk", + name: "Test compilation step finishes successfully on python sdk", args: args{ ctx: context.Background(), cacheService: cacheService, @@ -1105,6 +1107,9 @@ func Test_runStep(t *testing.T) { expectedStatus pb.Status }{ { + // Test case with calling runStep method on python sdk. + // cmd.Run return error during saving output. + // As a result, the pipeline status should be Status_STATUS_RUN_ERROR. name: "Test run step working on python sdk", args: args{ ctx: context.Background(), @@ -1121,6 +1126,9 @@ func Test_runStep(t *testing.T) { expectedStatus: pb.Status_STATUS_RUN_ERROR, }, { + // Test case with calling runStep method on go sdk. + // cmd.Run return error due to missing executable file. + // As a result, the pipeline status should be Status_STATUS_RUN_ERROR. name: "Test run step working on go sdk", args: args{ ctx: context.Background(), @@ -1137,6 +1145,8 @@ func Test_runStep(t *testing.T) { expectedStatus: pb.Status_STATUS_RUN_ERROR, }, { + // Test case with calling runStep method without preparing files with code. + // As a result, the pipeline status should be Status_STATUS_ERROR. name: "Test run step without preparing files with code", args: args{ ctx: context.Background(), @@ -1512,7 +1522,7 @@ func Test_readGraphFile(t *testing.T) { args args }{ { - name: "All success", + name: "Successfully saving the prepared graph to the cache", args: args{ pipelineLifeCycleCtx: pipelineLifeCycleCtx, backgroundCtx: context.Background(), From 69a5febe317dfae5167d45a9db25e20bd362f65f Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Mon, 28 Feb 2022 15:28:57 +0300 Subject: [PATCH 6/6] Refactoring code --- .../backend/internal/code_processing/code_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/backend/internal/code_processing/code_processing_test.go b/playground/backend/internal/code_processing/code_processing_test.go index 4921a0bd9233..13eaa1417517 100644 --- a/playground/backend/internal/code_processing/code_processing_test.go +++ b/playground/backend/internal/code_processing/code_processing_test.go @@ -73,7 +73,7 @@ func setup() { if err != nil { panic(err) } - filePath := filepath.Join(configFolder, pb.Sdk_SDK_JAVA.String()+jsonExtension) + filePath := filepath.Join(configFolder, fmt.Sprintf("%s%s", pb.Sdk_SDK_JAVA.String(), jsonExtension)) err = os.WriteFile(filePath, []byte(javaConfig), 0600) if err != nil { panic(err) @@ -648,7 +648,7 @@ func setupSDK(sdk pb.Sdk) { if err != nil { panic(err) } - filePath := filepath.Join(configFolder, sdk.String()+jsonExtension) + filePath := filepath.Join(configFolder, fmt.Sprintf("%s%s", sdk.String(), jsonExtension)) switch sdk { case pb.Sdk_SDK_JAVA: err = os.WriteFile(filePath, []byte(javaConfig), 0600)