From f49881096c42bd4a020f8c8c7684b7f77c52044f Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Mon, 10 Aug 2020 16:55:47 -0400 Subject: [PATCH 01/12] add readme tests (first attempt) --- internal/lifecycle/readme_test.go | 277 ++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 internal/lifecycle/readme_test.go diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go new file mode 100644 index 0000000..68b98c7 --- /dev/null +++ b/internal/lifecycle/readme_test.go @@ -0,0 +1,277 @@ +package lifecycle + +import ( + "bufio" + "os" + "os/exec" + "reflect" + "strings" + "testing" +) + +func setEnv(e map[string]string) error { + for k, v := range e { + err := os.Setenv(k, v) + if err != nil { + return err + } + } + + return nil +} + +func unsetEnv(e map[string]string) error { + for k, _ := range e { + err := os.Unsetenv(k) + if err != nil { + return err + } + } + + return nil +} + +func equalError(a, b error) bool { + if a == nil { + return b == nil + } + if b == nil { + return a == nil + } + return a.Error() == b.Error() +} + +type toCommandsTest struct { + in codeBlock + out []*exec.Cmd + err error + env map[string]string + serviceName string + gcrURL string +} + +var toCommandsTests = []toCommandsTest{ + { + in: []string { + "echo hello world", + }, + out: []*exec.Cmd{ + exec.Command("echo", "hello", "world"), + }, + }, + { + in: []string { + "echo hello world one", + "echo hello world two", + }, + out: []*exec.Cmd{ + exec.Command("echo", "hello", "world", "one"), + exec.Command("echo", "hello", "world", "two"), + }, + }, + { + in: []string { + "echo multi \\", + "line command", + }, + out: []*exec.Cmd{ + exec.Command("echo", "multi", "line", "command"), + }, + }, + { + in: []string { + "echo ${TEST_ENV}", + }, + out: []*exec.Cmd{ + exec.Command("echo", "hello", "world"), + }, + env: map[string]string{ + "TEST_ENV": "hello world", + }, + }, + { + in: []string { + "gcloud run services deploy hello_world", + }, + out: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name"), + }, + serviceName: "unique_service_name", + }, + { + in: []string { + "gcloud builds submit --tag=gcr.io/hello/world", + }, + out: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "builds", "submit", "--tag=gcr.io/unique/tag"), + }, + gcrURL: "gcr.io/unique/tag", + }, + { + in: []string { + "gcloud run services deploy hello_world --image=gcr.io/hello/world", + }, + out: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image=gcr.io/unique/tag"), + }, + serviceName: "unique_service_name", + gcrURL: "gcr.io/unique/tag", + }, + //{ this test breaks right now (issue #3) + // in: []string { + // "gcloud run services deploy hello_world --image gcr.io/hello/world", + // }, + // out: []*exec.Cmd{ + // exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image gcr.io/unique/tag"), + // }, + // serviceName: "unique_service_name", + // gcrURL: "gcr.io/unique/tag", + //}, + { + in: []string { + "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}", + }, + out: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image=gcr.io/unique/tag", "--add-cloudsql-instances=project:region:instance"), + }, + env: map[string]string{ + "TEST_CLOUD_SQL_CONNECTION": "project:region:instance", + }, + serviceName: "unique_service_name", + gcrURL: "gcr.io/unique/tag", + }, +} + +func TestToCommands(t *testing.T) { + for i, tt := range toCommandsTests { + err := setEnv(tt.env) + if err != nil { + t.Errorf("#%d: setEnv: %#v", i, err) + + err = unsetEnv(tt.env) + if err != nil { + t.Errorf("#%d: unsetEnv: %#v", i, err) + } + + continue + } + + h, err := tt.in.toCommands(tt.serviceName, tt.gcrURL) + eE := equalError(err, tt.err) + if !eE { + t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.err) + } + + if eE && !reflect.DeepEqual(h, tt.out) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tt.out) + } + + err = unsetEnv(tt.env) + if err != nil { + t.Errorf("#%d: unsetEnv: %#v", i, err) + } + } +} + +type extractTest struct { + in string + out []codeBlock + err error +} + +var extractTests = []extractTest{ + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo hello world\n" + + "```\n", + out: []codeBlock { + []string { + "echo hello world", + }, + }, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo line one\n" + + "echo line two\n" + + "```\n", + out: []codeBlock { + []string { + "echo line one", + "echo line two", + }, + }, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo multi \\\n" + + "line command\n" + + "```\n", + out: []codeBlock { + []string { + "echo multi \\", + "line command", + }, + }, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build command\n" + + "```\n" + + "markdown instructions\n" + + "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo deploy command\n" + + "```\n", + out: []codeBlock { + []string { + "echo build command", + }, + []string { + "echo deploy command", + }, + }, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build and deploy command\n" + + "```\n" + + "markdown instructions\n" + + "```\n" + + "echo irrelevant command\n" + + "```\n", + out: []codeBlock { + []string { + "echo build and deploy command", + }, + }, + }, + { + in: "```\n" + + "echo hello world\n" + + "```\n", + out: nil, + }, +} + +func TestExtractCodeBlocks(t *testing.T) { + for i, tt := range extractTests { + s := bufio.NewScanner(strings.NewReader(tt.in)) + + h, err := extractCodeBlocks(s) + if !equalError(err, tt.err) { + t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.err) + continue + } + + if !reflect.DeepEqual(h, tt.out) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tt.out) + continue + } + } +} From 3e7ce6ab006ff4d7554f470955d3d7328ac57789 Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Thu, 13 Aug 2020 17:26:10 -0400 Subject: [PATCH 02/12] refactor readme parsing code and tests - add more test cases - make code more testable --- internal/lifecycle/readme.go | 14 +- internal/lifecycle/readme_test.go | 392 +++++++++++++++++++----------- 2 files changed, 267 insertions(+), 139 deletions(-) diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 81a7b57..4cf688a 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -109,9 +109,19 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { defer file.Close() scanner := bufio.NewScanner(file) + + l, err := extractLifecycle(scanner, serviceName, gcrURL) + if err != nil { + return l, fmt.Errorf("[lifecycle.parseREADME] extracting commands out of %s: %w", filename, err) + } + return l, nil + +} + +func extractLifecycle(scanner *bufio.Scanner, serviceName, gcrURL string) (Lifecycle, error) { codeBlocks, err := extractCodeBlocks(scanner) if err != nil { - return nil, fmt.Errorf("lifecycle.extractCodeBlocks: %s: %w", filename, err) + return nil, fmt.Errorf("lifecycle.extractCodeBlocks: %w", err) } if len(codeBlocks) == 0 { @@ -122,7 +132,7 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { for _, b := range codeBlocks { cmds, err := b.toCommands(serviceName, gcrURL) if err != nil { - return l, fmt.Errorf("codeBlock.toCommands: code blocks in %s: %w", filename, err) + return l, fmt.Errorf("codeBlock.toCommands: %w", err) } l = append(l, cmds...) diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index 68b98c7..e9e649d 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -41,48 +41,126 @@ func equalError(a, b error) bool { return a.Error() == b.Error() } -type toCommandsTest struct { - in codeBlock - out []*exec.Cmd - err error - env map[string]string - serviceName string - gcrURL string +type test struct { + in string + codeBlocks []codeBlock + cmds []*exec.Cmd + toCommandsErr error + extractLifecycleErr error + extractCodeBlocksErr error + env map[string]string + serviceName string + gcrURL string } -var toCommandsTests = []toCommandsTest{ +var tests = []test{ { - in: []string { - "echo hello world", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo hello world\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo hello world", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ exec.Command("echo", "hello", "world"), }, }, { - in: []string { - "echo hello world one", - "echo hello world two", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo line one\n" + + "echo line two\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo line one", + "echo line two", + }, }, - out: []*exec.Cmd{ - exec.Command("echo", "hello", "world", "one"), - exec.Command("echo", "hello", "world", "two"), + cmds: []*exec.Cmd{ + exec.Command("echo", "line", "one"), + exec.Command("echo", "line", "two"), }, }, { - in: []string { - "echo multi \\", - "line command", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo multi \\\n" + + "line command\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo multi \\", + "line command", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ exec.Command("echo", "multi", "line", "command"), }, }, { - in: []string { - "echo ${TEST_ENV}", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build command\n" + + "```\n" + + "markdown instructions\n" + + "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo deploy command\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo build command", + }, + []string{ + "echo deploy command", + }, + }, + cmds: []*exec.Cmd{ + exec.Command("echo", "build", "command"), + exec.Command("echo", "deploy", "command"), + }, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build and deploy command\n" + + "```\n" + + "markdown instructions\n" + + "```\n" + + "echo irrelevant command\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo build and deploy command", + }, + }, + cmds: []*exec.Cmd{ + exec.Command("echo", "build", "and", "deploy", "command"), + }, + }, + { + in: "```\n" + + "echo hello world\n" + + "```\n", + codeBlocks: nil, + cmds: nil, + extractLifecycleErr: errNoREADMECodeBlocksFound, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo ${TEST_ENV}\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo ${TEST_ENV}", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ exec.Command("echo", "hello", "world"), }, env: map[string]string{ @@ -90,60 +168,146 @@ var toCommandsTests = []toCommandsTest{ }, }, { - in: []string { - "gcloud run services deploy hello_world", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud run services deploy hello_world\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud run services deploy hello_world", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name"), }, serviceName: "unique_service_name", }, { - in: []string { - "gcloud builds submit --tag=gcr.io/hello/world", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud builds submit --tag=gcr.io/hello/world\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud builds submit --tag=gcr.io/hello/world", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ exec.Command("gcloud", "--quiet", "builds", "submit", "--tag=gcr.io/unique/tag"), }, gcrURL: "gcr.io/unique/tag", }, { - in: []string { - "gcloud run services deploy hello_world --image=gcr.io/hello/world", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud builds submit --tag=gcr.io/hello/\\\n" + + "world\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud builds submit --tag=gcr.io/hello/\\", + "world", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "builds", "submit", "--tag=gcr.io/unique/tag"), + }, + gcrURL: "gcr.io/unique/tag", + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud run services deploy hello_world --image=gcr.io/hello/world\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud run services deploy hello_world --image=gcr.io/hello/world", + }, + }, + cmds: []*exec.Cmd{ exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image=gcr.io/unique/tag"), }, serviceName: "unique_service_name", - gcrURL: "gcr.io/unique/tag", + gcrURL: "gcr.io/unique/tag", }, //{ this test breaks right now (issue #3) - // in: []string { - // "gcloud run services deploy hello_world --image gcr.io/hello/world", + // in: "[//]: # ({sst-run-unix})\n" + + // "```\n" + + // "gcloud run services deploy hello_world --image gcr.io/hello/world\n" + + // "```\n", + // codeBlocks: []codeBlock { + // []string { + // "gcloud run services deploy hello_world --image gcr.io/hello/world", + // }, // }, - // out: []*exec.Cmd{ - // exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image gcr.io/unique/tag"), + // cmds: []*exec.Cmd{ + // exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image", "gcr.io/unique/tag"), // }, // serviceName: "unique_service_name", // gcrURL: "gcr.io/unique/tag", //}, { - in: []string { - "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}", + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}", + }, }, - out: []*exec.Cmd{ + cmds: []*exec.Cmd{ exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image=gcr.io/unique/tag", "--add-cloudsql-instances=project:region:instance"), }, env: map[string]string{ "TEST_CLOUD_SQL_CONNECTION": "project:region:instance", }, serviceName: "unique_service_name", - gcrURL: "gcr.io/unique/tag", + gcrURL: "gcr.io/unique/tag", + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud run services update hello_world --add-cloudsql-instances=\\\n" + + "project:region:instance\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud run services update hello_world --add-cloudsql-instances=\\", + "project:region:instance", + }, + }, + cmds: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "run", "services", "update", "unique_service_name", "--add-cloudsql-instances=project:region:instance"), + }, + serviceName: "unique_service_name", + gcrURL: "gcr.io/unique/tag", + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "gcloud run services update hello_world --add-cloudsql-instances=\\\n" + + "${TEST_CLOUD_SQL_CONNECTION}\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "gcloud run services update hello_world --add-cloudsql-instances=\\", + "${TEST_CLOUD_SQL_CONNECTION}", + }, + }, + cmds: []*exec.Cmd{ + exec.Command("gcloud", "--quiet", "run", "services", "update", "unique_service_name", "--add-cloudsql-instances=project:region:instance"), + }, + env: map[string]string{ + "TEST_CLOUD_SQL_CONNECTION": "project:region:instance", + }, + serviceName: "unique_service_name", + gcrURL: "gcr.io/unique/tag", }, } func TestToCommands(t *testing.T) { - for i, tt := range toCommandsTests { + for i, tt := range tests { err := setEnv(tt.env) if err != nil { t.Errorf("#%d: setEnv: %#v", i, err) @@ -156,14 +320,20 @@ func TestToCommands(t *testing.T) { continue } - h, err := tt.in.toCommands(tt.serviceName, tt.gcrURL) - eE := equalError(err, tt.err) - if !eE { - t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.err) + equalE := true + var cmds []*exec.Cmd + for j, codeBlock := range tt.codeBlocks { + h, err := codeBlock.toCommands(tt.serviceName, tt.gcrURL) + equalE = equalE && equalError(err, tt.toCommandsErr) + if !equalE { + t.Errorf("#%d.%d: error mismatch: %v, want %v", i, j, err, tt.toCommandsErr) + } + + cmds = append(cmds, h...) } - if eE && !reflect.DeepEqual(h, tt.out) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tt.out) + if equalE && !reflect.DeepEqual(cmds, tt.cmds) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, cmds, tt.cmds) } err = unsetEnv(tt.env) @@ -173,104 +343,52 @@ func TestToCommands(t *testing.T) { } } -type extractTest struct { - in string - out []codeBlock - err error -} +func TestExtractLifecycle(t *testing.T) { + for i, tt := range tests { + err := setEnv(tt.env) + if err != nil { + t.Errorf("#%d: setEnv: %#v", i, err) -var extractTests = []extractTest{ - { - in: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo hello world\n" + - "```\n", - out: []codeBlock { - []string { - "echo hello world", - }, - }, - }, - { - in: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo line one\n" + - "echo line two\n" + - "```\n", - out: []codeBlock { - []string { - "echo line one", - "echo line two", - }, - }, - }, - { - in: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo multi \\\n" + - "line command\n" + - "```\n", - out: []codeBlock { - []string { - "echo multi \\", - "line command", - }, - }, - }, - { - in: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo build command\n" + - "```\n" + - "markdown instructions\n" + - "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo deploy command\n" + - "```\n", - out: []codeBlock { - []string { - "echo build command", - }, - []string { - "echo deploy command", - }, - }, - }, - { - in: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo build and deploy command\n" + - "```\n" + - "markdown instructions\n" + - "```\n" + - "echo irrelevant command\n" + - "```\n", - out: []codeBlock { - []string { - "echo build and deploy command", - }, - }, - }, - { - in: "```\n" + - "echo hello world\n" + - "```\n", - out: nil, - }, + err = unsetEnv(tt.env) + if err != nil { + t.Errorf("#%d: unsetEnv: %#v", i, err) + } + + continue + } + + s := bufio.NewScanner(strings.NewReader(tt.in)) + var c []*exec.Cmd + c, err = extractLifecycle(s, tt.serviceName, tt.gcrURL) + + eE := equalError(err, tt.extractLifecycleErr) + if !eE { + t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.extractLifecycleErr) + } + + if eE && !reflect.DeepEqual(c, tt.cmds) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, c, tt.cmds) + } + + err = unsetEnv(tt.env) + if err != nil { + t.Errorf("#%d: unsetEnv: %#v", i, err) + } + } } func TestExtractCodeBlocks(t *testing.T) { - for i, tt := range extractTests { + for i, tt := range tests { s := bufio.NewScanner(strings.NewReader(tt.in)) h, err := extractCodeBlocks(s) - if !equalError(err, tt.err) { - t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.err) + if !equalError(err, tt.extractCodeBlocksErr) { + t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.extractCodeBlocksErr) continue } - if !reflect.DeepEqual(h, tt.out) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tt.out) + if !reflect.DeepEqual(h, tt.codeBlocks) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tt.codeBlocks) continue } } From 2dbdf61dc6f64160e97b3d2943e78b90b09b60e8 Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 14 Aug 2020 14:34:19 -0400 Subject: [PATCH 03/12] minor updates to test error messages --- internal/lifecycle/readme_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index e9e649d..5e47f60 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -326,7 +326,7 @@ func TestToCommands(t *testing.T) { h, err := codeBlock.toCommands(tt.serviceName, tt.gcrURL) equalE = equalE && equalError(err, tt.toCommandsErr) if !equalE { - t.Errorf("#%d.%d: error mismatch: %v, want %v", i, j, err, tt.toCommandsErr) + t.Errorf("#%d.%d: error mismatch\nhave: %v\nwant: %v", i, j, err, tt.toCommandsErr) } cmds = append(cmds, h...) @@ -363,7 +363,7 @@ func TestExtractLifecycle(t *testing.T) { eE := equalError(err, tt.extractLifecycleErr) if !eE { - t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.extractLifecycleErr) + t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tt.extractLifecycleErr) } if eE && !reflect.DeepEqual(c, tt.cmds) { @@ -383,7 +383,7 @@ func TestExtractCodeBlocks(t *testing.T) { h, err := extractCodeBlocks(s) if !equalError(err, tt.extractCodeBlocksErr) { - t.Errorf("#%d: error mismatch: %v, want %v", i, err, tt.extractCodeBlocksErr) + t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tt.extractCodeBlocksErr) continue } From de86d331388df7099b522fb709d85502e9c49985 Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 14 Aug 2020 15:07:03 -0400 Subject: [PATCH 04/12] add readme parsing tests --- internal/lifecycle/lifecycle.go | 2 +- internal/lifecycle/readme.go | 14 +++-- internal/lifecycle/readme_test.go | 97 ++++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 33 deletions(-) diff --git a/internal/lifecycle/lifecycle.go b/internal/lifecycle/lifecycle.go index c3137ba..91b3b9e 100644 --- a/internal/lifecycle/lifecycle.go +++ b/internal/lifecycle/lifecycle.go @@ -73,7 +73,7 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) { return nil, fmt.Errorf("lifecycle.parseREADME: %s: %w", readmePath, err) } - log.Println("No code blocks immediately preceded by %s found in README.md\n", codeTag) + log.Printf("No code blocks immediately preceded by %s found in README.md\n", codeTag) } else { log.Println("No README.md found") } diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 4cf688a..6ff3248 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -43,6 +43,10 @@ var ( mdCodeFenceStartRegexp = regexp.MustCompile("^\\w*`{3,}[^`]*$") errNoREADMECodeBlocksFound = fmt.Errorf("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found", codeTag) + errCodeBlockNotClosed = fmt.Errorf("unexpected EOF: code block not closed") + errCodeBlockStartNotFound = fmt.Errorf("expecting start of code block immediately after code tag") + errEOFAfterCodeTag = fmt.Errorf("unexpected EOF: file ended immediately after code tag") + errCodeBlockEndAfterLineCont = fmt.Errorf("unexpected end of code block: expecting command line continuation") ) // codeBlock is a slice of strings containing terminal commands. codeBlocks, for example, could be used to hold the @@ -68,7 +72,7 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) i++ if i >= len(cb) { - return nil, fmt.Errorf("unexpected end of code block: expecting command line continuation; code block dump:\n%s", strings.Join(cb, "\n")) + return nil, fmt.Errorf("%w; code block dump:\n%s", errCodeBlockEndAfterLineCont, strings.Join(cb, "\n")) } l := cb[i] @@ -112,7 +116,7 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { l, err := extractLifecycle(scanner, serviceName, gcrURL) if err != nil { - return l, fmt.Errorf("[lifecycle.parseREADME] extracting commands out of %s: %w", filename, err) + return l, err } return l, nil @@ -157,14 +161,14 @@ func extractCodeBlocks(scanner *bufio.Scanner) ([]codeBlock, error) { if err := scanner.Err(); err != nil { return nil, fmt.Errorf("line %d: bufio.Scanner.Scan: %w", lineNum, err) } - return nil, fmt.Errorf("unexpected EOF: file ended immediately after code tag") + return nil, errEOFAfterCodeTag } lineNum++ startCodeBlockLine := scanner.Text() m := mdCodeFenceStartRegexp.MatchString(startCodeBlockLine) if !m { - return nil, fmt.Errorf("line %d: expecting start of code block immediately after code tag", lineNum) + return nil, fmt.Errorf("line %d: %w", lineNum, errCodeBlockStartNotFound) } c := strings.Count(startCodeBlockLine, "`") @@ -188,7 +192,7 @@ func extractCodeBlocks(scanner *bufio.Scanner) ([]codeBlock, error) { } if !blockClosed { - return nil, fmt.Errorf("unexpected EOF: code block not closed") + return nil, errCodeBlockNotClosed } blocks = append(blocks, block) diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index 5e47f60..c6079a0 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -2,6 +2,7 @@ package lifecycle import ( "bufio" + "errors" "os" "os/exec" "reflect" @@ -38,7 +39,7 @@ func equalError(a, b error) bool { if b == nil { return a == nil } - return a.Error() == b.Error() + return errors.Is(a, b) } type test struct { @@ -68,6 +69,34 @@ var tests = []test{ exec.Command("echo", "hello", "world"), }, }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo hello world\n", + codeBlocks: nil, + cmds: nil, + extractLifecycleErr: errCodeBlockNotClosed, + extractCodeBlocksErr: errCodeBlockNotClosed, + }, + { + in: "[//]: # ({sst-run-unix})\n" + + "not start of code block\n" + + "```\n" + + "echo hello world\n" + + "```\n", + codeBlocks: nil, + cmds: nil, + extractLifecycleErr: errCodeBlockStartNotFound, + extractCodeBlocksErr: errCodeBlockStartNotFound, + }, + { + in: "instuctions\n" + + "[//]: # ({sst-run-unix})\n", + codeBlocks: nil, + cmds: nil, + extractLifecycleErr: errEOFAfterCodeTag, + extractCodeBlocksErr: errEOFAfterCodeTag, + }, { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -101,6 +130,20 @@ var tests = []test{ exec.Command("echo", "multi", "line", "command"), }, }, + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo multi \\\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo multi \\", + }, + }, + cmds: nil, + toCommandsErr: errCodeBlockEndAfterLineCont, + extractLifecycleErr: errCodeBlockEndAfterLineCont, + }, { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -307,12 +350,12 @@ var tests = []test{ } func TestToCommands(t *testing.T) { - for i, tt := range tests { - err := setEnv(tt.env) + for i, tc := range tests { + err := setEnv(tc.env) if err != nil { t.Errorf("#%d: setEnv: %#v", i, err) - err = unsetEnv(tt.env) + err = unsetEnv(tc.env) if err != nil { t.Errorf("#%d: unsetEnv: %#v", i, err) } @@ -322,21 +365,21 @@ func TestToCommands(t *testing.T) { equalE := true var cmds []*exec.Cmd - for j, codeBlock := range tt.codeBlocks { - h, err := codeBlock.toCommands(tt.serviceName, tt.gcrURL) - equalE = equalE && equalError(err, tt.toCommandsErr) + for j, codeBlock := range tc.codeBlocks { + h, err := codeBlock.toCommands(tc.serviceName, tc.gcrURL) + equalE = equalE && equalError(err, tc.toCommandsErr) if !equalE { - t.Errorf("#%d.%d: error mismatch\nhave: %v\nwant: %v", i, j, err, tt.toCommandsErr) + t.Errorf("#%d.%d: error mismatch\nhave: %v\nwant: %v", i, j, err, tc.toCommandsErr) } cmds = append(cmds, h...) } - if equalE && !reflect.DeepEqual(cmds, tt.cmds) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, cmds, tt.cmds) + if equalE && !reflect.DeepEqual(cmds, tc.cmds) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, cmds, tc.cmds) } - err = unsetEnv(tt.env) + err = unsetEnv(tc.env) if err != nil { t.Errorf("#%d: unsetEnv: %#v", i, err) } @@ -344,12 +387,12 @@ func TestToCommands(t *testing.T) { } func TestExtractLifecycle(t *testing.T) { - for i, tt := range tests { - err := setEnv(tt.env) + for i, tc := range tests { + err := setEnv(tc.env) if err != nil { t.Errorf("#%d: setEnv: %#v", i, err) - err = unsetEnv(tt.env) + err = unsetEnv(tc.env) if err != nil { t.Errorf("#%d: unsetEnv: %#v", i, err) } @@ -357,20 +400,20 @@ func TestExtractLifecycle(t *testing.T) { continue } - s := bufio.NewScanner(strings.NewReader(tt.in)) + s := bufio.NewScanner(strings.NewReader(tc.in)) var c []*exec.Cmd - c, err = extractLifecycle(s, tt.serviceName, tt.gcrURL) + c, err = extractLifecycle(s, tc.serviceName, tc.gcrURL) - eE := equalError(err, tt.extractLifecycleErr) + eE := equalError(err, tc.extractLifecycleErr) if !eE { - t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tt.extractLifecycleErr) + t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tc.extractLifecycleErr) } - if eE && !reflect.DeepEqual(c, tt.cmds) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, c, tt.cmds) + if eE && !reflect.DeepEqual(c, tc.cmds) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, c, tc.cmds) } - err = unsetEnv(tt.env) + err = unsetEnv(tc.env) if err != nil { t.Errorf("#%d: unsetEnv: %#v", i, err) } @@ -378,17 +421,17 @@ func TestExtractLifecycle(t *testing.T) { } func TestExtractCodeBlocks(t *testing.T) { - for i, tt := range tests { - s := bufio.NewScanner(strings.NewReader(tt.in)) + for i, tc := range tests { + s := bufio.NewScanner(strings.NewReader(tc.in)) h, err := extractCodeBlocks(s) - if !equalError(err, tt.extractCodeBlocksErr) { - t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tt.extractCodeBlocksErr) + if !equalError(err, tc.extractCodeBlocksErr) { + t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tc.extractCodeBlocksErr) continue } - if !reflect.DeepEqual(h, tt.codeBlocks) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tt.codeBlocks) + if !reflect.DeepEqual(h, tc.codeBlocks) { + t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tc.codeBlocks) continue } } From cd2dd9416d960fb15840a1bc3a203f6e9e23984f Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 14 Aug 2020 15:21:05 -0400 Subject: [PATCH 05/12] fix typo in lifecycle/readme.go --- internal/lifecycle/readme.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 6ff3248..0c7c163 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -118,8 +118,8 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { if err != nil { return l, err } - return l, nil + return l, nil } func extractLifecycle(scanner *bufio.Scanner, serviceName, gcrURL string) (Lifecycle, error) { From 8c6fca9b5fa5ebdf2a3137a127fd2a7507d8f58f Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Mon, 17 Aug 2020 12:17:02 -0400 Subject: [PATCH 06/12] address first round of PR reviews, code clean up - comment lifecycle.extractLifecycle - add comments to each test in readme_test.go - update mismatch error message format to readme_test.go - remove unnecessary equalError function in readme_test.go - other minor code clean up --- internal/lifecycle/readme.go | 18 ++-- internal/lifecycle/readme_test.go | 145 +++++++++++++++++------------- 2 files changed, 92 insertions(+), 71 deletions(-) diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 0c7c163..6f02ec7 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -102,9 +102,10 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) return cmds, nil } -// parseREADME parses a README file with the given name. It reads terminal commands surrounded by one of the codeTags -// listed above and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container -// Registry tag with the provided inputs. +// parseREADME parses a README file with the given name. It parses terminal commands in code blocks annotated by the +// codeTag and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container +// Registry tag with the provided inputs. It also expands environment variables and supports bash-style line +// continuations. func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { file, err := os.Open(filename) if err != nil { @@ -114,14 +115,13 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { scanner := bufio.NewScanner(file) - l, err := extractLifecycle(scanner, serviceName, gcrURL) - if err != nil { - return l, err - } - - return l, nil + return extractLifecycle(scanner, serviceName, gcrURL) } +// extractLifecycle is a helper function for parseREADME. It takes a scanner that reads from a Markdown file and parses +// terminal commands in code blocks annotated by the codeTag and loads them into a Lifecycle. In the process, it +// replaces the Cloud Run service name and Container Registry tag with the provided inputs. It also expands environment +// variables and supports bash-style line continuations. func extractLifecycle(scanner *bufio.Scanner, serviceName, gcrURL string) (Lifecycle, error) { codeBlocks, err := extractCodeBlocks(scanner) if err != nil { diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index c6079a0..970c59d 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -10,51 +10,41 @@ import ( "testing" ) +// setEnv takes a map of environment variables to their values and sets the program's environment accordingly. func setEnv(e map[string]string) error { for k, v := range e { - err := os.Setenv(k, v) - if err != nil { + if err := os.Setenv(k, v); err != nil { return err } } - return nil } +// unsetEnv takes a map of environment variables to their values and unsets the environment variables in the program's +// environment. func unsetEnv(e map[string]string) error { for k, _ := range e { - err := os.Unsetenv(k) - if err != nil { + if err := os.Unsetenv(k); err != nil { return err } } - return nil } -func equalError(a, b error) bool { - if a == nil { - return b == nil - } - if b == nil { - return a == nil - } - return errors.Is(a, b) -} - type test struct { - in string - codeBlocks []codeBlock - cmds []*exec.Cmd - toCommandsErr error - extractLifecycleErr error - extractCodeBlocksErr error - env map[string]string - serviceName string - gcrURL string + in string // inupt markdown string + codeBlocks []codeBlock // expected result of extractCodeBlocks on in + cmds []*exec.Cmd // expected result of toCommands on all codeBlocks and extractLifecycle on in + toCommandsErr error // expected toCommands return error + extractLifecycleErr error // expected extractLifecycle return error + extractCodeBlocksErr error // expected extractCodeBlocks return error + env map[string]string // map of environment variables to values for this test + serviceName string // Cloud Run service name that should replace existing names + gcrURL string // Container Registry URL that should replace existing URLs } var tests = []test{ + // single code block, single one-line command { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -69,6 +59,8 @@ var tests = []test{ exec.Command("echo", "hello", "world"), }, }, + + // code block not closed { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -78,6 +70,8 @@ var tests = []test{ extractLifecycleErr: errCodeBlockNotClosed, extractCodeBlocksErr: errCodeBlockNotClosed, }, + + // code block doesn't start immediately after code tag { in: "[//]: # ({sst-run-unix})\n" + "not start of code block\n" + @@ -89,6 +83,8 @@ var tests = []test{ extractLifecycleErr: errCodeBlockStartNotFound, extractCodeBlocksErr: errCodeBlockStartNotFound, }, + + // EOF immediately after code tag { in: "instuctions\n" + "[//]: # ({sst-run-unix})\n", @@ -97,6 +93,8 @@ var tests = []test{ extractLifecycleErr: errEOFAfterCodeTag, extractCodeBlocksErr: errEOFAfterCodeTag, }, + + // single code block, two one-line commands { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -114,6 +112,8 @@ var tests = []test{ exec.Command("echo", "line", "two"), }, }, + + // single code block, single multiline command { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -130,6 +130,8 @@ var tests = []test{ exec.Command("echo", "multi", "line", "command"), }, }, + + // line cont char but code block closes at next line { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -144,6 +146,8 @@ var tests = []test{ toCommandsErr: errCodeBlockEndAfterLineCont, extractLifecycleErr: errCodeBlockEndAfterLineCont, }, + + // two code blocks, one single-line command in each, with markdown instructions in the middle { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -167,6 +171,8 @@ var tests = []test{ exec.Command("echo", "deploy", "command"), }, }, + + // two code blocks, but only one is annotated with code tag { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -185,6 +191,8 @@ var tests = []test{ exec.Command("echo", "build", "and", "deploy", "command"), }, }, + + // one code block, but not annotated with code tag { in: "```\n" + "echo hello world\n" + @@ -193,6 +201,8 @@ var tests = []test{ cmds: nil, extractLifecycleErr: errNoREADMECodeBlocksFound, }, + + // expand environment variable test { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -210,6 +220,8 @@ var tests = []test{ "TEST_ENV": "hello world", }, }, + + // replace Cloud Run service name with provided name { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -225,6 +237,8 @@ var tests = []test{ }, serviceName: "unique_service_name", }, + + // replace Container Registry URL with provided URL { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -240,6 +254,8 @@ var tests = []test{ }, gcrURL: "gcr.io/unique/tag", }, + + // replace multiline GCR URL with provided URL { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -257,6 +273,8 @@ var tests = []test{ }, gcrURL: "gcr.io/unique/tag", }, + + // replace Cloud Run service name and GCR URL with provided inputs { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -273,7 +291,10 @@ var tests = []test{ serviceName: "unique_service_name", gcrURL: "gcr.io/unique/tag", }, - //{ this test breaks right now (issue #3) + + // replace Cloud Run service name and GCR URL with `--image url` syntax + // this test breaks right now (issue #3) + //{ // in: "[//]: # ({sst-run-unix})\n" + // "```\n" + // "gcloud run services deploy hello_world --image gcr.io/hello/world\n" + @@ -289,6 +310,8 @@ var tests = []test{ // serviceName: "unique_service_name", // gcrURL: "gcr.io/unique/tag", //}, + + // replace Cloud Run service name and GCR URL with provided inputs and expand environment variables { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -308,6 +331,8 @@ var tests = []test{ serviceName: "unique_service_name", gcrURL: "gcr.io/unique/tag", }, + + // replace Cloud Run service name provided name in command with multiline arguments { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -326,6 +351,8 @@ var tests = []test{ serviceName: "unique_service_name", gcrURL: "gcr.io/unique/tag", }, + + // replace Cloud Run service name provided name and expand environment variables in command with multiline arguments { in: "[//]: # ({sst-run-unix})\n" + "```\n" + @@ -351,71 +378,65 @@ var tests = []test{ func TestToCommands(t *testing.T) { for i, tc := range tests { - err := setEnv(tc.env) - if err != nil { - t.Errorf("#%d: setEnv: %#v", i, err) + if err := setEnv(tc.env); err != nil { + t.Errorf("#%d: setEnv: %v", i, err) - err = unsetEnv(tc.env) - if err != nil { - t.Errorf("#%d: unsetEnv: %#v", i, err) + if err = unsetEnv(tc.env); err != nil { + t.Errorf("#%d: unsetEnv: %v", i, err) } continue } - equalE := true + matchE := true var cmds []*exec.Cmd for j, codeBlock := range tc.codeBlocks { h, err := codeBlock.toCommands(tc.serviceName, tc.gcrURL) - equalE = equalE && equalError(err, tc.toCommandsErr) - if !equalE { - t.Errorf("#%d.%d: error mismatch\nhave: %v\nwant: %v", i, j, err, tc.toCommandsErr) + matchE = matchE && errors.Is(err, tc.toCommandsErr) + if !matchE { + t.Errorf("#%d.%d: error mismatch\nwant: %v\ngot: %v", i, j, tc.toCommandsErr, err) } cmds = append(cmds, h...) } - if equalE && !reflect.DeepEqual(cmds, tc.cmds) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, cmds, tc.cmds) + if matchE && !reflect.DeepEqual(cmds, tc.cmds) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) } - err = unsetEnv(tc.env) - if err != nil { - t.Errorf("#%d: unsetEnv: %#v", i, err) + if err := unsetEnv(tc.env); err != nil { + t.Errorf("#%d: unsetEnv: %v", i, err) } } } func TestExtractLifecycle(t *testing.T) { for i, tc := range tests { - err := setEnv(tc.env) - if err != nil { - t.Errorf("#%d: setEnv: %#v", i, err) + if err := setEnv(tc.env); err != nil { + t.Errorf("#%d: setEnv: %v", i, err) - err = unsetEnv(tc.env) - if err != nil { - t.Errorf("#%d: unsetEnv: %#v", i, err) + if err = unsetEnv(tc.env); err != nil { + t.Errorf("#%d: unsetEnv: %v", i, err) } continue } s := bufio.NewScanner(strings.NewReader(tc.in)) - var c []*exec.Cmd - c, err = extractLifecycle(s, tc.serviceName, tc.gcrURL) + var cmds []*exec.Cmd + cmds, err := extractLifecycle(s, tc.serviceName, tc.gcrURL) - eE := equalError(err, tc.extractLifecycleErr) - if !eE { - t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tc.extractLifecycleErr) + mE := errors.Is(err, tc.extractLifecycleErr) + if !mE { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractLifecycleErr, err) } - if eE && !reflect.DeepEqual(c, tc.cmds) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, c, tc.cmds) + if mE && !reflect.DeepEqual(cmds, tc.cmds) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) } - err = unsetEnv(tc.env) - if err != nil { - t.Errorf("#%d: unsetEnv: %#v", i, err) + if err = unsetEnv(tc.env); err != nil { + t.Errorf("#%d: unsetEnv: %v", i, err) } } } @@ -424,14 +445,14 @@ func TestExtractCodeBlocks(t *testing.T) { for i, tc := range tests { s := bufio.NewScanner(strings.NewReader(tc.in)) - h, err := extractCodeBlocks(s) - if !equalError(err, tc.extractCodeBlocksErr) { - t.Errorf("#%d: error mismatch\nhave: %v\nwant: %v", i, err, tc.extractCodeBlocksErr) + codeBlocks, err := extractCodeBlocks(s) + if !errors.Is(err, tc.extractCodeBlocksErr) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractCodeBlocksErr, err) continue } - if !reflect.DeepEqual(h, tc.codeBlocks) { - t.Errorf("#%d: result mismatch\nhave: %#+v\nwant: %#+v", i, h, tc.codeBlocks) + if !reflect.DeepEqual(codeBlocks, tc.codeBlocks) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.codeBlocks, codeBlocks) continue } } From 2cb24e8103897e1926db324222bba1a62b3d8f49 Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Thu, 20 Aug 2020 12:36:13 -0400 Subject: [PATCH 07/12] add actual file parsing test to readme parsing tests --- internal/lifecycle/readme_test.go | 106 +++++++++++++++++++++++------- internal/lifecycle/readme_test.md | 17 +++++ 2 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 internal/lifecycle/readme_test.md diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index 970c59d..4220b18 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -23,7 +23,7 @@ func setEnv(e map[string]string) error { // unsetEnv takes a map of environment variables to their values and unsets the environment variables in the program's // environment. func unsetEnv(e map[string]string) error { - for k, _ := range e { + for k := range e { if err := os.Unsetenv(k); err != nil { return err } @@ -32,7 +32,8 @@ func unsetEnv(e map[string]string) error { } type test struct { - in string // inupt markdown string + inFileName string // input Markdown file + inStr string // inupt markdown string codeBlocks []codeBlock // expected result of extractCodeBlocks on in cmds []*exec.Cmd // expected result of toCommands on all codeBlocks and extractLifecycle on in toCommandsErr error // expected toCommands return error @@ -46,7 +47,7 @@ type test struct { var tests = []test{ // single code block, single one-line command { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo hello world\n" + "```\n", @@ -62,7 +63,7 @@ var tests = []test{ // code block not closed { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo hello world\n", codeBlocks: nil, @@ -73,7 +74,7 @@ var tests = []test{ // code block doesn't start immediately after code tag { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "not start of code block\n" + "```\n" + "echo hello world\n" + @@ -86,7 +87,7 @@ var tests = []test{ // EOF immediately after code tag { - in: "instuctions\n" + + inStr: "instuctions\n" + "[//]: # ({sst-run-unix})\n", codeBlocks: nil, cmds: nil, @@ -96,7 +97,7 @@ var tests = []test{ // single code block, two one-line commands { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo line one\n" + "echo line two\n" + @@ -115,7 +116,7 @@ var tests = []test{ // single code block, single multiline command { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo multi \\\n" + "line command\n" + @@ -133,7 +134,7 @@ var tests = []test{ // line cont char but code block closes at next line { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo multi \\\n" + "```\n", @@ -149,7 +150,7 @@ var tests = []test{ // two code blocks, one single-line command in each, with markdown instructions in the middle { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo build command\n" + "```\n" + @@ -174,7 +175,7 @@ var tests = []test{ // two code blocks, but only one is annotated with code tag { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo build and deploy command\n" + "```\n" + @@ -194,7 +195,7 @@ var tests = []test{ // one code block, but not annotated with code tag { - in: "```\n" + + inStr: "```\n" + "echo hello world\n" + "```\n", codeBlocks: nil, @@ -204,7 +205,7 @@ var tests = []test{ // expand environment variable test { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "echo ${TEST_ENV}\n" + "```\n", @@ -223,7 +224,7 @@ var tests = []test{ // replace Cloud Run service name with provided name { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud run services deploy hello_world\n" + "```\n", @@ -240,7 +241,7 @@ var tests = []test{ // replace Container Registry URL with provided URL { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud builds submit --tag=gcr.io/hello/world\n" + "```\n", @@ -257,7 +258,7 @@ var tests = []test{ // replace multiline GCR URL with provided URL { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud builds submit --tag=gcr.io/hello/\\\n" + "world\n" + @@ -276,7 +277,7 @@ var tests = []test{ // replace Cloud Run service name and GCR URL with provided inputs { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud run services deploy hello_world --image=gcr.io/hello/world\n" + "```\n", @@ -295,7 +296,7 @@ var tests = []test{ // replace Cloud Run service name and GCR URL with `--image url` syntax // this test breaks right now (issue #3) //{ - // in: "[//]: # ({sst-run-unix})\n" + + // inStr: "[//]: # ({sst-run-unix})\n" + // "```\n" + // "gcloud run services deploy hello_world --image gcr.io/hello/world\n" + // "```\n", @@ -313,7 +314,7 @@ var tests = []test{ // replace Cloud Run service name and GCR URL with provided inputs and expand environment variables { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}\n" + "```\n", @@ -334,7 +335,7 @@ var tests = []test{ // replace Cloud Run service name provided name in command with multiline arguments { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud run services update hello_world --add-cloudsql-instances=\\\n" + "project:region:instance\n" + @@ -354,7 +355,7 @@ var tests = []test{ // replace Cloud Run service name provided name and expand environment variables in command with multiline arguments { - in: "[//]: # ({sst-run-unix})\n" + + inStr: "[//]: # ({sst-run-unix})\n" + "```\n" + "gcloud run services update hello_world --add-cloudsql-instances=\\\n" + "${TEST_CLOUD_SQL_CONNECTION}\n" + @@ -374,10 +375,33 @@ var tests = []test{ serviceName: "unique_service_name", gcrURL: "gcr.io/unique/tag", }, + + // markdown file input to codeBlocks test + { + inFileName: "readme_test.md", + codeBlocks: []codeBlock{ + []string{ + "echo hello world", + }, + []string{ + "echo line one", + "echo line two", + }, + }, + cmds: []*exec.Cmd{ + exec.Command("echo", "hello", "world"), + exec.Command("echo", "line", "one"), + exec.Command("echo", "line", "two"), + }, + }, } func TestToCommands(t *testing.T) { for i, tc := range tests { + if len(tc.codeBlocks) == 0 { + continue + } + if err := setEnv(tc.env); err != nil { t.Errorf("#%d: setEnv: %v", i, err) @@ -412,6 +436,10 @@ func TestToCommands(t *testing.T) { func TestExtractLifecycle(t *testing.T) { for i, tc := range tests { + if tc.inStr == "" { + continue + } + if err := setEnv(tc.env); err != nil { t.Errorf("#%d: setEnv: %v", i, err) @@ -422,7 +450,7 @@ func TestExtractLifecycle(t *testing.T) { continue } - s := bufio.NewScanner(strings.NewReader(tc.in)) + s := bufio.NewScanner(strings.NewReader(tc.inStr)) var cmds []*exec.Cmd cmds, err := extractLifecycle(s, tc.serviceName, tc.gcrURL) @@ -441,10 +469,40 @@ func TestExtractLifecycle(t *testing.T) { } } -func TestExtractCodeBlocks(t *testing.T) { +func TestExtractCodeBlocksStr(t *testing.T) { for i, tc := range tests { - s := bufio.NewScanner(strings.NewReader(tc.in)) + if tc.inStr == "" { + continue + } + + s := bufio.NewScanner(strings.NewReader(tc.inStr)) + + codeBlocks, err := extractCodeBlocks(s) + if !errors.Is(err, tc.extractCodeBlocksErr) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractCodeBlocksErr, err) + continue + } + + if !reflect.DeepEqual(codeBlocks, tc.codeBlocks) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.codeBlocks, codeBlocks) + continue + } + } +} + +func TestExtractCodeBlocksFile(t *testing.T) { + for i, tc := range tests { + if tc.inFileName == "" { + continue + } + + file, err := os.Open(tc.inFileName) + if err != nil { + t.Errorf("#%d: os.Open: %v", i, err) + } + defer file.Close() + s := bufio.NewScanner(file) codeBlocks, err := extractCodeBlocks(s) if !errors.Is(err, tc.extractCodeBlocksErr) { t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractCodeBlocksErr, err) diff --git a/internal/lifecycle/readme_test.md b/internal/lifecycle/readme_test.md new file mode 100644 index 0000000..0f0b9af --- /dev/null +++ b/internal/lifecycle/readme_test.md @@ -0,0 +1,17 @@ +This code block with one command should be picked up: +[//]: # ({sst-run-unix}) +```bash +echo hello world +``` + +This code block with two commands with indents should be picked up: +[//]: # ({sst-run-unix}) +```bash +echo line one + echo line two +``` + +This code block without a comment code tag should not be picked up: +```bash +echo hello world +``` From 5f389ddfe411f08f448bb243df51a134ef9c045a Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Thu, 20 Aug 2020 12:57:26 -0400 Subject: [PATCH 08/12] refactor file parsing test in readme parsing tests --- internal/lifecycle/readme_test.go | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index 4220b18..ce413a7 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -37,6 +37,7 @@ type test struct { codeBlocks []codeBlock // expected result of extractCodeBlocks on in cmds []*exec.Cmd // expected result of toCommands on all codeBlocks and extractLifecycle on in toCommandsErr error // expected toCommands return error + parseREADMEErr error // expected parseREADMEErr return error extractLifecycleErr error // expected extractLifecycle return error extractCodeBlocksErr error // expected extractCodeBlocks return error env map[string]string // map of environment variables to values for this test @@ -379,15 +380,6 @@ var tests = []test{ // markdown file input to codeBlocks test { inFileName: "readme_test.md", - codeBlocks: []codeBlock{ - []string{ - "echo hello world", - }, - []string{ - "echo line one", - "echo line two", - }, - }, cmds: []*exec.Cmd{ exec.Command("echo", "hello", "world"), exec.Command("echo", "line", "one"), @@ -490,27 +482,22 @@ func TestExtractCodeBlocksStr(t *testing.T) { } } -func TestExtractCodeBlocksFile(t *testing.T) { +func TestParseREADME(t *testing.T) { for i, tc := range tests { if tc.inFileName == "" { continue } - file, err := os.Open(tc.inFileName) - if err != nil { - t.Errorf("#%d: os.Open: %v", i, err) - } - defer file.Close() + var cmds []*exec.Cmd + cmds, err := parseREADME(tc.inFileName, tc.serviceName, tc.gcrURL) - s := bufio.NewScanner(file) - codeBlocks, err := extractCodeBlocks(s) - if !errors.Is(err, tc.extractCodeBlocksErr) { - t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractCodeBlocksErr, err) + if !errors.Is(err, tc.parseREADMEErr) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.parseREADMEErr, err) continue } - if !reflect.DeepEqual(codeBlocks, tc.codeBlocks) { - t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.codeBlocks, codeBlocks) + if !reflect.DeepEqual(cmds, tc.cmds) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) continue } } From 604a871f7501d9f004063a3cb603852cd943108f Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 21 Aug 2020 14:14:29 -0400 Subject: [PATCH 09/12] refactor readme_test.go --- internal/lifecycle/readme_test.go | 580 ++++++++++++++---------------- 1 file changed, 262 insertions(+), 318 deletions(-) diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index ce413a7..b47960f 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -31,83 +31,35 @@ func unsetEnv(e map[string]string) error { return nil } -type test struct { - inFileName string // input Markdown file - inStr string // inupt markdown string - codeBlocks []codeBlock // expected result of extractCodeBlocks on in - cmds []*exec.Cmd // expected result of toCommands on all codeBlocks and extractLifecycle on in - toCommandsErr error // expected toCommands return error - parseREADMEErr error // expected parseREADMEErr return error - extractLifecycleErr error // expected extractLifecycle return error - extractCodeBlocksErr error // expected extractCodeBlocks return error - env map[string]string // map of environment variables to values for this test - serviceName string // Cloud Run service name that should replace existing names - gcrURL string // Container Registry URL that should replace existing URLs +// uniqueServiceName is the Cloud Run Service name that will replace the existing service names in each codeBlock test. +const uniqueServiceName = "unique_service_name" + +// uniqueGCRURL is the Container Registry URL tag that will replace the existing Container Registry URL tag in each codeBlock test. +const uniqueGCRURL = "gcr.io/unique/tag" + +type toCommandsTest struct { + codeBlock codeBlock // input code block + cmds []*exec.Cmd // expected result of codeBlock.toCommands + err error // expected return error of codeBlock.toCommands + env map[string]string // map of environment variables to values for this test } -var tests = []test{ - // single code block, single one-line command +var toCommandsTests = []toCommandsTest{ + // single one-line command { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo hello world\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo hello world", - }, + codeBlock: codeBlock{ + "echo hello world", }, cmds: []*exec.Cmd{ exec.Command("echo", "hello", "world"), }, }, - // code block not closed - { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo hello world\n", - codeBlocks: nil, - cmds: nil, - extractLifecycleErr: errCodeBlockNotClosed, - extractCodeBlocksErr: errCodeBlockNotClosed, - }, - - // code block doesn't start immediately after code tag - { - inStr: "[//]: # ({sst-run-unix})\n" + - "not start of code block\n" + - "```\n" + - "echo hello world\n" + - "```\n", - codeBlocks: nil, - cmds: nil, - extractLifecycleErr: errCodeBlockStartNotFound, - extractCodeBlocksErr: errCodeBlockStartNotFound, - }, - - // EOF immediately after code tag - { - inStr: "instuctions\n" + - "[//]: # ({sst-run-unix})\n", - codeBlocks: nil, - cmds: nil, - extractLifecycleErr: errEOFAfterCodeTag, - extractCodeBlocksErr: errEOFAfterCodeTag, - }, - - // single code block, two one-line commands + // two one-line commands { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo line one\n" + - "echo line two\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo line one", - "echo line two", - }, + codeBlock: codeBlock{ + "echo line one", + "echo line two", }, cmds: []*exec.Cmd{ exec.Command("echo", "line", "one"), @@ -115,18 +67,11 @@ var tests = []test{ }, }, - // single code block, single multiline command + // single multiline command { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo multi \\\n" + - "line command\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo multi \\", - "line command", - }, + codeBlock: codeBlock{ + "echo multi \\", + "line command", }, cmds: []*exec.Cmd{ exec.Command("echo", "multi", "line", "command"), @@ -135,85 +80,17 @@ var tests = []test{ // line cont char but code block closes at next line { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo multi \\\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo multi \\", - }, + codeBlock: codeBlock{ + "echo multi \\", }, - cmds: nil, - toCommandsErr: errCodeBlockEndAfterLineCont, - extractLifecycleErr: errCodeBlockEndAfterLineCont, - }, - - // two code blocks, one single-line command in each, with markdown instructions in the middle - { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo build command\n" + - "```\n" + - "markdown instructions\n" + - "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo deploy command\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo build command", - }, - []string{ - "echo deploy command", - }, - }, - cmds: []*exec.Cmd{ - exec.Command("echo", "build", "command"), - exec.Command("echo", "deploy", "command"), - }, - }, - - // two code blocks, but only one is annotated with code tag - { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo build and deploy command\n" + - "```\n" + - "markdown instructions\n" + - "```\n" + - "echo irrelevant command\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo build and deploy command", - }, - }, - cmds: []*exec.Cmd{ - exec.Command("echo", "build", "and", "deploy", "command"), - }, - }, - - // one code block, but not annotated with code tag - { - inStr: "```\n" + - "echo hello world\n" + - "```\n", - codeBlocks: nil, - cmds: nil, - extractLifecycleErr: errNoREADMECodeBlocksFound, + cmds: nil, + err: errCodeBlockEndAfterLineCont, }, // expand environment variable test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "echo ${TEST_ENV}\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "echo ${TEST_ENV}", - }, + codeBlock: codeBlock{ + "echo ${TEST_ENV}", }, cmds: []*exec.Cmd{ exec.Command("echo", "hello", "world"), @@ -223,177 +100,99 @@ var tests = []test{ }, }, - // replace Cloud Run service name with provided name + // replace Cloud Run service name with provided name test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud run services deploy hello_world\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud run services deploy hello_world", - }, + codeBlock: codeBlock{ + "gcloud run services deploy hello_world", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name"), + exec.Command("gcloud", "--quiet", "run", "services", "deploy", uniqueServiceName), }, - serviceName: "unique_service_name", }, - // replace Container Registry URL with provided URL + // replace Container Registry URL with provided URL test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud builds submit --tag=gcr.io/hello/world\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud builds submit --tag=gcr.io/hello/world", - }, + codeBlock: codeBlock{ + "gcloud builds submit --tag=gcr.io/hello/world", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "builds", "submit", "--tag=gcr.io/unique/tag"), + exec.Command("gcloud", "--quiet", "builds", "submit", "--tag="+uniqueGCRURL), }, - gcrURL: "gcr.io/unique/tag", }, - // replace multiline GCR URL with provided URL + // replace multiline GCR URL with provided URL test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud builds submit --tag=gcr.io/hello/\\\n" + - "world\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud builds submit --tag=gcr.io/hello/\\", - "world", - }, + codeBlock: codeBlock{ + "gcloud builds submit --tag=gcr.io/hello/\\", + "world", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "builds", "submit", "--tag=gcr.io/unique/tag"), + exec.Command("gcloud", "--quiet", "builds", "submit", "--tag="+uniqueGCRURL), }, - gcrURL: "gcr.io/unique/tag", }, - // replace Cloud Run service name and GCR URL with provided inputs + // replace Cloud Run service name and GCR URL with provided inputs test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud run services deploy hello_world --image=gcr.io/hello/world\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud run services deploy hello_world --image=gcr.io/hello/world", - }, + codeBlock: codeBlock{ + "gcloud run services deploy hello_world --image=gcr.io/hello/world", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image=gcr.io/unique/tag"), + exec.Command("gcloud", "--quiet", "run", "services", "deploy", uniqueServiceName, "--image="+uniqueGCRURL), }, - serviceName: "unique_service_name", - gcrURL: "gcr.io/unique/tag", }, - // replace Cloud Run service name and GCR URL with `--image url` syntax + // replace Cloud Run service name and GCR URL with `--image url` syntax test // this test breaks right now (issue #3) //{ - // inStr: "[//]: # ({sst-run-unix})\n" + - // "```\n" + - // "gcloud run services deploy hello_world --image gcr.io/hello/world\n" + - // "```\n", - // codeBlocks: []codeBlock { - // []string { - // "gcloud run services deploy hello_world --image gcr.io/hello/world", - // }, + // codeBlock: codeBlock{ + // "gcloud run services deploy hello_world --image gcr.io/hello/world", // }, // cmds: []*exec.Cmd{ - // exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image", "gcr.io/unique/tag"), + // exec.Command("gcloud", "--quiet", "run", "services", "deploy", uniqueServiceName, "--image", uniqueGCRURL), // }, // serviceName: "unique_service_name", // gcrURL: "gcr.io/unique/tag", //}, - - // replace Cloud Run service name and GCR URL with provided inputs and expand environment variables { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}", - }, + codeBlock: codeBlock{ + "gcloud run services deploy hello_world --image=gcr.io/hello/world --add-cloudsql-instances=${TEST_CLOUD_SQL_CONNECTION}", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "run", "services", "deploy", "unique_service_name", "--image=gcr.io/unique/tag", "--add-cloudsql-instances=project:region:instance"), + exec.Command("gcloud", "--quiet", "run", "services", "deploy", uniqueServiceName, "--image="+uniqueGCRURL, "--add-cloudsql-instances=project:region:instance"), }, env: map[string]string{ "TEST_CLOUD_SQL_CONNECTION": "project:region:instance", }, - serviceName: "unique_service_name", - gcrURL: "gcr.io/unique/tag", }, - // replace Cloud Run service name provided name in command with multiline arguments + // replace Cloud Run service name provided name in command with multiline arguments test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud run services update hello_world --add-cloudsql-instances=\\\n" + - "project:region:instance\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud run services update hello_world --add-cloudsql-instances=\\", - "project:region:instance", - }, + codeBlock: codeBlock{ + "gcloud run services update hello_world --add-cloudsql-instances=\\", + "project:region:instance", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "run", "services", "update", "unique_service_name", "--add-cloudsql-instances=project:region:instance"), + exec.Command("gcloud", "--quiet", "run", "services", "update", uniqueServiceName, "--add-cloudsql-instances=project:region:instance"), }, - serviceName: "unique_service_name", - gcrURL: "gcr.io/unique/tag", }, - // replace Cloud Run service name provided name and expand environment variables in command with multiline arguments + // replace Cloud Run service name provided name and expand environment variables in command with multiline arguments test { - inStr: "[//]: # ({sst-run-unix})\n" + - "```\n" + - "gcloud run services update hello_world --add-cloudsql-instances=\\\n" + - "${TEST_CLOUD_SQL_CONNECTION}\n" + - "```\n", - codeBlocks: []codeBlock{ - []string{ - "gcloud run services update hello_world --add-cloudsql-instances=\\", - "${TEST_CLOUD_SQL_CONNECTION}", - }, + codeBlock: codeBlock{ + "gcloud run services update hello_world --add-cloudsql-instances=\\", + "${TEST_CLOUD_SQL_CONNECTION}", }, cmds: []*exec.Cmd{ - exec.Command("gcloud", "--quiet", "run", "services", "update", "unique_service_name", "--add-cloudsql-instances=project:region:instance"), + exec.Command("gcloud", "--quiet", "run", "services", "update", uniqueServiceName, "--add-cloudsql-instances=project:region:instance"), }, env: map[string]string{ "TEST_CLOUD_SQL_CONNECTION": "project:region:instance", }, - serviceName: "unique_service_name", - gcrURL: "gcr.io/unique/tag", - }, - - // markdown file input to codeBlocks test - { - inFileName: "readme_test.md", - cmds: []*exec.Cmd{ - exec.Command("echo", "hello", "world"), - exec.Command("echo", "line", "one"), - exec.Command("echo", "line", "two"), - }, }, } func TestToCommands(t *testing.T) { - for i, tc := range tests { - if len(tc.codeBlocks) == 0 { - continue - } - + for i, tc := range toCommandsTests { if err := setEnv(tc.env); err != nil { t.Errorf("#%d: setEnv: %v", i, err) @@ -404,19 +203,14 @@ func TestToCommands(t *testing.T) { continue } - matchE := true - var cmds []*exec.Cmd - for j, codeBlock := range tc.codeBlocks { - h, err := codeBlock.toCommands(tc.serviceName, tc.gcrURL) - matchE = matchE && errors.Is(err, tc.toCommandsErr) - if !matchE { - t.Errorf("#%d.%d: error mismatch\nwant: %v\ngot: %v", i, j, tc.toCommandsErr, err) - } + cmds, err := tc.codeBlock.toCommands(uniqueServiceName, uniqueGCRURL) - cmds = append(cmds, h...) + if !errors.Is(err, tc.err) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) + continue } - if matchE && !reflect.DeepEqual(cmds, tc.cmds) { + if err == nil && !reflect.DeepEqual(cmds, tc.cmds) { t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) } @@ -426,79 +220,229 @@ func TestToCommands(t *testing.T) { } } -func TestExtractLifecycle(t *testing.T) { - for i, tc := range tests { - if tc.inStr == "" { - continue - } - - if err := setEnv(tc.env); err != nil { - t.Errorf("#%d: setEnv: %v", i, err) +type parseREADMETest struct { + inFileName string // input Markdown file + lifecycle Lifecycle // expected result of parseREADME + err error // expected parseREADME return error +} - if err = unsetEnv(tc.env); err != nil { - t.Errorf("#%d: unsetEnv: %v", i, err) - } +var parseREADMETests = []parseREADMETest{ + // three code blocks, only two with comment code tags. one with one command, the other with two commands + { + inFileName: "readme_test.md", + lifecycle: Lifecycle{ + exec.Command("echo", "hello", "world"), + exec.Command("echo", "line", "one"), + exec.Command("echo", "line", "two"), + }, + }, +} +func TestParseREADME(t *testing.T) { + for i, tc := range parseREADMETests { + if tc.inFileName == "" { continue } - s := bufio.NewScanner(strings.NewReader(tc.inStr)) - var cmds []*exec.Cmd - cmds, err := extractLifecycle(s, tc.serviceName, tc.gcrURL) - - mE := errors.Is(err, tc.extractLifecycleErr) - if !mE { - t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractLifecycleErr, err) - } + // Cloud Run Service name and Container Registry URL tag replacement will be tested in TestToCommands + lifecycle, err := parseREADME(tc.inFileName, "", "") - if mE && !reflect.DeepEqual(cmds, tc.cmds) { - t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) + if !errors.Is(err, tc.err) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) + continue } - if err = unsetEnv(tc.env); err != nil { - t.Errorf("#%d: unsetEnv: %v", i, err) + if err == nil && !reflect.DeepEqual(lifecycle, tc.lifecycle) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.lifecycle, lifecycle) + continue } } } -func TestExtractCodeBlocksStr(t *testing.T) { - for i, tc := range tests { - if tc.inStr == "" { +type extractLifecycleTest struct { + in string // input Markdown string + lifecycle Lifecycle // expected results of extractLifecycle on in + err error // expected error +} + +var extractLifecycleTests = []extractLifecycleTest{ + // single code block + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo hello world\n" + + "```\n", + lifecycle: Lifecycle{ + exec.Command("echo", "hello", "world"), + }, + }, + + // two code blocks with markdown text in the middle + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build command\n" + + "```\n" + + "markdown instructions\n" + + "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo deploy command\n" + + "```\n", + lifecycle: Lifecycle{ + exec.Command("echo", "build", "command"), + exec.Command("echo", "deploy", "command"), + }, + }, +} + +func TestExtractLifecycle(t *testing.T) { + for i, tc := range extractLifecycleTests { + if tc.in == "" { continue } - s := bufio.NewScanner(strings.NewReader(tc.inStr)) + s := bufio.NewScanner(strings.NewReader(tc.in)) - codeBlocks, err := extractCodeBlocks(s) - if !errors.Is(err, tc.extractCodeBlocksErr) { - t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.extractCodeBlocksErr, err) + // Cloud Run Service name and Container Registry URL tag replacement will be tested in TestToCommands + lifecycle, err := extractLifecycle(s, "", "") + + if !errors.Is(err, tc.err) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) continue } - if !reflect.DeepEqual(codeBlocks, tc.codeBlocks) { - t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.codeBlocks, codeBlocks) - continue + if err == nil && !reflect.DeepEqual(lifecycle, tc.lifecycle) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.lifecycle, lifecycle) } } } -func TestParseREADME(t *testing.T) { - for i, tc := range tests { - if tc.inFileName == "" { +type extractCodeBlocksTest struct { + in string // input Markdown string + codeBlocks []codeBlock // expected result of extractCodeBlocks + err error // expected return error of extractCodeBlocks +} + +var extractCodeBlocksTests = []extractCodeBlocksTest{ + // single code block + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo hello world\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo hello world", + }, + }, + }, + + // code block not closed + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo hello world\n", + codeBlocks: nil, + err: errCodeBlockNotClosed, + }, + + // code block doesn't start immediately after code tag + { + in: "[//]: # ({sst-run-unix})\n" + + "not start of code block\n" + + "```\n" + + "echo hello world\n" + + "```\n", + codeBlocks: nil, + err: errCodeBlockStartNotFound, + }, + + // EOF immediately after code tag + { + in: "instuctions\n" + + "[//]: # ({sst-run-unix})\n", + codeBlocks: nil, + err: errEOFAfterCodeTag, + }, + + // single code block, two lines + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo line one\n" + + "echo line two\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo line one", + "echo line two", + }, + }, + }, + + // two code blocks with markdown instructions in the middle + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build command\n" + + "```\n" + + "markdown instructions\n" + + "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo deploy command\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo build command", + }, + []string{ + "echo deploy command", + }, + }, + }, + + // two code blocks, but only one is annotated with code tag + { + in: "[//]: # ({sst-run-unix})\n" + + "```\n" + + "echo build and deploy command\n" + + "```\n" + + "markdown instructions\n" + + "```\n" + + "echo irrelevant command\n" + + "```\n", + codeBlocks: []codeBlock{ + []string{ + "echo build and deploy command", + }, + }, + }, + + // one code block, but not annotated with code tag + { + in: "```\n" + + "echo hello world\n" + + "```\n", + codeBlocks: nil, + }, +} + +func TestExtractCodeBlocks(t *testing.T) { + for i, tc := range extractCodeBlocksTests { + if tc.in == "" { continue } - var cmds []*exec.Cmd - cmds, err := parseREADME(tc.inFileName, tc.serviceName, tc.gcrURL) + s := bufio.NewScanner(strings.NewReader(tc.in)) + codeBlocks, err := extractCodeBlocks(s) - if !errors.Is(err, tc.parseREADMEErr) { - t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.parseREADMEErr, err) + if !errors.Is(err, tc.err) { + t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) continue } - if !reflect.DeepEqual(cmds, tc.cmds) { - t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) - continue + if err == nil && !reflect.DeepEqual(codeBlocks, tc.codeBlocks) { + t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.codeBlocks, codeBlocks) } } } From 8ffb0abc4c20993823cebebceed8f34feaa26c27 Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 21 Aug 2020 15:56:57 -0400 Subject: [PATCH 10/12] minor readme code refactor - TestParseReadme code fix - README -> Readme in code --- internal/lifecycle/lifecycle.go | 10 ++++------ internal/lifecycle/readme.go | 16 ++++++++-------- internal/lifecycle/readme_test.go | 24 ++++++++++++++---------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/internal/lifecycle/lifecycle.go b/internal/lifecycle/lifecycle.go index 91b3b9e..f22b4af 100644 --- a/internal/lifecycle/lifecycle.go +++ b/internal/lifecycle/lifecycle.go @@ -18,11 +18,11 @@ import ( "errors" "fmt" "github.com/GoogleCloudPlatform/serverless-sample-tester/internal/util" + "github.com/spf13/viper" "log" "os" "os/exec" "path/filepath" - "github.com/spf13/viper" ) // Lifecycle is a list of ordered exec.Cmd that should be run to execute a certain process. @@ -58,10 +58,8 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) { readmePath = filepath.Join(sampleDir, "README.md") } - - if _, err := os.Stat(readmePath); err == nil { - lifecycle, err := parseREADME(readmePath, serviceName, gcrURL) + lifecycle, err := parseReadme(readmePath, serviceName, gcrURL) // Show README location log.Println("README.md location: " + readmePath) if err == nil { @@ -69,8 +67,8 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) { return lifecycle, nil } - if !errors.Is(err, errNoREADMECodeBlocksFound) { - return nil, fmt.Errorf("lifecycle.parseREADME: %s: %w", readmePath, err) + if !errors.Is(err, errNoReadmeCodeBlocksFound) { + return nil, fmt.Errorf("lifecycle.parseReadme: %s: %w", readmePath, err) } log.Printf("No code blocks immediately preceded by %s found in README.md\n", codeTag) diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 6f02ec7..0fcb90e 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -42,7 +42,7 @@ var ( mdCodeFenceStartRegexp = regexp.MustCompile("^\\w*`{3,}[^`]*$") - errNoREADMECodeBlocksFound = fmt.Errorf("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found", codeTag) + errNoReadmeCodeBlocksFound = fmt.Errorf("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found", codeTag) errCodeBlockNotClosed = fmt.Errorf("unexpected EOF: code block not closed") errCodeBlockStartNotFound = fmt.Errorf("expecting start of code block immediately after code tag") errEOFAfterCodeTag = fmt.Errorf("unexpected EOF: file ended immediately after code tag") @@ -72,7 +72,7 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) i++ if i >= len(cb) { - return nil, fmt.Errorf("%w; code block dump:\n%s", errCodeBlockEndAfterLineCont, strings.Join(cb, "\n")) + return nil, fmt.Errorf("%w; code block dump:\n%s", errCodeBlockEndAfterLineCont, strings.Join(cb, "\n")) } l := cb[i] @@ -102,11 +102,11 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) return cmds, nil } -// parseREADME parses a README file with the given name. It parses terminal commands in code blocks annotated by the +// parseReadme parses a README file with the given name. It parses terminal commands in code blocks annotated by the // codeTag and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container // Registry tag with the provided inputs. It also expands environment variables and supports bash-style line // continuations. -func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { +func parseReadme(filename, serviceName, gcrURL string) (Lifecycle, error) { file, err := os.Open(filename) if err != nil { return nil, fmt.Errorf("os.Open: %w", err) @@ -118,7 +118,7 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { return extractLifecycle(scanner, serviceName, gcrURL) } -// extractLifecycle is a helper function for parseREADME. It takes a scanner that reads from a Markdown file and parses +// extractLifecycle is a helper function for parseReadme. It takes a scanner that reads from a Markdown file and parses // terminal commands in code blocks annotated by the codeTag and loads them into a Lifecycle. In the process, it // replaces the Cloud Run service name and Container Registry tag with the provided inputs. It also expands environment // variables and supports bash-style line continuations. @@ -129,7 +129,7 @@ func extractLifecycle(scanner *bufio.Scanner, serviceName, gcrURL string) (Lifec } if len(codeBlocks) == 0 { - return nil, errNoREADMECodeBlocksFound + return nil, errNoReadmeCodeBlocksFound } var l Lifecycle @@ -218,7 +218,7 @@ func replaceServiceName(command, serviceName string) string { sp := strings.Split(command, " ") // Detects if the user specified the Cloud Run service name in an environment variable - for i := 0; i < len(sp); i++ { + for i := 0; i < len(sp); i++ { if sp[i] == os.ExpandEnv("$CLOUD_RUN_SERVICE_NAME") { sp[i] = serviceName return strings.Join(sp, " ") @@ -226,7 +226,7 @@ func replaceServiceName(command, serviceName string) string { } // Searches for specific gcloud keywords and takes service name from them - for i := 0; i < len(sp) - 1; i++ { + for i := 0; i < len(sp)-1; i++ { if sp[i] == "deploy" || sp[i] == "update" { sp[i+1] = serviceName return strings.Join(sp, " ") diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index b47960f..c3cb241 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -193,6 +193,10 @@ var toCommandsTests = []toCommandsTest{ func TestToCommands(t *testing.T) { for i, tc := range toCommandsTests { + if len(tc.codeBlock) == 0 { + continue + } + if err := setEnv(tc.env); err != nil { t.Errorf("#%d: setEnv: %v", i, err) @@ -204,13 +208,13 @@ func TestToCommands(t *testing.T) { } cmds, err := tc.codeBlock.toCommands(uniqueServiceName, uniqueGCRURL) + errorMatch := errors.Is(err, tc.err) - if !errors.Is(err, tc.err) { + if !errorMatch { t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) - continue } - if err == nil && !reflect.DeepEqual(cmds, tc.cmds) { + if (errorMatch && err == nil) && !reflect.DeepEqual(cmds, tc.cmds) { t.Errorf("#%d: result mismatch\nwant: %#+v\ngot: %#+v", i, tc.cmds, cmds) } @@ -220,13 +224,13 @@ func TestToCommands(t *testing.T) { } } -type parseREADMETest struct { +type parseReadmeTest struct { inFileName string // input Markdown file - lifecycle Lifecycle // expected result of parseREADME - err error // expected parseREADME return error + lifecycle Lifecycle // expected result of parseReadme + err error // expected parseReadme return error } -var parseREADMETests = []parseREADMETest{ +var parseReadmeTests = []parseReadmeTest{ // three code blocks, only two with comment code tags. one with one command, the other with two commands { inFileName: "readme_test.md", @@ -238,14 +242,14 @@ var parseREADMETests = []parseREADMETest{ }, } -func TestParseREADME(t *testing.T) { - for i, tc := range parseREADMETests { +func TestParseReadme(t *testing.T) { + for i, tc := range parseReadmeTests { if tc.inFileName == "" { continue } // Cloud Run Service name and Container Registry URL tag replacement will be tested in TestToCommands - lifecycle, err := parseREADME(tc.inFileName, "", "") + lifecycle, err := parseReadme(tc.inFileName, "", "") if !errors.Is(err, tc.err) { t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) From 60fecfe6a5f1855bd0316b34fb78f2f774291d6f Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 21 Aug 2020 22:58:32 -0400 Subject: [PATCH 11/12] revert README -> Readme in code --- internal/lifecycle/lifecycle.go | 4 ++-- internal/lifecycle/readme.go | 6 +++--- internal/lifecycle/readme_test.go | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/lifecycle/lifecycle.go b/internal/lifecycle/lifecycle.go index f22b4af..9aa8801 100644 --- a/internal/lifecycle/lifecycle.go +++ b/internal/lifecycle/lifecycle.go @@ -59,7 +59,7 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) { } if _, err := os.Stat(readmePath); err == nil { - lifecycle, err := parseReadme(readmePath, serviceName, gcrURL) + lifecycle, err := parseREADME(readmePath, serviceName, gcrURL) // Show README location log.Println("README.md location: " + readmePath) if err == nil { @@ -68,7 +68,7 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) { } if !errors.Is(err, errNoReadmeCodeBlocksFound) { - return nil, fmt.Errorf("lifecycle.parseReadme: %s: %w", readmePath, err) + return nil, fmt.Errorf("lifecycle.parseREADME: %s: %w", readmePath, err) } log.Printf("No code blocks immediately preceded by %s found in README.md\n", codeTag) diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 0fcb90e..03e52e3 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -102,11 +102,11 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) return cmds, nil } -// parseReadme parses a README file with the given name. It parses terminal commands in code blocks annotated by the +// parseREADME parses a README file with the given name. It parses terminal commands in code blocks annotated by the // codeTag and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container // Registry tag with the provided inputs. It also expands environment variables and supports bash-style line // continuations. -func parseReadme(filename, serviceName, gcrURL string) (Lifecycle, error) { +func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) { file, err := os.Open(filename) if err != nil { return nil, fmt.Errorf("os.Open: %w", err) @@ -118,7 +118,7 @@ func parseReadme(filename, serviceName, gcrURL string) (Lifecycle, error) { return extractLifecycle(scanner, serviceName, gcrURL) } -// extractLifecycle is a helper function for parseReadme. It takes a scanner that reads from a Markdown file and parses +// extractLifecycle is a helper function for parseREADME. It takes a scanner that reads from a Markdown file and parses // terminal commands in code blocks annotated by the codeTag and loads them into a Lifecycle. In the process, it // replaces the Cloud Run service name and Container Registry tag with the provided inputs. It also expands environment // variables and supports bash-style line continuations. diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index c3cb241..403dac1 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -224,13 +224,13 @@ func TestToCommands(t *testing.T) { } } -type parseReadmeTest struct { +type parseREADMETest struct { inFileName string // input Markdown file - lifecycle Lifecycle // expected result of parseReadme - err error // expected parseReadme return error + lifecycle Lifecycle // expected result of parseREADME + err error // expected parseREADME return error } -var parseReadmeTests = []parseReadmeTest{ +var parseREADMETests = []parseREADMETest{ // three code blocks, only two with comment code tags. one with one command, the other with two commands { inFileName: "readme_test.md", @@ -242,14 +242,14 @@ var parseReadmeTests = []parseReadmeTest{ }, } -func TestParseReadme(t *testing.T) { - for i, tc := range parseReadmeTests { +func TestParseREADME(t *testing.T) { + for i, tc := range parseREADMETests { if tc.inFileName == "" { continue } // Cloud Run Service name and Container Registry URL tag replacement will be tested in TestToCommands - lifecycle, err := parseReadme(tc.inFileName, "", "") + lifecycle, err := parseREADME(tc.inFileName, "", "") if !errors.Is(err, tc.err) { t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) From 3da466b0fb8762856eeb327e3830e7c988ad26f9 Mon Sep 17 00:00:00 2001 From: Saketram Durbha Date: Fri, 21 Aug 2020 23:06:44 -0400 Subject: [PATCH 12/12] don't wrap errors in codeBlock.toCommands --- internal/lifecycle/readme.go | 4 ++-- internal/lifecycle/readme_test.go | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index 03e52e3..703420f 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -46,7 +46,7 @@ var ( errCodeBlockNotClosed = fmt.Errorf("unexpected EOF: code block not closed") errCodeBlockStartNotFound = fmt.Errorf("expecting start of code block immediately after code tag") errEOFAfterCodeTag = fmt.Errorf("unexpected EOF: file ended immediately after code tag") - errCodeBlockEndAfterLineCont = fmt.Errorf("unexpected end of code block: expecting command line continuation") + errCodeBlockEndAfterLineCont = "end of code block: expecting command line continuation" ) // codeBlock is a slice of strings containing terminal commands. codeBlocks, for example, could be used to hold the @@ -72,7 +72,7 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) i++ if i >= len(cb) { - return nil, fmt.Errorf("%w; code block dump:\n%s", errCodeBlockEndAfterLineCont, strings.Join(cb, "\n")) + return nil, fmt.Errorf("%s; code block dump:\n%s", errCodeBlockEndAfterLineCont, strings.Join(cb, "\n")) } l := cb[i] diff --git a/internal/lifecycle/readme_test.go b/internal/lifecycle/readme_test.go index 403dac1..2869bf6 100644 --- a/internal/lifecycle/readme_test.go +++ b/internal/lifecycle/readme_test.go @@ -40,7 +40,7 @@ const uniqueGCRURL = "gcr.io/unique/tag" type toCommandsTest struct { codeBlock codeBlock // input code block cmds []*exec.Cmd // expected result of codeBlock.toCommands - err error // expected return error of codeBlock.toCommands + err string // expected string contained in return error of codeBlock.toCommands env map[string]string // map of environment variables to values for this test } @@ -208,10 +208,16 @@ func TestToCommands(t *testing.T) { } cmds, err := tc.codeBlock.toCommands(uniqueServiceName, uniqueGCRURL) - errorMatch := errors.Is(err, tc.err) + + var errorMatch bool + if err == nil { + errorMatch = tc.err == "" + } else { + errorMatch = strings.Contains(err.Error(), tc.err) + } if !errorMatch { - t.Errorf("#%d: error mismatch\nwant: %v\ngot: %v", i, tc.err, err) + t.Errorf("#%d: error mismatch\nwant: %s\ngot: %v", i, tc.err, err) } if (errorMatch && err == nil) && !reflect.DeepEqual(cmds, tc.cmds) {